    /**
     * The TreeStore is a store implementation that allows for nested data.
     *
     * It provides convenience methods for loading nodes, as well as the ability to use
     * the hierarchical tree structure combined with a store. This class also relays many events from
     * the Tree for convenience.
     *
     * # Using Models
     *
     * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
     * The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed
     * in the {@link Ext.data.NodeInterface} documentation.
     *
     * # Reading Nested Data
     *
     * For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
     * so the reader can find nested data for each node. If a root is not specified, it will default to
     * 'children'.
     */
    Ext.define('Ext.data.TreeStore', {
        extend: 'Ext.data.NodeStore',
        alias: 'store.tree',

        config: {
            /**
             * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
             * The root node for this store. For example:
             *
             *     root: {
             *         expanded: true,
             *         text: "My Root",
             *         children: [
             *             { text: "Child 1", leaf: true },
             *             { text: "Child 2", expanded: true, children: [
             *                 { text: "GrandChild", leaf: true }
             *             ] }
             *         ]
             *     }
             *
             * Setting the `root` config option is the same as calling {@link #setRootNode}.
             * @accessor
             */
            root: undefined,

            /**
             * @cfg {Boolean} clearOnLoad
             * Remove previously existing child nodes before loading. Default to true.
             * @accessor
             */
            clearOnLoad : true,

            /**
             * @cfg {String} nodeParam
             * The name of the parameter sent to the server which contains the identifier of the node.
             * Defaults to 'node'.
             * @accessor
             */
            nodeParam: 'node',

            /**
             * @cfg {String} defaultRootId
             * The default root id. Defaults to 'root'
             * @accessor
             */
            defaultRootId: 'root',

            /**
             * @cfg {String} defaultRootProperty
             * The root property to specify on the reader if one is not explicitly defined.
             * @accessor
             */
            defaultRootProperty: 'children',

            /**
             * @cfg {Boolean} recursive
             * @private
             * @hide
             */
            recursive: true

            /**
             * @cfg {Object} node
             * @private
             * @hide
             */
        },

        applyProxy: function() {
            return Ext.data.Store.prototype.applyProxy.apply(this, arguments);
        },

        applyRoot: function(root) {
            var me = this;
            root = root || {};
            root = Ext.apply({}, root);

            if (!root.isModel) {
                Ext.applyIf(root, {
                    id: me.getDefaultRootId(),
                    text: 'Root',
                    allowDrag: false
                });

                root = Ext.data.ModelManager.create(root, me.getModel());
            }

            Ext.data.NodeInterface.decorate(root);
            root.set(root.raw);

            return root;
        },

        updateRoot: function(root, oldRoot) {
            if (oldRoot) {
                oldRoot.unBefore({
                    expand: 'onNodeBeforeExpand',
                    scope: this
                });
                oldRoot.unjoin(this);
            }

            root.onBefore({
                expand: 'onNodeBeforeExpand',
                scope: this
            });

            this.onNodeAppend(null, root);
            this.setNode(root);

            if (!root.isLoaded() && !root.isLoading() && root.isExpanded()) {
                this.load({
                    node: root
                });
            }

            /**
             * @event rootchange
             * Fires whenever the root node changes on this TreeStore.
             * @param {Ext.data.TreeStore} store This tree Store
             * @param {Ext.data.Model} newRoot The new root node
             * @param {Ext.data.Model} oldRoot The old root node
             */
            this.fireEvent('rootchange', this, root, oldRoot);
        },

        /**
         * Returns the record node by id
         * @return {Ext.data.NodeInterface}
         */
        getNodeById: function(id) {
            return this.data.getByKey(id);
        },

        onNodeBeforeExpand: function(node, options, e) {
            if (node.isLoading()) {
                e.pause();
                this.on('load', function() {
                    e.resume();
                }, this, {single: true});
            }
            else if (!node.isLoaded()) {
                e.pause();
                this.load({
                    node: node,
                    callback: function() {
                        e.resume();
                    }
                });
            }
        },

        onNodeAppend: function(parent, node) {
            var proxy = this.getProxy(),
                reader = proxy.getReader(),
                data = node.raw,
                records = [],
                rootProperty = reader.getRootProperty(),
                dataRoot, processedData, i, ln;

            if (!node.isLeaf()) {
                dataRoot = reader.getRoot(data);
                if (dataRoot) {
                    processedData = reader.extractData(dataRoot);
                    for (i = 0, ln = processedData.length; i < ln; i++) {
                        if (processedData[i].node[rootProperty]) {
                            processedData[i].data[rootProperty] = processedData[i].node[rootProperty];
                        }
                        records.push(processedData[i].data);
                    }
                    if (records.length) {
                        this.fillNode(node, records);
                    }
                    delete data[rootProperty];
                }
            }
        },

        updateAutoLoad: function(autoLoad) {
            if (autoLoad) {
                var root = this.getRoot();
                if (!root.isLoaded() && !root.isLoading()) {
                    this.load({node: root});
                }
            }
        },

        /**
         * Loads the Store using its configured {@link #proxy}.
         * @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}
         * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
         * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
         * default to the root node.
         */
        load: function(options) {
            options = options || {};
            options.params = options.params || {};

            var me = this,
                node = options.node = options.node || me.getRoot();

            options.params[me.getNodeParam()] = node.getId();

            if (me.getClearOnLoad()) {
                node.removeAll(true);
            }
            node.set('loading', true);

            return me.callParent([options]);
        },

        updateProxy: function(proxy) {
            this.callParent(arguments);

            var reader = proxy.getReader();
            if (!reader.getRootProperty()) {
                reader.setRootProperty(this.getDefaultRootProperty());
                reader.buildExtractors();
            }
        },

        // inherit docs
        removeAll: function() {
            this.getRootNode().removeAll(true);
            this.callParent(arguments);
        },

        // inherit docs
        onProxyLoad: function(operation) {
            var me = this,
                records = operation.getRecords(),
                successful = operation.wasSuccessful(),
                node = operation.getNode();

            node.beginEdit();
            node.set('loading', false);
            if (successful) {
                records = me.fillNode(node, records);
            }
            node.endEdit();

            node.fireEvent('load', node, records, successful);

            me.loading = false;
            me.fireEvent('load', this, records, successful);

            //this is a callback that would have been passed to the 'read' function and is optional
            Ext.callback(operation.getCallback(), operation.getScope() || me, [records, operation, successful]);
        },

        /**
         * Fills a node with a series of child records.
         * @private
         * @param {Ext.data.NodeInterface} node The node to fill
         * @param {Ext.data.Model[]} records The records to add
         */
        fillNode: function(node, records) {
            var ln = records ? records.length : 0,
                i, child;

            for (i = 0; i < ln; i++) {
                // true/true to suppress any events fired by the node, or the new child node
                child = node.appendChild(records[i], true, true);
                this.onNodeAppend(node, child);
            }
            node.set('loaded', true);

            return records;
        }

        // <deprecated product=touch since=2.0>
    }, function() {
        this.override({
            /**
             * Sets the root node for this tree.
             * @param {Ext.data.Model} node
             * @return {Ext.data.Model}
             * @deprecated
             */
            setRootNode: function(node) {
                // <debug>
                Ext.Logger.warn('setRootNode has been deprecated. Please use setRoot instead.');
                // </debug>
                return this.setRoot(node);
            },

            /**
             * Returns the root node for this tree.
             * @return {Ext.data.Model}
             * @deprecated
             */
            getRootNode: function(node) {
                // <debug>
                Ext.Logger.warn('getRootNode has been deprecated. Please use getRoot instead.');
                // </debug>
                return this.getRoot();
            }
        });
        // </deprecated>
    });