/** * A plugin for use in grids which use the {@link Ext.grid.selection.SpreadsheetModel spreadsheet} * selection model, with {@link Ext.grid.selection.Model#extensible extensible} configured as * `true` or `"y"`, meaning that the selection may be extended up or down using a draggable * extension handle. * * This plugin propagates values from the selection into the extension area. * * If just *one* row is selected, the values in that row are replicated unchanged into the * extension area. * * If more than one row is selected, the two rows closest to the selected block are taken to * provide a numeric difference, and that difference is used to calculate the sequence of values * all the way into the extension area. * */Ext.define('Ext.grid.selection.Replicator', { extend: 'Ext.plugin.Abstract', alias: 'plugin.selectionreplicator', /** * * @property {Ext.grid.column.Column[]} columns * An array of the columns encompassed by the selection block. This is gathered before * {@link #replicateSelection} is called, so is available to subclasses which implement their * own {@link #replicateSelection} method. */ init: function(grid) { this.gridListeners = grid.on({ beforeselectionextend: this.onBeforeSelectionExtend, scope: this, destroyable: true }); }, onBeforeSelectionExtend: function(ownerGrid, sel, extension) { var columns = this.columns = []; sel.eachColumn(function(column) { columns.push(column); }); return this.replicateSelection(ownerGrid, sel, extension); }, /** * This is the method which is called when the * {@link Ext.grid.selection.SpreadsheetModel spreadsheet} selection model's extender handle is * dragged and released. * * It is passed contextual information about the selection and the extension area. * * Subclass authors may override it to gain access to the event and perform their own data * replication. * * By default, the selection is extended to encompass the selection area. Returning `false` from * this method vetoes that. * * @param {Ext.panel.Table} ownerGrid The owning grid. * @param {Ext.dataview.selection.Selection} sel An object describing the contiguous * selected area. * @param {Object} extension An object describing the type and size of extension. * @param {String} extension.type `"rows"` or `"columns"` * @param {Ext.grid.Location} extension.start The start (top left) cell of the extension area. * @param {Ext.grid.Location} extension.end The end (bottom right) cell of the extension area. * @param {number} [extension.columns] The number of columns extended * (-ve means on the left side). * @param {number} [extension.rows] The number of rows extended (-ve means on the top side). */ replicateSelection: function(ownerGrid, sel, extension) { var me = this, columns, colCount, j, column, values, startIdx, endIdx, selFirstRowIdx, selLastRowIdx, i, increment, store, record, prevValues, prevValue, selectedRowCount, lastTwoRecords, x, y; // This can only handle extending rows if (extension.columns || sel.isColumns) { return; } columns = me.columns; selFirstRowIdx = sel.getFirstRowIndex(); selLastRowIdx = sel.getLastRowIndex(); selectedRowCount = selLastRowIdx - selFirstRowIdx + 1; lastTwoRecords = []; colCount = columns.length; store = columns[0].getGrid().getStore(); // Single row, just duplicate values into extension if (selectedRowCount === 1) { values = me.getColumnValues(sel.view.getStore().getAt(selFirstRowIdx)); } // Multiple rows, take the numeric values from the closest two rows, calculate an array // of differences and propagate it else { values = new Array(colCount); if (extension.rows < 0) { lastTwoRecords = [ store.getAt(selFirstRowIdx + 1), store.getAt(selFirstRowIdx) ]; } else { lastTwoRecords = [ store.getAt(selLastRowIdx - 1), store.getAt(selLastRowIdx) ]; } lastTwoRecords[0] = me.getColumnValues(lastTwoRecords[0]); lastTwoRecords[1] = me.getColumnValues(lastTwoRecords[1]); // The values array will be the differences between all numeric columns in // the selection of the closet two records. for (j = 0; j < colCount; j++) { x = lastTwoRecords[1][j]; y = lastTwoRecords[0][j]; if (!isNaN(x) && !isNaN(y)) { values[j] = Number(x) - Number(y); } } } // Loop from end to start of extension area if (extension.rows < 0) { startIdx = extension.end.recordIndex; endIdx = extension.start.recordIndex - 1; increment = -1; } else { startIdx = extension.start.recordIndex; endIdx = extension.end.recordIndex + 1; increment = 1; } // Replicate single selected row if (selectedRowCount === 1) { for (i = startIdx; i !== endIdx; i += increment) { record = store.getAt(i); for (j = 0; j < colCount; j++) { column = columns[j]; if (column.getDataIndex()) { record.set(column.getDataIndex(), values[j]); } } } } // Add differences from closest two rows else { for (i = startIdx; i !== endIdx; i += increment) { record = store.getAt(i); prevValues = me.getColumnValues(store.getAt(i - increment)); for (j = 0; j < colCount; j++) { column = columns[j]; if (column.getDataIndex()) { prevValue = prevValues[j]; if (!isNaN(prevValue)) { record.set( column.getDataIndex(), Ext.coerce(Number(prevValue) + values[j], prevValue) ); } } } } } }, /** * A utility method, which, when passed a record, uses the {@link #columns} property to * extract the values of that record which are encompassed by the selection. * * Note that columns with no {@link Ext.grid.column.Column#dataIndex dataIndex} cannot * yield a value. * @param {Ext.data.Model} record The record from which to read values. * @return {Mixed[]} The values of the fields used by the selected column range for the * passed record. */ getColumnValues: function(record) { var columns = this.columns, len = columns.length, i, column, result = new Array(columns.length); for (i = 0; i < len; i++) { column = columns[i]; // If there's a dataIndex, get the value if (column.getDataIndex()) { result[i] = record.get(column.getDataIndex()); } } return result; }, destroy: function() { this.gridListeners = Ext.destroy(this.gridListeners); this.callParent(); }});