/**
 * DataView makes it easy to create lots of components dynamically, usually based off a {@link Ext.data.Store Store}.
 * It's great for rendering lots of data from your server backend or any other data source and is what powers
 * components like {@link Ext.List}.
 *
 * Use DataView whenever you want to show sets of the same component many times, for examples in apps like these:
 *
 * - List of messages in an email app
 * - Showing latest news/tweets
 * - Tiled set of albums in an HTML5 music player
 *
 * # Creating a Simple DataView
 *
 * At its simplest, a DataView is just a Store full of data and a simple template that we use to render each item:
 *
 *     @example miniphone preview
 *     var gbTeam = Ext.create('Ext.DataView', {
 *         fullscreen: true,
 *         store: {
 *             fields: ['name', 'age'],
 *             data: [
 *                 {name: 'Peter',  age: 26},
 *                 {name: 'Ray',   age: 21},
 *                 {name: 'Egon', age: 24},
 *                 {name: 'Winston', age: 24}
 *             ]
 *         },
 *
 *         itemTpl: '<div>{name} is {age} years old</div>'
 *     });
 *
 * Here we just defined everything inline so it's all local with nothing being loaded from a server. For each of the 5
 * data items defined in our Store, DataView will render a {@link Ext.Component Component} and pass in the name and age
 * data. The component will use the tpl we provided above, rendering the data in the curly bracket placeholders we
 * provided.
 *
 * Because DataView is integrated with Store, any changes to the Store are immediately reflected on the screen. For
 * example, if we add a new record to the Store it will be rendered into our DataView:
 *
 *     gbTeam.getStore().add({
 *         name: 'Gozer',
 *         age: 567
 *     });
 *
 * We didn't have to manually update the DataView, it's just automatically updated. The same happens if we modify one
 * of the existing records in the Store:
 *
 *     gbTeam.getStore().getAt(0).set('age', 42);
 *
 * This will get the first record in the Store (Peter), change the age to 42 and automatically update what's on the
 * screen.
 *
 *     @example miniphone
 *     var gbTeam = Ext.create('Ext.DataView', {
 *         fullscreen: true,
 *         store: {
 *             fields: ['name', 'age'],
 *             data: [
 *                 {name: 'Peter',  age: 26},
 *                 {name: 'Ray',   age: 21},
 *                 {name: 'Egon', age: 24},
 *                 {name: 'Winston', age: 24}
 *             ]
 *         },
 *
 *         itemTpl: '<div>{name} is {age} years old</div>'
 *     });
 *
 *     gbTeam.getStore().add({
 *         name: 'Gozer',
 *         age: 21
 *     });
 *
 *     gbTeam.getStore().getAt(0).set('age', 42);
 *
 * # Loading data from a server
 *
 * We often want to load data from our server or some other web service so that we don't have to hard code it all
 * locally. Let's say we want to load some horror movies from Rotten Tomatoes into a DataView, and for each one
 * render the cover image and title. To do this all we have to do is grab an api key from rotten tomatoes (http://developer.rottentomatoes.com/)
 * and modify the {@link #store} and {@link #itemTpl} a little:
 *
 *     @example portrait
 *     Ext.create('Ext.DataView', {
 *         fullscreen: true,
 *         store: {
 *             autoLoad: true,
 *             fields: ['id', 'title',
 *              {
 *                  name:'thumbnail_image',
 *                  convert: function(v, record) {return record.raw.posters.thumbnail; }
 *              }],
 *
 *             proxy: {
 *                 type: 'jsonp',
 *                 // Modify this line with your API key, pretty please...
 *                 url: 'http://api.rottentomatoes.com/api/public/v1.0/movies.json?apikey=hbjgfgryw8tygxztr5wtag3u&q=Horror',
 *
 *                 reader: {
 *                     type: 'json',
 *                     rootProperty: 'results'
 *                 }
 *             }
 *         },
 *
 *         itemTpl: '<img src="{thumbnail_image}" /><p>{title}</p><div style="clear: both"></div>'
 *     });
 *
 * The Store no longer has hard coded data, instead we've provided a {@link Ext.data.proxy.Proxy Proxy}, which fetches
 * the data for us. In this case we used a JSON-P proxy so that we can load from Twitter's JSON-P search API. We also
 * specified the fields present for each tweet, and used Store's {@link Ext.data.Store#autoLoad autoLoad} configuration
 * to load automatically. Finally, we configured a Reader to decode the response from Twitter, telling it to expect
 * JSON and that the tweets can be found in the 'results' part of the JSON response.
 *
 * The last thing we did is update our template to render the image, Twitter username and message. All we need to do
 * now is add a little CSS to style the list the way we want it and we end up with a very basic Twitter viewer. Click
 * the preview button on the example above to see it in action.
 */
