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