    /**
     * NestedList provides a miller column interface to navigate between nested sets
     * and provide a clean interface with limited screen real-estate.
     *
     *     @example miniphone preview
     *      var data = {
     *          text: 'Groceries',
     *          items: [{
     *              text: 'Drinks',
     *              items: [{
     *                  text: 'Water',
     *                  items: [{
     *                      text: 'Sparkling',
     *                      leaf: true
     *                  }, {
     *                      text: 'Still',
     *                      leaf: true
     *                  }]
     *              }, {
     *                  text: 'Coffee',
     *                  leaf: true
     *              }, {
     *                  text: 'Espresso',
     *                  leaf: true
     *              }, {
     *                  text: 'Redbull',
     *                  leaf: true
     *              }, {
     *                  text: 'Coke',
     *                  leaf: true
     *              }, {
     *                  text: 'Diet Coke',
     *                  leaf: true
     *              }]
     *          }, {
     *              text: 'Fruit',
     *              items: [{
     *                  text: 'Bananas',
     *                  leaf: true
     *              }, {
     *                  text: 'Lemon',
     *                  leaf: true
     *              }]
     *          }, {
     *              text: 'Snacks',
     *              items: [{
     *                  text: 'Nuts',
     *                  leaf: true
     *              }, {
     *                  text: 'Pretzels',
     *                  leaf: true
     *              }, {
     *                  text: 'Wasabi Peas',
     *                  leaf: true
     *              }]
     *          }]
     *      };
     *
     *      Ext.define('ListItem', {
     *          extend: 'Ext.data.Model',
     *          fields: [{
     *              name: 'text',
     *              type: 'string'
     *          }]
     *      });
     *
     *      var store = Ext.create('Ext.data.TreeStore', {
     *          model: 'ListItem',
     *          defaultRootProperty: 'items',
     *          root: data
     *      });
     *
     *      var nestedList = Ext.create('Ext.NestedList', {
     *          fullscreen: true,
     *          title: 'Groceries',
     *          displayField: 'text',
     *          store: store
     *      });
     *
     */
    Ext.define('Ext.dataview.NestedList', {
        alternateClassName: 'Ext.NestedList',
        extend: 'Ext.Container',
        xtype : 'nestedlist',
        requires: [
            'Ext.List',
            'Ext.Toolbar',
            'Ext.Button',
            'Ext.XTemplate',
            'Ext.data.StoreManager',
            'Ext.data.NodeStore',
            'Ext.data.TreeStore'
        ],

        config: {
            // @inherit
            cls: Ext.baseCSSPrefix + 'nested-list',

            /**
             * @cfg {String/Object/Boolean} cardSwitchAnimation
             * @deprecated 2.0.0 please use {@link Ext.layout.Card#animation}
             */

            /**
             * @cfg {String} backText
             * The label to display for the back button. Defaults to "Back".
             * @accessor
             */
            backText: 'Back',

            /**
             * @cfg {Boolean} useTitleAsBackText
             * @accessor
             */
            useTitleAsBackText: true,

            /**
             * @cfg {Boolean} updateTitleText
             * Update the title with the currently selected category. Defaults to true.
             * @accessor
             */
            updateTitleText: true,

            /**
             * @cfg {String} displayField
             * Display field to use when setting item text and title.
             * This configuration is ignored when overriding getItemTextTpl or
             * getTitleTextTpl for the item text or title. (Defaults to 'text')
             * @accessor
             */
            displayField: 'text',

            /**
             * @cfg {String} loadingText
             * Loading text to display when a subtree is loading.
             * @accessor
             */
            loadingText: 'Loading...',

            /**
             * @cfg {String} emptyText
             * Empty text to display when a subtree is empty.
             * @accessor
             */
            emptyText: 'No items available.',

            /**
             * @cfg {Boolean/Function} onItemDisclosure
             * Maps to the Ext.List onItemDisclosure configuration for individual lists. (Defaults to false)
             * @accessor
             */
            onItemDisclosure: false,

            /**
             * @cfg {Boolean} allowDeselect
             * Set to true to alow the user to deselect leaf items via interaction.
             * Defaults to false.
             * @accessor
             */
            allowDeselect: false,

            /**
             * @deprecated 2.0.0 Please set the {@link #toolbar} configuration to `false` instead
             * @cfg {Boolean} useToolbar True to show the header toolbar.
             * @accessor
             */
            useToolbar: null,

            /**
             * @cfg {Ext.Toolbar/Object/Boolean} toolbar
             * The configuration to be used for the toolbar displayed in this nested list.
             * @accessor
             */
            toolbar: {
                docked: 'top',
                xtype: 'titlebar',
                ui: 'light',
                inline: true
            },

            /**
             * @cfg {String} title The title of the toolbar
             * @accessor
             */
            title: '',

            /**
             * @cfg {String} layout
             * @hide
             * @accessor
             */
            layout: {
                type: 'card',
                animation: {
                    type: 'slide',
                    duration: 250,
                    direction: 'left'
                }
            },

            /**
             * @cfg {Ext.data.TreeStore} store The tree store to be used for this nested list.
             */
            store: null,

            /**
             * @cfg {Ext.Container} detailContainer The container of the detailCard.
             * @accessor
             */
            detailContainer: undefined,

            /**
             * @cfg {Ext.Component} detailCard to provide a final card for leaf nodes.
             * @accessor
             */
            detailCard: null,

            /**
             * @cfg {Object} backButton The configuration for the back button used in the nested list
             */
            backButton: {
                ui: 'back',
                hidden: true
            },

            /**
             * @cfg {Object} listConfig An optional config object which is merged with the default
             * configuration used to create each nested list
             */
            listConfig: null,

            // @private
            lastNode: null,

            // @private
            lastActiveList: null
        },

        /**
         * @event itemtap
         * Fires when a node is tapped on
         * @param {Ext.dataview.NestedList} this
         * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active
         * @param {Number} index The index of the item tapped
         * @param {Ext.dom.Element} target The element tapped
         * @param {Ext.data.Record} record The record tapped
         * @param {Ext.event.Event} e The event object
         */

        /**
         * @event itemdoubletap
         * Fires when a node is double tapped on
         * @param {Ext.dataview.NestedList} this
         * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active
         * @param {Number} index The index of the item that was tapped
         * @param {Object} item The item tapped
         * @param {Ext.event.Event} e The event object
         */

        /**
         * @event containertap
         * Fires when a tap occurs and it is not on a template node.
         * @param {Ext.dataview.NestedList} this
         * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active
         * @param {Ext.event.Event} e The raw event object
         */

        /**
         * @event selectionchange
         * Fires when the selected nodes change.
         * @param {Ext.dataview.NestedList} this
         * @param {Ext.dataview.List} list The Ext.datavaie.List that is currently active
         * @param {Array} selections Array of the selected nodes
         */

        /**
         * @event beforeselect
         * Fires before a selection is made.
         * @param {Ext.dataview.NestedList} this
         * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active
         * @param {HTMLElement} node The node to be selected
         * @param {Array} selections Array of currently selected nodes
         */

        /**
         * @event listchange
         * Fires when the user taps a list item
         * @param {Ext.dataview.NestedList} this
         * @param {Object} listitem The new active list
         */

        /**
         * @event leafitemtap
         * Fires when the user taps a leaf list item
         * @param {Ext.dataview.NestedList} this
         * @param {Ext.List} list The subList the item is on
         * @param {Number} index The index of the item tapped
         * @param {Ext.dom.Element} target The element tapped
         * @param {Ext.data.Record} record The record tapped
         * @param {Ext.event.Event} e The event
         */

        /**
         * @event back
         * @preventable doBack
         * Fires when the user taps Back
         * @param {Ext.dataview.NestedList} this
         * @param {HTMLElement} node The node to be selected
         * @param {Ext.dataview.List} lastActiveList The Ext.dataview.List that was last active
         * @param {Boolean} detailCardActive Flag set if the detail card is currently active
         */

        /**
         * @event beforeload
         * Fires before a request is made for a new data object.
         * @param {Ext.dataview.NestedList} this
         * @param {Ext.data.Store} store The store instance
         * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
         * load the Store
         */

        /**
         * @event load
         * Fires whenever records have been loaded into the store.
         * @param {Ext.dataview.NestedList} this
         * @param {Ext.data.Store} store The store instance
         * @param {Ext.util.Grouper[]} records An array of records
         * @param {Boolean} successful True if the operation was successful.
         * @param {Ext.data.Operation} operation The associated operation
         */

            //@private
        initialize: function() {
            var me = this;
            me.callParent();

            me.on({
                delegate: '> list',
                itemdoubletap: 'onItemDoubleTap',
                itemtap: 'onItemTap',
                beforeselect: 'onBeforeSelect',
                containertap: 'onContainerTap',
                selectionchange: 'onSelectionChange',
                scope: me
            });
        },

        applyDetailContainer: function(config) {
            if (!config) {
                config = this;
            }

            return config;
        },

        /**
         * Called when an list item has been tapped
         * @param {Ext.List} list The subList the item is on
         * @param {Number} index The id of the item tapped
         * @param {Ext.Element} target The list item tapped
         * @param {Ext.data.Record} record The record whichw as tapped
         * @param {Ext.event.Event} e The event
         */
        onItemTap: function(list, index, target, record, e) {
            var me = this,
                store = list.getStore(),
                node = store.getAt(index);

            me.fireEvent('itemtap', this, list, index, target, record, e);
            if (node.isLeaf()) {
                me.fireEvent('leafitemtap', this, list, index, target, record, e);
                me.goToLeaf(node);
            }
            else {
                this.goToNode(node);
            }
        },

        onBeforeSelect: function() {
            this.fireEvent('beforeselect', [this, Array.prototype.slice.call(arguments)]);
        },

        onContainerTap: function() {
            this.fireEvent('containertap', [this, Array.prototype.slice.call(arguments)]);
        },

        onSelectionChange: function() {
            this.fireEvent('selectionchange', [this, Array.prototype.slice.call(arguments)]);
        },

        onItemDoubleTap: function() {
            this.fireEvent('itemdoubletap', [this, Array.prototype.slice.call(arguments)]);
        },

        onStoreBeforeLoad: function() {
            this.fireEvent('beforeload', [this, Array.prototype.slice.call(arguments)]);
        },

        onStoreLoad: function() {
            this.fireEvent('load', [this, Array.prototype.slice.call(arguments)]);
        },

        /**
         * Called when the backButton has been tapped
         */
        onBackTap: function() {
            var me = this,
                node = me.getLastNode(),
                detailCard = me.getDetailCard(),
                detailCardActive = detailCard && me.getActiveItem() == detailCard,
                lastActiveList = me.getLastActiveList();

            this.fireAction('back', [this, node, lastActiveList, detailCardActive], 'doBack');
        },

        doBack: function(me, node, lastActiveList, detailCardActive) {
            var layout = me.getLayout(),
                animation = (layout) ? layout.getAnimation() : null;

            if (detailCardActive && lastActiveList) {
                if (animation) {
                    animation.setReverse(true);
                }
                me.setActiveItem(lastActiveList);
                me.setLastNode(node.parentNode);
                me.syncToolbar();
            }
            else {
                this.goToNode(node.parentNode);
            }
        },

        updateData: function(data) {
            if (!this.getStore()) {
                this.setStore(new Ext.data.TreeStore({
                    root: data
                }));
            }
        },

        applyStore: function(store) {
            if (store) {
                store = Ext.data.StoreManager.lookup(store);

                //<debug warn>
                if (!store)  {
                    Ext.Logger.warn("The specified Store cannot be found", this);
                }
                //</debug>
            }

            return store;
        },

        updateStore: function(newStore, oldStore) {
            var me = this;
            if (oldStore && Ext.isObject(oldStore) && oldStore.isStore) {
                if (oldStore.autoDestroy) {
                    oldStore.destroy();
                }
                oldStore.un({
                    rootchange: 'onStoreRootChange',
                    scope: this
                });
            }

            if (newStore) {
                me.goToNode(newStore.getRoot());
                newStore.on({
                    rootchange: 'onStoreRootChange',
                    scope: this
                });
            }
        },

        onStoreRootChange: function(store, node) {
            this.goToNode(node);
        },

        applyBackButton: function(config) {
            return Ext.factory(config, Ext.Button, this.getBackButton());
        },

        applyDetailCard: function(config) {
            return this.factoryItem(config);
        },

        updateBackButton: function(newButton, oldButton) {
            if (newButton) {
                var me = this;
                newButton.on('tap', me.onBackTap, me);
                newButton.setText(me.getBackText());
                me.getToolbar().insert(0, newButton);
            }
            else if (oldButton) {
                oldButton.destroy();
            }
        },

        applyToolbar: function(config) {
            return Ext.factory(config, Ext.TitleBar, this.getToolbar());
        },

        updateToolbar: function(newToolbar, oldToolbar) {
            var me = this;
            if (newToolbar) {
                newToolbar.setTitle(me.getTitle());
                if (!newToolbar.getParent()) {
                    me.add(newToolbar);
                }
            }
            else if (oldToolbar) {
                oldToolbar.destroy();
            }
        },

        updateUseToolbar: function(newUseToolbar, oldUseToolbar) {
            if (!newUseToolbar) {
                this.setToolbar(false);
            }
        },

        updateTitle: function(newTitle) {
            var me = this,
                toolbar = me.getToolbar();
            if (toolbar) {
                if (me.getUpdateTitleText()) {
                    toolbar.setTitle(newTitle);
                }
            }
        },

        /**
         * Override this method to provide custom template rendering of individual
         * nodes. The template will receive all data within the Record and will also
         * receive whether or not it is a leaf node.
         * @param {Ext.data.Record} node
         */
        getItemTextTpl: function(node) {
            return '{' + this.getDisplayField() + '}';
        },

        /**
         * Override this method to provide custom template rendering of titles/back
         * buttons when useTitleAsBackText is enabled.
         * @param {Ext.data.Record} node
         */
        getTitleTextTpl: function(node) {
            return '{' + this.getDisplayField() + '}';
        },

        /**
         * @private
         */
        renderTitleText: function(node, forBackButton) {
            if (!node.titleTpl) {
                node.titleTpl = Ext.create('Ext.XTemplate', this.getTitleTextTpl(node));
            }

            if (node.isRoot()) {
                var initialTitle = this.getInitialConfig('title');
                return (forBackButton && initialTitle === '') ? this.getInitialConfig('backText') : initialTitle;
            }

            return  node.titleTpl.applyTemplate(node.data);
        },

        /**
         * Method to handle going to a specific node within this nested list. Node must be part of the
         * internal {@link #store}.
         * @param {Ext.data.NodeInterface} node The specified node to navigate to
         */
        goToNode: function(node) {
            if (!node) {
                return;
            }

            var me = this,

                activeItem = me.getActiveItem(),
                detailCard = me.getDetailCard(),
                detailCardActive = detailCard && me.getActiveItem() == detailCard,
                reverse = me.goToNodeReverseAnimation(node),
                firstList = me.firstList,
                secondList = me.secondList,
                layout = me.getLayout(),
                animation = (layout) ? layout.getAnimation() : null,
                list;

            //if the node is a leaf, throw an error
            if (node.isLeaf()) {
                throw new Error('goToNode: passed a node which is a leaf.');
            }

            //if we are currently at the passed node, do nothing.
            if (node == me.getLastNode() && !detailCardActive) {
                return;
            }

            if (detailCardActive) {
                if (animation) {
                    animation.setReverse(true);
                }
                me.setActiveItem(me.getLastActiveList());
            }
            else {
                if (firstList && secondList) {
                    //firstList and secondList have both been created
                    activeItem = me.getActiveItem();

                    me.setLastActiveList(activeItem);
                    list = (activeItem == firstList) ? secondList : firstList;
                    list.getStore().setNode(node);
                    node.expand();

                    if (animation) {
                        animation.setReverse(reverse);
                    }
                    me.setActiveItem(list);
                    list.deselectAll();
                }
                else if (firstList) {
                    //only firstList has been created
                    me.setLastActiveList(me.getActiveItem());
                    me.setActiveItem(me.getList(node));
                    me.secondList = me.getActiveItem();
                }
                else {
                    //no lists have been created
                    me.setActiveItem(me.getList(node));
                    me.firstList = me.getActiveItem();
                }
            }

            me.fireEvent('listchange', this, me.getActiveItem());

            me.setLastNode(node);

            me.syncToolbar();
        },

        /**
         * The leaf you want to navigate to. You should pass a node instance.
         * @param {Ext.data.NodeInterface} node The specified node to navigate to
         */
        goToLeaf: function(node) {
            if (!node.isLeaf()) {
                throw new Error('goToLeaf: passed a node which is not a leaf.');
            }

            var me = this,
                card = me.getDetailCard(node),
                container = me.getDetailContainer(),
                sharedContainer = container == this,
                layout = me.getLayout(),
                animation = (layout) ? layout.getAnimation() : false;

            if (card) {
                if (container.getItems().indexOf(card) === -1) {
                    container.add(card);
                }
                if (sharedContainer) {
                    if (me.getActiveItem() instanceof Ext.dataview.List) {
                        me.setLastActiveList(me.getActiveItem());
                    }
                    me.setLastNode(node);
                }
                if (animation) {
                    animation.setReverse(false);
                }
                container.setActiveItem(card);
                me.syncToolbar();
            }
        },

        /**
         * @private
         * Method which updates the {@link #backButton} and {@link #toolbar} with the latest information from
         * the current node.
         */
        syncToolbar: function(forceDetail) {
            var me = this,
                detailCard = me.getDetailCard(),
                node = me.getLastNode(),
                detailActive = forceDetail || (detailCard && (me.getActiveItem() == detailCard)),
                parentNode = (detailActive) ? node : node.parentNode,
                backButton = me.getBackButton();

            //show/hide the backButton, and update the backButton text, if one exists
            if (backButton) {
                backButton[parentNode ? 'show' : 'hide']();
                if (parentNode && me.getUseTitleAsBackText()) {
                    backButton.setText(me.renderTitleText(node.parentNode, true));
                }
            }

            if (node) {
                me.setTitle(me.renderTitleText(node));
            }
        },

        updateBackText: function(newText) {
            this.getBackButton().setText(newText);
        },

        /**
         * @private
         * Returns true if the passed node should have a reverse animation from the previous current node
         * @param {Ext.data.NodeInterface} node
         */
        goToNodeReverseAnimation: function(node) {
            var me = this,
                lastNode = me.getLastNode();
            if (!lastNode) {
                return false;
            }

            return (!lastNode.contains(node) && lastNode.isAncestor(node)) ? true : false;
        },

        /**
         * @private
         * Returns the list config for a specified node.
         * @param {HTMLElement} node The node for the list config
         */
        getList: function(node) {
            var me = this,
                nodeStore = Ext.create('Ext.data.NodeStore', {
                    recursive: false,
                    node: node,
                    rootVisible: false,
                    model: me.getStore().getModel()
                });

            node.expand();

            return Ext.Object.merge({
                xtype: 'list',
                pressedDelay: 0,
                autoDestroy: true,
                store: nodeStore,
                onItemDisclosure: me.getOnItemDisclosure(),
                allowDeselect : me.getAllowDeselect(),
                itemTpl: '<span<tpl if="leaf == true"> class="x-list-item-leaf"</tpl>>' + me.getItemTextTpl(node) + '</span>'
            }, this.getListConfig());
        }
    });