    /**
     * 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,

        /**
         * @cfg {Ext.data.Model} node
         * The Record you want to bind this Store to. Note that
         * this record will be decorated with the Ext.data.NodeInterface if this is not the
         * case yet.
         */
        node: null,

        /**
         * @cfg {Boolean} recursive
         * Set this to true if you want this NodeStore to represent
         * all the descendents 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.
         */
        recursive: false,

        /**
         * @cfg {Boolean} rootVisible
         * False to not include the root node in this Stores collection.
         */
        rootVisible: false,

        /**
         * @cfg {Ext.data.TreeStore} treeStore
         * The TreeStore that is used by this NodeStore's Ext.tree.View.
         */

        /**
         * @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,

        constructor: function(config) {
            var me = this,
                node;

            config = config || {};
            Ext.apply(me, config);

            //<debug>
            if (Ext.isDefined(me.proxy)) {
                Ext.Error.raise("A NodeStore cannot be bound to a proxy. Instead bind it to a record " +
                                "decorated with the NodeInterface by setting the node config.");
            }
            me.useModelWarning = false;
            //</debug>

            config.proxy = {type: 'proxy'};
            me.callParent([config]);

            node = me.node;
            if (node) {
                me.node = null;
                me.setNode(node);
            }
        },

        // 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();
        },

        setNode: function(node) {
            var me = this;
            if (me.node && me.node != node) {
                // We want to unbind our listeners on the old node
                me.mun(me.node, {
                    expand: me.onNodeExpand,
                    collapse: me.onNodeCollapse,
                    append: me.onNodeAppend,
                    insert: me.onNodeInsert,
                    bulkremove: me.onBulkRemove,
                    remove: me.onNodeRemove,
                    sort: me.onNodeSort,
                    scope: me
                });
                me.node = null;
            }

            if (node) {
                Ext.data.NodeInterface.decorate(node.self);
                me.removeAll();
                if (me.rootVisible) {
                    me.add(node);
                } else if (!node.isExpanded() && me.treeStore.autoLoad !== false) {
                    node.expand();
                }

                me.mon(node, {
                    expand: me.onNodeExpand,
                    collapse: me.onNodeCollapse,
                    append: me.onNodeAppend,
                    insert: me.onNodeInsert,
                    bulkremove: me.onBulkRemove,
                    remove: me.onNodeRemove,
                    sort: me.onNodeSort,
                    scope: me
                });
                me.node = node;
                if (node.isExpanded() && node.isLoaded()) {
                    me.onNodeExpand(node, node.childNodes, true);
                }
            }
        },

        onNodeSort: function(node, childNodes) {
            var me = this;

            if ((me.indexOf(node) !== -1 || (node === me.node && !me.rootVisible) && node.isExpanded())) {
                Ext.suspendLayouts();
                me.onNodeCollapse(node, childNodes, true);
                me.onNodeExpand(node, childNodes, true);
                Ext.resumeLayouts(true);
            }
        },

        // Triggered by a NodeInterface's bubbled "expand" event.
        onNodeExpand: function(parent, records, suppressEvent) {
            var me = this,
                insertIndex = me.indexOf(parent) + 1,
                toAdd = [];

            // Used by the TreeView to bracket recursive expand & collapse ops
            // and refresh the size. This is most effective when folder nodes are loaded,
            // and this method is able to recurse.
            if (!suppressEvent) {
                me.fireEvent('beforeexpand', parent, records, insertIndex);
            }

            me.handleNodeExpand(parent, records, toAdd);

            // The add event from this insertion is handled by TreeView.onAdd.
            // That implementation calls parent and then ensures the previous sibling's joining lines are correct.
            // The datachanged event is relayed by the TreeStore. Internally, that's not used.
            me.insert(insertIndex, toAdd);

            // Triggers the TreeView's onExpand method which calls refreshSize,
            // and fires its afteritemexpand event
            if (!suppressEvent) {
                me.fireEvent('expand', parent, records);
            }
        },

        // Collects child nodes to remove into the passed toRemove array.
        // When available, all descendant nodes are pushed into that array using recursion.
        handleNodeExpand: function(parent, records, toAdd) {
            var me = this,
                ln = records ? records.length : 0,
                i, record;

            // recursive is hardcoded to true in TreeView.
            if (!me.recursive && parent !== me.node) {
                return;
            }

            if (parent !== this.node && !me.isVisible(parent)) {
                return;
            }

            if (ln) {
                // The view items corresponding to these are rendered.
                // Loop through and expand any of the non-leaf nodes which are expanded
                for (i = 0; i < ln; i++) {
                    record = records[i];

                    // Add to array being collected by recursion when child nodes are loaded.
                    // Must be done here in loop so that child nodes are inserted into the stream in place
                    // in recursive calls.
                    toAdd.push(record);

                    if (record.isExpanded()) {
                        if (record.isLoaded()) {
                            // Take a shortcut - appends to toAdd array
                            me.handleNodeExpand(record, record.childNodes, toAdd);
                        }
                        else {
                            // Might be asynchronous if child nodes are not immediately available
                            record.set('expanded', false);
                            record.expand();
                        }
                    }
                }
            }
        },

        // Triggered by NodeInterface's bubbled bulkremove event
        onBulkRemove: function(parent, childNodes, isMove) {
            this.onNodeCollapse(parent, childNodes, true);
        },

        // Triggered by a NodeInterface's bubbled "collapse" event.
        onNodeCollapse: function(parent, records, suppressEvent, callback, scope) {
            var me = this,
                collapseIndex = me.indexOf(parent) + 1,
                node, lastNodeIndexPlus, sibling, found;

            if (!me.recursive && parent !== me.node) {
                return;
            }

            // Used by the TreeView to bracket recursive expand & collapse ops.
            // The TreeViewsets up the animWrap object if we are animating.
            // It also caches the collapse callback to call when it receives the
            // end collapse event. See below.
            if (!suppressEvent) {
                me.fireEvent('beforecollapse', parent, records, collapseIndex, callback, scope);
            }

            // Only attempt to remove the records if they are there.
            // Collapsing an ancestor node *immediately removes from the view, ALL its descendant nodes at all levels*.
            // But if the collapse was recursive, all descendant root nodes will still fire their
            // events. But we must ignore those events here - we have nothing to do.
            if (records.length && me.data.contains(records[0])) {

                // Calculate the index *one beyond* the last node we are going to remove
                // Need to loop up the tree to find the nearest view sibling, since it could
                // exist at some level above the current node.
                node = parent;
                while (node.parentNode) {
                    sibling = node.nextSibling;
                    if (sibling) {
                        found = true;
                        lastNodeIndexPlus = me.indexOf(sibling);
                        break;
                    } else {
                        node = node.parentNode;
                    }
                }
                if (!found) {
                    lastNodeIndexPlus = me.getCount();
                }

                // Remove the whole collapsed node set.
                me.removeAt(collapseIndex, lastNodeIndexPlus - collapseIndex);
            }

            // Triggers the TreeView's onCollapse method which calls refreshSize,
            // and fires its afteritecollapse event
            if (!suppressEvent) {
                me.fireEvent('collapse', parent, records, collapseIndex);
            }
        },

        onNodeAppend: function(parent, node, index) {
            var me = this,
                refNode, sibling;

            // Only react to a node append if it is to a node which is expanded, and is part of a tree
            if (me.isVisible(node)) {
                if (index === 0) {
                    refNode = parent;
                } else {
                    sibling = node.previousSibling;
                    while (sibling.isExpanded() && sibling.lastChild) {
                        sibling = sibling.lastChild;
                    }
                    refNode = sibling;
                }
                me.insert(me.indexOf(refNode) + 1, node);
                if (!node.isLeaf() && node.isExpanded()) {
                    if (node.isLoaded()) {
                        // Take a shortcut
                        me.onNodeExpand(node, node.childNodes, true);
                    } else if (!me.treeStore.fillCount ) {
                        // If the node has been marked as expanded, it means the children
                        // should be provided as part of the raw data. If we're filling the nodes,
                        // the children may not have been loaded yet, so only do this if we're
                        // not in the middle of populating the nodes.
                        node.set('expanded', false);
                        node.expand();
                    }
                }
            }
        },

        onNodeInsert: function(parent, node, refNode) {
            var me = this,
                index = this.indexOf(refNode);

            if (index != -1 && me.isVisible(node)) {
                me.insert(index, node);
                if (!node.isLeaf() && node.isExpanded()) {
                    if (node.isLoaded()) {
                        // Take a shortcut
                        me.onNodeExpand(node, node.childNodes, true);
                    }
                    else {
                        node.set('expanded', false);
                        node.expand();
                    }
                }
            }
        },

        onNodeRemove: function(parent, node, isMove) {
            var me = this;
            if (me.indexOf(node) != -1) {

                // If the removed node is a non-leaf and is expanded, use the onCollapse method to get rid
                // of all descendants at any level.
                if (!node.isLeaf() && node.isExpanded()) {

                    // onCollapse expects to be able to use the "collapsing" node's parentNode
                    // and nextSibling pointers so temporarily reinstate them.
                    // Reinstating them is safe because we pass the suppressEvents flag, and no user code
                    // is executed.
                    node.parentNode = node.removeContext.parentNode;
                    node.nextSibling = node.removeContext.nextSibling;
                    me.onNodeCollapse(node, node.childNodes, true);
                    node.parentNode = node.nextSibling = null;
                }
                me.remove(node);
            }
        },

        isVisible: function(node) {
            var parent = node.parentNode;
            while (parent) {
                // Hit root and it is expanded, the node is visible
                if (parent === this.node && parent.data.expanded) {
                    return true;
                }

                // Hit a collapsed ancestor, the node is not visible
                if (!parent.data.expanded) {
                    return false;
                }

                parent = parent.parentNode;
            }
            // Walked off the top - the node is not part of the tree structure
            return false;
        }
    });