/** * Node Store * @private */Ext.define('Ext.data.NodeStore', { extend: 'Ext.data.Store', alias: 'store.node', requires: ['Ext.data.NodeInterface'], /** * @property {Boolean} isNodeStore * `true` in this class to identify an object as an instantiated NodeStore, or subclass thereof. */ isNodeStore: true, config: { /** * @cfg {Ext.data.Model} node The Record you want to bind this Store to. Note that * this record will be decorated with the {@link Ext.data.NodeInterface} if this is not the * case yet. * @accessor */ node: null, /** * @cfg {Boolean} recursive Set this to `true` if you want this NodeStore to represent * all the descendants of the node in its flat data collection. This is useful for * rendering a tree structure to a DataView and is being used internally by * the TreeView. Any records that are moved, removed, inserted or appended to the * node at any depth below the node this store is bound to will be automatically * updated in this Store's internal flat data structure. * @accessor */ recursive: false, /** * @cfg {Boolean} rootVisible `false` to not include the root node in this Stores collection. * @accessor */ rootVisible: false, /** * @cfg {Boolean} folderSort * Set to `true` to automatically prepend a leaf sorter. */ folderSort: false }, /** * @protected * Recursion level counter. Incremented when expansion or collaping of a node is initiated, * including when nested nodes below the expanding/collapsing root begin expanding or collapsing. * * This ensures that collapsestart, collapsecomplete, expandstart and expandcomplete only * fire on the top level node being collapsed/expanded. * * Also, allows listeners to the `add` and `remove` events to know whether a collapse of expand is in progress. */ isExpandingOrCollapsing: 0, // NodeStores are never buffered or paged. They are loaded from the TreeStore to reflect all visible // nodes. // BufferedRenderer always asks for the *total* count, so this must return the count. getTotalCount: function() { return this.getCount(); }, afterEdit: function(record, modifiedFields) { // Only for when being used as the flat NodeStore of a List if (this.getNode() && modifiedFields) { if (Ext.Array.indexOf(modifiedFields, 'loaded') !== -1) { return this.add(this.retrieveChildNodes(record)); } if (Ext.Array.indexOf(modifiedFields, 'expanded') !== -1) { return this.filter(); } if (Ext.Array.indexOf(modifiedFields, 'sorted') !== -1) { return this.sort(); } } this.callParent(arguments); }, afterReject : function(record) { var me = this; // Must pass the 5th param (modifiedFieldNames) as null, otherwise the // event firing machinery appends the listeners "options" object to the arg list // which may get used as the modified fields array by a handler. // This array is used for selective grid cell updating by Grid View. // Null will be treated as though all cells need updating. if (me.contains(record)) { me.onUpdate(record, Ext.data.Model.REJECT, null); me.fireEvent('update', me, record, Ext.data.Model.REJECT, null); } }, afterCommit : function(record, modifiedFieldNames) { var me = this; if (!modifiedFieldNames) { modifiedFieldNames = null; } if (me.contains(record)) { me.onUpdate(record, Ext.data.Model.COMMIT, modifiedFieldNames); me.fireEvent('update', me, record, Ext.data.Model.COMMIT, modifiedFieldNames); } }, onNodeAppend: function(parent, node) { this.add([node].concat(this.retrieveChildNodes(node))); }, onNodeInsert: function(parent, node) { this.add([node].concat(this.retrieveChildNodes(node))); }, onNodeRemove: function(parent, node) { this.remove([node].concat(this.retrieveChildNodes(node))); }, onNodeExpand: function(parent, records) { this.loadRecords(records); }, applyNode: function(node) { if (node) { Ext.data.NodeInterface.decorate(node); } return node; }, updateNode: function(node, oldNode) { var me = this, data; if (oldNode && !oldNode.isDestroyed) { oldNode.un({ append : 'onNodeAppend', insert : 'onNodeInsert', remove : 'onNodeRemove', load : 'onNodeLoad', scope: me }); oldNode.unjoin(me); } if (node) { node.on({ scope : me, append : 'onNodeAppend', insert : 'onNodeInsert', remove : 'onNodeRemove', load : 'onNodeLoad' }); node.join(me); data = []; if (node.childNodes.length) { data = data.concat(me.retrieveChildNodes(node)); } if (me.getRootVisible()) { data.push(node); } else if (node.isLoaded() || node.isLoading()) { node.set('expanded', true); } me.getData().clear(); me.fireEvent('clear', me); me.suspendEvents(); me.add(data); me.resumeEvents(); if(data.length === 0) { me.loaded = node.loaded = true; } me.fireEvent('refresh', me, me.data); } }, /** * Private method used to deeply retrieve the children of a record without recursion. * @private * @param {Ext.data.NodeInterface} root * @return {Array} */ retrieveChildNodes: function(root) { var node = this.getNode(), recursive = this.getRecursive(), added = [], child = root; if (!root.childNodes.length || (!recursive && root !== node)) { return added; } if (!recursive) { return root.childNodes; } while (child) { if (child._added) { delete child._added; if (child === root) { break; } else { child = child.nextSibling || child.parentNode; } } else { if (child !== root) { added.push(child); } if (child.firstChild) { child._added = true; child = child.firstChild; } else { child = child.nextSibling || child.parentNode; } } } return added; }, /** * @param {Object} node * @return {Boolean} */ isVisible: function(node) { var parent = node.parentNode; if (!this.getRecursive() && parent !== this.getNode()) { return false; } while (parent) { if (!parent.isExpanded()) { return false; } //we need to check this because for a nodestore the node is not likely to be the root //so we stop going up the chain when we hit the original node as we don't care about any //ancestors above the configured node if (parent === this.getNode()) { break; } parent = parent.parentNode; } return true; }});