/**
 * This class encapsulates the user interface for a tabular data set.
 * It acts as a centralized manager for controlling the various interface
 * elements of the view. This includes handling events, such as row and cell
 * level based DOM events. It also reacts to events from the underlying {@link Ext.selection.Model}
 * to provide visual feedback to the user.
 *
 * This class does not provide ways to manipulate the underlying data of the configured
 * {@link Ext.data.Store}.
 *
 * This is the base class for both {@link Ext.grid.View} and {@link Ext.tree.View} and is not
 * to be used directly.
 */
Ext.define('Ext.view.Table', {
    extend: 'Ext.view.View',
    xtype: [ 'tableview', 'gridview' ],
    alternateClassName: 'Ext.grid.View',
 
    requires: [
        'Ext.grid.CellContext',
        'Ext.view.TableLayout',
        'Ext.grid.locking.RowSynchronizer',
        'Ext.view.NodeCache',
        'Ext.util.DelayedTask',
        'Ext.util.MixedCollection',
        'Ext.scroll.TableScroller'
    ],
 
    // View is now queryable by virtue of having managed widgets either in widget columns
    // or in RowWidget plugin
    mixins: [
        'Ext.mixin.Queryable'
    ],
 
    /**
     * @property {Boolean} 
     * `true` in this class to identify an object as an instantiated Ext.view.TableView,
     * or subclass thereof.
     */
    isTableView: true,
 
    config: {
        selectionModel: {
            type: 'rowmodel'
        }
    },
 
    inheritableStatics: {
        // Events a TableView may fire. Used by Ext.grid.locking.View to relay events to its
        // ownerGrid in order to quack like a genuine Ext.table.View.
        // 
        // The events below are to be relayed only from the normal side view because the events
        // are relayed from the selection model, so both sides will fire them.
        /**
         * @private
         * @static
         * @inheritable
         */
        normalSideEvents: [
            "deselect",
            "select",
            "beforedeselect",
            "beforeselect",
            "selectionchange"
        ],
 
        // These events are relayed from both views because they are fired independently.
        /**
         * @private
         * @static
         * @inheritable
         */
        events: [
            "blur",
            "focus",
            "move",
            "resize",
            "destroy",
            "beforedestroy",
            "boxready",
            "afterrender",
            "render",
            "beforerender",
            "removed",
            "hide",
            "beforehide",
            "show",
            "beforeshow",
            "enable",
            "disable",
            "added",
            "deactivate",
            "beforedeactivate",
            "activate",
            "beforeactivate",
            "cellkeydown",
            "beforecellkeydown",
            "cellmouseup",
            "beforecellmouseup",
            "cellmousedown",
            "beforecellmousedown",
            "cellcontextmenu",
            "beforecellcontextmenu",
            "celldblclick",
            "beforecelldblclick",
            "cellclick",
            "beforecellclick",
            "refresh",
            "itemremove",
            "itemadd",
            "beforeitemupdate",
            "itemupdate",
            "viewready",
            "beforerefresh",
            "unhighlightitem",
            "highlightitem",
            "focuschange",
            "containerkeydown",
            "containercontextmenu",
            "containerdblclick",
            "containerclick",
            "containermouseout",
            "containermouseover",
            "containermouseup",
            "containermousedown",
            "beforecontainerkeydown",
            "beforecontainercontextmenu",
            "beforecontainerdblclick",
            "beforecontainerclick",
            "beforecontainermouseout",
            "beforecontainermouseover",
            "beforecontainermouseup",
            "beforecontainermousedown",
            "itemkeydown",
            "itemcontextmenu",
            "itemdblclick",
            "itemclick",
            "itemmouseleave",
            "itemmouseenter",
            "itemmouseup",
            "itemmousedown",
            "rowclick",
            "rowcontextmenu",
            "rowdblclick",
            "rowkeydown",
            "rowmouseup",
            "rowmousedown",
            "rowkeydown",
            "beforeitemkeydown",
            "beforeitemcontextmenu",
            "beforeitemdblclick",
            "beforeitemclick",
            "beforeitemmouseleave",
            "beforeitemmouseenter",
            "beforeitemmouseup",
            "beforeitemmousedown",
            "statesave",
            "beforestatesave",
            "staterestore",
            "beforestaterestore",
            "uievent",
            "groupcollapse",
            "groupexpand",
            "scroll"
        ]
    },
 
    scrollable: true,
 
    componentLayout: 'tableview',
 
    baseCls: Ext.baseCSSPrefix + 'grid-view',
 
    unselectableCls: Ext.baseCSSPrefix + 'unselectable',
 
    /**
     * @cfg {String} [firstCls='x-grid-cell-first']
     * A CSS class to add to the *first* cell in every row to enable special styling
     * for the first column.
     * If no styling is needed on the first column, this may be configured as `null`.
     */
    firstCls: Ext.baseCSSPrefix + 'grid-cell-first',
 
    /**
     * @cfg {String} [lastCls='x-grid-cell-last']
     * A CSS class to add to the *last* cell in every row to enable special styling
     * for the last column.
     * If no styling is needed on the last column, this may be configured as `null`.
     */
    lastCls: Ext.baseCSSPrefix + 'grid-cell-last',
 
    itemCls: Ext.baseCSSPrefix + 'grid-item',
    selectedItemCls: Ext.baseCSSPrefix + 'grid-item-selected',
    selectedCellCls: Ext.baseCSSPrefix + 'grid-cell-selected',
    focusedItemCls: Ext.baseCSSPrefix + 'grid-item-focused',
    overItemCls: Ext.baseCSSPrefix + 'grid-item-over',
    altRowCls: Ext.baseCSSPrefix + 'grid-item-alt',
    dirtyCls: Ext.baseCSSPrefix + 'grid-dirty-cell',
    rowClsRe: new RegExp('(?:^|\\s*)' + Ext.baseCSSPrefix + 'grid-item-alt(?:\\s+|$)', 'g'),
    cellRe: new RegExp(Ext.baseCSSPrefix + 'grid-cell-([^\\s]+)(?:\\s|$)', ''),
    positionBody: true,
    positionCells: false,
    stripeOnUpdate: null,
 
    /**
     * @property {Boolean} actionableMode
     * This value is `true` when the grid has been set to actionable mode by the user.
     *
     * See http://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#grid
     * @readonly
     */
    actionableMode: false,
 
    // cfg docs inherited
    trackOver: true,
 
    /**
     * Override this function to apply custom CSS classes to rows during rendering. This function
     * should return the CSS class name (or empty string '' for none) that will be added
     * to the row's wrapping element. To apply multiple class names, simply return them
     * space-delimited within the string (e.g. 'my-class another-class').
     * Example usage:
     *
     *     viewConfig: {
     *         getRowClass: function(record, rowIndex, rowParams, store){
     *             return record.get("valid") ? "row-valid" : "row-error";
     *         }
     *     }
     *
     * @param {Ext.data.Model} record The record corresponding to the current row.
     * @param {Number} index The row index.
     * @param {Object} rowParams **DEPRECATED.** For row body use the
     * {@link Ext.grid.feature.RowBody#getAdditionalData getAdditionalData} method of the rowbody
     * feature.
     * @param {Ext.data.Store} store The store this grid is bound to
     * @return {String} a CSS class name to add to the row.
     * @method
     */
    getRowClass: null,
 
    /**
     * @cfg {Boolean} stripeRows
     * True to stripe the rows.
     *
     * This causes the CSS class **`x-grid-row-alt`** to be added to alternate rows of
     * the grid. A default CSS rule is provided which sets a background color, but you can override
     * this with a rule which either overrides the **background-color** style using the `!important`
     * modifier, or which uses a CSS selector of higher specificity.
     */
    stripeRows: true,
 
    /**
     * @cfg {Boolean} markDirty
     * True to show the dirty cell indicator when a cell has been modified.
     */
    markDirty: true,
 
    /**
     * @cfg {Boolean} [enableTextSelection=false]
     * True to enable text selection inside this view.
     */
 
    ariaRole: 'rowgroup',
    rowAriaRole: 'row',
    cellAriaRole: 'gridcell',
 
    /**
     * @property {Number} usageBitMask
     * A single bit that identifies this view among potentially a pair of views in a locked
     * grid.
     * @readonly
     * @since 7.1
     * @private
     */
    usageBitMask: 1,
 
    /**
     * @property {Ext.view.Table} ownerGrid
     * A reference to the top-level owning grid component. This is actually the TablePanel
     * so it could be a tree.
     * @readonly
     * @private
     * @since 5.0.0
     */
 
    /**
     * @method disable
     * Disable this view.
     *
     * Disables interaction with, and masks this view.
     *
     * Note that the encapsulating {@link Ext.panel.Table} panel is *not* disabled, and other
     * *docked* components such as the panel header, the column header container, and docked
     * toolbars will still be enabled. The panel itself can be disabled if that is required,
     * or individual docked components could be disabled.
     *
     * See {@link Ext.panel.Table #disableColumnHeaders disableColumnHeaders} and
     * {@link Ext.panel.Table #enableColumnHeaders enableColumnHeaders}.
     *
     * @param {Boolean} [silent=false] Passing `true` will suppress the `disable` event
     * from being fired.
     * @since 1.1.0
     */
 
    /* eslint-disable indent, max-len */
    /**
     * @cfg tpl
     * @private
     * Outer tpl for TableView just to satisfy the validation within AbstractView.initComponent.
     */
    tpl: [
        '{%',
            'view = values.view;',
            'if (!(columns = values.columns)) {',
                'columns = values.columns = view.ownerCt.getVisibleColumnManager().getColumns();',
            '}',
            'values.fullWidth = 0;',
            // Stamp cellWidth into the columns
            'for (i = 0, len = columns.length; i < len; i++) {',
                'column = columns[i];',
                'values.fullWidth += (column.cellWidth = column.lastBox ? column.lastBox.width : column.width || column.minWidth);',
            '}',
 
            // Add the row/column line classes to the container element.
            'tableCls=values.tableCls=[];',
        '%}',
        '<div class="' + Ext.baseCSSPrefix + 'grid-item-container" role="presentation" style="width:{fullWidth}px">',
            '{[view.renderTHead(values, out, parent)]}',
            '{%',
                'view.renderRows(values.rows, values.columns, values.viewStartIndex, out);',
            '%}',
            '{[view.renderTFoot(values, out, parent)]}',
        '</div>',
 
        // This template is shared on the Ext.view.Table prototype, so we have to
        // clean up the closed over variables. Otherwise we'll retain the last values
        // of the template execution!
        '{% ',
            'view = columns = column = null;',
        '%}',
        {
            definitions: 'var view, tableCls, columns, i, len, column;',
            priority: 0
        }
    ],
 
    outerRowTpl: [
        '<table id="{rowId}" role="presentation" ',
            'data-boundView="{view.id}" ',
            'data-recordId="{record.internalId}" ',
            'data-recordIndex="{recordIndex}" ',
            'class="{[values.itemClasses.join(" ")]}" cellpadding="0" cellspacing="0" style="{itemStyle};width:0">',
 
                // Do NOT emit a <TBODY> tag in case the nextTpl has to emit a <COLGROUP> column sizer element.
                // Browser will create a tbody tag when it encounters the first <TR>
                '{%',
                    'this.nextTpl.applyOut(values, out, parent)',
                '%}',
        '</table>',
        {
            priority: 9999
        }
    ],
 
    rowTpl: [
        '{%',
            'var dataRowCls = values.recordIndex === -1 ? "" : " ' + Ext.baseCSSPrefix + 'grid-row";',
        '%}',
        '<tr class="{[values.rowClasses.join(" ")]} {[dataRowCls]}"',
                ' role="{rowRole}" {rowAttr:attributes}>',
            '<tpl for="columns">' +
                '{%',
                    'parent.view.renderCell(values, parent.record, parent.recordIndex, parent.rowIndex, xindex - 1, out, parent)',
                 '%}',
            '</tpl>',
        '</tr>',
        {
            priority: 0
        }
    ],
 
    cellTpl: [
        '<td class="{tdCls}" {tdAttr} {cellAttr:attributes}',
            ' style="width:{column.cellWidth}px;',
            '{% if(values.tdStyle){out.push(values.tdStyle);}%}"',
            '{% if (values.column.cellFocusable === false) {%}',
                ' role="presentation"',
            '{% } else { %}',
                ' role="{cellRole}" tabindex="-1"',
            '{% } %}',
            '  data-columnid="{[values.column.getItemId()]}">',
            '<div {unselectableAttr} class="' + Ext.baseCSSPrefix + 'grid-cell-inner {innerCls}" ',
                'style="text-align:{align};',
                '{% if (values.style) {out.push(values.style);} %}" ',
                '{cellInnerAttr:attributes}>{value}</div>',
        '</td>',
        {
            priority: 0
        }
    ],
    /* eslint-enable indent, max-len */
 
    /**
     * @private
     * Flag to disable refreshing SelectionModel on view refresh. Table views render rows
     * with selected CSS class already added if necessary.
     */
    refreshSelmodelOnRefresh: false,
 
    scrollableType: 'table',
 
    tableValues: {},
 
    // Private properties used during the row and cell render process.
    // They are allocated here on the prototype, and cleared/re-used to avoid GC churn
    // during repeated rendering.
    rowValues: {
        itemClasses: [],
        rowClasses: []
    },
 
    cellValues: {
        classes: [
            // for styles shared between cell and rowwrap
            Ext.baseCSSPrefix + 'grid-cell ' + Ext.baseCSSPrefix + 'grid-td'
        ]
    },
 
    /**
     * @event beforecellclick
     * Fired before the cell click is processed. Return false to cancel the default action.
     * @param {Ext.view.Table} this 
     * @param {HTMLElement} td The TD element for the cell.
     * @param {Number} cellIndex 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the cell.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines the target cell.
     */
 
    /**
     * @event cellclick
     * Fired when table cell is clicked.
     * @param {Ext.view.Table} this 
     * @param {HTMLElement} td The TD element for the cell.
     * @param {Number} cellIndex 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the cell.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines the target cell.
     */
 
    /**
     * @event beforecelldblclick
     * Fired before the cell double click is processed. Return false to cancel the default action.
     * @param {Ext.view.Table} this 
     * @param {HTMLElement} td The TD element for the cell.
     * @param {Number} cellIndex 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the cell.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines the target cell.
     */
 
    /**
     * @event celldblclick
     * Fired when table cell is double clicked.
     * @param {Ext.view.Table} this 
     * @param {HTMLElement} td The TD element for the cell.
     * @param {Number} cellIndex 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the cell.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines the target cell.
     */
 
    /**
     * @event beforecellcontextmenu
     * Fired before the cell right click is processed. Return false to cancel the default action.
     * @param {Ext.view.Table} this 
     * @param {HTMLElement} td The TD element for the cell.
     * @param {Number} cellIndex 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the cell.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines the target cell.
     */
 
    /**
     * @event cellcontextmenu
     * Fired when table cell is right clicked.
     * @param {Ext.view.Table} this 
     * @param {HTMLElement} td The TD element for the cell.
     * @param {Number} cellIndex 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the cell.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines the target cell.
     */
 
    /**
     * @event beforecellmousedown
     * Fired before the cell mouse down is processed. Return false to cancel the default action.
     * @param {Ext.view.Table} this 
     * @param {HTMLElement} td The TD element for the cell.
     * @param {Number} cellIndex 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the cell.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines the target cell.
     */
 
    /**
     * @event cellmousedown
     * Fired when the mousedown event is captured on the cell.
     * @param {Ext.view.Table} this 
     * @param {HTMLElement} td The TD element for the cell.
     * @param {Number} cellIndex 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the cell.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines the target cell.
     */
 
    /**
     * @event beforecellmouseup
     * Fired before the cell mouse up is processed. Return false to cancel the default action.
     * @param {Ext.view.Table} this 
     * @param {HTMLElement} td The TD element for the cell.
     * @param {Number} cellIndex 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the cell.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines the target cell.
     */
 
    /**
     * @event cellmouseup
     * Fired when the mouseup event is captured on the cell.
     * @param {Ext.view.Table} this 
     * @param {HTMLElement} td The TD element for the cell.
     * @param {Number} cellIndex 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the cell.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines the target cell.
     */
 
    /**
     * @event beforecellkeydown
     * Fired before the cell key down is processed. Return false to cancel the default action.
     * @param {Ext.view.Table} this 
     * @param {HTMLElement} td The TD element for the cell.
     * @param {Number} cellIndex 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the cell.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines the target cell.
     */
 
    /**
     * @event cellkeydown
     * Fired when the keydown event is captured on the cell.
     * @param {Ext.view.Table} this 
     * @param {HTMLElement} td The TD element for the cell.
     * @param {Number} cellIndex 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the cell.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines the target cell.
     */
 
    /**
     * @event rowclick
     * Fired when a table row is clicked.
     * @param {Ext.view.Table} this 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} element The TR element for the row.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines
     * the target row.
     */
 
    /**
     * @event rowdblclick
     * Fired when table row is double clicked.
     * @param {Ext.view.Table} this 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} element The TR element for the row.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines
     * the target row.
     */
 
    /**
     * @event rowcontextmenu
     * Fired when table row is right clicked.
     * @param {Ext.view.Table} this 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the row.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines
     * the target row.
     */
 
    /**
     * @event rowmousedown
     * Fired when the mousedown event is captured on the row.
     * @param {Ext.view.Table} this 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} tr The TR element for the row.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines
     * the target row.
     */
 
    /**
     * @event rowmouseup
     * Fired when the mouseup event is captured on the row.
     * @param {Ext.view.Table} this 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} element The TR element for the row.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines
     * the target row.
     */
 
    /**
     * @event rowkeydown
     * Fired when the keydown event is captured on the row.
     * @param {Ext.view.Table} this 
     * @param {Ext.data.Model} record 
     * @param {HTMLElement} element The TR element for the row.
     * @param {Number} rowIndex 
     * @param {Ext.event.Event} e 
     * @param {Ext.grid.CellContext} e.position A CellContext object which defines
     * the target row.
     */
 
    /**
     * @event beforerowexit
     * Fired when View is asked to exit Actionable mode in the current row,
     * and proceed to the previous/next row. If the handler returns `false`,
     * View processing is aborted.
     * @param {Ext.view.Table} this 
     * @param {Ext.event.Event} keyEvent The key event that caused navigation.
     * @param {HTMLElement} prevRow Currently active table row.
     * @param {HTMLElement} nextRow Table row that is going to be focused and activated.
     * @param {Boolean} forward `true` if we're navigating forward (Tab), `false` if
     * navigating backward (Shift-Tab).
     */
 
    constructor: function(config) {
        // Adjust our base class if we are inside a TreePanel
        if (config.grid.isTree) {
            config.baseCls = Ext.baseCSSPrefix + 'tree-view';
        }
 
        this.callParent([config]);
    },
 
    /**
     * @private
     * Returns `true` if this view has been configured with variableRowHeight (or this has been set
     * by a plugin/feature) which might insert arbitrary markup into a grid item. Or if at least one
     * visible column has been configured with variableRowHeight. Or if the store is grouped.
     */
    hasVariableRowHeight: function(fromLockingPartner) {
        var me = this;
 
        return me.variableRowHeight || me.store.isGrouped() ||
               me.getVisibleColumnManager().hasVariableRowHeight() ||
               // If not already called from a locking partner, and there is a locking partner,
               // and the partner has variableRowHeight, then WE have variableRowHeight too.
               (!fromLockingPartner && me.lockingPartner &&
               me.lockingPartner.hasVariableRowHeight(true));
    },
 
    initComponent: function() {
        var me = this;
 
        if (me.columnLines) {
            me.addCls(me.grid.colLinesCls);
        }
 
        if (me.rowLines) {
            me.addCls(me.grid.rowLinesCls);
        }
 
        /**
         * @private
         * @property {Ext.dom.Fly} body
         * A flyweight Ext.Element which encapsulates a reference to the view's main row
         * containing element.
         * *Note that the `dom` reference will not be present until the first data refresh*
         */
        me.body = new Ext.dom.Fly();
        me.body.id = me.id + 'gridBody';
 
        // If trackOver has been turned off, null out the overCls because documented behaviour
        // in AbstractView is to turn trackOver on if overItemCls is set.
        if (!me.trackOver) {
            me.overItemCls = null;
        }
 
        me.headerCt.view = me;
 
        // Features need a reference to the grid.
        // Grid needs an immediate reference to its view so that the view
        // can reliably be got from the grid during initialization
        me.grid.view = me;
        me.initFeatures(me.grid);
 
        me.itemSelector = me.getItemSelector();
        me.all = new Ext.view.NodeCache(me);
 
        me.actionRowFly = new Ext.dom.Fly();
 
        me.callParent();
    },
 
    /**
     * @private
     * Create a config object for this view's selection model based upon the passed grid's
     * configurations.
     */
    applySelectionModel: function(selModel, oldSelModel) {
        var me = this,
            grid = me.ownerGrid,
            defaultType = selModel.type,
            disableSelection = me.disableSelection || grid.disableSelection,
            allowDeselect = grid.allowDeselect;
 
        // If this is the initial configuration, pull overriding configs
        // in from the owning TablePanel.
        if (!oldSelModel) {
            // Favour a passed instance
            if (!(selModel && selModel.isSelectionModel)) {
                selModel = grid.selModel || selModel;
            }
        }
 
        if (selModel) {
            if (selModel.isSelectionModel) {
                selModel.allowDeselect = allowDeselect !== undefined
                    ? allowDeselect
                    : selModel.allowDeselect;
                selModel.locked = disableSelection;
            }
            else {
                if (typeof selModel === 'string') {
                    selModel = {
                        type: selModel
                    };
                }
                else {
                    // Copy obsolete selType property to type property now that selection models
                    // are Factoryable
                    // TODO: Remove selType config after deprecation period
                    selModel.type = grid.selType ||
                        selModel.selType ||
                        selModel.type ||
                        defaultType;
                }
 
                if (!selModel.mode) {
                    if (grid.simpleSelect) {
                        selModel.mode = 'SIMPLE';
                    }
                    else if (grid.multiSelect) {
                        selModel.mode = 'MULTI';
                    }
                }
 
                selModel = Ext.Factory.selection(Ext.applyIf({
                    allowDeselect: allowDeselect,
                    locked: disableSelection
                }, selModel));
            }
        }
 
        return selModel;
    },
 
    updateSelectionModel: function(selModel, oldSelModel) {
        var me = this;
 
        if (oldSelModel) {
            oldSelModel.un({
                scope: me,
                lastselectedchanged: me.updateBindSelection,
                selectionchange: me.updateBindSelection
            });
            Ext.destroy(me.selModelRelayer);
        }
 
        me.selModelRelayer = me.relayEvents(selModel, [
            'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect', 'focuschange'
        ]);
 
        selModel.on({
            scope: me,
            lastselectedchanged: me.updateBindSelection,
            selectionchange: me.updateBindSelection
        });
 
        me.selModel = selModel;
    },
 
    getVisibleColumnManager: function() {
        return this.ownerCt.getVisibleColumnManager();
    },
 
    getColumnManager: function() {
        return this.ownerCt.getColumnManager();
    },
 
    getTopLevelVisibleColumnManager: function() {
        // ownerGrid refers to the topmost responsible Ext.panel.Grid.
        // This could be this view's ownerCt, or if part of a locking arrangement, the locking grid
        return this.ownerGrid.getVisibleColumnManager();
    },
 
    /**
     * @private
     * Move a grid column from one position to another
     * @param {Number} fromIdx The index from which to move columns
     * @param {Number} toIdx The index at which to insert columns.
     * @param {Number} [colsToMove=1] The number of columns to move beginning at the `fromIdx`
     */
    moveColumn: function(fromIdx, toIdx, colsToMove) {
        var me = this,
            multiMove = colsToMove > 1,
            range = multiMove && document.createRange ? document.createRange() : null,
            fragment = multiMove && !range ? document.createDocumentFragment() : null,
            destinationCellIdx = toIdx,
            colCount = me.getGridColumns().length,
            lastIndex = colCount - 1,
            i, j, rows, len, tr, cells, colGroups, doFirstLastClasses;
 
        doFirstLastClasses =
            (me.firstCls || me.lastCls) &&
            (toIdx === 0 || toIdx === colCount || fromIdx === 0 || fromIdx === lastIndex);
 
        // Dragging between locked and unlocked side first refreshes the view,
        // and calls onHeaderMoved with fromIndex and toIndex the same.
        if (me.rendered && toIdx !== fromIdx) {
            // Grab all rows which have column cells in.
            // That is data rows.
            rows = me.el.query(me.rowSelector);
 
            for (= 0, len = rows.length; i < len; i++) {
                tr = rows[i];
                cells = tr.childNodes;
 
                // Keep first cell class and last cell class correct *only if needed*
                if (doFirstLastClasses) {
 
                    if (cells.length === 1) {
                        Ext.fly(cells[0]).addCls(me.firstCls);
                        Ext.fly(cells[0]).addCls(me.lastCls);
                        continue;
                    }
 
                    if (fromIdx === 0) {
                        Ext.fly(cells[0]).removeCls(me.firstCls);
                        Ext.fly(cells[1]).addCls(me.firstCls);
                    }
                    else if (fromIdx === lastIndex) {
                        Ext.fly(cells[lastIndex]).removeCls(me.lastCls);
                        Ext.fly(cells[lastIndex - 1]).addCls(me.lastCls);
                    }
 
                    if (toIdx === 0) {
                        Ext.fly(cells[0]).removeCls(me.firstCls);
                        Ext.fly(cells[fromIdx]).addCls(me.firstCls);
                    }
                    else if (toIdx === colCount) {
                        Ext.fly(cells[lastIndex]).removeCls(me.lastCls);
                        Ext.fly(cells[fromIdx]).addCls(me.lastCls);
                    }
                }
 
                // Move multi using the best technique.
                // Extract a range straight into a fragment if possible.
                if (multiMove) {
                    if (range) {
                        range.setStartBefore(cells[fromIdx]);
                        range.setEndAfter(cells[fromIdx + colsToMove - 1]);
                        fragment = range.extractContents();
                    }
                    else {
                        for (= 0; j < colsToMove; j++) {
                            fragment.appendChild(cells[fromIdx]);
                        }
                    }
 
                    tr.insertBefore(fragment, cells[destinationCellIdx] || null);
                }
                else {
                    tr.insertBefore(cells[fromIdx], cells[destinationCellIdx] || null);
                }
            }
 
            // Shuffle the <col> elements in all <colgroup>s
            colGroups = me.el.query('colgroup');
 
            for (= 0, len = colGroups.length; i < len; i++) {
                // Extract the colgroup
                tr = colGroups[i];
 
                // Move multi using the best technique.
                // Extract a range straight into a fragment if possible.
                if (multiMove) {
                    if (range) {
                        range.setStartBefore(tr.childNodes[fromIdx]);
                        range.setEndAfter(tr.childNodes[fromIdx + colsToMove - 1]);
                        fragment = range.extractContents();
                    }
                    else {
                        for (= 0; j < colsToMove; j++) {
                            fragment.appendChild(tr.childNodes[fromIdx]);
                        }
                    }
 
                    tr.insertBefore(fragment, tr.childNodes[destinationCellIdx] || null);
                }
                else {
                    tr.insertBefore(tr.childNodes[fromIdx],
                                    tr.childNodes[destinationCellIdx] || null);
                }
            }
        }
    },
 
    // scroll the view to the top
    scrollToTop: Ext.emptyFn,
 
    /**
     * Add a listener to the main view element. It will be destroyed with the view.
     * @private
     */
    addElListener: function(eventName, fn, scope) {
        this.mon(this, eventName, fn, scope, {
            element: 'el'
        });
    },
 
    /**
     * Get the leaf columns used for rendering the grid rows.
     * @private
     */
    getGridColumns: function() {
        return this.ownerCt.getVisibleColumnManager().getColumns();
    },
 
    /**
     * Get a leaf level header by index regardless of what the nesting
     * structure is.
     * @private
     * @param {Number} index The index
     */
    getHeaderAtIndex: function(index) {
        return this.ownerCt.getVisibleColumnManager().getHeaderAtIndex(index);
    },
 
    /**
     * Get the cell (td) for a particular record and column.
     * @param {Ext.data.Model} record 
     * @param {Ext.grid.column.Column/Number} column
     * @param {Boolean} [returnElement=false] `true` to return an Ext.Element,
     * else a raw `<td>` is returned.
     * @private
     */
    getCell: function(record, column, returnElement) {
        var row = this.getRow(record),
            cell;
 
        if (row) {
            if (typeof column === 'number') {
                column = this.getHeaderAtIndex(column);
            }
 
            cell = row.querySelector(column.getCellSelector());
 
            return returnElement ? Ext.get(cell) : cell;
        }
    },
 
    /**
     * Get a reference to a feature
     * @param {String} id The id of the feature
     * @return {Ext.grid.feature.Feature} The feature. Undefined if not found
     */
    getFeature: function(id) {
        var features = this.featuresMC;
 
        if (features) {
            return features.get(id);
        }
    },
 
    /**
     * @private
     * Finds a features by ftype in the features array
     */
    findFeature: function(ftype) {
        if (this.features) {
            return Ext.Array.findBy(this.features, function(feature) {
                if (feature.ftype === ftype) {
                    return true;
                }
            });
        }
    },
 
    /**
     * Initializes each feature and bind it to this view.
     * @private
     */
    initFeatures: function(grid) {
        var me = this,
            features, feature, i, len;
 
        // Row container element emitted by tpl
        me.tpl = this.lookupTpl('tpl');
 
        // The rowTpl emits a <div>
        me.rowTpl = me.lookupTpl('rowTpl');
        me.addRowTpl(me.lookupTpl('outerRowTpl'));
 
        // Each cell is emitted by the cellTpl
        me.cellTpl = me.lookupTpl('cellTpl');
 
        me.featuresMC = new Ext.util.MixedCollection();
        features = me.features = me.constructFeatures();
 
        for (= 0, len = features ? features.length : 0; i < len; i++) {
            feature = features[i];
 
            // inject a reference to view and grid - Features need both
            feature.view = me;
            feature.grid = grid;
 
            me.featuresMC.add(feature);
 
            feature.init(grid);
        }
    },
 
    renderTHead: function(values, out, parent) {
        var headers = values.view.headerFns,
            len, i;
 
        if (headers) {
            for (= 0, len = headers.length; i < len; ++i) {
                headers[i].call(this, values, out, parent);
            }
        }
    },
 
    // Currently, we don't have ordering support for header/footer functions,
    // they will be pushed on at construction time. If the need does arise,
    // we can add this functionality in the future, but for now it's not
    // really necessary since currently only the summary feature uses this.
    addHeaderFn: function(fn) {
        var headers = this.headerFns;
 
        if (!headers) {
            headers = this.headerFns = [];
        }
 
        headers.push(fn);
    },
 
    renderTFoot: function(values, out, parent) {
        var footers = values.view.footerFns,
            len, i;
 
        if (footers) {
            for (= 0, len = footers.length; i < len; ++i) {
                footers[i].call(this, values, out, parent);
            }
        }
    },
 
    addFooterFn: function(fn) {
        var footers = this.footerFns;
 
        if (!footers) {
            footers = this.footerFns = [];
        }
 
        footers.push(fn);
    },
 
    addTpl: function(newTpl) {
        return this.insertTpl('tpl', newTpl);
    },
 
    addRowTpl: function(newTpl) {
        return this.insertTpl('rowTpl', newTpl);
    },
 
    addCellTpl: function(newTpl) {
        return this.insertTpl('cellTpl', newTpl);
    },
 
    insertTpl: function(which, newTpl) {
        var me = this,
            tpl, prevTpl;
 
        // Clone an instantiated XTemplate
        if (newTpl.isTemplate) {
            newTpl = Ext.Object.chain(newTpl);
        }
        // If we have been passed an object of the form
        // {
        //      before: fn
        //      after: fn
        // }
        // Create a template from it using the object as the member configuration
        else {
            newTpl = new Ext.XTemplate('{%this.nextTpl.applyOut(values, out, parent);%}', newTpl);
        }
 
        // Stop at the first TPL who's priority is less than the passed rowTpl
        for (tpl = me[which]; newTpl.priority < tpl.priority; tpl = tpl.nextTpl) {
            prevTpl = tpl;
        }
 
        // If we had skipped over some, link the previous one to the passed rowTpl
        if (prevTpl) {
            prevTpl.nextTpl = newTpl;
        }
        // First one
        else {
            me[which] = newTpl;
        }
 
        newTpl.nextTpl = tpl;
 
        return newTpl;
    },
 
    tplApplyOut: function(values, out, parent) {
        if (this.before) {
            if (this.before(values, out, parent) === false) {
                return;
            }
        }
 
        this.nextTpl.applyOut(values, out, parent);
 
        if (this.after) {
            this.after(values, out, parent);
        }
    },
 
    /**
     * @private
     * Converts the features array as configured, into an array of instantiated Feature objects.
     *
     * Must have no side effects other than Feature instantiation.
     *
     * MUST NOT update the this.features property, and MUST NOT update the instantiated Features.
     */
    constructFeatures: function() {
        var me = this,
            features = me.features,
            feature, result, i, len;
 
        if (features) {
            result = [];
 
            for (= 0, len = features.length; i < len; i++) {
                feature = features[i];
 
                if (!feature.isFeature) {
                    feature = Ext.create('feature.' + feature.ftype, feature);
                }
 
                result[i] = feature;
            }
        }
 
        return result;
    },
 
    beforeRender: function() {
        this.callParent();
 
        if (!this.enableTextSelection) {
            this.protoEl.unselectable();
        }
    },
 
    updateScrollable: function(scrollable) {
        var me = this,
            ownerGrid = me.grid.ownerGrid;
 
        if (!ownerGrid.lockable && scrollable.isScroller && scrollable !== ownerGrid.scrollable) {
            ownerGrid.scrollable = scrollable;
        }
    },
 
    afterComponentLayout: function(width, height, oldWidth, oldHeight) {
        var me = this,
            ownerGrid = me.grid.ownerGrid;
 
        if (ownerGrid.mixins.lockable) {
            ownerGrid.syncLockableLayout();
        }
 
        me.callParent([width, height, oldWidth, oldHeight]);
    },
 
    getElConfig: function() {
        var config = this.callParent();
 
        // Table views are special in this regard; they should not have
        // aria-hidden and aria-disabled attributes.
        delete config['aria-hidden'];
        delete config['aria-disabled'];
 
        return config;
    },
 
    onBindStore: function(store) {
        var me = this,
            bufferedRenderer = me.bufferedRenderer;
 
        if (bufferedRenderer && bufferedRenderer.store !== store) {
            bufferedRenderer.bindStore(store);
        }
 
        // Clear view el unless we're reconfiguring - a refresh will happen.
        if (me.all && me.all.getCount() && !me.grid.reconfiguring) {
            me.clearViewEl(true);
        }
 
        me.callParent(arguments);
    },
 
    onOwnerGridHide: function() {
        var scroller = this.getScrollable(),
            bufferedRenderer = this.bufferedRederer;
 
        // Hide using display sets scroll to zero.
        // We should not tell any partners about this.
        if (scroller) {
            scroller.suspendPartnerSync();
        }
 
        // A buffered renderer should also not respond to that scroll.
        if (bufferedRenderer) {
            bufferedRenderer.disable();
        }
    },
 
    onOwnerGridShow: function() {
        var scroller = this.getScrollable(),
            bufferedRenderer = this.bufferedRederer;
 
        // Hide using display sets scroll to zero.
        // We should not tell any partners about this.
        if (scroller) {
            scroller.resumePartnerSync();
        }
 
        // A buffered renderer should also not respond to that scroll.
        if (bufferedRenderer) {
            bufferedRenderer.enable();
        }
    },
 
    getStoreListeners: function(store) {
        var me = this,
            result = me.callParent([store]),
            dataSource = me.dataSource;
 
        if (dataSource && dataSource.isFeatureStore) {
            // GroupStore triggers a refresh on add/remove, we don't want to have
            // it process twice
            delete result.add;
            delete result.remove;
        }
 
        // The BufferedRenderer handles clearing down the view on its onStoreClear method
        if (me.bufferedRenderer) {
            delete result.clear;
        }
 
        result.beforepageremove = me.beforePageRemove;
 
        return result;
    },
 
    beforePageRemove: function(pageMap, pageNumber) {
        var rows = this.all,
            pageSize = pageMap.getPageSize();
 
        // If the rendered block needs the page, access it which moves it
        // to the end of the LRU cache, and veto removal.
        if (rows.startIndex >= (pageNumber - 1) * pageSize &&
            rows.endIndex <= (pageNumber * pageSize - 1)) {
            pageMap.get(pageNumber);
 
            return false;
        }
    },
 
    /**
     * @private
     * Template method implemented starting at the AbstractView class.
     */
    onViewScroll: function(scroller, x, y) {
        // We ignore scrolling caused by focusing
        if (!this.destroyed && !this.ignoreScroll) {
            this.callParent([scroller, x, y]);
        }
    },
 
    /** 
     * @private
     * Create the DOM element which encapsulates the passed record.
     * Used when updating existing rows, so drills down into resulting structure.
     */
    createRowElement: function(record, index, updateColumns) {
        var me = this,
            div = me.renderBuffer,
            tplData = me.collectData([record], index),
            result;
 
        tplData.columns = updateColumns;
        me.tpl.overwrite(div, tplData);
 
        // We don't want references to be retained on the prototype
        me.cleanupData();
 
        // Return first element within node containing element
        result = div.dom.querySelector(me.getNodeContainerSelector()).firstChild;
        Ext.fly(result).saveTabbableState(me.saveTabOptions);
 
        return result;
    },
 
    /** 
     * @private
     * Override so that we can use a quicker way to access the row nodes.
     * They are simply all child nodes of the nodeContainer element.
     */
    bufferRender: function(records, index) {
        var me = this,
            div = me.renderBuffer,
            range = document.createRange ? document.createRange() : null,
            result;
 
        me.tpl.overwrite(div, me.collectData(records, index));
 
        // We don't want references to be retained on the prototype
        me.cleanupData();
 
        // Newly added rows must be untabbable by default
        div.saveTabbableState(me.saveTabOptions);
 
        div = div.dom.querySelector(me.getNodeContainerSelector());
 
        if (range) {
            range.selectNodeContents(div);
            result = range.extractContents();
        }
        else {
            result = document.createDocumentFragment();
 
            while (div.firstChild) {
                result.appendChild(div.firstChild);
            }
        }
 
        return {
            fragment: result,
            children: Ext.Array.toArray(result.childNodes)
        };
    },
 
    collectData: function(records, startIndex) {
        var me = this,
            tableValues = me.tableValues;
 
        me.rowValues.view = me;
 
        tableValues.view = me;
        tableValues.rows = records;
        tableValues.columns = null;
        tableValues.viewStartIndex = startIndex;
        tableValues.tableStyle = 'width:' + me.headerCt.getTableWidth() + 'px';
 
        return tableValues;
    },
 
    cleanupData: function() {
        var tableValues = this.tableValues;
 
        // Clean up references on the prototype
        tableValues.view = tableValues.columns = tableValues.rows = this.rowValues.view = null;
    },
 
    /** 
     * @private
     * Called when the table changes height.
     * For example, see examples/grid/group-summary-grid.html
     * If we have flexed column headers, we need to update the header layout
     * because it may have to accommodate (or cease to accommodate) a vertical scrollbar.
     * Only do this on platforms which have a space-consuming scrollbar.
     * Only do it when vertical scrolling is enabled.
     */
    refreshSize: function(forceLayout) {
        var me = this,
            bodySelector = me.getBodySelector(),
            lockingPartner = me.lockingPartner,
            restoreFocus;
 
        // keeping the current position to be restored by afterComponentLayout
        // once it's called because of resumeLayouts.
        if (!me.actionableMode) {
            restoreFocus = me.saveFocusState();
        }
 
        // On every update of the layout system due to data update, capture the view's main element
        // in our private flyweight.
        // IF there *is* a main element. Some TplFactories emit naked rows.
        if (bodySelector) {
            // use "down" instead of "child" because the grid table element is not a direct
            // child of the view element when a touch scroller is in use.
            me.body.attach(me.el.dom.querySelector(bodySelector));
        }
 
        if (!me.hasLoadingHeight) {
            // Suspend layouts in case the superclass requests a layout. We might too, so they
            // must be coalesced.
            Ext.suspendLayouts();
 
            me.callParent([forceLayout]);
 
            // We only need to adjust for height changes in the data if we, or any visible columns
            // have been configured with variableRowHeight: true
            // OR, if we are being passed the forceUpdate flag which is passed when the view's
            // item count changes.
            if (forceLayout || (me.hasVariableRowHeight() && me.dataSource.getCount())) {
                me.grid.updateLayout();
            }
 
            // Only flush layouts if there's no *visible* locking partner, or
            // the two partners have both refreshed to the same rendered block size.
            // If we are the first of a locking view pair, refreshing in response to a change of
            // view height, our rendered block size will be out of sync with our partner's
            // so row height equalization (called as part of a layout) will walk off the end.
            // This must be deferred until both views have refreshed to the same size.
            Ext.resumeLayouts(
                !lockingPartner || !lockingPartner.grid.isVisible() ||
                (lockingPartner.all.getCount() === me.all.getCount())
            );
 
            // Restore focus to the previous position in case layout cycles
            // scrolled the view back up.
            if (restoreFocus) {
                restoreFocus();
            }
        }
    },
 
    /**
     * @private
     * TableView is unable to lay out in isolation. It acquires information from
     * the HeaderContainer, so a request to layout a TableView MUST propagate upwards
     * into the grid.
     */
    isLayoutRoot: function() {
        return false;
    },
 
    clearViewEl: function(leaveNodeContainer) {
        var me = this,
            nodeContainer;
 
        // AbstractView will clear the view correctly
        // It also resets the scrollrange.
        if (me.rendered) {
            me.callParent();
 
            // If we are also removing the noe container, destroy it.
            if (!leaveNodeContainer) {
                nodeContainer = Ext.get(me.getNodeContainer());
 
                if (nodeContainer && nodeContainer.dom !== me.getTargetEl().dom) {
                    nodeContainer.destroy();
                }
            }
        }
    },
 
    getRefItems: function(deep) {
        // @private
        // CQ interface
        var me = this,
            rowContexts = me.ownerGrid.liveRowContexts,
            isLocked = !!me.isLockedView,
            result = me.callParent([deep]),
            widgetCount, i, widgets, widget, recordId;
 
        // Add the widgets from the RowContexts.
        // If deep, add any descendant widgets within them.
        for (recordId in rowContexts) {
            widgets = rowContexts[recordId].getWidgets();
            widgetCount = widgets.length;
 
            for (= 0; i < widgetCount; i++) {
                widget = widgets[i];
 
                // If we're in a lockable assembly, check that we're in the same side
                if (isLocked === widget.$fromLocked) {
                    result[result.length] = widget;
 
                    if (deep && widget.getRefItems) {
                        result.push.apply(result, widget.getRefItems(true));
                    }
                }
            }
        }
 
        return result;
    },
 
    getMaskTarget: function() {
        // Masking a TableView masks its IMMEDIATE parent GridPanel's body.
        // Disabling/enabling a locking view relays the call to both child views.
        return this.ownerCt.body;
    },
 
    statics: {
        getBoundView: function(node) {
            return Ext.getCmp(node.getAttribute('data-boundView'));
        }
    },
 
    getRecord: function(node) {
        // If store.destroy has been called before some delayed event fires on a node,
        // we must ignore the event.
        if (this.store.destroyed) {
            return;
        }
 
        if (node.isModel) {
            return node;
        }
 
        node = this.getNode(node);
 
        // Must use the internalId stamped into the DOM because if this is called after a sort
        // or filter, but before the refresh, then the "data-recordIndex" will be stale.
        if (node) {
            return this.dataSource.getByInternalId(node.getAttribute('data-recordId'));
        }
    },
 
    indexOf: function(node) {
        node = this.getNode(node);
 
        if (!node && node !== 0) {
            return -1;
        }
 
        return this.all.indexOf(node);
    },
 
    indexInStore: function(node) {
        // We cannot use the stamped in data-recordindex because that is the index in the original
        // configured store NOT the index in the dataSource that is being used -
        // that may be a GroupStore.
        return node ? this.dataSource.indexOf(this.getRecord(node)) : -1;
    },
 
    indexOfRow: function(record) {
        var dataSource = this.dataSource,
            idx;
 
        if (record.isCollapsedPlaceholder) {
            idx = dataSource.indexOfPlaceholder(record);
        }
        else {
            idx = dataSource.indexOf(record);
        }
 
        return idx;
    },
 
    renderRows: function(rows, columns, viewStartIndex, out) {
        var me = this,
            rowValues = me.rowValues,
            rowCount = rows.length,
            i;
 
        rowValues.view = me;
        rowValues.columns = columns;
 
        // The roles are the same for all data rows and cells
        rowValues.rowRole = me.rowAriaRole;
        me.cellValues.cellRole = me.cellAriaRole;
 
        for (= 0; i < rowCount; i++, viewStartIndex++) {
            rowValues.itemClasses.length = rowValues.rowClasses.length = 0;
            me.renderRow(rows[i], viewStartIndex, out);
        }
 
        // Dereference objects since rowValues is a persistent on our prototype
        rowValues.view = rowValues.columns = rowValues.record = null;
    },
 
    /* Alternative column sizer element renderer.
    renderTHeadColumnSizer: function(values, out) {
        var columns = this.getGridColumns(),
            len = columns.length, i,
            column, width;
 
        out.push('<thead><tr class="' + Ext.baseCSSPrefix + 'grid-header-row">');
        
        for (i = 0; i < len; i++) {
            column = columns[i];
            
            width = column.lastBox 
                ? column.lastBox.width
                : Ext.grid.header.Container.prototype.defaultWidth;
            
            out.push(
                '<th class="', Ext.baseCSSPrefix, 'grid-cell-', columns[i].getItemId(),
                '" style="width:' + width + 'px"></th>'
            );
        }
        out.
        push('</tr></thead>');
    },
    */
 
    renderColumnSizer: function(values, out) {
        var columns = values.columns || this.getGridColumns(),
            len = columns.length,
            i, column, width;
 
        out.push('<colgroup role="presentation">');
 
        for (= 0; i < len; i++) {
            column = columns[i];
 
            width = column.cellWidth
                ? column.cellWidth
                : Ext.grid.header.Container.prototype.defaultWidth;
 
            out.push(
                '<col role="presentation" class="', Ext.baseCSSPrefix, 'grid-cell-',
                columns[i].getItemId(), '" style="width:' + width + 'px">'
            );
        }
 
        out.push('</colgroup>');
    },
 
    /**
     * @private
     * Renders the HTML markup string for a single row into the passed array as a sequence
     * of strings, or returns the HTML markup for a single row.
     *
     * @param {Ext.data.Model} record The record to render.
     * @param {Number} rowIdx The index of the row
     * @param {String[]} [out] A string array onto which to append the resulting HTML string.
     * If omitted, the resulting HTML string is returned.
     * @return {String} **only when the out parameter is omitted** The resulting HTML string.
     */
    renderRow: function(record, rowIdx, out) {
        var me = this,
            isMetadataRecord = rowIdx === -1,
            selModel = me.selectionModel,
            rowValues = me.rowValues,
            itemClasses = rowValues.itemClasses,
            rowClasses = rowValues.rowClasses,
            itemCls = me.itemCls,
            cls,
            rowTpl = me.rowTpl;
 
        // Define the rowAttr object now. We don't want to do it in the treeview treeRowTpl
        // because anything this is processed in a deferred callback (such as deferring initial
        // view refresh in gridview) could poke rowAttr that are then shared in tableview.rowTpl.
        // See EXTJSIV-9341.
        //
        // For example, the following shows the shared ref between a treeview's rowTpl nextTpl
        // and the superclass tableview.rowTpl:
        //
        //      tree.view.rowTpl.nextTpl === grid.view.rowTpl
        //
        rowValues.rowAttr = {};
 
        // Set up mandatory properties on rowValues
        rowValues.record = record;
        rowValues.recordId = record.internalId;
 
        // recordIndex is index in true store (NOT the data source - possibly a GroupStore)
        rowValues.recordIndex = me.store.indexOf(record);
 
        // rowIndex is the row number in the view.
        rowValues.rowIndex = rowIdx;
        rowValues.rowId = me.getRowId(record);
        rowValues.itemCls = rowValues.rowCls = '';
 
        if (!rowValues.columns) {
            rowValues.columns = me.ownerCt.getVisibleColumnManager().getColumns();
        }
 
        itemClasses.length = rowClasses.length = 0;
 
        // If it's a metadata record such as a summary record.
        // So do not decorate it with the regular CSS.
        // The Feature which renders it must know how to decorate it.
        if (!isMetadataRecord) {
            itemClasses[0] = itemCls;
 
            if (!me.ownerCt.disableSelection && selModel.isRowSelected) {
                // Selection class goes on the outermost row, so it goes into itemClasses
                if (selModel.isRowSelected(record)) {
                    itemClasses.push(me.selectedItemCls);
                }
            }
 
            if (me.stripeRows && rowIdx % 2 !== 0) {
                itemClasses.push(me.altRowCls);
            }
 
            if (me.getRowClass) {
                cls = me.getRowClass(record, rowIdx, null, me.dataSource);
 
                if (cls) {
                    rowClasses.push(cls);
                }
            }
        }
 
        if (out) {
            rowTpl.applyOut(rowValues, out, me.tableValues);
        }
        else {
            return rowTpl.apply(rowValues, me.tableValues);
        }
    },
 
    /**
     * @private
     * Emits the HTML representing a single grid cell into the passed output stream
     * (which is an array of strings).
     *
     * @param {Ext.grid.column.Column} column The column definition for which to render a cell.
     * @param {Ext.data.Model} record The record
     * @param {Number} recordIndex The row index (zero based within the {@link #store}) for which
     * to render the cell.
     * @param {Number} rowIndex The row index (zero based within this view for which
     * to render the cell.
     * @param {Number} columnIndex The column index (zero based) for which to render the cell.
     * @param {String[]} out The output stream into which the HTML strings are appended.
     */
    renderCell: function(column, record, recordIndex, rowIndex, columnIndex, out) {
        var me = this,
            renderer = column.renderer,
            fullIndex,
            selModel = me.selectionModel,
            cellValues = me.cellValues,
            classes = cellValues.classes,
            fieldValue = record.data[column.dataIndex],
            cellTpl = me.cellTpl,
            enableTextSelection = column.enableTextSelection,
            value, clsInsertPoint,
            lastFocused = me.navigationModel.getPosition();
 
        // Only use the view's setting if it's not been overridden on the column
        if (enableTextSelection == null) {
            enableTextSelection = me.enableTextSelection;
        }
 
        cellValues.record = record;
        cellValues.column = column;
        cellValues.recordIndex = recordIndex;
        cellValues.rowIndex = rowIndex;
        cellValues.columnIndex = cellValues.cellIndex = columnIndex;
        cellValues.align = column.textAlign;
        cellValues.innerCls = column.innerCls;
        cellValues.tdCls = cellValues.tdStyle = cellValues.tdAttr = cellValues.style = "";
        cellValues.unselectableAttr = enableTextSelection ? '' : 'unselectable="on"';
 
        // Begin setup of classes to add to cell
        classes[1] = column.getCellId();
 
        // On IE8, array[len] = 'foo' is twice as fast as array.push('foo')
        // So keep an insertion point and use assignment to help IE!
        clsInsertPoint = 2;
 
        if (renderer && renderer.call) {
            // Avoid expensive header index calculation (uses Array#indexOf)
            // if renderer doesn't use it.
            fullIndex = renderer.length > 4
                ? me.ownerCt.columnManager.getHeaderIndex(column)
                : columnIndex;
 
            value = renderer.call(
                column.usingDefaultRenderer ? column : column.scope || me.ownerCt,
                fieldValue, cellValues, record, recordIndex, fullIndex, me.dataSource, me
            );
 
            if (cellValues.css) {
                // This warning attribute is used by the compat layer
                // TODO: remove when compat layer becomes deprecated
                record.cssWarning = true;
                cellValues.tdCls += ' ' + cellValues.css;
                cellValues.css = null;
            }
 
            // Add any tdCls which was added to the cellValues by the renderer.
            if (cellValues.tdCls) {
                classes[clsInsertPoint++] = cellValues.tdCls;
            }
        }
        else {
            value = fieldValue;
        }
 
        cellValues.value = (
            value == null ||
            value.length === 0 ||
            (Ext.isString(value) && value.replace(/\s/g, '').length === 0)
        ) ? column.emptyCellText
            : value;
 
        if (column.tdCls) {
            classes[clsInsertPoint++] = column.tdCls;
        }
 
        if (me.markDirty && record.dirty && record.isModified(column.dataIndex)) {
            classes[clsInsertPoint++] = me.dirtyCls;
 
            if (column.dirtyTextElementId) {
                cellValues.tdAttr = (cellValues.tdAttr ? cellValues.tdAttr + ' ' : '') +
                                    'aria-describedby="' + column.dirtyTextElementId + '"';
            }
        }
 
        if (column.isFirstVisible) {
            classes[clsInsertPoint++] = me.firstCls;
        }
 
        if (column.isLastVisible) {
            classes[clsInsertPoint++] = me.lastCls;
        }
 
        if (!enableTextSelection) {
            classes[clsInsertPoint++] = me.unselectableCls;
        }
 
        if (selModel && (selModel.isCellModel || selModel.isSpreadsheetModel) &&
            selModel.isCellSelected(me, recordIndex, column)) {
            classes[clsInsertPoint++] = me.selectedCellCls;
        }
 
        if (lastFocused && lastFocused.record.id === record.id && lastFocused.column === column) {
            classes[clsInsertPoint++] = me.focusedItemCls;
        }
 
        // Chop back array to only what we've set
        classes.length = clsInsertPoint;
 
        cellValues.tdCls = classes.join(' ');
 
        cellTpl.applyOut(cellValues, out);
 
        // Dereference objects since cellValues is a persistent var in the XTemplate's scope chain
        cellValues.column = cellValues.record = null;
    },
 
    /**
     * Returns the table row given the passed Record, or index or node.
     * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo The node or record, or row index.
     * to return the top level row.
     * @return {HTMLElement} The node or null if it wasn't found
     */
    getRow: function(nodeInfo) {
        var me = this,
            rowSelector = me.rowSelector;
 
        if ((!nodeInfo && nodeInfo !== 0) || !me.rendered) {
            return null;
        }
 
        // An id
        if (Ext.isString(nodeInfo)) {
            nodeInfo = Ext.getDom(nodeInfo);
 
            return nodeInfo && nodeInfo.querySelectorAll(rowSelector)[0];
        }
 
        // Row index
        if (Ext.isNumber(nodeInfo)) {
            nodeInfo = me.all.item(nodeInfo, true);
 
            return nodeInfo && nodeInfo.querySelectorAll(rowSelector)[0];
        }
 
        // Record
        if (nodeInfo.isModel) {
            return me.getRowByRecord(nodeInfo);
        }
 
        // If we were passed an event, use its target
        nodeInfo = Ext.fly(nodeInfo.target || nodeInfo);
 
        // Passed an item, go down and get the row
        if (nodeInfo.is(me.itemSelector)) {
            return me.getRowFromItem(nodeInfo);
        }
 
        // Passed a child element of a row
        return nodeInfo.findParent(rowSelector, me.getTargetEl()); // already an HTMLElement
    },
 
    getRowId: function(record) {
        return this.id + '-record-' + record.internalId;
    },
 
    constructRowId: function(internalId) {
        return this.id + '-record-' + internalId;
    },
 
    getNodeById: function(id) {
        id = this.constructRowId(id);
 
        return this.retrieveNode(id, false);
    },
 
    getRowById: function(id) {
        id = this.constructRowId(id);
 
        return this.retrieveNode(id, true);
    },
 
    getNodeByRecord: function(record) {
        return this.retrieveNode(this.getRowId(record), false);
    },
 
    getRowByRecord: function(record) {
        return this.retrieveNode(this.getRowId(record), true);
    },
 
    getRowFromItem: function(item) {
        var rows = Ext.getDom(item).tBodies[0].childNodes,
            len = rows.length,
            i;
 
        for (= 0; i < len; i++) {
            if (Ext.fly(rows[i]).is(this.rowSelector)) {
                return rows[i];
            }
        }
    },
 
    retrieveNode: function(id, dataRow) {
        var result = this.el.getById(id, true);
 
        if (dataRow && result) {
            return result.querySelector(this.rowSelector, true);
        }
 
        return result;
    },
 
    // Links back from grid rows are installed by the XTemplate as data attributes
    updateIndexes: Ext.emptyFn,
 
    // Outer table
    bodySelector: 'div.' + Ext.baseCSSPrefix + 'grid-item-container',
 
    // Element which contains rows
    nodeContainerSelector: 'div.' + Ext.baseCSSPrefix + 'grid-item-container',
 
    // view item. This wraps a data row
    itemSelector: 'table.' + Ext.baseCSSPrefix + 'grid-item',
 
    // Grid row which contains cells as opposed to wrapping item.
    rowSelector: 'tr.' + Ext.baseCSSPrefix +