/**
* A plugin for use in grids which use the {@link Ext.grid.selection.SpreadsheetModel spreadsheet}
* selection model, with {@link Ext.grid.selection.SpreadsheetModel#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.grid.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.CellContext} extension.start The start (top left) cell of the extension
* area.
* @param {Ext.grid.CellContext} 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) {
// This can only handle extending rows
if (extension.columns || sel.isColumns) {
return;
}
// eslint-disable-next-line vars-on-top
var me = this,
columns = me.columns,
selFirstRowIdx = sel.getFirstRowIndex(),
selLastRowIdx = sel.getLastRowIndex(),
selectedRowCount = selLastRowIdx - selFirstRowIdx + 1,
lastTwoRecords = [],
colCount, i, j, column, values, startIdx, endIdx, increment, store, record,
prevValues, prevValue, x, y;
colCount = columns.length;
store = columns[0].getView().dataSource;
// Single row, just duplicate values into extension
if (selectedRowCount === 1) {
values = me.getColumnValues(sel.view.dataSource.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.rowIdx;
endIdx = extension.start.rowIdx - 1;
increment = -1;
}
else {
startIdx = extension.start.rowIdx;
endIdx = extension.end.rowIdx + 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.dataIndex) {
record.set(column.dataIndex, 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.dataIndex) {
prevValue = prevValues[j];
if (!isNaN(prevValue)) {
record.set(
column.dataIndex,
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,
result = new Array(columns.length),
i, column;
for (i = 0; i < len; i++) {
column = columns[i];
// If there's a dataIndex, get the value
if (column.dataIndex) {
result[i] = record.get(column.dataIndex);
}
}
return result;
},
destroy: function() {
this.gridListeners = Ext.destroy(this.gridListeners);
this.callParent();
}
});