/**
 * This class is the base class for both {@link Ext.tree.Panel TreePanel} and
 * {@link Ext.grid.Panel GridPanel}.
 *
 * TablePanel aggregates:
 *
 *  - a Selection Model
 *  - a View
 *  - a Store
 *  - Ext.grid.header.Container
 */
Ext.define('Ext.panel.Table', {
    extend: 'Ext.panel.Panel',
 
    alias: 'widget.tablepanel',
    
    requires: [
        'Ext.layout.container.Fit'
    ],
 
    uses: [
        'Ext.selection.RowModel',
        'Ext.selection.CellModel',
        'Ext.selection.CheckboxModel',
        'Ext.grid.plugin.BufferedRenderer',
        'Ext.grid.header.Container',
        'Ext.grid.locking.Lockable',
        'Ext.grid.NavigationModel'
    ],
 
    extraBaseCls: Ext.baseCSSPrefix + 'grid',
    extraBodyCls: Ext.baseCSSPrefix + 'grid-body',
 
    defaultBindProperty: 'store',
 
    layout: 'fit',
    
    ariaRole: 'grid',
 
    config: {
        selection: null
    },
 
    publishes: ['selection'],
    twoWayBindable: ['selection'],
 
    /**
     * @cfg [autoLoad=false]
     * Use `true` to load the store as soon as this component is fully constructed. It is
     * best to initiate the store load this way to allow this component and potentially
     * its plugins (such as `{@link Ext.grid.filters.Filters}` to be ready to load.
     */
    autoLoad: false,
 
    /**
     * @cfg {Boolean} [variableRowHeight=false]
     * @deprecated 5.0.0 Use {@link Ext.grid.column.Column#variableRowHeight} instead.
     * Configure as `true` if the row heights are not all the same height as the first row.
     */
    variableRowHeight: false,
 
    /**
     * @cfg {Number}
     * This configures the zone which causes new rows to be appended to the view. As soon as the edge
     * of the rendered grid is this number of rows from the edge of the viewport, the view is moved.
     */
    numFromEdge: 2,
 
    /**
     * @cfg {Number}
     * TableViews are buffer rendered in 5.x which means that only the visible subset of data rows
     * are rendered into the DOM. These are removed and added as scrolling demands.
     *
     * This configures the number of extra rows to render on the trailing side of scrolling
     * **outside the {@link #numFromEdge}** buffer as scrolling proceeds.
     */
    trailingBufferZone: 10,
 
    /**
     * @cfg {Number}
     * TableViews are buffer rendered in 5.x which means that only the visible subset of data rows
     * are rendered into the DOM. These are removed and added as scrolling demands.
     *
     * This configures the number of extra rows to render on the leading side of scrolling
     * **outside the {@link #numFromEdge}** buffer as scrolling proceeds.
     */
    leadingBufferZone: 20,
 
    /**
     * @property {Boolean} hasView 
     * True to indicate that a view has been injected into the panel.
     */
    hasView: false,
 
    // each panel should dictate what viewType and selType to use 
    /**
     * @cfg {String} viewType 
     * An xtype of view to use. This is automatically set to 'gridview' by {@link Ext.grid.Panel Grid}
     * and to 'treeview' by {@link Ext.tree.Panel Tree}.
     * @protected
     */
    viewType: null,
 
    /**
     * @cfg {Object} viewConfig 
     * A config object that will be applied to the grid's UI view. Any of the config options available for
     * {@link Ext.view.Table} can be specified here. This option is ignored if {@link #view} is specified.
     */
 
    /**
     * @cfg {Ext.view.Table} view
     * The {@link Ext.view.Table} used by the grid. Use {@link #viewConfig} to just supply some config options to
     * view (instead of creating an entire View instance).
     */
 
    /**
     * @cfg {String} [selType]
     * An xtype of selection model to use. This is used to create selection model if just
     * a config object or nothing at all given in {@link #selModel} config.
     */
    selType: 'rowmodel',
 
    /**
     * @cfg {Ext.selection.Model/Object} selModel
     * A {@link Ext.selection.Model selection model} instance or config object.  In latter case the {@link #selType}
     * config option determines to which type of selection model this config is applied.
     */
 
    /**
     * @cfg {Boolean} [multiSelect=false]
     * True to enable 'MULTI' selection mode on selection model.
     * @deprecated 4.1.1 Use {@link Ext.selection.Model#mode} 'MULTI' instead.
     */
 
    /**
     * @cfg {Boolean} [simpleSelect=false]
     * True to enable 'SIMPLE' selection mode on selection model.
     * @deprecated 4.1.1 Use {@link Ext.selection.Model#mode} 'SIMPLE' instead.
     */
 
    /**
     * @cfg {Ext.data.Store} store (required)
     * The {@link Ext.data.Store Store} the grid should use as its data source.
     */
 
    /**
     * @cfg {String/Boolean} scroll
     * Scrollers configuration. Valid values are 'both', 'horizontal' or 'vertical'.
     * True implies 'both'. False implies 'none'.
     */
    scroll: true,
 
    /**
     * @cfg {Boolean} [reserveScrollbar=false]
     * Set this to true to **always** leave a scrollbar sized space at the end of the grid content when
     * fitting content into the width of the grid.
     *
     * If the grid's record count fluctuates enough to hide and show the scrollbar regularly, this setting
     * avoids the multiple layouts associated with switching from scrollbar present to scrollbar not present.
     */
 
    /**
     * @cfg {Ext.grid.column.Column[]/Object} columns (required)
     * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this
     * grid. Each column definition provides the header text for the column, and a definition of where the data for that
     * column comes from.
     *
     * This can also be a configuration object for a {@link Ext.grid.header.Container HeaderContainer} which may override
     * certain default configurations if necessary. For example, the special layout may be overridden to use a simpler
     * layout, or one can set default values shared by all columns:
     * 
     *     columns: {
     *         items: [
     *             {
     *                 text: "Column A",
     *                 dataIndex: "field_A"
     *             },{
     *                 text: "Column B",
     *                 dataIndex: "field_B"
     *             }, 
     *             ...
     *         ],
     *         defaults: {
     *             flex: 1
     *         }
     *     }
     */
 
    /**
     * @cfg {Boolean} forceFit 
     * True to force the columns to fit into the available width. Headers are first sized according to configuration,
     * whether that be a specific width, or flex. Then they are all proportionally changed in width so that the entire
     * content width is used. For more accurate control, it is more optimal to specify a flex setting on the columns
     * that are to be stretched & explicit widths on columns that are not.
     */
 
    /**
     * @cfg {Ext.grid.feature.Feature[]/Object[]/Ext.enums.Feature[]} features
     * An array of grid Features to be added to this grid. Can also be just a single feature instead of array.
     *
     * Features config behaves much like {@link #plugins}.
     * A feature can be added by either directly referencing the instance:
     *
     *     features: [Ext.create('Ext.grid.feature.GroupingSummary', {groupHeaderTpl: 'Subject: {name}'})],
     *
     * By using config object with ftype:
     *
     *     features: [{ftype: 'groupingsummary', groupHeaderTpl: 'Subject: {name}'}],
     *
     * Or with just a ftype:
     *
     *     features: ['grouping', 'groupingsummary'],
     *
     * See {@link Ext.enums.Feature} for list of all ftypes.
     */
 
    /**
     * @cfg {Boolean} [hideHeaders=false]
     * True to hide column headers.
     */
 
    /**
     * @cfg {Boolean} [deferRowRender=false]
     * Configure as `true` to enable deferred row rendering.
     *
     * This allows the View to execute a refresh quickly, with the update of the row structure deferred so
     * that layouts with GridPanels appear, and lay out more quickly.
     */
    deferRowRender: false,
     
    /**
     * @cfg {Boolean} [sortableColumns=true]
     * False to disable column sorting via clicking the header and via the Sorting menu items.
     */
    sortableColumns: true,
 
    /**
     * @cfg {Boolean} [multiColumnSort=false]
     * Configure as `true` to have columns remember their sorted state after other columns have been clicked upon to sort.
     *
     * As subsequent columns are clicked upon, they become the new primary sort key.
     *
     * The maximum number of sorters allowed in a Store is configurable via its underlying data collection. See {@link Ext.util.Collection#multiSortLimit}
     */
    multiColumnSort: false,
 
    /**
     * @cfg {Boolean} [enableLocking=false]
     * Configure as `true` to enable locking support for this grid. Alternatively, locking will also be automatically
     * enabled if any of the columns in the {@link #columns columns} configuration contain a {@link Ext.grid.column.Column#locked locked} config option.
     * 
     * A locking grid is processed in a special way. The configuration options are cloned and *two* grids are created to be the locked (left) side
     * and the normal (right) side. This Panel becomes merely a {@link Ext.container.Container container} which arranges both in an {@link Ext.layout.container.HBox HBox} layout.
     * 
     * {@link #plugins Plugins} may be targeted at either locked, or unlocked grid, or, both, in which case the plugin is cloned and used on both sides.
     * 
     * Plugins may also be targeted at the containing locking Panel.
     * 
     * This is configured by specifying a `lockableScope` property in your plugin which may have the following values:
     * 
     *  * `"both"` (the default) - The plugin is added to both grids
     *  * `"top"` - The plugin is added to the containing Panel
     *  * `"locked"` - The plugin is added to the locked (left) grid
     *  * `"normal"` - The plugin is added to the normal (right) grid
     *
     * If `both` is specified, then each copy of the plugin gains a property `lockingPartner` which references its sibling on the other side so that they
     * can synchronize operations is necessary.
     * 
     * {@link #features Features} may also be configured with `lockableScope` and may target the locked grid, the normal grid or both grids. Features
     * also get a `lockingPartner` reference injected.
     */
    enableLocking: false,
 
    // private property used to determine where to go down to find views 
    // this is here to support locking. 
    scrollerOwner: true,
 
    /**
     * @cfg {Boolean} [enableColumnMove=true]
     * False to disable column dragging within this grid.
     */
    enableColumnMove: true,
    
    /**
     * @cfg {Boolean} [sealedColumns=false]
     * True to constrain column dragging so that a column cannot be dragged in or out of it's
     * current group. Only relevant while {@link #enableColumnMove} is enabled.
     */
    sealedColumns: false,
 
    /**
     * @cfg {Boolean} [enableColumnResize=true]
     * False to disable column resizing within this grid.
     */
    enableColumnResize: true,
 
    /**
     * @cfg {Boolean} [enableColumnHide=true]
     * False to disable column hiding within this grid.
     */
 
    /**
     * @cfg {Boolean} columnLines Adds column line styling
     */
 
    /**
     * @cfg {Boolean} [rowLines=true] Adds row line styling
     */
    rowLines: true,
 
    /**
     * @cfg {Boolean} [disableSelection=false]
     * True to disable selection model.
     */
 
    /**
     * @cfg {String} emptyText Default text (HTML tags are accepted) to display in the 
     * Panel body when the Store is empty. When specified, and the Store is empty, the 
     * text will be rendered inside a DIV with the CSS class "x-grid-empty". The emptyText 
     * will not display until the first load of the associated store by default. If you 
     * want the text to be displayed prior to the first store load use the 
     * {@link Ext.view.Table#deferEmptyText deferEmptyText} config in the {@link #viewConfig} config.
     */
    
    /**
     * @cfg {Boolean} [allowDeselect=false]
     * True to allow deselecting a record. This config is forwarded to {@link Ext.selection.Model#allowDeselect}.
     */
    
    /**
     * @cfg {Boolean} [bufferedRenderer=true]
     * Buffered rendering is enabled by default.
     * 
     * Configure as `false` to disable buffered rendering. See {@link #Ext.grid.plugin.BufferedRenderer}.
     *
     * @since 5.0.0
     */
    bufferedRenderer: true,
 
    /**
     * @property {Boolean} optimizedColumnMove 
     * If you are writing a grid plugin or a {Ext.grid.feature.Feature Feature} which creates a column-based structure which
     * needs a view refresh when columns are moved, then set this property in the grid.
     *
     * An example is the built in {@link Ext.grid.feature.AbstractSummary Summary} Feature. This creates summary rows, and the
     * summary columns must be in the same order as the data columns. This plugin sets the `optimizedColumnMove` to `false.
     */
 
    /**
     * @property {Ext.view.Table} ownerGrid
     * A reference to the top-level owning grid component.
     * 
     * This is a reference to this GridPanel if this GridPanel is not part of a locked grid arrangement.
     * @readonly
     * @private
     * @since 5.0.0
     */
    ownerGrid: null,
 
    colLinesCls: Ext.baseCSSPrefix + 'grid-with-col-lines',
    rowLinesCls: Ext.baseCSSPrefix + 'grid-with-row-lines',
    noRowLinesCls: Ext.baseCSSPrefix + 'grid-no-row-lines',
    hiddenHeaderCtCls: Ext.baseCSSPrefix + 'grid-header-ct-hidden',
    hiddenHeaderCls: Ext.baseCSSPrefix + 'grid-header-hidden',
    resizeMarkerCls: Ext.baseCSSPrefix + 'grid-resize-marker',
    emptyCls: Ext.baseCSSPrefix + 'grid-empty',
    
    focusable: true,
    tabIndex: 0,
 
    /**
     * @event viewready
     * Fires when the grid view is available (use this for selecting a default row).
     * @param {Ext.panel.Table} this
     */
 
    constructor: function (config) {
        var me = this,
            store;
 
        me.ownerGrid = (config && config.ownerGrid) || me;
 
        me.callParent([config]);
 
        store = this.store;
 
        // Any further changes become stateful. 
        store.trackStateChanges = true;
 
        if (me.autoLoad) {
            store.unblockLoad();
            store.load();
        }
    },
 
    initComponent: function() {
        //<debug> 
        if (this.verticalScroller) {
            Ext.Error.raise("The verticalScroller config is not supported.");
        }
        if (!this.viewType) {
            Ext.Error.raise("You must specify a viewType config.");
        }
        if (this.headers) {
            Ext.Error.raise("The headers config is not supported. Please specify columns instead.");
        }
        //</debug> 
 
        var me = this,
            headerCtCfg = me.columns || me.colModel || [],
            view,
            i, len,
            bufferedRenderer,
            // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store. 
            store       = me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store'),
            columns;
 
        me.enableLocking = me.enableLocking || me.hasLockedColumns(headerCtCfg);
 
        // Block store loads during construction or initialization of plugins! 
        if (me.autoLoad) {
            me.store.blockLoad();
        }
 
        // Construct the plugins now rather than in the constructor of AbstractComponent because the component may have a subclass 
        // that has overridden initComponent and defined plugins in it. For plugins like RowExpander that rely upon a grid feature, 
        // this is a problem because the view needs to know about all its features before it's constructed. Constructing the plugins 
        // now ensures that plugins defined in the instance config or in initComponent are all constructed before the view. 
        // See EXTJSIV-11927. 
        // 
        // Note that any components that do not inherit from this class will still have their plugins constructed in 
        // AbstractComponent:initComponent. 
        if (me.plugins) {
            me.plugins = me.constructPlugins();
        }
 
        // Add the row/column line classes to the body element so that the settings are not inherited by docked grids (https://sencha.jira.com/browse/EXTJSIV-9263). 
        if (me.columnLines) {
            me.addBodyCls(me.colLinesCls);
        }
 
        me.addBodyCls(me.rowLines ? me.rowLinesCls : me.noRowLinesCls);
        me.addBodyCls(me.extraBodyCls);
 
 
        // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a 
        // special view will be injected by the Ext.grid.locking.Lockable mixin, so no processing of . 
        if (me.enableLocking) {
            me.self.mixin('lockable', Ext.grid.locking.Lockable);
            me.injectLockable();
        }
        // Not lockable - create the HeaderContainer 
        else {
            // It's a fully instantiated HeaderContainer 
            if (headerCtCfg.isRootHeader) {
                me.headerCt = headerCtCfg;
 
                me.headerCt.forceFit = !!me.forceFit;
 
                // If it's an instance then the column managers were already created and bound to the headerCt. 
                me.columnManager = headerCtCfg.columnManager;
                me.visibleColumnManager = headerCtCfg.visibleColumnManager;
            }
            // It's an array of Column definitions, or a config object of a HeaderContainer 
            else {
                if (Ext.isArray(headerCtCfg)) {
                    headerCtCfg = {
                        items: headerCtCfg
                    };
                }
                Ext.apply(headerCtCfg, {
                    grid: me,
                    forceFit: me.forceFit,
                    sortable: me.sortableColumns,
                    enableColumnMove: me.enableColumnMove,
                    enableColumnResize: me.enableColumnResize,
                    columnLines: me.columnLines,
                    sealed: me.sealedColumns
                });
                if (me.hideHeaders) {
                    headerCtCfg.height = 0;
                    // don't set the hidden property, we still need these to layout 
                    headerCtCfg.hiddenHeaders = true;
                }
 
                if (Ext.isDefined(me.enableColumnHide)) {
                    headerCtCfg.enableColumnHide = me.enableColumnHide;
                }
                me.headerCt = new Ext.grid.header.Container(headerCtCfg);
            }
        }
 
        // Maintain backward compatibiliy by providing the initial leaf column set as a property. 
        me.columns = columns = me.headerCt.getGridColumns();
 
        me.scrollTask = new Ext.util.DelayedTask(me.syncHorizontalScroll, me);
 
        me.cls = (me.cls || '') + (' ' + me.extraBaseCls);
 
        // autoScroll is not a valid configuration 
        delete me.autoScroll;
 
        bufferedRenderer = me.plugins && Ext.Array.findBy(me.plugins, function(p) {
            return p.isBufferedRenderer;
        });
 
        // If we find one in the plugins, just use that. 
        if (bufferedRenderer) {
            me.bufferedRenderer = bufferedRenderer;
        }
 
        // If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property) 
        // then a special lockable view containing 2 side-by-side grids will have been injected so we do not need to set up any UI. 
        if (!me.hasView) {
 
            // If the Store is paging blocks of the dataset in, then it can only be sorted remotely. 
            if (store.isBufferedStore && !store.remoteSort) {
                for (= 0, len = columns.length; i < len; i++) {
                    columns[i].sortable = false;
                }
            }
 
            if (me.hideHeaders) {
                me.headerCt.addCls(me.hiddenHeaderCtCls);
                me.addCls(me.hiddenHeaderCls);
            }
 
            me.relayHeaderCtEvents(me.headerCt);
            me.features = me.features || [];
            if (!Ext.isArray(me.features)) {
                me.features = [me.features];
            }
            me.dockedItems = [].concat(me.dockedItems || []);
            me.dockedItems.unshift(me.headerCt);
            me.viewConfig = me.viewConfig || {};
 
            // AbstractDataView will look up a Store configured as an object 
            // getView converts viewConfig into a View instance 
            view = me.getView();
 
            me.items = [view];
            me.hasView = true;
 
            // Add a listener to synchronize the horizontal scroll position of the headers 
            // with the table view's element... Unless we are not showing headers! 
            if (!me.hideHeaders) {
                view.on({
                    scroll: me.onHorizontalScroll,
                    scope: me,
                    onFrame: !!Ext.global.requestAnimationFrame
                });
            }
 
            // Attach this Panel to the Store 
            me.bindStore(store, true);
 
            me.mon(view, {
                viewready: me.onViewReady,
                refresh: me.onRestoreHorzScroll,
                scope: me
            });
        }
 
        // Relay events from the View whether it be a LockingView, or a regular GridView 
        me.relayEvents(me.view, [
            /**
             * @event beforeitemmousedown
             * @inheritdoc Ext.view.View#beforeitemmousedown
             */
            'beforeitemmousedown',
            /**
             * @event beforeitemmouseup
             * @inheritdoc Ext.view.View#beforeitemmouseup
             */
            'beforeitemmouseup',
            /**
             * @event beforeitemmouseenter
             * @inheritdoc Ext.view.View#beforeitemmouseenter
             */
            'beforeitemmouseenter',
            /**
             * @event beforeitemmouseleave
             * @inheritdoc Ext.view.View#beforeitemmouseleave
             */
            'beforeitemmouseleave',
            /**
             * @event beforeitemclick
             * @inheritdoc Ext.view.View#beforeitemclick
             */
            'beforeitemclick',
            /**
             * @event beforeitemdblclick
             * @inheritdoc Ext.view.View#beforeitemdblclick
             */
            'beforeitemdblclick',
            /**
             * @event beforeitemcontextmenu
             * @inheritdoc Ext.view.View#beforeitemcontextmenu
             */
            'beforeitemcontextmenu',
            /**
             * @event itemmousedown
             * @inheritdoc Ext.view.View#itemmousedown
             */
            'itemmousedown',
            /**
             * @event itemmouseup
             * @inheritdoc Ext.view.View#itemmouseup
             */
            'itemmouseup',
            /**
             * @event itemmouseenter
             * @inheritdoc Ext.view.View#itemmouseenter
             */
            'itemmouseenter',
            /**
             * @event itemmouseleave
             * @inheritdoc Ext.view.View#itemmouseleave
             */
            'itemmouseleave',
            /**
             * @event itemclick
             * @inheritdoc Ext.view.View#itemclick
             */
            'itemclick',
            /**
             * @event itemdblclick
             * @inheritdoc Ext.view.View#itemdblclick
             */
            'itemdblclick',
            /**
             * @event itemcontextmenu
             * @inheritdoc Ext.view.View#itemcontextmenu
             */
            'itemcontextmenu',
            /**
             * @event beforecellclick
             * @inheritdoc Ext.view.Table#beforecellclick
             */
            'beforecellclick',
            /**
             * @event cellclick
             * @inheritdoc Ext.view.Table#cellclick
             */
            'cellclick',
            /**
             * @event beforecelldblclick
             * @inheritdoc Ext.view.Table#beforecelldblclick
             */
            'beforecelldblclick',
            /**
             * @event celldblclick
             * @inheritdoc Ext.view.Table#celldblclick
             */
            'celldblclick',
            /**
             * @event beforecellcontextmenu
             * @inheritdoc Ext.view.Table#beforecellcontextmenu
             */
            'beforecellcontextmenu',
            /**
             * @event cellcontextmenu
             * @inheritdoc Ext.view.Table#cellcontextmenu
             */
            'cellcontextmenu',
            /**
             * @event beforecellmousedown
             * @inheritdoc Ext.view.Table#beforecellmousedown
             */
            'beforecellmousedown',
            /**
             * @event cellmousedown
             * @inheritdoc Ext.view.Table#cellmousedown
             */
            'cellmousedown',
            /**
             * @event beforecellmouseup
             * @inheritdoc Ext.view.Table#beforecellmouseup
             */
            'beforecellmouseup',
            /**
             * @event cellmouseup
             * @inheritdoc Ext.view.Table#cellmouseup
             */
            'cellmouseup',
            /**
             * @event beforecellkeydown
             * @inheritdoc Ext.view.Table#beforecellkeydown
             */
            'beforecellkeydown',
            /**
             * @event cellkeydown
             * @inheritdoc Ext.view.Table#cellkeydown
             */
            'cellkeydown',
            /**
             * @event rowclick
             * @inheritdoc Ext.view.Table#rowclick
             */
            'rowclick',
            /**
             * @event rowdblclick
             * @inheritdoc Ext.view.Table#rowdblclick
             */
            'rowdblclick',
            /**
             * @event rowcontextmenu
             * @inheritdoc Ext.view.Table#rowcontextmenu
             */
            'rowcontextmenu',
            /**
             * @event rowmousedown
             * @inheritdoc Ext.view.Table#rowmousedown
             */
            'rowmousedown',
            /**
             * @event rowmouseup
             * @inheritdoc Ext.view.Table#rowmouseup
             */
            'rowmouseup',
            /**
             * @event rowkeydown
             * @inheritdoc Ext.view.Table#rowkeydown
             */
            'rowkeydown',
            /**
             * @event beforeitemkeydown
             * @inheritdoc Ext.view.Table#beforeitemkeydown
             */
            'beforeitemkeydown',
            /**
             * @event itemkeydown
             * @inheritdoc Ext.view.Table#itemkeydown
             */
            'itemkeydown',
            /**
             * @event beforecontainermousedown
             * @inheritdoc Ext.view.View#beforecontainermousedown
             */
            'beforecontainermousedown',
            /**
             * @event beforecontainermouseup
             * @inheritdoc Ext.view.View#beforecontainermouseup
             */
            'beforecontainermouseup',
            /**
             * @event beforecontainermouseover
             * @inheritdoc Ext.view.View#beforecontainermouseover
             */
            'beforecontainermouseover',
            /**
             * @event beforecontainermouseout
             * @inheritdoc Ext.view.View#beforecontainermouseout
             */
            'beforecontainermouseout',
            /**
             * @event beforecontainerclick
             * @inheritdoc Ext.view.View#beforecontainerclick
             */
            'beforecontainerclick',
            /**
             * @event beforecontainerdblclick
             * @inheritdoc Ext.view.View#beforecontainerdblclick
             */
            'beforecontainerdblclick',
            /**
             * @event beforecontainercontextmenu
             * @inheritdoc Ext.view.View#beforecontainercontextmenu
             */
            'beforecontainercontextmenu',
            /**
             * @event beforecontainerkeydown
             * @inheritdoc Ext.view.View#beforecontainerkeydown
             */
            'beforecontainerkeydown',
            /**
             * @event containermouseup
             * @inheritdoc Ext.view.View#containermouseup
             */
            'containermouseup',
            /**
             * @event containermousedown
             * @inheritdoc Ext.view.View#containermousedown
             */
            'containermousedown',
            /**
             * @event containermouseover
             * @inheritdoc Ext.view.View#containermouseover
             */
            'containermouseover',
            /**
             * @event containermouseout
             * @inheritdoc Ext.view.View#containermouseout
             */
            'containermouseout',
            /**
             * @event containerclick
             * @inheritdoc Ext.view.View#containerclick
             */
            'containerclick',
            /**
             * @event containerdblclick
             * @inheritdoc Ext.view.View#containerdblclick
             */
            'containerdblclick',
            /**
             * @event containercontextmenu
             * @inheritdoc Ext.view.View#containercontextmenu
             */
            'containercontextmenu',
            /**
             * @event containerkeydown
             * @inheritdoc Ext.view.View#containerkeydown
             */
            'containerkeydown',
            /**
             * @event selectionchange
             * @inheritdoc Ext.selection.Model#selectionchange
             */
            'selectionchange',
            /**
             * @event beforeselect
             * @inheritdoc Ext.selection.RowModel#beforeselect
             */
            'beforeselect',
            /**
             * @event select
             * @inheritdoc Ext.selection.RowModel#select
             */
            'select',
            /**
             * @event beforedeselect
             * @inheritdoc Ext.selection.RowModel#beforedeselect
             */
            'beforedeselect',
            /**
             * @event deselect
             * @inheritdoc Ext.selection.RowModel#deselect
             */
            'deselect'
        ]);
 
        me.callParent(arguments);
        me.addStateEvents(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange', 'filterchange', 'groupchange']);
    },
 
    beforeRender: function() {
        var me = this,
            bufferedRenderer = me.bufferedRenderer;
 
        // Don't create a buffered renderer for a locked grid. 
        if (!me.lockable) {
            // If we're auto heighting, we can't buffered render, so don't create it 
            if (bufferedRenderer && me.getSizeModel().height.auto) {
                //<debug> 
                if (bufferedRenderer.isBufferedRenderer) {
                    Ext.Error.raise('Cannot use buffered rendering with auto height');
                }
                //</debug> 
                me.bufferedRenderer = bufferedRenderer = false;
            }
        
            if (bufferedRenderer && !bufferedRenderer.isBufferedRenderer) {
                // Create a BufferedRenderer as a plugin if we have not already configured with one. 
                bufferedRenderer = {
                    xclass: 'Ext.grid.plugin.BufferedRenderer'
                };
                Ext.copyTo(bufferedRenderer, me, 'variableRowHeight,numFromEdge,trailingBufferZone,leadingBufferZone,scrollToLoadBuffer');
                me.bufferedRenderer = me.addPlugin(bufferedRenderer);
            }
        }
        me.callParent(arguments);
    },
    
    initFocusableEvents: function() {
        var me = this,
            view = me.getView();
 
        me.callParent();
        me.focusEnterLeaveListeners = view.getFocusEl().on({
            focusenter: me.onFocusEnter,
            focusleave: me.onFocusLeave,
            scope: me,
            destroyable: true
        });
    },
 
    onFocus: function(e) {
        this.callParent([e]);
 
        // Focusing the main el delegates focus to a descendant cell. 
        this.handleFocusEnter(e);
    },
    
    onFocusEnter: function(e) {
        this.handleFocusEnter(e);
    },
 
    handleFocusEnter: function(e) {
        var me = this,
            view = me.getView(),
            targetView,
            navigationModel = view.getNavigationModel(),
            lastFocused,
            focusPosition,
            br = view.bufferedRenderer,
            firstRecord;
 
        if (!me.containsFocus) {
            lastFocused = focusPosition = view.getLastFocused();
 
            // Default to the first cell if the NavigationModel has never focused anything 
            if (!focusPosition) {
                targetView = view.isLockingView ? (view.lockedGrid.isVisible() ? view.lockedView : view.normalView) : view;
                firstRecord = view.dataSource.getAt(br ? br.getFirstVisibleRowIndex() : 0);
 
                // A non-row producing record like a collapsed placeholder. 
                // We cannot focus these yet. 
                if (!firstRecord.isNonData) {
                    focusPosition = new Ext.grid.CellContext(targetView).setPosition({
                        row: firstRecord,
                        column: 0
                    });
                }
            }
 
            // Not a descendant which we allow to carry focus. Blur it. 
            if (!focusPosition) {
                e.stopEvent();
                e.getTarget().blur();
                return;
            }
            navigationModel.setPosition(focusPosition, null, e, null, !!lastFocused);
 
            // We now contain focus is that was successful 
            me.containsFocus = !!navigationModel.getPosition();
        }
        
        if (me.containsFocus) {
            this.getView().el.dom.setAttribute('tabindex', '-1');
        }
    },
 
    onFocusLeave: function(e) {
        var view = this.getView();
 
        // Ignore this event if we do not actually contain focus. 
        // CellEditors are rendered into the view's encapculating element, 
        // So focusleave will fire when they are programatically blurred. 
        // We will not have focus at that point. 
        if (this.containsFocus) {
 
            // Blur the focused cell 
            view.getNavigationModel().setPosition(null, null, e, null, true);
 
            this.containsFocus = false;
            view.focusEl = view.el;
            view.focusEl.dom.setAttribute('tabindex', 0);
        }
    },
 
    // Private. Determine if there are any columns with a locked configuration option 
    hasLockedColumns: function(columns) {
        var i,
            len,
            column;
 
        // Fully instantiated HeaderContainer 
        if (columns.isRootHeader) {
            columns = columns.items.items;
        }
        // Config object with items 
        else if (Ext.isObject(columns)) {
            columns = columns.items;
        }
        for (= 0, len = columns.length; i < len; i++) {
            column = columns[i];
            if (!column.processed && column.locked) {
                return true;
            }
        }
    },
 
    relayHeaderCtEvents: function (headerCt) {
        this.relayEvents(headerCt, [
            /**
             * @event columnresize
             * @inheritdoc Ext.grid.header.Container#columnresize
             */
            'columnresize',
            /**
             * @event columnmove
             * @inheritdoc Ext.grid.header.Container#columnmove
             */
            'columnmove',
            /**
             * @event columnhide
             * @inheritdoc Ext.grid.header.Container#columnhide
             */
            'columnhide',
            /**
             * @event columnshow
             * @inheritdoc Ext.grid.header.Container#columnshow
             */
            'columnshow',
            /**
             * @event columnschanged
             * @inheritdoc Ext.grid.header.Container#columnschanged
             */
            'columnschanged',
            /**
             * @event sortchange
             * @inheritdoc Ext.grid.header.Container#sortchange
             */
            'sortchange',
            /**
             * @event headerclick
             * @inheritdoc Ext.grid.header.Container#headerclick
             */
            'headerclick',
            /**
             * @event headercontextmenu
             * @inheritdoc Ext.grid.header.Container#headercontextmenu
             */
            'headercontextmenu',
            /**
             * @event headertriggerclick
             * @inheritdoc Ext.grid.header.Container#headertriggerclick
             */
            'headertriggerclick'
        ]);
    },
 
    getState: function(){
        var me = this,
            state = me.callParent(),
            storeState = me.store.getState();
 
        state = me.addPropertyToState(state, 'columns', me.headerCt.getColumnsState());
 
        if (storeState) {
            state.storeState = storeState;
        }
        return state;
    },
 
    applyState: function (state) {
        var me = this,
            sorter = state.sort,
            storeState = state.storeState,
            store = me.store,
            columns = state.columns;
 
        delete state.columns;
 
        // Ensure superclass has applied *its* state. 
        // Component saves dimensions (and anchor/flex) plus collapsed state. 
        me.callParent(arguments);
 
        if (columns) {
            me.headerCt.applyColumnsState(columns);
        }
 
        // Old stored sort state. Deprecated and will die out. 
        if (sorter) {
            if (store.remoteSort) {
                // Pass false to prevent a sort from occurring. 
                store.sort({
                    property: sorter.property,
                    direction: sorter.direction,
                    root: sorter.root
                }, null, false);
            } else {
                store.sort(sorter.property, sorter.direction);
            }
        }
        // New storeState which encapsulates groupers, sorters and filters. 
        else if (storeState) {
            store.applyState(storeState);
        }
    },
 
    /**
     * Returns the store associated with this Panel.
     * @return {Ext.data.Store} The store
     */
    getStore: function(){
        return this.store;
    },
 
    /**
     * Gets the view for this panel.
     * @return {Ext.view.Table}
     */
    getView: function() {
        var me = this,
            sm,
            viewConfig;
 
        if (!me.view) {
            sm = me.getSelectionModel();
 
            viewConfig = Ext.apply({
                // TableView injects the view reference into this grid so that we have a reference as early as possible 
                // and Features need a reference to the grid. 
                // For these reasons, we configure a reference to this grid into the View 
                grid: me,
                ownerGrid: me.ownerGrid,
                deferInitialRefresh: me.deferRowRender,
                variableRowHeight: me.variableRowHeight,
                preserveScrollOnRefresh: true,
                trackOver: me.trackMouseOver !== false,
                throttledUpdate: me.throttledUpdate === true,
                scroll: me.scroll,
                xtype: me.viewType,
                store: me.store,
                headerCt: me.headerCt,
                columnLines: me.columnLines,
                rowLines: me.rowLines,
                selModel: sm,
                navigationModel: 'grid',
                features: me.features,
                panel: me,
                emptyText: me.emptyText || ''
            }, me.viewConfig);
 
            // Reconcile conflicting scroll requests in the grid's scroll configuration and viewConfig's scroll configuration. 
            // If the grid has scroll:'vertical', and the viewConfig has scroll"horizontal', the outcome must be scroll: 'both' 
            if (me.scroll && me.viewConfig.scroll && me.scroll !== me.viewConfig.scroll) {
                viewConfig.scroll = 'both';
            }
 
            Ext.widget(viewConfig);
 
            // Normalize the application of the markup wrapping the emptyText config. 
            // `emptyText` can now be defined on the grid as well as on its viewConfig, and this led to the emptyText not 
            // having the wrapping markup when it was defined in the viewConfig. It should be backwards compatible. 
            // Note that in the unlikely event that emptyText is defined on both the grid config and the viewConfig that the viewConfig wins. 
            if (me.view.emptyText) {
                me.view.emptyText = '<div class="' + me.emptyCls + '">' + me.view.emptyText + '</div>';
            }
 
            // TableView's custom component layout, Ext.view.TableLayout requires a reference to the headerCt because it depends on the headerCt doing its work. 
            me.view.getComponentLayout().headerCt = me.headerCt;
 
            me.mon(me.view, {
                uievent: me.processEvent,
                scope: me
            });
            sm.view = me.view;
            me.headerCt.view = me.view;
 
            // Plugins and features may need to access the view as soon as it is created. 
            if (me.hasListeners.viewcreated) {
                me.fireEvent('viewcreated', me, me.view);
            }
        }
        return me.view;
    },
 
    getColumnManager: function() {
        return this.columnManager;
    },
 
    getVisibleColumnManager: function() {
        return this.visibleColumnManager;
    },
 
    getTopLevelColumnManager: function() {
        return this.ownerGrid.getColumnManager();
    },
 
    getTopLevelVisibleColumnManager: function() {
        return this.ownerGrid.getVisibleColumnManager();
    },
 
    /**
     * @private
     * autoScroll is never valid for all classes which extend TablePanel.
     */
    setAutoScroll: Ext.emptyFn,
 
    /**
     * @private
     * Processes UI events from the view. Propagates them to whatever internal Components need to process them.
     * @param {String} type Event type, eg 'click'
     * @param {Ext.view.Table} view TableView Component
     * @param {HTMLElement} cell Cell HTMLElement the event took place within
     * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
     * @param {Number} cellIndex Cell index within the row
     * @param {Ext.event.Event} e Original event
     */
    processEvent: function(type, view, cell, recordIndex, cellIndex, e, record, row) {
        var header = e.position.column;
 
        if (header) {
            return header.processEvent.apply(header, arguments);
        }
    },
 
    scrollByDeltaY: function(yDelta, animate) {
        this.getView().scrollBy(0, yDelta, animate);
    },
 
    scrollByDeltaX: function(xDelta, animate) {
        this.getView().scrollBy(xDelta, 0, animate);
    },
 
    afterCollapse: function() {
        this.saveScrollPos();
        this.callParent(arguments);
    },
 
    afterExpand: function() {
        this.callParent(arguments);
        this.restoreScrollPos();
    },
 
    saveScrollPos: Ext.emptyFn,
 
    restoreScrollPos: Ext.emptyFn,
 
    onHeaderResize: function() {
        // Touch scroll manager needs to know about the new width 
        if (this.view.scrollManager) {
            this.view.scrollManager.refresh();
        }
    },
 
    // Update the view when a header moves 
    onHeaderMove: function(headerCt, header, colsToMove, fromIdx, toIdx) {
        var me = this;
 
        // If there are Features or Plugins which create DOM which must match column order, they set the optimizedColumnMove flag to false. 
        // In this case we must refresh the view on column move. 
        if (me.optimizedColumnMove === false) {
            me.view.refreshView();
        }
 
        // Simplest case for default DOM structure is just to swap the columns round in the view. 
        else {
            me.view.moveColumn(fromIdx, toIdx, colsToMove);
        }
        me.delayScroll();
    },
 
    // Section onHeaderHide is invoked after view. 
    onHeaderHide: function(headerCt, header) {
        if (this.view.refreshCounter) {
            this.view.refreshView();
        }
    },
 
    onHeaderShow: function(headerCt, header) {
        if (this.view.refreshCounter) {
            this.view.refreshView();
        }
    },
 
    // To be triggered on add/remove/move for a leaf header 
    onHeadersChanged: function(headerCt, header) {
        var me = this;
        if (me.rendered && !me.reconfiguring) {
            me.view.refreshView();
            me.delayScroll();
        }
    },
 
    delayScroll: function(){
        var target = this.view;
        if (target) {
            // Do not cause a layout by reading scrollX now. 
            // It must be read from the target when the task finally executes. 
            this.scrollTask.delay(10, null, null, [target]);
        }
    },
 
    /**
     * @private
     * Fires the TablePanel's viewready event when the view declares that its internal DOM is ready
     */
    onViewReady: function() {
         this.fireEvent('viewready', this);   
    },
 
    /**
     * @private
     * Tracks when things happen to the view and preserves the horizontal scroll position.
     */
    onRestoreHorzScroll: function() {
        var me = this,
            x = me.scrollXPos;
 
        if (x) {
            // We need to restore the body scroll position here 
            me.syncHorizontalScroll(me, true);
        }
    },
 
    getScrollerOwner: function() {
        var rootCmp = this;
        if (!this.scrollerOwner) {
            rootCmp = this.up('[scrollerOwner]');
        }
        return rootCmp;
    },
 
    /**
     * Gets left hand side marker for header resizing.
     * @private
     */
    getLhsMarker: function() {
        var me = this;
        return me.lhsMarker || (me.lhsMarker = Ext.DomHelper.append(me.el, {
            role: 'presentation',
            cls: me.resizeMarkerCls
        }, true));
    },
 
    /**
     * Gets right hand side marker for header resizing.
     * @private
     */
    getRhsMarker: function() {
        var me = this;
 
        return me.rhsMarker || (me.rhsMarker = Ext.DomHelper.append(me.el, {
            role: 'presentation',
            cls: me.resizeMarkerCls
        }, true));
    },
 
    /**
     * Returns the grid's selection. See `{@link Ext.selection.Model#getSelection}`.
     * @inheritdoc Ext.selection.Model#getSelection
     */
    getSelection: function () {
        return this.getSelectionModel().getSelection();
    },
 
    updateSelection: function(selection) {
        var me = this,
            sm;
 
        if (!me.ignoreNextSelection) {
            me.ignoreNextSelection = true;
            sm = me.getSelectionModel();
            if (selection) {
                sm.select(selection);
            } else {
                sm.deselectAll();
            }
            me.ignoreNextSelection = false;
        }
    },
 
    updateBindSelection: function(selModel, selection) {
        var me = this,
            selected = null;
 
        if (!me.ignoreNextSelection) {
            me.ignoreNextSelection = true;
            if (selection.length) {
                selected = selModel.getLastSelected();
                me.hasHadSelection = true;
            }
            if (me.hasHadSelection) {
                me.setSelection(selected);
            }
            me.ignoreNextSelection = false;
        }
    },
 
    getNavigationModel: function() {
        return this.getView().getNavigationModel();
    },
 
    /**
     * Returns the selection model being used and creates it via the configuration if it has not been created already.
     * @return {Ext.selection.Model} selModel
     */
    getSelectionModel: function(){
        var me = this,
            selModel = me.selModel,
            applyMode, mode, type;
        
        if (!selModel) {
            selModel = {};
            // no config, set our own mode 
            applyMode = true;
        }
 
        if (!selModel.events) {
            // only config provided, set our mode if one doesn't exist on the config 
            type = selModel.selType || me.selType;
            applyMode = !selModel.mode;
            selModel = me.selModel = Ext.create('selection.' + type, selModel);
        }
 
        if (me.simpleSelect) {
            mode = 'SIMPLE';
        } else if (me.multiSelect) {
            mode = 'MULTI';
        }
 
        Ext.applyIf(selModel, {
            allowDeselect: me.allowDeselect
        });
        
        if (mode && applyMode) {
            selModel.setSelectionMode(mode);
        }
 
        if (!selModel.hasRelaySetup) {
            me.relayEvents(selModel, [
                'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
            ]);
            selModel.hasRelaySetup = true;
            if (selModel.isRowModel) {
                selModel.on('selectionchange', me.updateBindSelection, me);
            }
        }
 
        // lock the selection model if user 
        // has disabled selection 
        if (me.disableSelection) {
            selModel.locked = true;
        }
        return selModel;
    },
 
    getScrollTarget: function(){
        var owner = this.getScrollerOwner(),
            items = owner.query('tableview');
 
        return items[1] || items[0];
    },
 
    onHorizontalScroll: function(view) {
        this.syncHorizontalScroll(view);
    },
 
    syncHorizontalScroll: function(target, setBody) {
        var me = this,
            x = me.view.getScrollX(),
            scrollTarget;
 
        setBody = setBody === true;
        // Only set the horizontal scroll if we've changed position, 
        // so that we don't set this on vertical scrolls 
        if (me.rendered && (setBody || x !== me.scrollXPos)) {
            // Only set the body position if we're reacting to a refresh, otherwise 
            // we just need to set the header. 
            if (setBody) {   
                scrollTarget = me.getScrollTarget();
                scrollTarget.setScrollX(x);
            }
            me.headerCt.setScrollX(x);
            me.scrollXPos = x;
        }
    },
 
    // template method meant to be overriden 
    onStoreLoad: Ext.emptyFn,
 
    getEditorParent: function() {
        return this.body;
    },
 
    bindStore: function(store, initial) {
        var me = this,
            view = me.getView(),
            bufferedRenderer = me.bufferedRenderer;
 
        // Normally, this method will always be called with a valid store (because there is a symmetric 
        // .unbindStore method), but there are cases where this method will be called and passed a null 
        // value, i.e., a panel is used as a pickerfield. See EXTJS-13089. 
        if (store) {
            // Bind to store immediately because subsequent processing looks for grid's store property 
            me.store = store;
 
            // If we're in a reconfigure (we already have a BufferedRenderer which is bound to our old store), 
            // rebind the BufferedRenderer 
            if (bufferedRenderer && bufferedRenderer.isBufferedRenderer && bufferedRenderer.store) {
                bufferedRenderer.bindStore(store);
            }
 
            if (view.store !== store) {
                // If coming from a reconfigure, we need to set the actual store property on the view. Setting the 
                // store will then also set the dataSource. 
                // 
                // Note that if it's a grid feature then this is sorted out in view.bindStore(), and it's own 
                // implementation of .bindStore() will be called. 
                view.bindStore(store, false);
            }
 
            me.mon(store, {
                load: me.onStoreLoad,
                scope: me
            });
            me.storeRelayers = me.relayEvents(store, [
                /**
                 * @event filterchange
                 * @inheritdoc Ext.data.Store#filterchange
                 */
                'filterchange',
                /**
                 * @event groupchange
                 * @inheritdoc Ext.data.Store#groupchange
                 */
                'groupchange'
            ]);
        } else {
            me.unbindStore();
        }
    },
 
    unbindStore: function() {
        var me = this,
            store = me.store,
            view;
 
        if (store) {
            me.store = null;
 
            me.mun(store, {
                load: me.onStoreLoad,
                scope: me
            });
 
            Ext.destroy(me.storeRelayers);
 
            view = me.view;
            if (view.store) {
                view.bindStore(null);
            }
        }
    },
 
    setColumns: function(columns) {
        // If being reconfigured from zero columns to zero columns, skip operation. 
        // This can happen if columns are being set from a binding and the initial value 
        // of the bound data in the ViewModel is [] 
        if (columns.length || this.getColumnManager().getColumns().length) {
            this.reconfigure(undefined, columns);
        }
    },
 
    setStore: function (store) {
        this.reconfigure(store);
    },
 
    /**
     * @method reconfigure
     * Reconfigures the grid / tree with a new store/columns. Either the store or the 
     * columns can be omitted if you don't wish to change them.
     *
     * The {@link #enableLocking} config should be set to `true` before the reconfigure 
     * method is executed if locked columns are intended to be used.
     *
     * @param {Ext.data.Store} [store] The new store. You can pass `null` if no new store.
     * @param {Object[]} [columns] An array of column configs
     */
    reconfigure: function(store, columns) {
        var me = this,
            oldStore = me.store,
            headerCt = me.headerCt,
            oldColumns = headerCt ? headerCt.items.getRange() : me.columns;
 
        // Allow optional store argument to be fully omitted, and the columns argument to be solo 
        if (arguments.length === 1 && Ext.isArray(store)) {
            columns = store;
            store = null;
        }
 
        // Make copy in case the beforereconfigure listener mutates it. 
        if (columns) {
            columns = Ext.Array.slice(columns);
        }
 
        me.reconfiguring = true;
        me.fireEvent('beforereconfigure', me, store, columns, oldStore, oldColumns);
        if (me.lockable) {
            me.reconfigureLockable(store, columns);
        } else {
            Ext.suspendLayouts();
            if (columns) {
                // new columns, delete scroll pos 
                delete me.scrollXPos;
                headerCt.removeAll();
                headerCt.add(columns);
            }
            // The following test compares the result of an assignment of the store var with the oldStore var 
            // This saves a large amount of code. 
            if (store && (store = Ext.StoreManager.lookup(store)) !== oldStore) {
                me.unbindStore();
                me.bindStore(store);
            } else {
                me.getView().refreshView();
            }
            Ext.resumeLayouts(true);
        }
        me.fireEvent('reconfigure', me, store, columns, oldStore, oldColumns);
        delete me.reconfiguring;
    },
 
    beforeDestroy: function(){
        var task = this.scrollTask;
        if (task) {
            task.cancel();
            this.scrollTask = null;
        }
        Ext.destroy(this.focusEnterLeaveListeners);
        this.callParent();
    },
 
    onDestroy: function(){
        var me = this;
        if (me.lockable) {
            me.destroyLockable();
        }
        me.unbindStore();
        me.callParent();
        me.columns = me.storeRelayers = me.columnManager = me.visibleColumnManager = null;
    },
    
    destroy: function() {
        // Clear out references here because other things (plugins/features) may need to know about them during destruction 
        var me = this;
        me.callParent();
        if (me.isDestroyed) {
            me.view = me.selModel = me.headerCt = null;
        }
    },
 
    privates: {
        getFocusEl: function() {
            return this.getView().getFocusEl();
        }
    }
});