/**
* A class which encapsulates a range of cells defining a selection in a grid.
*
* Note that when range start and end points are represented by an array, the
* order is traditional `x, y` order, that is column index followed by row index.
* @since 5.1.0
*/
Ext.define('Ext.grid.selection.Cells', {
extend: 'Ext.grid.selection.Selection',
type: 'cells',
/**
* @property {Boolean} isCells
* This property indicates the this selection represents selected cells.
* @readonly
*/
isCells: true,
//-------------------------------------------------------------------------
// Base Selection API
clone: function() {
var me = this,
result = new me.self(me.view);
if (me.startCell) {
result.startCell = me.startCell.clone();
result.endCell = me.endCell.clone();
}
return result;
},
/**
* Returns `true` if the passed {@link Ext.grid.CellContext cell context} is selected.
* @param {Ext.grid.CellContext} cellContext The cell context to test.
* @return {Boolean} `true` if the passed {@link Ext.grid.CellContext cell context} is selected.
*/
contains: function(cellContext) {
var range;
if (!cellContext || !cellContext.isCellContext) {
return false;
}
if (this.startCell) {
// get start and end rows in the range
range = this.getRowRange();
if (cellContext.rowIdx >= range[0] && cellContext.rowIdx <= range[1]) {
// get start and end columns in the range
range = this.getColumnRange();
return (cellContext.colIdx >= range[0] && cellContext.colIdx <= range[1]);
}
}
return false;
},
eachRow: function(fn, scope) {
var me = this,
rowRange = me.getRowRange(),
context = new Ext.grid.CellContext(me.view),
rowIdx;
for (rowIdx = rowRange[0]; rowIdx <= rowRange[1]; rowIdx++) {
context.setRow(rowIdx);
if (fn.call(scope || me, context.record) === false) {
return;
}
}
},
eachColumn: function(fn, scope) {
var me = this,
colRange = me.getColumnRange(),
context = new Ext.grid.CellContext(me.view),
colIdx;
for (colIdx = colRange[0]; colIdx <= colRange[1]; colIdx++) {
context.setColumn(colIdx);
if (fn.call(scope || me, context.column, colIdx) === false) {
return;
}
}
},
eachCell: function(fn, scope) {
var me = this,
rowRange = me.getRowRange(),
colRange = me.getColumnRange(),
context = new Ext.grid.CellContext(me.view),
rowIdx, colIdx;
for (rowIdx = rowRange[0]; rowIdx <= rowRange[1]; rowIdx++) {
context.setRow(rowIdx);
for (colIdx = colRange[0]; colIdx <= colRange[1]; colIdx++) {
context.setColumn(colIdx);
if (fn.call(scope || me, context, colIdx, rowIdx) === false) {
return;
}
}
}
},
/**
* @return {Number} The row index of the first row in the range or zero if no range.
*/
getFirstRowIndex: function() {
return this.startCell ? Math.min(this.startCell.rowIdx, this.endCell.rowIdx) : 0;
},
/**
* @return {Number} The row index of the last row in the range or -1 if no range.
*/
getLastRowIndex: function() {
return this.startCell ? Math.max(this.startCell.rowIdx, this.endCell.rowIdx) : -1;
},
/**
* @return {Number} The column index of the first column in the range or zero if no range.
*/
getFirstColumnIndex: function() {
return this.startCell ? Math.min(this.startCell.colIdx, this.endCell.colIdx) : 0;
},
/**
* @return {Number} The column index of the last column in the range or -1 if no range.
*/
getLastColumnIndex: function() {
return this.startCell ? Math.max(this.startCell.colIdx, this.endCell.colIdx) : -1;
},
//-------------------------------------------------------------------------
privates: {
/**
* @private
*/
clear: function() {
var me = this,
view = me.view;
if (view.getVisibleColumnManager().getColumns().length) {
me.eachCell(function(cellContext) {
view.onCellDeselect(cellContext);
});
}
me.startCell = me.endCell = null;
},
/**
* Used during drag/shift+downarrow range selection on start.
* @param {Ext.grid.CellContext} startCell The start cell of the cell drag selection.
* @param {Ext.grid.CellContext} endCell The end cell of the cell drag selection.
* @private
*/
setRangeStart: function(startCell, endCell) {
// Must clone them. Users might use one instance and reconfigure it to navigate.
this.startCell = (this.endCell = startCell.clone()).clone();
this.view.onCellSelect(startCell);
},
/**
* Used during drag/shift+downarrow range selection on drag.
* @param {Ext.grid.CellContext} endCell The end cell of the cell drag selection.
* @private
*/
setRangeEnd: function(endCell) {
var me = this,
view = me.view,
// rows should get all locked rows on shift key select
rows = view.isLockingView ? view.lockedView.all : view.all,
cell = new Ext.grid.CellContext(view),
maxColIdx = view.getVisibleColumnManager().getColumns().length - 1,
range, lastRange, rowStart, rowEnd, colStart, colEnd, rowIdx, colIdx;
me.endCell = endCell.clone();
range = me.getRange();
lastRange = me.lastRange || range;
rowStart = Math.max(Math.min(range[0][1], lastRange[0][1]), rows.startIndex);
rowEnd = Math.min(Math.max(range[1][1], lastRange[1][1]), rows.endIndex);
colStart = Math.min(range[0][0], lastRange[0][0]);
colEnd = Math.min(Math.max(range[1][0], lastRange[1][0]), maxColIdx);
// Loop through the union of last range and current range
for (rowIdx = rowStart; rowIdx <= rowEnd; rowIdx++) {
for (colIdx = colStart; colIdx <= colEnd; colIdx++) {
cell.setPosition(rowIdx, colIdx);
// If we are outside the current range, deselect
if (rowIdx < range[0][1] || rowIdx > range[1][1] || colIdx < range[0][0] ||
colIdx > range[1][0]) {
view.onCellDeselect(cell);
}
else {
view.onCellSelect(cell);
}
}
}
me.lastRange = range;
},
extendRange: function(extensionVector) {
var me = this,
newEndCell;
if (extensionVector[extensionVector.type] < 0) {
newEndCell = me.endCell.clone().setPosition(
me.getLastRowIndex(), me.getLastColumnIndex()
);
me.startCell = extensionVector.start.clone();
me.setRangeEnd(newEndCell);
me.view.getNavigationModel().setPosition(extensionVector.start);
}
else {
me.startCell = me.startCell.setPosition(
me.getFirstRowIndex(), me.getFirstColumnIndex()
);
me.setRangeEnd(extensionVector.end);
me.view.getNavigationModel().setPosition(extensionVector.end);
}
},
reduceRange: function(extensionVector) {
var me = this,
newEndCell;
if (extensionVector[extensionVector.type] < 0) {
newEndCell = extensionVector.end.clone();
me.startCell = extensionVector.start.clone();
me.setRangeEnd(newEndCell);
me.view.getNavigationModel().setPosition(extensionVector.start);
}
},
/**
* Returns the `[[x, y],[x,y]]` coordinates in top-left to bottom-right order
* of the current selection.
*
* If no selection, returns [[0, 0],[-1, -1]] so that an incrementing iteration
* will not execute.
*
* @return {Number[][]}
* @private
*/
getRange: function() {
return [[this.getFirstColumnIndex(), this.getFirstRowIndex()],
[this.getLastColumnIndex(), this.getLastRowIndex()]];
},
/**
* Returns the size of the selection rectangle.
* @return {Number}
* @private
*/
getRangeSize: function() {
return this.getCount();
},
/**
* Returns the number of cells selected.
* @return {Number} The nuimber of cells selected
* @private
*/
getCount: function() {
var range = this.getRange();
return (range[1][0] - range[0][0] + 1) * (range[1][1] - range[0][1] + 1);
},
/**
* @private
*/
selectAll: function() {
var me = this,
view = me.view;
me.clear();
me.setRangeStart(new Ext.grid.CellContext(view).setPosition(0, 0));
me.setRangeEnd(new Ext.grid.CellContext(view).setPosition(
view.dataSource.getCount() - 1,
view.getVisibleColumnManager().getColumns().length - 1
));
},
/**
* @return {Boolean}
* @private
*/
isAllSelected: function() {
var start = this.rangeStart,
end = this.rangeEnd;
// All selected only if we encompass the entire store and every visible column
if (start) {
if (!start.colIdx && !start.rowIdx) {
// eslint-disable-next-line max-len
return end.colIdx === end.view.getVisibleColumnManager().getColumns().length - 1 && end.rowIdx === end.view.dataSource.getCount - 1;
}
}
return false;
},
/**
* @return {Number[]} The column range which encapsulates the range.
* @private
*/
getColumnRange: function() {
return [this.getFirstColumnIndex(), this.getLastColumnIndex()];
},
/**
* @private
* Called through {@link Ext.grid.selection.SpreadsheetModel#getLastSelected}
* by {@link Ext.panel.Table#updateBindSelection} when publishing the `selection` property.
* It should yield the last record selected.
*/
getLastSelected: function() {
return this.view.dataSource.getAt(this.endCell.rowIdx);
},
/**
* Returns the row range which encapsulates the range - the view range that needs
* updating.
* @return {Number[]}
* @private
*/
getRowRange: function() {
return [this.getFirstRowIndex(), this.getLastRowIndex()];
},
onSelectionFinish: function() {
var me = this;
if (me.getCount()) {
me.view.getSelectionModel().onSelectionFinish(
me,
new Ext.grid.CellContext(me.view).setPosition(
me.getFirstRowIndex(), me.getFirstColumnIndex()
),
new Ext.grid.CellContext(me.view).setPosition(
me.getLastRowIndex(), me.getLastColumnIndex()
)
);
}
else {
me.view.getSelectionModel().onSelectionFinish(me);
}
}
}
});