/** * 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 {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; // 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 = grid.allowDeselect || selModel.selectionMode !== 'SINGLE'; selModel.locked = disableSelection; } else { if (typeof selModel === 'string') { selModel = { type: selModel }; } // Copy obsolete selType property to type property now that selection models // are Factoryable // TODO: Remove selType config after deprecation period else { 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.apply({ allowDeselect: grid.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 (i = 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 (j = 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 (i = 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 (j = 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 (i = 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 (i = 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 (i = 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 (i = 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 (i = 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 (i = 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 (i = 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) ? 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 (i = 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 + 'grid-row', // cell cellSelector: 'td.' + Ext.baseCSSPrefix + 'grid-cell', // Select column sizers and cells. // This may target `<COL>` elements as well as `<TD>` elements // `<COLGROUP>` element is inserted if the first row does not have the regular cell pattern // (eg is a colspanning group header row) sizerSelector: '.' + Ext.baseCSSPrefix + 'grid-cell', innerSelector: 'div.' + Ext.baseCSSPrefix + 'grid-cell-inner', /** * Returns a CSS selector which selects the outermost element(s) in this view. */ getBodySelector