/** * @class Ext.grid.View * @extends Ext.DataView * * The grid view binds a store to the underlying html markup of a grid. In most * cases you may configure a grid view from the Ext.grid.GridPanel with the * viewConfig configuration. * * The selection model is shared across sibling grid views. */ Ext.define('Ext.grid.View', { extend: 'Ext.DataView', alias: 'widget.gridview', requires: [ 'Ext.util.DelayedTask', 'Ext.grid.TableChunker' ], cls: Ext.baseCSSPrefix + 'grid-view ' + Ext.baseCSSPrefix + 'unselectable', // row itemSelector: '.' + Ext.baseCSSPrefix + 'grid-row', // cell cellSelector: '.' + Ext.baseCSSPrefix + 'grid-cell', selectedItemCls: Ext.baseCSSPrefix + 'grid-row-selected', focusedItemCls: Ext.baseCSSPrefix + 'grid-row-focused', overItemCls: Ext.baseCSSPrefix + 'grid-row-over', altRowCls: Ext.baseCSSPrefix + 'grid-row-alt', rowClsRe: /(?:^|\s*)grid3-row-(first|last|alt)(?:\s+|$)/g,
/** * @cfg {Boolean} stripeRows true to stripe the rows. Default is false. *

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 colour, 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: false, // cfg docs inherited trackOver: true,
/** * Override this function to apply custom CSS classes to rows during rendering. You can also supply custom * parameters to the row template for the current row to customize how it is rendered using the rowParams * parameter. This function should return the CSS class name (or empty string '' for none) that will be added * to the row's wrapping div. To apply multiple class names, simply return them space-delimited within the string * (e.g., 'my-class another-class'). Example usage:

viewConfig: {
    forceFit: true,
    showPreview: true, // custom property
    enableRowBody: true, // required to create a second, full-width row to show expanded Record data
    getRowClass: function(record, rowIndex, rp, ds){ // rp = rowParams
        if(this.showPreview){
            rp.body = '<p>'+record.data.excerpt+'</p>';
            return 'x-grid3-row-expanded';
        }
        return 'x-grid3-row-collapsed';
    }
},
    
* @param {Record} record The {@link Ext.data.Record} corresponding to the current row. * @param {Number} index The row index. * @param {Object} rowParams (DEPRECATED) A config object that is passed to the row template during rendering that allows * customization of various aspects of a grid row. *

If {@link #enableRowBody} is configured true, then the following properties may be set * by this function, and will be used to render a full-width expansion row below each grid row:

* * The following property will be passed in, and may be appended to: * * @param {Store} store The {@link Ext.data.Store} this grid is bound to * @method getRowClass * @return {String} a CSS class name to add to the row. */ getRowClass: null, initComponent: function() { this.scrollState = {}; this.initFeatures(); this.setNewTemplate(); this.store.on('load', this.onStoreLoad, this); Ext.grid.View.superclass.initComponent.call(this); this.addEvents(
/** * @event rowfocus * @param {Ext.data.Record} record * @param {HTMLElement} row * @param {Number} rowIdx */ 'rowfocus' ); }, // scroll to top of the grid when store loads onStoreLoad: function(){ if (Ext.isGecko) { if (!this.scrollToTopTask) { this.scrollToTopTask = new Ext.util.DelayedTask(this.scrollToTop, this); } this.scrollToTopTask.delay(1); } else { this.scrollToTop(); } },
/** * Scroll the GridView to the top by scrolling the scroller. */ scrollToTop : function(){ var section = this.up('gridsection'), verticalScroller = section.verticalScroller; verticalScroller.scrollToTop(); }, /** * Initializes each feature and bind it to this view. * @private */ initFeatures: function() { this.features = this.features || []; var features = this.features, ln = features.length, i = 0; for (; i < ln; i++) { // ensure feature hasnt already been instantiated if (!features[i].isFeature) { // inject a reference to view features[i].view = this; features[i] = Ext.create('feature.'+features[i].ftype, features[i]); } } }, /** * Gives features an injection point to attach events to the markup that * has been created for this view. * @private */ attachEventsForFeatures: function() { var features = this.features, ln = features.length, i = 0; for (; i < ln; i++) { if (features[i].isFeature) { features[i].attachEvents(); } } }, afterRender: function() { Ext.grid.View.superclass.afterRender.call(this); this.el.on('scroll', this.fireBodyScroll, this); this.attachEventsForFeatures(); }, fireBodyScroll: function(e, t) { this.fireEvent('bodyscroll', e, t); }, /** * Uses the headerCt to transform data from dataIndex keys in a record to * headerId keys in each header and then run them through each feature to * get additional data for variables they have injected into the view template. * @private */ prepareData: function(data, idx, record) { var orig = this.headerCt.prepareData(data, idx, record), features = this.features, ln = features.length, i = 0, node, feature; for (; i < ln; i++) { feature = features[i]; if (feature.isFeature) { Ext.apply(orig, feature.getAdditionalData(data, idx, record, orig, this)); } } return orig; }, collectData: function(records, startIndex) { var preppedRecords = Ext.grid.View.superclass.collectData.apply(this, arguments), headerCt = this.headerCt, fullWidth = headerCt.getFullWidth(), features = this.features, ln = features.length, i = 0, feature, o = { rows: preppedRecords, fullWidth: fullWidth }, j = 0, jln = preppedRecords.length, rowParams; // process row classes, rowParams has been deprecated and has been moved // to the individual features that implement the behavior. if (this.getRowClass) { for (; j < jln; j++) { rowParams = {}; preppedRecords[j]['rowCls'] = this.getRowClass(preppedRecords[j], j, rowParams, this.store); if (rowParams.alt) { throw "GridView: getRowClass alt is no longer supported."; } if (rowParams.tstyle) { throw "GridView: getRowClass tstyle is no longer supported."; } if (rowParams.cells) { throw "GridView: getRowClass cells is no longer supported."; } if (rowParams.body) { throw "GridView: getRowClass body is no longer supported. Use getAdditionalData of the rowbody feature."; } if (rowParams.bodyStyle) { throw "GridView: getRowClass bodyStyle is no longer supported."; } if (rowParams.cols) { throw "GridView: getRowClass cols is no longer supported."; } } } // currently only one feature may implement collectData. This is to modify // whats returned to the view before its renderered for (; i < ln; i++) { feature = features[i]; if (feature.isFeature && feature.collectData && !feature.disabled) { o = feature.collectData(records, startIndex, fullWidth, o); break; } } return o; }, /** * When a header is resized, setWidth on the individual columns resizer class, * the top level table, save/restore scroll state, generate a new template and * restore focus to the grid view's element so that keyboard navigation * continues to work. * @private */ onHeaderResize: function(header, w) { var el = this.el; if (el) { this.saveScrollState(); // Grab the col and set the width, css // class is generated in TableChunker. el.select('.' + Ext.baseCSSPrefix + 'grid-col-resizer-'+header.id).setWidth(w); el.select('.' + Ext.baseCSSPrefix + 'grid-table-resizer').setWidth(this.headerCt.getFullWidth()); this.restoreScrollState(); this.setNewTemplate(); this.el.focus(); } }, /** * When a header is shown restore its oldWidth if it was previously hidden. * @private */ onHeaderShow: function(headerCt, header, idx) { // restore headers that were dynamically hidden if (header.oldWidth) { this.onHeaderResize(header, header.oldWidth); delete header.oldWidth; // flexed headers will have a calculated size set // this additional check has to do with the fact that // defaults: {width: 100} will fight with a flex value } else if (header.width && !header.flex) { this.onHeaderResize(header, header.width); } this.setNewTemplate(); },
/** * When the header hides treat it as a resize to 0. */ onHeaderHide: function(headerCt, header, idx) { this.onHeaderResize(header, 0); },
/** * Set a new template based on the current columns displayed in the * grid. */ setNewTemplate: function() { var columns = this.headerCt.getColumnsForTpl(); this.tpl = this.getTableChunker().getTableTpl({ columns: columns, features: this.features }); },
/** * Get the configured chunker or default of Ext.grid.TableChunker */ getTableChunker: function() { return this.chunker || Ext.grid.TableChunker; },
/** * Add a CSS Class to a specific row. * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model representing this row * @param {String} cls */ addRowCls: function(rowInfo, cls) { var row = this.getNode(rowInfo); if (row) { Ext.fly(row).addCls(cls); } },
/** * Remove a CSS Class from a specific row. * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model representing this row * @param {String} cls */ removeRowCls: function(rowInfo, cls) { var row = this.getNode(rowInfo); if (row) { Ext.fly(row).removeCls(cls); } }, // GridSelectionModel invokes onRowSelect as selection changes onRowSelect : function(rowIdx) { this.addRowCls(rowIdx, this.selectedItemCls); }, // GridSelectionModel invokes onRowDeelect as selection changes onRowDeselect : function(rowIdx) { this.removeRowCls(rowIdx, this.selectedItemCls); }, // GridSelectionModel invokes onRowFocus to 'highlight' // the last row focused onRowFocus: function(rowIdx, highlight) { var row = this.getNode(rowIdx), grid = this.up('gridpanel'); if (highlight) { this.addRowCls(rowIdx, this.focusedItemCls); this.focusRow(rowIdx); //this.el.dom.setAttribute('aria-activedescendant', row.id); } else { this.removeRowCls(rowIdx, this.focusedItemCls); } },
/** * Focus a particular row and bring it into view. Will fire the rowfocus event. * @cfg {Mixed} An HTMLElement template node, index of a template node, the * id of a template node or the record associated with the node. */ focusRow: function(rowIdx) { var row = this.getNode(rowIdx), el = this.el, adjustment = 0, elRegion = el.getRegion(), gridpanel = this.up('gridpanel'), rowRegion, record; if (row) { rowRegion = Ext.fly(row).getRegion(); // row is above if (rowRegion.top < elRegion.top) { adjustment = rowRegion.top - elRegion.top; // row is below } else if (rowRegion.bottom > elRegion.bottom) { adjustment = rowRegion.bottom - elRegion.bottom; } record = this.getRecord(row); rowIdx = this.store.indexOf(record); if (adjustment) { // scroll the grid itself, so that all gridview's update. gridpanel.scrollByDeltaY(adjustment); } this.fireEvent('rowfocus', record, row, rowIdx); } }, /** * Scroll by delta. This affects this individual view ONLY and does not * synchronize across views or scrollers. * @param {Number} delta * @param {String} dir (optional) Valid values are scrollTop and scrollLeft. Defaults to scrollTop. * @private */ scrollByDelta: function(delta, dir) { dir = dir || 'scrollTop'; var elDom = this.el.dom; elDom[dir] = (elDom[dir] += delta); }, // after adding a row stripe rows from then on onAdd: function(ds, records, index) { Ext.grid.View.superclass.onAdd.call(this, ds, records, index); this.doStripeRows(index); }, // after removing a row stripe rows from then on onRemove: function(ds, records, index) { Ext.grid.View.superclass.onRemove.call(this, ds, records, index); this.doStripeRows(index); }, onUpdate: function(ds, index) { Ext.grid.View.superclass.onUpdate.call(this, ds, index); },
/** * Save the scrollState in a private variable. * Must be used in conjunction with restoreScrollState */ saveScrollState: function() { var dom = this.el.dom, state = this.scrollState; state.left = dom.scrollLeft; state.top = dom.scrollTop; }, /** * Restore the scrollState. * Must be used in conjunction with saveScrollState * @private */ restoreScrollState: function() { var dom = this.el.dom, state = this.scrollState, headerEl = this.headerCt.el.dom; headerEl.scrollLeft = dom.scrollLeft = state.left; dom.scrollTop = state.top; },
/** * Refresh the grid view. * Saves and restores the scroll state, generates a new template, stripes rows * and invalidates the scrollers. * @param {Boolean} firstPass This is a private flag for internal use only. */ refresh: function(firstPass) { this.saveScrollState(); this.setNewTemplate(); Ext.grid.View.superclass.refresh.call(this, firstPass); this.doStripeRows(0); this.up('gridpanel').invalidateScroller(); this.restoreScrollState(); if (!firstPass) { // give focus back to gridview this.el.focus(); } }, /** * Stripe rows from a particular row index * @param {Number} startRow * @private */ doStripeRows: function(startRow) { // ensure stripeRows configuration is turned on if (this.stripeRows) { var rows = this.getNodes(startRow), rowsLn = rows.length, i = 0, row; for (; i < rowsLn; i++) { row = rows[i]; // Remove prior applied row classes. row.className = row.className.replace(this.rowClsRe, ' '); // Every odd row will get an additional cls if (i % 2 === 1) { row.className += (' ' + this.altRowCls); } } } }, // Currently processing click, dblclick and contextmenu for cell's // No Support for mousedown atm processEvent: function(name, item, index, e) { var t = e.getTarget(), cell = Ext.fly(t).is(this.cellSelector) ? t : Ext.fly(t).up(this.cellSelector); if (cell) { cell = cell.dom ? cell.dom : cell; this.fireEvent('cell' + name, this, cell, index, cell.cellIndex, e); } // Process event features and fire events for a particular feature such // as groupclick, groupdblclick, etc var features = this.features, ln = features.length, i = 0, node, feature; for (; i < ln; i++) { feature = features[i]; if (feature.hasFeatureEvent) { node = Ext.fly(t).is(feature.eventSelector) ? t : Ext.fly(t).up(feature.eventSelector); if (node) { node = node.dom ? node.dom : node; this.fireEvent(feature.eventPrefix + name, this, node, index, {}, e); } } } }, // process click events onContainerClick: function(e) { this.processEvent('click', null, null, e); }, // process click events onItemClick: function(item, index, e) { var result = Ext.grid.View.superclass.onItemClick.call(this, item, index, e); if (result) { this.processEvent('click', item, index, e); } return result; }, // process dblclick events onDblClick: function(e) { Ext.grid.View.superclass.onDblClick.call(this, e); var item = e.getTarget(this.itemSelector, this.getTargetEl()); if (item) { this.processEvent('dblclick', item, this.indexOf(item), e); } }, // proccess contextmenu events onContextMenu: function(e) { Ext.grid.View.superclass.onContextMenu.call(this, e); var item = e.getTarget(this.itemSelector, this.getTargetEl()); if (item) { this.processEvent('contextmenu', item, this.indexOf(item), e); } } });