Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / source / TreeStore.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-data-TreeStore'>/**
19 </span> * The TreeStore is a store implementation that is backed by by an {@link Ext.data.Tree}.
20  * It provides convenience methods for loading nodes, as well as the ability to use
21  * the hierarchical tree structure combined with a store. This class is generally used
22  * in conjunction with {@link Ext.tree.Panel}. This class also relays many events from
23  * the Tree for convenience.
24  *
25  * # Using Models
26  *
27  * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
28  * The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed
29  * in the {@link Ext.data.NodeInterface} documentation.
30  *
31  * # Reading Nested Data
32  *
33  * For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
34  * so the reader can find nested data for each node. If a root is not specified, it will default to
35  * 'children'.
36  */
37 Ext.define('Ext.data.TreeStore', {
38     extend: 'Ext.data.AbstractStore',
39     alias: 'store.tree',
40     requires: ['Ext.data.Tree', 'Ext.data.NodeInterface', 'Ext.data.NodeStore'],
41
42 <span id='Ext-data-TreeStore-cfg-root'>    /**
43 </span>     * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
44      * The root node for this store. For example:
45      *
46      *     root: {
47      *         expanded: true,
48      *         text: &quot;My Root&quot;,
49      *         children: [
50      *             { text: &quot;Child 1&quot;, leaf: true },
51      *             { text: &quot;Child 2&quot;, expanded: true, children: [
52      *                 { text: &quot;GrandChild&quot;, leaf: true }
53      *             ] }
54      *         ]
55      *     }
56      *
57      * Setting the `root` config option is the same as calling {@link #setRootNode}.
58      */
59
60 <span id='Ext-data-TreeStore-cfg-clearOnLoad'>    /**
61 </span>     * @cfg {Boolean} clearOnLoad
62      * Remove previously existing child nodes before loading. Default to true.
63      */
64     clearOnLoad : true,
65
66 <span id='Ext-data-TreeStore-cfg-nodeParam'>    /**
67 </span>     * @cfg {String} nodeParam
68      * The name of the parameter sent to the server which contains the identifier of the node.
69      * Defaults to 'node'.
70      */
71     nodeParam: 'node',
72
73 <span id='Ext-data-TreeStore-cfg-defaultRootId'>    /**
74 </span>     * @cfg {String} defaultRootId
75      * The default root id. Defaults to 'root'
76      */
77     defaultRootId: 'root',
78
79 <span id='Ext-data-TreeStore-cfg-defaultRootProperty'>    /**
80 </span>     * @cfg {String} defaultRootProperty
81      * The root property to specify on the reader if one is not explicitly defined.
82      */
83     defaultRootProperty: 'children',
84
85 <span id='Ext-data-TreeStore-cfg-folderSort'>    /**
86 </span>     * @cfg {Boolean} folderSort
87      * Set to true to automatically prepend a leaf sorter. Defaults to `undefined`.
88      */
89     folderSort: false,
90
91     constructor: function(config) {
92         var me = this,
93             root,
94             fields;
95
96         config = Ext.apply({}, config);
97
98 <span id='Ext-data-TreeStore-property-fields'>        /**
99 </span>         * If we have no fields declare for the store, add some defaults.
100          * These will be ignored if a model is explicitly specified.
101          */
102         fields = config.fields || me.fields;
103         if (!fields) {
104             config.fields = [{name: 'text', type: 'string'}];
105         }
106
107         me.callParent([config]);
108
109         // We create our data tree.
110         me.tree = Ext.create('Ext.data.Tree');
111
112         me.relayEvents(me.tree, [
113 <span id='Ext-data-TreeStore-event-append'>            /**
114 </span>             * @event append
115              * @alias Ext.data.Tree#append
116              */
117             &quot;append&quot;,
118
119 <span id='Ext-data-TreeStore-event-remove'>            /**
120 </span>             * @event remove
121              * @alias Ext.data.Tree#remove
122              */
123             &quot;remove&quot;,
124
125 <span id='Ext-data-TreeStore-event-move'>            /**
126 </span>             * @event move
127              * @alias Ext.data.Tree#move
128              */
129             &quot;move&quot;,
130
131 <span id='Ext-data-TreeStore-event-insert'>            /**
132 </span>             * @event insert
133              * @alias Ext.data.Tree#insert
134              */
135             &quot;insert&quot;,
136
137 <span id='Ext-data-TreeStore-event-beforeappend'>            /**
138 </span>             * @event beforeappend
139              * @alias Ext.data.Tree#beforeappend
140              */
141             &quot;beforeappend&quot;,
142
143 <span id='Ext-data-TreeStore-event-beforeremove'>            /**
144 </span>             * @event beforeremove
145              * @alias Ext.data.Tree#beforeremove
146              */
147             &quot;beforeremove&quot;,
148
149 <span id='Ext-data-TreeStore-event-beforemove'>            /**
150 </span>             * @event beforemove
151              * @alias Ext.data.Tree#beforemove
152              */
153             &quot;beforemove&quot;,
154
155 <span id='Ext-data-TreeStore-event-beforeinsert'>            /**
156 </span>             * @event beforeinsert
157              * @alias Ext.data.Tree#beforeinsert
158              */
159             &quot;beforeinsert&quot;,
160
161 <span id='Ext-data-TreeStore-event-expand'>             /**
162 </span>              * @event expand
163               * @alias Ext.data.Tree#expand
164               */
165              &quot;expand&quot;,
166
167 <span id='Ext-data-TreeStore-event-collapse'>             /**
168 </span>              * @event collapse
169               * @alias Ext.data.Tree#collapse
170               */
171              &quot;collapse&quot;,
172
173 <span id='Ext-data-TreeStore-event-beforeexpand'>             /**
174 </span>              * @event beforeexpand
175               * @alias Ext.data.Tree#beforeexpand
176               */
177              &quot;beforeexpand&quot;,
178
179 <span id='Ext-data-TreeStore-event-beforecollapse'>             /**
180 </span>              * @event beforecollapse
181               * @alias Ext.data.Tree#beforecollapse
182               */
183              &quot;beforecollapse&quot;,
184
185 <span id='Ext-data-TreeStore-event-rootchange'>             /**
186 </span>              * @event rootchange
187               * @alias Ext.data.Tree#rootchange
188               */
189              &quot;rootchange&quot;
190         ]);
191
192         me.tree.on({
193             scope: me,
194             remove: me.onNodeRemove,
195             // this event must follow the relay to beforeitemexpand to allow users to
196             // cancel the expand:
197             beforeexpand: me.onBeforeNodeExpand,
198             beforecollapse: me.onBeforeNodeCollapse,
199             append: me.onNodeAdded,
200             insert: me.onNodeAdded
201         });
202
203         me.onBeforeSort();
204
205         root = me.root;
206         if (root) {
207             delete me.root;
208             me.setRootNode(root);
209         }
210
211         me.addEvents(
212 <span id='Ext-data-TreeStore-event-sort'>            /**
213 </span>             * @event sort
214              * Fires when this TreeStore is sorted.
215              * @param {Ext.data.NodeInterface} node The node that is sorted.
216              */
217             'sort'
218         );
219
220         //&lt;deprecated since=0.99&gt;
221         if (Ext.isDefined(me.nodeParameter)) {
222             if (Ext.isDefined(Ext.global.console)) {
223                 Ext.global.console.warn('Ext.data.TreeStore: nodeParameter has been deprecated. Please use nodeParam instead.');
224             }
225             me.nodeParam = me.nodeParameter;
226             delete me.nodeParameter;
227         }
228         //&lt;/deprecated&gt;
229     },
230
231     // inherit docs
232     setProxy: function(proxy) {
233         var reader,
234             needsRoot;
235
236         if (proxy instanceof Ext.data.proxy.Proxy) {
237             // proxy instance, check if a root was set
238             needsRoot = Ext.isEmpty(proxy.getReader().root);
239         } else if (Ext.isString(proxy)) {
240             // string type, means a reader can't be set
241             needsRoot = true;
242         } else {
243             // object, check if a reader and a root were specified.
244             reader = proxy.reader;
245             needsRoot = !(reader &amp;&amp; !Ext.isEmpty(reader.root));
246         }
247         proxy = this.callParent(arguments);
248         if (needsRoot) {
249             reader = proxy.getReader();
250             reader.root = this.defaultRootProperty;
251             // force rebuild
252             reader.buildExtractors(true);
253         }
254     },
255
256     // inherit docs
257     onBeforeSort: function() {
258         if (this.folderSort) {
259             this.sort({
260                 property: 'leaf',
261                 direction: 'ASC'
262             }, 'prepend', false);
263         }
264     },
265
266 <span id='Ext-data-TreeStore-method-onBeforeNodeExpand'>    /**
267 </span>     * Called before a node is expanded.
268      * @private
269      * @param {Ext.data.NodeInterface} node The node being expanded.
270      * @param {Function} callback The function to run after the expand finishes
271      * @param {Object} scope The scope in which to run the callback function
272      */
273     onBeforeNodeExpand: function(node, callback, scope) {
274         if (node.isLoaded()) {
275             Ext.callback(callback, scope || node, [node.childNodes]);
276         }
277         else if (node.isLoading()) {
278             this.on('load', function() {
279                 Ext.callback(callback, scope || node, [node.childNodes]);
280             }, this, {single: true});
281         }
282         else {
283             this.read({
284                 node: node,
285                 callback: function() {
286                     Ext.callback(callback, scope || node, [node.childNodes]);
287                 }
288             });
289         }
290     },
291
292     //inherit docs
293     getNewRecords: function() {
294         return Ext.Array.filter(this.tree.flatten(), this.filterNew);
295     },
296
297     //inherit docs
298     getUpdatedRecords: function() {
299         return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
300     },
301
302 <span id='Ext-data-TreeStore-method-onBeforeNodeCollapse'>    /**
303 </span>     * Called before a node is collapsed.
304      * @private
305      * @param {Ext.data.NodeInterface} node The node being collapsed.
306      * @param {Function} callback The function to run after the collapse finishes
307      * @param {Object} scope The scope in which to run the callback function
308      */
309     onBeforeNodeCollapse: function(node, callback, scope) {
310         callback.call(scope || node, node.childNodes);
311     },
312
313     onNodeRemove: function(parent, node) {
314         var removed = this.removed;
315
316         if (!node.isReplace &amp;&amp; Ext.Array.indexOf(removed, node) == -1) {
317             removed.push(node);
318         }
319     },
320
321     onNodeAdded: function(parent, node) {
322         var proxy = this.getProxy(),
323             reader = proxy.getReader(),
324             data = node.raw || node.data,
325             dataRoot, children;
326
327         Ext.Array.remove(this.removed, node);
328
329         if (!node.isLeaf() &amp;&amp; !node.isLoaded()) {
330             dataRoot = reader.getRoot(data);
331             if (dataRoot) {
332                 this.fillNode(node, reader.extractData(dataRoot));
333                 delete data[reader.root];
334             }
335         }
336     },
337
338 <span id='Ext-data-TreeStore-method-setRootNode'>    /**
339 </span>     * Sets the root node for this store.  See also the {@link #root} config option.
340      * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
341      * @return {Ext.data.NodeInterface} The new root
342      */
343     setRootNode: function(root) {
344         var me = this;
345
346         root = root || {};
347         if (!root.isNode) {
348             // create a default rootNode and create internal data struct.
349             Ext.applyIf(root, {
350                 id: me.defaultRootId,
351                 text: 'Root',
352                 allowDrag: false
353             });
354             root = Ext.ModelManager.create(root, me.model);
355         }
356         Ext.data.NodeInterface.decorate(root);
357
358         // Because we have decorated the model with new fields,
359         // we need to build new extactor functions on the reader.
360         me.getProxy().getReader().buildExtractors(true);
361
362         // When we add the root to the tree, it will automaticaly get the NodeInterface
363         me.tree.setRootNode(root);
364
365         // If the user has set expanded: true on the root, we want to call the expand function
366         if (!root.isLoaded() &amp;&amp; (me.autoLoad === true || root.isExpanded())) {
367             me.load({
368                 node: root
369             });
370         }
371
372         return root;
373     },
374
375 <span id='Ext-data-TreeStore-method-getRootNode'>    /**
376 </span>     * Returns the root node for this tree.
377      * @return {Ext.data.NodeInterface}
378      */
379     getRootNode: function() {
380         return this.tree.getRootNode();
381     },
382
383 <span id='Ext-data-TreeStore-method-getNodeById'>    /**
384 </span>     * Returns the record node by id
385      * @return {Ext.data.NodeInterface}
386      */
387     getNodeById: function(id) {
388         return this.tree.getNodeById(id);
389     },
390
391 <span id='Ext-data-TreeStore-method-load'>    /**
392 </span>     * Loads the Store using its configured {@link #proxy}.
393      * @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}
394      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
395      * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
396      * default to the root node.
397      */
398     load: function(options) {
399         options = options || {};
400         options.params = options.params || {};
401
402         var me = this,
403             node = options.node || me.tree.getRootNode(),
404             root;
405
406         // If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just
407         // create one for them.
408         if (!node) {
409             node = me.setRootNode({
410                 expanded: true
411             });
412         }
413
414         if (me.clearOnLoad) {
415             node.removeAll(true);
416         }
417
418         Ext.applyIf(options, {
419             node: node
420         });
421         options.params[me.nodeParam] = node ? node.getId() : 'root';
422
423         if (node) {
424             node.set('loading', true);
425         }
426
427         return me.callParent([options]);
428     },
429
430
431 <span id='Ext-data-TreeStore-method-fillNode'>    /**
432 </span>     * Fills a node with a series of child records.
433      * @private
434      * @param {Ext.data.NodeInterface} node The node to fill
435      * @param {Ext.data.Model[]} records The records to add
436      */
437     fillNode: function(node, records) {
438         var me = this,
439             ln = records ? records.length : 0,
440             i = 0, sortCollection;
441
442         if (ln &amp;&amp; me.sortOnLoad &amp;&amp; !me.remoteSort &amp;&amp; me.sorters &amp;&amp; me.sorters.items) {
443             sortCollection = Ext.create('Ext.util.MixedCollection');
444             sortCollection.addAll(records);
445             sortCollection.sort(me.sorters.items);
446             records = sortCollection.items;
447         }
448
449         node.set('loaded', true);
450         for (; i &lt; ln; i++) {
451             node.appendChild(records[i], undefined, true);
452         }
453
454         return records;
455     },
456
457     // inherit docs
458     onProxyLoad: function(operation) {
459         var me = this,
460             successful = operation.wasSuccessful(),
461             records = operation.getRecords(),
462             node = operation.node;
463
464         me.loading = false;
465         node.set('loading', false);
466         if (successful) {
467             records = me.fillNode(node, records);
468         }
469         // The load event has an extra node parameter
470         // (differing from the load event described in AbstractStore)
471 <span id='Ext-data-TreeStore-event-load'>        /**
472 </span>         * @event load
473          * Fires whenever the store reads data from a remote data source.
474          * @param {Ext.data.TreeStore} this
475          * @param {Ext.data.NodeInterface} node The node that was loaded.
476          * @param {Ext.data.Model[]} records An array of records.
477          * @param {Boolean} successful True if the operation was successful.
478          */
479         // deprecate read?
480         me.fireEvent('read', me, operation.node, records, successful);
481         me.fireEvent('load', me, operation.node, records, successful);
482         //this is a callback that would have been passed to the 'read' function and is optional
483         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
484     },
485
486 <span id='Ext-data-TreeStore-method-onCreateRecords'>    /**
487 </span>     * Creates any new records when a write is returned from the server.
488      * @private
489      * @param {Ext.data.Model[]} records The array of new records
490      * @param {Ext.data.Operation} operation The operation that just completed
491      * @param {Boolean} success True if the operation was successful
492      */
493     onCreateRecords: function(records, operation, success) {
494         if (success) {
495             var i = 0,
496                 length = records.length,
497                 originalRecords = operation.records,
498                 parentNode,
499                 record,
500                 original,
501                 index;
502
503             /*
504              * Loop over each record returned from the server. Assume they are
505              * returned in order of how they were sent. If we find a matching
506              * record, replace it with the newly created one.
507              */
508             for (; i &lt; length; ++i) {
509                 record = records[i];
510                 original = originalRecords[i];
511                 if (original) {
512                     parentNode = original.parentNode;
513                     if (parentNode) {
514                         // prevent being added to the removed cache
515                         original.isReplace = true;
516                         parentNode.replaceChild(record, original);
517                         delete original.isReplace;
518                     }
519                     record.phantom = false;
520                 }
521             }
522         }
523     },
524
525 <span id='Ext-data-TreeStore-method-onUpdateRecords'>    /**
526 </span>     * Updates any records when a write is returned from the server.
527      * @private
528      * @param {Ext.data.Model[]} records The array of updated records
529      * @param {Ext.data.Operation} operation The operation that just completed
530      * @param {Boolean} success True if the operation was successful
531      */
532     onUpdateRecords: function(records, operation, success){
533         if (success) {
534             var me = this,
535                 i = 0,
536                 length = records.length,
537                 data = me.data,
538                 original,
539                 parentNode,
540                 record;
541
542             for (; i &lt; length; ++i) {
543                 record = records[i];
544                 original = me.tree.getNodeById(record.getId());
545                 parentNode = original.parentNode;
546                 if (parentNode) {
547                     // prevent being added to the removed cache
548                     original.isReplace = true;
549                     parentNode.replaceChild(record, original);
550                     original.isReplace = false;
551                 }
552             }
553         }
554     },
555
556 <span id='Ext-data-TreeStore-method-onDestroyRecords'>    /**
557 </span>     * Removes any records when a write is returned from the server.
558      * @private
559      * @param {Ext.data.Model[]} records The array of removed records
560      * @param {Ext.data.Operation} operation The operation that just completed
561      * @param {Boolean} success True if the operation was successful
562      */
563     onDestroyRecords: function(records, operation, success){
564         if (success) {
565             this.removed = [];
566         }
567     },
568
569     // inherit docs
570     removeAll: function() {
571         this.getRootNode().destroy(true);
572         this.fireEvent('clear', this);
573     },
574
575     // inherit docs
576     doSort: function(sorterFn) {
577         var me = this;
578         if (me.remoteSort) {
579             //the load function will pick up the new sorters and request the sorted data from the proxy
580             me.load();
581         } else {
582             me.tree.sort(sorterFn, true);
583             me.fireEvent('datachanged', me);
584         }
585         me.fireEvent('sort', me);
586     }
587 });
588 </pre>
589 </body>
590 </html>