/** * 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 = 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) { 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); } } }});