    /** * Node Store * @private */Ext.define('Ext.data.NodeStore', {    extend: 'Ext.data.Store',    alias: 'store.node',    requires: [        'Ext.data.TreeModel',        '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    },     implicitModel: 'Ext.data.TreeModel',     // 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();    },     updateFolderSort: function(folderSort) {        var data = this.getData();         data.setTrackGroups(false);        if (folderSort) {            data.setGrouper({                groupFn: this.folderSortFn            });        } else {            data.setGrouper(null);        }    },     folderSortFn: function(node) {        return node.data.leaf ? 1 : 0;    },     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) {        if (parent === this.getNode()) {            this.add([node].concat(this.retrieveChildNodes(node)));        }    },     onNodeInsert: function(parent, node, refNode) {        var me = this,            idx;         if (parent === me.getNode()) {            idx = me.indexOf(refNode) || 0;            me.insert(0, [node].concat(me.retrieveChildNodes(node)));        }    },     onNodeRemove: function(parent, node) {        if (parent === this.getNode()) {            this.remove([node].concat(this.retrieveChildNodes(node)));        }    },     onNodeExpand: function(parent, records) {        if (parent === this.getNode()) {            this.loadRecords(records);        }    },     applyNode: function(node) {        if (node) {            if (!node.isModel) {                node = new (this.getModel())(node);            }            if (!node.isNode) {                Ext.data.NodeInterface.decorate(node);            }        }        return node;    },     updateNode: function(node, oldNode) {        var me = this,            data;         if (oldNode && !oldNode.destroyed) {            oldNode.un({                append  : 'onNodeAppend',                insert  : 'onNodeInsert',                remove  : 'onNodeRemove',                scope: me            });            oldNode.unjoin(me);        }         if (node) {            node.on({                scope   : me,                append  : 'onNodeAppend',                insert  : 'onNodeInsert',                remove  : 'onNodeRemove'            });             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();            if (me.isInitializing) {                me.inlineData = data;            } else {                me.add(data);            }            me.resumeEvents();             if (data.length === 0) {                me.loaded = node.loaded = true;            }             me.fireEvent('refresh', me, me.data);        }    },     /**     * @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;    },     privates: {        /**         * Private method used to deeply retrieve the children of a record without recursion.         * @private         * @param {Ext.data.NodeInterface} root         * @return {Ext.data.NodeInterface[]}         */        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;        }    }});