Ext.define('Ext.dataview.DataView', {
    extend: 'Ext.Container',

    alternateClassName: 'Ext.DataView',

    mixins: ['Ext.mixin.Selectable'],

    xtype: 'dataview',

    requires: [
        'Ext.LoadMask',
        'Ext.data.StoreManager',
        'Ext.dataview.component.Container',
        'Ext.dataview.element.Container'
    ],

    /**
     * @event containertap
     * Fires when a tap occurs and it is not on a template node.
     * @removed 2.0.0
     */

    /**
     * @event itemtouchstart
     * Fires whenever an item is touched
     * @param {Ext.dataview.DataView} this
     * @param {Number} index The index of the item touched
     * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem touched
     * @param {Ext.data.Model} record The record associated to the item
     * @param {Ext.event.Event} e The event object
     */

    /**
     * @event itemtouchmove
     * Fires whenever an item is moved
     * @param {Ext.dataview.DataView} this
     * @param {Number} index The index of the item moved
     * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem moved
     * @param {Ext.data.Model} record The record associated to the item
     * @param {Ext.event.Event} e The event object
     */

    /**
     * @event itemtouchend
     * Fires whenever an item is touched
     * @param {Ext.dataview.DataView} this
     * @param {Number} index The index of the item touched
     * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem touched
     * @param {Ext.data.Model} record The record associated to the item
     * @param {Ext.event.Event} e The event object
     */

    /**
     * @event itemtap
     * Fires whenever an item is tapped
     * @param {Ext.dataview.DataView} this
     * @param {Number} index The index of the item tapped
     * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem tapped
     * @param {Ext.data.Model} record The record associated to the item
     * @param {Ext.event.Event} e The event object
     */

    /**
     * @event itemtaphold
     * Fires whenever an item's taphold event fires
     * @param {Ext.dataview.DataView} this
     * @param {Number} index The index of the item touched
     * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem touched
     * @param {Ext.data.Model} record The record associated to the item
     * @param {Ext.event.Event} e The event object
     */

    /**
     * @event itemsingletap
     * Fires whenever an item is singletapped
     * @param {Ext.dataview.DataView} this
     * @param {Number} index The index of the item singletapped
     * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem singletapped
     * @param {Ext.data.Model} record The record associated to the item
     * @param {Ext.event.Event} e The event object
     */

    /**
     * @event itemdoubletap
     * Fires whenever an item is doubletapped
     * @param {Ext.dataview.DataView} this
     * @param {Number} index The index of the item doubletapped
     * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem doubletapped
     * @param {Ext.data.Model} record The record associated to the item
     * @param {Ext.event.Event} e The event object
     */

    /**
     * @event itemswipe
     * Fires whenever an item is swiped
     * @param {Ext.dataview.DataView} this
     * @param {Number} index The index of the item swiped
     * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem swiped
     * @param {Ext.data.Model} record The record associated to the item
     * @param {Ext.event.Event} e The event object
     */

    /**
     * @event select
     * @preventable doItemSelect
     * Fires whenever an item is selected
     * @param {Ext.dataview.DataView} this
     * @param {Ext.data.Model} record The record associated to the item
     */

    /**
     * @event deselect
     * @preventable doItemDeselect
     * Fires whenever an item is deselected
     * @param {Ext.dataview.DataView} this
     * @param {Ext.data.Model} record The record associated to the item
     * @param {Boolean} supressed Flag to suppress the event
     */

    /**
     * @event refresh
     * @preventable doRefresh
     * Fires whenever the DataView is refreshed
     * @param {Ext.dataview.DataView} this
     */

    /**
     * @hide
     * @event add
     */

    /**
     * @hide
     * @event remove
     */

    /**
     * @hide
     * @event move
     */

    config: {
        /**
         * @cfg layout
         * Hide layout config in DataView. It only causes confusion.
         * @accessor
         * @private
         */

        /**
         * @cfg {Ext.data.Store/Object} store
         * Can be either a Store instance or a configuration object that will be turned into a Store. The Store is used
         * to populate the set of items that will be rendered in the DataView. See the DataView intro documentation for
         * more information about the relationship between Store and DataView.
         * @accessor
         */
        store: null,

        /**
         * @cfg {Object[]} data
         * @inheritdoc
         */
        data: null,

        /**
         * @cfg baseCls
         * @inheritdoc
         */
        baseCls: Ext.baseCSSPrefix + 'dataview',

        /**
         * @cfg {String} emptyText
         * The text to display in the view when there is no data to display
         */
        emptyText: null,

        /**
         * @cfg {Boolean} deferEmptyText `true` to defer `emptyText` being applied until the store's first load.
         */
        deferEmptyText: true,

        /**
         * @cfg {String/String[]/Ext.XTemplate} itemTpl
         * The `tpl` to use for each of the items displayed in this DataView.
         */
        itemTpl: '<div>{text}</div>',

        /**
         * @cfg {String} pressedCls
         * The CSS class to apply to an item on the view while it is being pressed.
         * @accessor
         */
        pressedCls: 'x-item-pressed',

        /**
         * @cfg {String} itemCls
         * An additional CSS class to apply to items within the DataView.
         * @accessor
         */
        itemCls: null,

        /**
         * @cfg {String} selectedCls
         * The CSS class to apply to an item on the view while it is selected.
         * @accessor
         */
        selectedCls: 'x-item-selected',

        /**
         * @cfg {String} triggerEvent
         * Determines what type of touch event causes an item to be selected.
         * Valid options are: 'itemtap', 'itemsingletap', 'itemdoubletap', 'itemswipe', 'itemtaphold'.
         * @accessor
         */
        triggerEvent: 'itemtap',

        /**
         * @cfg {String} triggerCtEvent
         * Determines what type of touch event is recognized as a touch on the container.
         * Valid options are 'tap' and 'singletap'.
         * @accessor
         */
        triggerCtEvent: 'tap',

        /**
         * @cfg {Boolean} deselectOnContainerClick
         * When set to true, tapping on the DataView's background (i.e. not on
         * an item in the DataView) will deselect any currently selected items.
         * @accessor
         */
        deselectOnContainerClick: true,

        /**
         * @cfg scrollable
         * @inheritdoc
         */
        scrollable: true,

        /**
         * @cfg {Boolean/Object} inline
         * When set to `true` the items within the DataView will have their display set to inline-block
         * and be arranged horizontally. By default the items will wrap to the width of the DataView.
         * Passing an object with `{ wrap: false }` will turn off this wrapping behavior and overflowed
         * items will need to be scrolled to horizontally.
         * @accessor
         */
        inline: null,

        /**
         * @cfg {Number} pressedDelay
         * The amount of delay between the `tapstart` and the moment we add the `pressedCls`.
         *
         * Settings this to `true` defaults to 100ms.
         * @accessor
         */
        pressedDelay: 100,

        /**
         * @cfg {String/Boolean} loadingText
         * A string to display during data load operations.  If specified, this text will be
         * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
         * contents will continue to display normally until the new data is loaded and the contents are replaced.
         */
        loadingText: 'Loading...',

        /**
         * @cfg {Boolean} useComponents
         * Flag the use a component based DataView implementation.  This allows the full use of components in the
         * DataView at the cost of some performance.
         *
         * @accessor
         */
        useComponents: null,

        /**
         * @cfg {Object} itemConfig
         * A configuration object that is passed to every item created by a component based DataView. Because each
         * item that a DataView renders is a Component, we can pass configuration options to each component to
         * easily customize how each child component behaves.
         *
         * __Note:__ this is only used when `{@link #useComponents}` is `true`.
         * @accessor
         */
        itemConfig: {},

        /**
         * @cfg {Number} maxItemCache
         * Maintains a cache of reusable components when using a component based DataView.  Improving performance at
         * the cost of memory.
         *
         * __Note:__ this is currently only used when `{@link #useComponents}` is `true`.
         * @accessor
         */
        maxItemCache: 20,

        /**
         * @cfg {String} defaultType
         * The xtype used for the component based DataView.
         *
         * __Note:__ this is only used when `{@link #useComponents}` is `true`.
         * @accessor
         */
        defaultType: 'dataitem',

        /**
         * @cfg {Boolean} scrollToTopOnRefresh
         * Scroll the DataView to the top when the DataView is refreshed.
         * @accessor
         */
        scrollToTopOnRefresh: true
    },

    defaultBindProperty: 'store',

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

        me.hasLoadedStore = false;

        me.mixins.selectable.constructor.apply(me, arguments);

        me.indexOffset = 0;

        me.callParent(arguments);

        //<debug>
        var layout = this.getLayout();
        if (layout && !layout.isAuto) {
            Ext.Logger.error('The base layout for a DataView must always be an Auto Layout');
        }
        //</debug>

        me.initSelectable();
    },

    updateItemCls: function(newCls, oldCls) {
        var container = this.container;
        if (container) {
            if (oldCls) {
                container.doRemoveItemCls(oldCls);
            }
            if (newCls) {
                container.doAddItemCls(newCls);
            }
        }
    },

    storeEventHooks: {
        beforeload: 'onBeforeLoad',
        groupchange: 'onStoreGroupChange',
        load: 'onLoad',
        refresh: 'refresh',
        add: 'onStoreAdd',
        remove: 'onStoreRemove',
        update: 'onStoreUpdate'
    },

    initialize: function() {
        this.callParent();
        var me = this,
            container,
            triggerEvent = me.getTriggerEvent();

        me.on(me.getTriggerCtEvent(), me.onContainerTrigger, me);

        container = me.container = this.add(new Ext.dataview[me.getUseComponents() ? 'component' : 'element'].Container({
            baseCls: this.getBaseCls()
        }));
        container.dataview = me;

        if (triggerEvent) {
            me.on(triggerEvent, me.onItemTrigger, me);
        }

        container.on({
            itemtouchstart: 'onItemTouchStart',
            itemtouchend: 'onItemTouchEnd',
            itemtap: 'onItemTap',
            itemtaphold: 'onItemTapHold',
            itemtouchmove: 'onItemTouchMove',
            itemsingletap: 'onItemSingleTap',
            itemdoubletap: 'onItemDoubleTap',
            itemswipe: 'onItemSwipe',
            scope: me
        });

        if (me.getStore()) {
            if (me.isPainted()) {
                me.refresh();
            }
            else {
                me.on({
                    painted: 'refresh',
                    single: true
                });
            }
        }
    },

    applyInline: function(config) {
        if (Ext.isObject(config)) {
            config = Ext.apply({}, config);
        }
        return config;
    },

    updateInline: function(newInline, oldInline) {
        var baseCls = this.getBaseCls();
        if (oldInline) {
            this.removeCls([baseCls + '-inlineblock', baseCls + '-nowrap']);
        }
        if (newInline) {
            this.addCls(baseCls + '-inlineblock');
            if (Ext.isObject(newInline) && newInline.wrap === false) {
                this.addCls(baseCls + '-nowrap');
            }
            else {
                this.removeCls(baseCls + '-nowrap');
            }
        }
    },

    /**
     * Function which can be overridden to provide custom formatting for each Record that is used by this
     * DataView's {@link #tpl template} to render each node.
     * @param {Object/Object[]} data The raw data object that was used to create the Record.
     * @param {Number} index the index number of the Record being prepared for rendering.
     * @param {Ext.data.Model} record The Record being prepared for rendering.
     * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s `overwrite()` method.
     * (either an array if your params are numeric (i.e. `{0}`) or an object (i.e. `{foo: 'bar'}`))
     */
    prepareData: function(data, index, record) {
        return data;
    },

    // apply to the selection model to maintain visual UI cues
    onContainerTrigger: function(e) {
        var me = this;
        if (e.target != me.element.dom) {
            return;
        }
        if (me.getDeselectOnContainerClick() && me.getStore()) {
            me.deselectAll();
        }
    },

    // apply to the selection model to maintain visual UI cues
    onItemTrigger: function(me, index) {
        if (!this.destroyed) {
            this.selectWithEvent(this.getStore().getAt(index));
        }
    },

    doAddPressedCls: function(record) {
        var me = this,
            item = me.getItemAt(me.getStore().indexOf(record));
        if (Ext.isElement(item)) {
            item = Ext.get(item);
        }
        if (item) {
            if (item.isComponent) {
                item.renderElement.addCls(me.getPressedCls());
            } else {
                item.addCls(me.getPressedCls());
            }
        }
    },

    onItemTouchStart: function(container, target, index, e) {
        var me = this,
            store = me.getStore(),
            record = store && store.getAt(index);

        me.fireAction('itemtouchstart', [me, index, target, record, e], 'doItemTouchStart');
    },

    doItemTouchStart: function(me, index, target, record) {
        var pressedDelay = me.getPressedDelay();

        if (record) {
            if (pressedDelay > 0) {
                me.pressedTimeout = Ext.defer(me.doAddPressedCls, pressedDelay, me, [record]);
            }
            else {
                me.doAddPressedCls(record);
            }
        }
    },

    onItemTouchEnd: function(container, target, index, e) {
        var me = this,
            store = me.getStore(),
            record = store && store.getAt(index);

        if (this.hasOwnProperty('pressedTimeout')) {
            clearTimeout(this.pressedTimeout);
            delete this.pressedTimeout;
        }

        if (record && target) {
            if (target.isComponent) {
                target.renderElement.removeCls(me.getPressedCls());
            } else {
                target.removeCls(me.getPressedCls());
            }
        }

        me.fireEvent('itemtouchend', me, index, target, record, e);
    },

    onItemTouchMove: function(container, target, index, e) {
        var me = this,
            store = me.getStore(),
            record = store && store.getAt(index);

        if (me.hasOwnProperty('pressedTimeout')) {
            clearTimeout(me.pressedTimeout);
            delete me.pressedTimeout;
        }

        if (record && target) {
            if (target.isComponent) {
                target.renderElement.removeCls(me.getPressedCls());
            } else {
                target.removeCls(me.getPressedCls());
            }
        }
        me.fireEvent('itemtouchmove', me, index, target, record, e);
    },

    onItemTap: function(container, target, index, e) {
        var me = this,
            store = me.getStore(),
            record = store && store.getAt(index);

        me.fireEvent('itemtap', me, index, target, record, e);
    },

    onItemTapHold: function(container, target, index, e) {
        var me = this,
            store = me.getStore(),
            record = store && store.getAt(index);

        me.fireEvent('itemtaphold', me, index, target, record, e);
    },

    onItemSingleTap: function(container, target, index, e) {
        var me = this,
            store = me.getStore(),
            record = store && store.getAt(index);

        me.fireEvent('itemsingletap', me, index, target, record, e);
    },

    onItemDoubleTap: function(container, target, index, e) {
        var me = this,
            store = me.getStore(),
            record = store && store.getAt(index);

        me.fireEvent('itemdoubletap', me, index, target, record, e);
    },

    onItemSwipe: function(container, target, index, e) {
        var me = this,
            store = me.getStore(),
            record = store && store.getAt(index);

        me.fireEvent('itemswipe', me, index, target, record, e);
    },

    // invoked by the selection model to maintain visual UI cues
    onItemSelect: function(record, suppressEvent) {
        var me = this;
        if (suppressEvent) {
            me.doItemSelect(me, record);
        } else {
            me.fireAction('select', [me, record], 'doItemSelect');
        }
    },

    // invoked by the selection model to maintain visual UI cues
    doItemSelect: function(me, record) {
        if (me.container && !me.destroyed) {
            var item = me.getItemAt(me.getStore().indexOf(record));
            if (Ext.isElement(item)) {
                item = Ext.get(item);
            }
            if (item) {
                if (item.isComponent) {
                    item.renderElement.removeCls(me.getPressedCls());
                    item.renderElement.addCls(me.getSelectedCls());
                } else {
                    item.removeCls(me.getPressedCls());
                    item.addCls(me.getSelectedCls());
                }
            }
        }
    },

    // invoked by the selection model to maintain visual UI cues
    onItemDeselect: function(record, suppressEvent) {
        var me = this;
        if (me.container && !me.destroyed) {
            if (suppressEvent) {
                me.doItemDeselect(me, record);
            }
            else {
                me.fireAction('deselect', [me, record, suppressEvent], 'doItemDeselect');
            }
        }
    },

    doItemDeselect: function(me, record) {
        var item = me.getItemAt(me.getStore().indexOf(record));

        if (Ext.isElement(item)) {
            item = Ext.get(item);
        }

        if (item) {
            if (item.isComponent) {
                item.renderElement.removeCls([me.getPressedCls(), me.getSelectedCls()]);
            } else {
                item.removeCls([me.getPressedCls(), me.getSelectedCls()]);
            }
        }
    },

    updateData: function(data) {
        var store = this.getStore();
        if (!store) {
            this.setStore(Ext.create('Ext.data.Store', {
                data: data,
                autoDestroy: true
            }));
        } else {
            store.add(data);
        }
    },

    applyStore: function(store) {
        var me = this,
            bindEvents = Ext.apply({}, me.storeEventHooks, { scope: me }),
            proxy, reader;

        if (store) {
            store = Ext.data.StoreManager.lookup(store);
            if (store && Ext.isObject(store) && store.isStore) {
                store.on(bindEvents);
                proxy = store.getProxy();
                if (proxy) {
                    reader = proxy.getReader();
                    if (reader) {
                        reader.on('exception', 'handleException', this);
                    }
                }
            }
            //<debug>
            else {
                Ext.Logger.warn("The specified Store cannot be found", this);
            }
            //</debug>
        }

        return store;
    },

    /**
     * Method called when the Store's Reader throws an exception
     * @method handleException
     */
    handleException: function() {
        this.setMasked(false);
    },

    updateStore: function(newStore, oldStore) {
        var me = this,
            bindEvents = Ext.apply({}, me.storeEventHooks, { scope: me }),
            proxy, reader;

        if (oldStore && Ext.isObject(oldStore) && oldStore.isStore) {
            oldStore.un(bindEvents);

            if (!me.destroyed) {
                me.onStoreClear();
            }

            if (oldStore.getAutoDestroy()) {
                oldStore.destroy();
            }
            else {
                proxy = oldStore.getProxy();
                if (proxy) {
                    reader = proxy.getReader();
                    if (reader) {
                        reader.un('exception', 'handleException', this);
                    }
                }
            }
        }

        if (newStore) {
            if (newStore.isLoaded()) {
                this.hasLoadedStore = true;
            }

            if (newStore.isLoading()) {
                me.onBeforeLoad();
            }
            if (me.container) {
                me.refresh();
            }
        }
    },

    onBeforeLoad: function() {
        var loadingText = this.getLoadingText();
        if (loadingText && this.isPainted()) {
            this.setMasked({
                xtype: 'loadmask',
                message: loadingText
            });
        }

        this.hideEmptyText();
    },

    updateEmptyText: function(newEmptyText, oldEmptyText) {
        var me = this,
            store;

        if (oldEmptyText && me.emptyTextCmp) {
            me.remove(me.emptyTextCmp, true);
            delete me.emptyTextCmp;
        }

        if (newEmptyText) {
            me.emptyTextCmp = me.add({
                xtype: 'component',
                cls: me.getBaseCls() + '-emptytext',
                html: newEmptyText,
                hidden: true
            });
            store = me.getStore();
            if (store && me.hasLoadedStore && !store.getCount()) {
                me.showEmptyText();
            }
        }
    },

    onLoad: function(store) {
        //remove any masks on the store
        this.hasLoadedStore = true;
        this.setMasked(false);

        if (!store.getCount()) {
            this.showEmptyText();
        }
    },

    /**
     * Refreshes the view by reloading the data from the store and re-rendering the template.
     */
    refresh: function() {
        var me = this,
            container = me.container;

        if (!me.getStore()) {
            if (!me.hasLoadedStore && !me.getDeferEmptyText()) {
                me.showEmptyText();
            }
            return;
        }
        if (container) {
            me.fireAction('refresh', [me], 'doRefresh');
        }
    },

    applyItemTpl: function(config) {
        return (Ext.isObject(config) && config.isTemplate) ? config : new Ext.XTemplate(config);
    },

    onAfterRender: function() {
        var me = this;
        me.updateStore(me.getStore());
    },

    /**
     * Returns an item at the specified index.
     * @param {Number} index Index of the item.
     * @return {Ext.dom.Element/Ext.dataview.component.DataItem} item Item at the specified index.
     */
    getItemAt: function(index) {
        return this.getViewItems()[index - this.indexOffset];
    },

    /**
     * Returns an index for the specified item.
     * @param {Number} item The item to locate.
     * @return {Number} Index for the specified item.
     */
    getItemIndex: function(item) {
        var index = this.getViewItems().indexOf(item);
        return (index === -1) ? index : this.indexOffset + index;
    },

    /**
     * Returns an array of the current items in the DataView.
     * @return {Ext.dom.Element[]/Ext.dataview.component.DataItem[]} Array of Items.
     */
    getViewItems: function() {
        return this.container.getViewItems();
    },

    doRefresh: function(me) {
        var container = me.container,
            store = me.getStore(),
            records = store.getRange(),
            items = me.getViewItems(),
            recordsLn = records.length,
            itemsLn = items.length,
            deltaLn = recordsLn - itemsLn,
            scroller = me.getScrollable(),
            i, item;

        if (this.getScrollToTopOnRefresh() && scroller) {
            scroller.scrollTo(0, 0);
        }

        // No items, hide all the items from the collection.
        if (recordsLn < 1) {
            me.onStoreClear();
            return;
        } else {
            me.hideEmptyText();
        }

        // Too many items, hide the unused ones
        if (deltaLn < 0) {
            container.moveItemsToCache(itemsLn + deltaLn, itemsLn - 1);
            // Items can changed, we need to refresh our references
            items = me.getViewItems();
            itemsLn = items.length;
        }
        // Not enough items, create new ones
        else if (deltaLn > 0) {
            container.moveItemsFromCache(store.getRange(itemsLn));
        }

        // Update Data and insert the new html for existing items
        for (i = 0; i < itemsLn; i++) {
            item = items[i];
            container.updateListItem(records[i], item);
        }

        if (this.hasSelection()) {
            var selection = this.getSelection(),
                selectionLn = this.getSelectionCount(),
                record;
            for (i = 0; i < selectionLn; i++) {
                record = selection[i];
                this.doItemSelect(this, record);
            }
        }
    },

    showEmptyText: function() {
        if (this.getEmptyText() && (this.hasLoadedStore || !this.getDeferEmptyText())) {
            this.emptyTextCmp.show();
        }
    },

    hideEmptyText: function() {
        if (this.getEmptyText()) {
            this.emptyTextCmp.hide();
        }
    },

    destroy: function() {
        var store = this.getStore(),
            proxy = (store && store.getProxy()),
            reader = (proxy && proxy.getReader());

        if (reader) {
            // TODO: Use un() instead of clearListeners() when TOUCH-2723 is fixed.
//          reader.un('exception', 'handleException', this);
            reader.clearListeners();
        }

        this.callParent();

        this.setStore(null);
    },

    onStoreClear: function() {
        var me = this,
            container = me.container,
            items = me.getViewItems();

        container.moveItemsToCache(0, items.length - 1);
        this.showEmptyText();
    },

    /**
     * @private
     * @param {Ext.data.Store} store
     * @param {Ext.util.Grouper} grouper
     */
    onStoreGroupChange: Ext.emptyFn,

    /**
     * @private
     * @param {Ext.data.Store} store
     * @param {Array} records
     */
    onStoreAdd: function(store, records) {
        if (records) {
            this.hideEmptyText();
            this.container.moveItemsFromCache(records);
        }
    },

    /**
     * @private
     * @param {Ext.data.Store} store
     * @param {Array} records
     * @param {Array} indices
     */
    onStoreRemove: function(store, records, indices) {
        var container = this.container,
            ln = records.length,
            i;
        for (i = 0; i < ln; i++) {
            container.moveItemsToCache(indices[i], indices[i]);
        }
    },

    /**
     * @private
     * @param {Ext.data.Store} store
     * @param {Ext.data.Model} record
     * @param {Number} newIndex
     * @param {Number} oldIndex
     */
    onStoreUpdate: function(store, record, type, modifiedFieldNames, info) {
        var me = this,
            container = me.container,
            item;

        if (info.indexChanged) {
            container.updateAtNewIndex(info.oldIndex, info.newIndex, record);
            if (me.isSelected(record)) {
                me.doItemSelect(me, record);
            }
        } else {
            item = me.getViewItems()[me.getStore().indexOf(record)];
            if (item) {
                // Bypassing setter because sometimes we pass the same record (different data)
                container.updateListItem(record, item);
            }
        }
    }
});
