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