2 * @class Ext.data.TreeStore
3 * @extends Ext.data.AbstractStore
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.
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.
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
20 Ext.define('Ext.data.TreeStore', {
21 extend: 'Ext.data.AbstractStore',
23 requires: ['Ext.data.Tree', 'Ext.data.NodeInterface', 'Ext.data.NodeStore'],
26 * @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing
27 * child nodes before loading.
32 * @cfg {String} nodeParam The name of the parameter sent to the server which contains
33 * the identifier of the node. Defaults to <tt>'node'</tt>.
38 * @cfg {String} defaultRootId
39 * The default root id. Defaults to 'root'
41 defaultRootId: 'root',
44 * @cfg {String} defaultRootProperty
45 * The root property to specify on the reader if one is not explicitly defined.
47 defaultRootProperty: 'children',
50 * @cfg {Boolean} folderSort Set to true to automatically prepend a leaf sorter (defaults to <tt>undefined</tt>)
54 constructor: function(config) {
60 config = Ext.apply({}, config);
63 * If we have no fields declare for the store, add some defaults.
64 * These will be ignored if a model is explicitly specified.
66 fields = config.fields || me.fields;
68 config.fields = [{name: 'text', type: 'string'}];
71 me.callParent([config]);
73 // We create our data tree.
74 me.tree = Ext.create('Ext.data.Tree');
78 remove: me.onNodeRemove,
79 beforeexpand: me.onBeforeNodeExpand,
80 beforecollapse: me.onBeforeNodeCollapse,
81 append: me.onNodeAdded,
82 insert: me.onNodeAdded
93 me.relayEvents(me.tree, [
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
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
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
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
135 * @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
144 * @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
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
164 * @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
175 * Fires when this node is expanded.
176 * @param {Node} this The expanding node
182 * Fires when this node is collapsed.
183 * @param {Node} this The collapsing node
188 * @event beforeexpand
189 * Fires before this node is expanded.
190 * @param {Node} this The expanding node
195 * @event beforecollapse
196 * Fires before this node is collapsed.
197 * @param {Node} this The collapsing node
203 * Fires when this TreeStore is sorted.
204 * @param {Node} node The node that is sorted.
210 * Fires whenever the root node is changed in the tree.
211 * @param {Ext.data.Model} root The new root
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.
226 //<deprecated since=0.99>
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.');
231 me.nodeParam = me.nodeParameter;
232 delete me.nodeParameter;
238 setProxy: function(proxy) {
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
249 // object, check if a reader and a root were specified.
250 reader = proxy.reader;
251 needsRoot = !(reader && !Ext.isEmpty(reader.root));
253 proxy = this.callParent(arguments);
255 reader = proxy.getReader();
256 reader.root = this.defaultRootProperty;
258 reader.buildExtractors(true);
263 onBeforeSort: function() {
264 if (this.folderSort) {
268 }, 'prepend', false);
273 * Called before a node is expanded.
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
279 onBeforeNodeExpand: function(node, callback, scope) {
280 if (node.isLoaded()) {
281 Ext.callback(callback, scope || node, [node.childNodes]);
283 else if (node.isLoading()) {
284 this.on('load', function() {
285 Ext.callback(callback, scope || node, [node.childNodes]);
286 }, this, {single: true});
291 callback: function() {
292 Ext.callback(callback, scope || node, [node.childNodes]);
299 getNewRecords: function() {
300 return Ext.Array.filter(this.tree.flatten(), this.filterNew);
304 getUpdatedRecords: function() {
305 return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
309 * Called before a node is collapsed.
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
315 onBeforeNodeCollapse: function(node, callback, scope) {
316 callback.call(scope || node, node.childNodes);
319 onNodeRemove: function(parent, node) {
320 var removed = this.removed;
322 if (!node.isReplace && Ext.Array.indexOf(removed, node) == -1) {
327 onNodeAdded: function(parent, node) {
328 var proxy = this.getProxy(),
329 reader = proxy.getReader(),
330 data = node.raw || node.data,
333 Ext.Array.remove(this.removed, node);
335 if (!node.isLeaf() && !node.isLoaded()) {
336 dataRoot = reader.getRoot(data);
338 this.fillNode(node, reader.extractData(dataRoot));
339 delete data[reader.root];
345 * Sets the root node for this store
346 * @param {Ext.data.Model/Ext.data.NodeInterface} root
347 * @return {Ext.data.NodeInterface} The new root
349 setRootNode: function(root) {
354 // create a default rootNode and create internal data struct.
356 id: me.defaultRootId,
360 root = Ext.ModelManager.create(root, me.model);
362 Ext.data.NodeInterface.decorate(root);
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);
368 // When we add the root to the tree, it will automaticaly get the NodeInterface
369 me.tree.setRootNode(root);
371 // If the user has set expanded: true on the root, we want to call the expand function
372 if (!root.isLoaded() && root.isExpanded()) {
382 * Returns the root node for this tree.
383 * @return {Ext.data.NodeInterface}
385 getRootNode: function() {
386 return this.tree.getRootNode();
390 * Returns the record node by id
391 * @return {Ext.data.NodeInterface}
393 getNodeById: function(id) {
394 return this.tree.getNodeById(id);
398 * 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.
404 load: function(options) {
405 options = options || {};
406 options.params = options.params || {};
409 node = options.node || me.tree.getRootNode(),
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.
415 node = me.setRootNode({
420 if (me.clearOnLoad) {
424 Ext.applyIf(options, {
427 options.params[me.nodeParam] = node ? node.getId() : 'root';
430 node.set('loading', true);
433 return me.callParent([options]);
438 * Fills a node with a series of child records.
440 * @param {Ext.data.NodeInterface} node The node to fill
441 * @param {Array} records The records to add
443 fillNode: function(node, records) {
445 ln = records ? records.length : 0,
446 i = 0, sortCollection;
448 if (ln && me.sortOnLoad && !me.remoteSort && me.sorters && me.sorters.items) {
449 sortCollection = Ext.create('Ext.util.MixedCollection');
450 sortCollection.addAll(records);
451 sortCollection.sort(me.sorters.items);
452 records = sortCollection.items;
455 node.set('loaded', true);
456 for (; i < ln; i++) {
457 node.appendChild(records[i], undefined, true);
464 onProxyLoad: function(operation) {
466 successful = operation.wasSuccessful(),
467 records = operation.getRecords(),
468 node = operation.node;
470 node.set('loading', false);
472 records = me.fillNode(node, records);
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]);
482 * Create any new records when a write is returned from the server.
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
488 onCreateRecords: function(records, operation, success) {
491 length = records.length,
492 originalRecords = operation.records,
499 * 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.
503 for (; i < length; ++i) {
505 original = originalRecords[i];
507 parentNode = original.parentNode;
509 // prevent being added to the removed cache
510 original.isReplace = true;
511 parentNode.replaceChild(record, original);
512 delete original.isReplace;
514 record.phantom = false;
521 * Update any records when a write is returned from the server.
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
527 onUpdateRecords: function(records, operation, success){
531 length = records.length,
537 for (; i < length; ++i) {
539 original = me.tree.getNodeById(record.getId());
540 parentNode = original.parentNode;
542 // prevent being added to the removed cache
543 original.isReplace = true;
544 parentNode.replaceChild(record, original);
545 original.isReplace = false;
552 * Remove any records when a write is returned from the server.
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
558 onDestroyRecords: function(records, operation, success){
565 removeAll: function() {
566 this.getRootNode().destroy(true);
567 this.fireEvent('clear', this);
571 doSort: function(sorterFn) {
574 //the load function will pick up the new sorters and request the sorted data from the proxy
577 me.tree.sort(sorterFn, true);
578 me.fireEvent('datachanged', me);
580 me.fireEvent('sort', me);