| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- /**
- * This feature is used to place a summary row at the bottom of the grid. If using a grouping,
- * see {@link Ext.grid.feature.GroupingSummary}. There are 2 aspects to calculating the summaries,
- * calculation and rendering.
- *
- * ## Calculation
- * The summary value needs to be calculated for each column in the grid. This is controlled
- * by the summaryType option specified on the column. There are several built in summary types,
- * which can be specified as a string on the column configuration. These call underlying methods
- * on the store:
- *
- * - {@link Ext.data.Store#count count}
- * - {@link Ext.data.Store#sum sum}
- * - {@link Ext.data.Store#min min}
- * - {@link Ext.data.Store#max max}
- * - {@link Ext.data.Store#average average}
- *
- * Alternatively, the summaryType can be a function definition. If this is the case,
- * the function is called with an array of records to calculate the summary value.
- *
- * ## Rendering
- * Similar to a column, the summary also supports a summaryRenderer function. This
- * summaryRenderer is called before displaying a value. The function is optional, if
- * not specified the default calculated value is shown. The summaryRenderer is called with:
- *
- * - value {Object} - The calculated value.
- * - summaryData {Object} - Contains all raw summary values for the row.
- * - field {String} - The name of the field we are calculating
- * - metaData {Object} - A collection of metadata about the current cell; can be used or modified by the renderer.
- *
- * ## Example Usage
- *
- * @example
- * Ext.define('TestResult', {
- * extend: 'Ext.data.Model',
- * fields: ['student', {
- * name: 'mark',
- * type: 'int'
- * }]
- * });
- *
- * Ext.create('Ext.grid.Panel', {
- * width: 400,
- * height: 200,
- * title: 'Summary Test',
- * style: 'padding: 20px',
- * renderTo: document.body,
- * features: [{
- * ftype: 'summary'
- * }],
- * store: {
- * model: 'TestResult',
- * data: [{
- * student: 'Student 1',
- * mark: 84
- * },{
- * student: 'Student 2',
- * mark: 72
- * },{
- * student: 'Student 3',
- * mark: 96
- * },{
- * student: 'Student 4',
- * mark: 68
- * }]
- * },
- * columns: [{
- * dataIndex: 'student',
- * text: 'Name',
- * summaryType: 'count',
- * summaryRenderer: function(value, summaryData, dataIndex) {
- * return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : '');
- * }
- * }, {
- * dataIndex: 'mark',
- * text: 'Mark',
- * summaryType: 'average'
- * }]
- * });
- */
- Ext.define('Ext.grid.feature.Summary', {
- /* Begin Definitions */
- extend: 'Ext.grid.feature.AbstractSummary',
- alias: 'feature.summary',
- /**
- * @cfg {String} dock
- * Configure `'top'` or `'bottom'` top create a fixed summary row either above or below the scrollable table.
- *
- */
- dock: undefined,
- summaryItemCls: Ext.baseCSSPrefix + 'grid-row-summary-item',
- dockedSummaryCls: Ext.baseCSSPrefix + 'docked-summary',
- summaryRowCls: Ext.baseCSSPrefix + 'grid-row-summary ' + Ext.baseCSSPrefix + 'grid-row-total',
- summaryRowSelector: '.' + Ext.baseCSSPrefix + 'grid-row-summary.' + Ext.baseCSSPrefix + 'grid-row-total',
- panelBodyCls: Ext.baseCSSPrefix + 'summary-',
- // turn off feature events.
- hasFeatureEvent: false,
- fullSummaryTpl: {
- fn: function (out, values, parent) {
- var me = this.summaryFeature,
- record = me.summaryRecord,
- view = values.view,
- bufferedRenderer = view.bufferedRenderer;
- this.nextTpl.applyOut(values, out, parent);
- if (!me.disabled && me.showSummaryRow && !view.addingRows && view.store.isLast(values.record)) {
- if (bufferedRenderer && !me.dock) {
- bufferedRenderer.variableRowHeight = true;
- }
- me.outputSummaryRecord((record && record.isModel) ? record : me.createSummaryRecord(view), values, out, parent);
- }
- },
- priority: 300,
- beginRowSync: function (rowSync) {
- rowSync.add('fullSummary', this.summaryFeature.summaryRowSelector);
- },
- syncContent: function (destRow, sourceRow, columnsToUpdate) {
- destRow = Ext.fly(destRow, 'syncDest');
- sourceRow = Ext.fly(sourceRow, 'sycSrc');
- var summaryFeature = this.summaryFeature,
- selector = summaryFeature.summaryRowSelector,
- destSummaryRow = destRow.down(selector, true),
- sourceSummaryRow = sourceRow.down(selector, true);
- // Sync just the updated columns in the summary row.
- if (destSummaryRow && sourceSummaryRow) {
- // If we were passed a column set, only update those, otherwise do the entire row
- if (columnsToUpdate) {
- this.summaryFeature.view.updateColumns(destSummaryRow, sourceSummaryRow, columnsToUpdate);
- } else {
- Ext.fly(destSummaryRow).syncContent(sourceSummaryRow);
- }
- }
- }
- },
- // override
- fixed: undefined,
- fixedSummaryCls: Ext.baseCSSPrefix + 'fixed-summary',
- init: function (grid) {
- var me = this,
- view = me.view,
- dock = me.dock,
- fixed = me.fixed;
- me.callParent([grid]);
- // when 'fixed' is true, 'dock' must be a defined value, default 'bottom'
- if(fixed) {
- dock = me.dock = dock || 'bottom';
- }
- if (dock) {
- grid.addBodyCls(me.panelBodyCls + dock);
- grid.headerCt.on({
- add: me.onStoreUpdate,
- // we need to fire onStoreUpdate afterlayout for docked items
- // to re-run the renderSummaryRow on show/hide columns.
- afterlayout: me.onStoreUpdate,
- remove: me.onStoreUpdate,
- scope: me
- });
- grid.on({
- beforerender: function () {
- var tableCls = [me.summaryTableCls];
- if (view.columnLines) {
- tableCls[tableCls.length] = view.ownerCt.colLinesCls;
- }
- me.summaryBar = grid.addDocked({
- childEls: ['innerCt', 'item'],
- renderTpl: [
- '<div id="{id}-innerCt" data-ref="innerCt" role="presentation">',
- '<table id="{id}-item" data-ref="item" cellPadding="0" cellSpacing="0" class="' + tableCls.join(' ') + '">',
- '<tr class="' + me.summaryRowCls + '"></tr>',
- '</table>',
- '</div>'
- ],
- scrollable: {
- x: false,
- y: false
- },
- hidden: !me.showSummaryRow,
- itemId: 'summaryBar',
- cls: [me.dockedSummaryCls, me.dockedSummaryCls + '-' + dock, (fixed ? me.fixedSummaryCls : '')],
- xtype: 'component',
- dock: dock,
- weight: 10000000
- })[0];
- },
- afterrender: function () {
- grid.getView().getScrollable().addPartner(me.summaryBar.getScrollable(), 'x');
- me.onStoreUpdate();
- me.columnSizer = me.summaryBar.el;
- },
- single: true
- });
- } else {
- if (grid.bufferedRenderer) {
- me.wrapsItem = true;
- view.addRowTpl(me.fullSummaryTpl).summaryFeature = me;
- view.on('refresh', me.onViewRefresh, me);
- } else {
- me.wrapsItem = false;
- me.view.addFooterFn(me.renderSummaryRow);
- }
- }
- grid.headerCt.on({
- afterlayout: me.afterHeaderCtLayout,
- scope: me
- });
- grid.ownerGrid.on({
- beforereconfigure: me.onBeforeReconfigure,
- columnmove: me.onStoreUpdate,
- scope: me
- });
- me.bindStore(grid, grid.getStore());
- },
- onBeforeReconfigure: function (grid, store) {
- this.summaryRecord = null;
- if (store) {
- this.bindStore(grid, store);
- }
- },
- bindStore: function (grid, store) {
- var me = this;
- Ext.destroy(me.storeListeners);
- me.storeListeners = store.on({
- scope: me,
- destroyable: true,
- update: me.onStoreUpdate,
- datachanged: me.onStoreUpdate
- });
- me.callParent([grid, store]);
- },
- renderSummaryRow: function (values, out, parent) {
- var view = values.view,
- me = view.findFeature('summary'),
- record;
- // If we get to here we won't be buffered
- if (!me.disabled && me.showSummaryRow && !view.addingRows && !view.updatingRows) {
- record = me.summaryRecord;
- out.push('<table cellpadding="0" cellspacing="0" class="' + me.summaryItemCls + '" style="table-layout: fixed; width: 100%;">');
- me.outputSummaryRecord((record && record.isModel) ? record : me.createSummaryRecord(view), values, out, parent);
- out.push('</table>');
- }
- },
- toggleSummaryRow: function (visible, fromLockingPartner) {
- var me = this,
- bar = me.summaryBar;
- me.callParent([visible, fromLockingPartner]);
- if (bar) {
- bar.setVisible(me.showSummaryRow);
- me.onViewScroll();
- }
- },
- getSummaryBar: function () {
- return this.summaryBar;
- },
- getSummaryRowPlaceholder: function (view) {
- var placeholderCls = this.summaryItemCls,
- nodeContainer, row;
- nodeContainer = Ext.fly(view.getNodeContainer());
- if (!nodeContainer) {
- return null;
- }
- row = nodeContainer.down('.' + placeholderCls, true);
- if (!row) {
- row = nodeContainer.createChild({
- tag: 'table',
- cellpadding: 0,
- cellspacing: 0,
- cls: placeholderCls,
- style: 'table-layout: fixed; width: 100%',
- children: [{
- tag: 'tbody' // Ensure tBodies property is present on the row
- }]
- }, false, true);
- }
- return row;
- },
- vetoEvent: function (record, row, rowIndex, e) {
- return !e.getTarget(this.summaryRowSelector);
- },
- onViewScroll: function () {
- this.summaryBar.setScrollX(this.view.getScrollX());
- },
- onViewRefresh: function (view) {
- var me = this,
- record, row;
- // Only add this listener if in buffered mode, if there are no rows then
- // we won't have anything rendered, so we need to push the row in here
- if (!me.disabled && me.showSummaryRow && !view.all.getCount()) {
- record = me.createSummaryRecord(view);
- row = me.getSummaryRowPlaceholder(view);
- row.tBodies[0].appendChild(view.createRowElement(record, -1).querySelector(me.summaryRowSelector));
- }
- },
- createSummaryRecord: function (view) {
- var me = this,
- columns = view.headerCt.getGridColumns(),
- remoteRoot = me.remoteRoot,
- summaryRecord = me.summaryRecord || (me.summaryRecord = new Ext.data.Model({
- id: view.id + '-summary-record'
- })),
- colCount = columns.length,
- i, column,
- dataIndex, summaryValue;
- // Set the summary field values
- summaryRecord.beginEdit();
- if (remoteRoot) {
- summaryValue = me.generateSummaryData();
- if (summaryValue) {
- summaryRecord.set(summaryValue);
- }
- } else {
- for (i = 0; i < colCount; i++) {
- column = columns[i];
- // In summary records, if there's no dataIndex, then the value in regular rows must come from a renderer.
- // We set the data value in using the column ID.
- dataIndex = column.dataIndex || column.getItemId();
- // We need to capture this value because it could get overwritten when setting on the model if there
- // is a convert() method on the model.
- summaryValue = me.getSummary(view.store, column.summaryType, dataIndex);
- summaryRecord.set(dataIndex, summaryValue);
- // Capture the columnId:value for the summaryRenderer in the summaryData object.
- me.setSummaryData(summaryRecord, column.getItemId(), summaryValue);
- }
- }
- summaryRecord.endEdit(true);
- // It's not dirty
- summaryRecord.commit(true);
- summaryRecord.isSummary = true;
- return summaryRecord;
- },
- onStoreUpdate: function () {
- var me = this,
- view = me.view,
- selector = me.summaryRowSelector,
- dock = me.dock,
- fixed = me.fixed,
- record, newRowDom, oldRowDom, newCellDoms, p;
- if (!view.rendered) {
- return;
- }
- record = me.createSummaryRecord(view);
- newRowDom = Ext.fly(view.createRowElement(record, -1)).down(selector, true);
- if (!newRowDom) {
- return;
- }
- if(fixed) {
- newCellDoms = newRowDom.children;
- for(let i = newCellDoms.length - 1; i >= 0 ; i--) {
- let innerText = newCellDoms[i].innerText.trim();
- if(innerText.length === 0) {
- newRowDom.removeChild(newCellDoms[i])
- }else {
- newCellDoms[i].style.removeProperty('width');
- }
- }
- }
- // Summary row is inside the docked summaryBar Component
- if (dock) {
- p = me.summaryBar.item.dom.firstChild;
- oldRowDom = p.firstChild;
- p.insertBefore(newRowDom, oldRowDom);
- p.removeChild(oldRowDom);
- }
- // Summary row is a regular row in a THEAD inside the View.
- // Downlinked through the summary record's ID
- else {
- oldRowDom = view.el.down(selector, true);
- p = oldRowDom && oldRowDom.parentNode;
- if (p) {
- p.removeChild(oldRowDom);
- }
- // We're always inserting the new summary row into the last rendered row,
- // unless no rows exist. In that case we will be appending to the special
- // placeholder in the node container.
- p = view.getRow(view.all.last());
- if (p) {
- p = p.parentElement;
- }
- // View might not have nodeContainer yet.
- else {
- p = me.getSummaryRowPlaceholder(view);
- p = p && p.tBodies && p.tBodies[0];
- }
- if (p) {
- p.appendChild(newRowDom);
- }
- }
- },
- // Synchronize column widths in the docked summary Component or the inline summary row
- // depending on whether we are docked or not.
- afterHeaderCtLayout: function (headerCt) {
- var me = this,
- view = me.view,
- columns = view.getVisibleColumnManager().getColumns(),
- column,
- len = columns.length,
- i,
- summaryEl,
- el, width, innerCt;
- if (me.showSummaryRow && view.refreshCounter) {
- if(me.fixed) {
- return;
- }
- if (me.dock) {
- summaryEl = me.summaryBar.el;
- width = headerCt.getTableWidth();
- innerCt = me.summaryBar.innerCt;
- // Stretch the innerCt of the summary bar upon headerCt layout
- me.summaryBar.item.setWidth(width);
- // headerCt's tooNarrow flag is set by its layout if the columns overflow.
- // Must not measure+set in after layout phase, this is a write phase.
- if (headerCt.tooNarrow) {
- width += Ext.getScrollbarSize().width;
- }
- innerCt.setWidth(width);
- } else {
- summaryEl = Ext.fly(Ext.fly(view.getNodeContainer()).down('.' + me.summaryItemCls, true));
- }
- // If the layout was in response to a clearView, there'll be no summary element
- if (summaryEl) {
- for (i = 0; i < len; i++) {
- column = columns[i];
- el = summaryEl.down(view.getCellSelector(column), true);
- if (el) {
- Ext.fly(el).setWidth(column.width || (column.lastBox ? column.lastBox.width : 100));
- }
- }
- }
- }
- },
- destroy: function () {
- var me = this;
- me.summaryRecord = me.storeListeners = Ext.destroy(me.storeListeners);
- me.callParent();
- }
- });
|