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