    /**
     * 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 touchTeam = Ext.create('Ext.DataView', {
     *         fullscreen: true,
     *         store: {
     *             fields: ['name', 'age'],
     *             data: [
     *                 {name: 'Jamie',  age: 100},
     *                 {name: 'Rob',   age: 21},
     *                 {name: 'Tommy', age: 24},
     *                 {name: 'Jacky', age: 24},
     *                 {name: 'Ed',   age: 26}
     *             ]
     *         },
     *
     *         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:
     *
     *     touchTeam.getStore().add({
     *         name: 'Abe Elias',
     *         age: 33
     *     });
     *
     * 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:
     *
     *     touchTeam.getStore().getAt(0).set('age', 42);
     *
     * This will get the first record in the Store (Jamie), change the age to 42 and automatically update what's on the
     * screen.
     *
     *     @example miniphone
     *     var touchTeam = Ext.create('Ext.DataView', {
     *         fullscreen: true,
     *         store: {
     *             fields: ['name', 'age'],
     *             data: [
     *                 {name: 'Jamie',  age: 100},
     *                 {name: 'Rob',   age: 21},
     *                 {name: 'Tommy', age: 24},
     *                 {name: 'Jacky', age: 24},
     *                 {name: 'Ed',   age: 26}
     *             ]
     *         },
     *
     *         itemTpl: '<div>{name} is {age} years old</div>'
     *     });
     *
     *     touchTeam.getStore().add({
     *         name: 'Abe Elias',
     *         age: 33
     *     });
     *
     *     touchTeam.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 all of the latest tweets about Sencha Touch into a DataView, and for each one
     * render the user's profile picture, user name and tweet message. To do this all we have to do is modify the
     * {@link #store} and {@link #itemTpl} a little:
     *
     *     @example portrait
     *     Ext.create('Ext.DataView', {
     *         fullscreen: true,
     *         cls: 'twitterView',
     *         store: {
     *             autoLoad: true,
     *             fields: ['from_user', 'text', 'profile_image_url'],
     *
     *             proxy: {
     *                 type: 'jsonp',
     *                 url: 'http://search.twitter.com/search.json?q=Sencha Touch',
     *
     *                 reader: {
     *                     type: 'json',
     *                     root: 'results'
     *                 }
     *             }
     *         },
     *
     *         itemTpl: '<img src="{profile_image_url}" /><h2>{from_user}</h2><p>{text}</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 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 assosciated to the item
         * @param {Ext.EventObject} 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 assosciated to the item
         * @param {Ext.EventObject} 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 assosciated to the item
         * @param {Ext.EventObject} 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 assosciated to the item
         * @param {Ext.EventObject} 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 assosciated to the item
         * @param {Ext.EventObject} 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 assosciated to the item
         * @param {Ext.EventObject} 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 assosciated to the item
         * @param {Ext.EventObject} 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 assosciated 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 assosciated to the item
         * @param {Boolean} supressed Flag to supress 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,

            // @inherit
            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 'tap' and 'singletap'.
             * @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,

            // @inherit
            scrollable: true,

            /**
             * @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} loadingText
             * A string to display during data load operations (defaults to 'Loading...').  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.
             *
             * Checkout the [DataView Guide](#!/guide/dataview) for more information on using this configuration.
             * @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 useComponents is true.
             * @accessor
             */
            itemConfig: {},

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

            /**
             * @cfg {String} defaultType
             * The xtype used for the component based DataView. Defaults to dataitem.
             * Note this is only used when useComponents is true.
             * @accessor
             */
            defaultType: 'dataitem',

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

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

            // <debug warn>
            if (config && config.layout) {
                Ext.Logger.warn('Attempting to create a DataView with a layout. DataViews do not have a layout configuration as their items are laid out automatically.');
                delete config.layout;
            }
            // </debug>

            me.hasLoadedStore = false;

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

            me.callParent(arguments);
        },

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

        storeEventHooks: {
            beforeload: 'onBeforeLoad',
            load: 'onLoad',
            refresh: 'refresh',
            addrecords: 'onStoreAdd',
            removerecords: 'onStoreRemove',
            updaterecord: 'onStoreUpdate'
        },

        doInitialize: function() {
            var me = this,
                container;

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

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

            container.on(me.getTriggerEvent(), me.onItemTrigger, me);

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

            if (this.getStore()) {
                this.refresh();
            }
        },

        //@private
        initialize: function() {
            this.callParent();
            this.doInitialize();
        },

        /**
         * 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} recordIndex 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(container, target, index, e) {
            this.selectWithEvent(this.getStore().getAt(index));
        },

        doAddPressedCls: function(record) {
            var me = this,
            item = me.container.getViewItems()[me.getStore().indexOf(record)];
            if (Ext.isElement(item)) {
                item = Ext.get(item);
            }
            item.addCls(me.getPressedCls());
        },

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

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

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

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

        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) {
                var item = me.container.getViewItems()[me.getStore().indexOf(record)];
                if (Ext.isElement(item)) {
                    item = Ext.get(item);
                }
                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) {
                if (suppressEvent) {
                    me.doItemDeselect(me, record);
                }
                else {
                    me.fireAction('deselect', [me, record, suppressEvent], 'doItemDeselect');
                }
            }
        },

        doItemDeselect: function(me, record) {
            var item = me.container.getViewItems()[me.getStore().indexOf(record)];

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

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

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

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

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

            return store;
        },

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

            if (oldStore && Ext.isObject(oldStore) && oldStore.isStore) {
                if (oldStore.autoDestroy) {
                    oldStore.destroy();
                }
                else {
                    oldStore.un(bindEvents);
                }
            }

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

                if (newStore.isLoading()) {
                    this.hasLoadedStore = true;
                    me.onBeforeLoad();
                }
                if (me.container) {
                    me.refresh();
                }
            }
        },

        onBeforeLoad: function() {
            var scrollable = this.getScrollable();
            if (scrollable) {
                scrollable.getScroller().stopAnimation();
            }

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

                //disable scorlling while it is masked
                if (scrollable) {
                    scrollable.getScroller().setDisabled(true);
                }
            }

            this.hideEmptyText();
        },

        updateEmptyText: function(newEmptyText) {
            var me = this;
            if (newEmptyText) {
                me.emptyTextCmp = me.add({
                    xtype: 'component',
                    cls: me.getBaseCls() + '-emptytext',
                    html: newEmptyText,
                    hidden: true
                });
            }
            else if (me.emptyTextCmp) {
                me.remove(me.emptyTextCmp, true);
                delete me.emptyTextCmp;
            }
        },

        onLoad: function() {
            var scrollable = this.getScrollable();

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

            //enable the scroller again
            if (scrollable) {
                scrollable.getScroller().setDisabled(false);
            }
        },

        /**
         * 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.callParent(arguments);
            me.updateStore(me.getStore());
        },

        getViewItems: function() {
            return this.container.getViewItems();
        },

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

            if (this.getScrollToTopOnRefresh() && scrollable) {
                scrollable.getScroller().scrollToTop();
            }

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

            // 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 = container.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);
            }
        },

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

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

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

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

        // private
        onStoreAdd: function(store, records) {
            if (records) {
                this.container.moveItemsFromCache(records);
            }
        },

        // private
        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
        onStoreUpdate: function(store, record, newIndex, oldIndex) {
            var me = this,
                container = me.container;
            oldIndex = (typeof oldIndex === 'undefined') ? newIndex : oldIndex;

            if (oldIndex !== newIndex) {
                container.moveItemsToCache(oldIndex, oldIndex);
                container.moveItemsFromCache([record]);
            }
            else {
                // Bypassing setter because sometimes we pass the same record (different data)
                container.updateListItem(record, container.getViewItems()[newIndex]);
            }
        }
    }, function() {
        //<deprecated product=touch since=2.0>

        /**
         * Binds a new {@link Ext.data.Store Store} to this DataView.
         * @deprecated 2.0 please use {@link #setStore} instead
         * @method bindStore
         */
        Ext.deprecateClassMethod(this, 'bindStore', this.prototype.setStore, "'bindStore()' is deprecated, please use 'setStore' instead");
        //</deprecated>
    });