fin-hypergrid
Version:
Canvas-based high-performance grid
444 lines (384 loc) • 13.7 kB
JavaScript
'use strict';
var Feature = require('./Feature');
/**
* @constructor
*/
var RowSelection = Feature.extend('RowSelection', {
/**
* The pixel location of the mouse pointer during a drag operation.
* @type {Point}
* @default null
* @memberOf RowSelection.prototype
*/
currentDrag: null,
/**
* The cell coordinates of the where the mouse pointer is during a drag operation.
* @type {Object}
* @default null
* @memberOf RowSelection.prototype
*/
lastDragCell: null,
/**
* a millisecond value representing the previous time an autoscroll started
* @type {number}
* @default 0
* @memberOf RowSelection.prototype
*/
sbLastAuto: 0,
/**
* a millisecond value representing the time the current autoscroll started
* @type {number}
* @default 0
* @memberOf RowSelection.prototype
*/
sbAutoStart: 0,
dragArmed: false,
/**
* @memberOf RowSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleMouseUp: function(grid, event) {
if (this.dragArmed) {
this.dragArmed = false;
moveCellSelection(grid);
grid.fireSyntheticRowSelectionChangedEvent();
} else if (this.dragging) {
this.dragging = false;
moveCellSelection(grid);
grid.fireSyntheticRowSelectionChangedEvent();
} else if (this.next) {
this.next.handleMouseUp(grid, event);
}
},
/**
* @memberOf RowSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleMouseDown: function(grid, event) {
var leftClick = !event.primitiveEvent.detail.isRightClick,
rowNumberClick = leftClick &&
grid.properties.showRowNumbers &&
event.isHandleColumn &&
event.mousePointInClickRect;
if (rowNumberClick && !grid.fireSyntheticRowHeaderClickedEvent(event)) {
return;
}
var rowSelectable = grid.properties.rowSelection && (leftClick && grid.properties.autoSelectRows || rowNumberClick);
if (rowSelectable && event.isHeaderHandle) {
//global row selection
grid.toggleSelectAllRows();
} else if (rowSelectable && event.isDataRow) {
// if we are in the fixed area, do not apply the scroll values
this.dragArmed = true;
this.extendSelection(grid, event.dataCell.y, event.primitiveEvent.detail.keys);
} else if (this.next) {
this.next.handleMouseDown(grid, event);
}
},
/**
* @memberOf RowSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleMouseDrag: function(grid, event) {
if (
this.dragArmed &&
grid.properties.rowSelection &&
!event.primitiveEvent.detail.isRightClick
) {
//if we are in the fixed area do not apply the scroll values
this.lastDragRow = event.dataCell.y;
this.dragging = true;
this.currentDrag = event.primitiveEvent.detail.mouse;
this.checkDragScroll(grid, this.currentDrag);
this.handleMouseDragCellSelection(grid, this.lastDragRow, event.primitiveEvent.detail.keys);
} else if (this.next) {
this.next.handleMouseDrag(grid, event);
}
},
/**
* @memberOf RowSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleKeyDown: function(grid, event) {
var handler;
if (
grid.getLastSelectionType() === 'row' &&
(handler = this['handle' + event.detail.char])
) {
handler.call(this, grid, event.detail);
} else if (this.next) {
this.next.handleKeyDown(grid, event);
}
},
/**
* @memberOf RowSelection.prototype
* @desc Handle a mousedrag selection
* @param {Hypergrid} grid
* @param {Object} mouse - the event details
* @param {Array} keys - array of the keys that are currently pressed down
*/
handleMouseDragCellSelection: function(grid, y, keys) {
var mouseY = grid.getMouseDown().y;
grid.clearMostRecentRowSelection();
grid.selectRow(mouseY, y);
grid.setDragExtent(grid.newPoint(0, y - mouseY));
grid.repaint();
},
/**
* @memberOf RowSelection.prototype
* @desc this checks while were dragging if we go outside the visible bounds, if so, kick off the external autoscroll check function (above)
* @param {Hypergrid} grid
* @param {Object} mouse - the event details
*/
checkDragScroll: function(grid, mouse) {
if (
grid.properties.scrollingEnabled &&
grid.getDataBounds().contains(mouse)
) {
if (grid.isScrollingNow()) {
grid.setScrollingNow(false);
}
} else {
if (!grid.isScrollingNow()) {
grid.setScrollingNow(true);
this.scrollDrag(grid);
}
}
},
/**
* @memberOf RowSelection.prototype
* @desc this function makes sure that while we are dragging outside of the grid visible bounds, we srcroll accordingly
* @param {Hypergrid} grid
*/
scrollDrag: function(grid) {
if (!grid.isScrollingNow()) {
return;
}
var b = grid.getDataBounds(),
yOffset;
if (this.currentDrag.y < b.origin.y) {
yOffset = -1;
} else if (this.currentDrag.y > b.origin.y + b.extent.y) {
yOffset = 1;
}
if (yOffset) {
if (this.lastDragRow >= grid.getFixedRowCount()) {
this.lastDragRow += yOffset;
}
grid.scrollBy(0, yOffset);
}
this.handleMouseDragCellSelection(grid, this.lastDragRow, []); // update the selection
grid.repaint();
setTimeout(this.scrollDrag.bind(this, grid), 25);
},
/**
* @memberOf RowSelection.prototype
* @desc extend a selection or create one if there isnt yet
* @param {Hypergrid} grid
* @param {Object} gridCell - the event details
* @param {Array} keys - array of the keys that are currently pressed down
*/
extendSelection: function(grid, y, keys) {
if (!grid.abortEditing()) { return; }
var mouseY = grid.getMouseDown().y,
hasSHIFT = keys.indexOf('SHIFT') !== -1;
if (y < 0) { // outside of the grid?
return; // do nothing
}
if (hasSHIFT) {
grid.clearMostRecentRowSelection();
grid.selectRow(y, mouseY);
grid.setDragExtent(grid.newPoint(0, y - mouseY));
} else {
grid.toggleSelectRow(y, keys);
grid.setMouseDown(grid.newPoint(0, y));
grid.setDragExtent(grid.newPoint(0, 0));
}
grid.repaint();
},
/**
* @memberOf RowSelection.prototype
* @param {Hypergrid} grid
*/
handleDOWNSHIFT: function(grid) {
this.moveShiftSelect(grid, 1);
},
/**
* @memberOf RowSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleUPSHIFT: function(grid) {
this.moveShiftSelect(grid, -1);
},
/**
* @memberOf RowSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleLEFTSHIFT: function(grid) {},
/**
* @memberOf RowSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleRIGHTSHIFT: function(grid) {},
/**
* @memberOf RowSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleDOWN: function(grid) {
this.moveSingleSelect(grid, 1);
},
/**
* @memberOf RowSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleUP: function(grid) {
this.moveSingleSelect(grid, -1);
},
/**
* @memberOf RowSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleLEFT: function(grid) {},
/**
* @memberOf RowSelection.prototype
* @param {Hypergrid} grid
* @param {Object} event - the event details
*/
handleRIGHT: function(grid) {
var mouseCorner = grid.getMouseDown().plus(grid.getDragExtent()),
maxColumns = grid.getColumnCount() - 1,
newX = grid.getHScrollValue(),
newY = mouseCorner.y;
newX = Math.min(maxColumns, newX);
grid.clearSelections();
grid.select(newX, newY, 0, 0);
grid.setMouseDown(grid.newPoint(newX, newY));
grid.setDragExtent(grid.newPoint(0, 0));
grid.repaint();
},
/**
* @memberOf RowSelection.prototype
* @desc If we are holding down the same navigation key, accelerate the increment we scroll
* #### returns: integer
*/
getAutoScrollAcceleration: function() {
var count = 1;
var elapsed = this.getAutoScrollDuration() / 2000;
count = Math.max(1, Math.floor(elapsed * elapsed * elapsed * elapsed));
return count;
},
/**
* @memberOf RowSelection.prototype
* @desc set the start time to right now when we initiate an auto scroll
*/
setAutoScrollStartTime: function() {
this.sbAutoStart = Date.now();
},
/**
* @memberOf RowSelection.prototype
* @desc update the autoscroll start time if we haven't autoscrolled within the last 500ms otherwise update the current autoscroll time
*/
pingAutoScroll: function() {
var now = Date.now();
if (now - this.sbLastAuto > 500) {
this.setAutoScrollStartTime();
}
this.sbLastAuto = Date.now();
},
/**
* @memberOf RowSelection.prototype
* @desc answer how long we have been auto scrolling
* #### returns: integer
*/
getAutoScrollDuration: function() {
if (Date.now() - this.sbLastAuto > 500) {
return 0;
}
return Date.now() - this.sbAutoStart;
},
/**
* @memberOf RowSelection.prototype
* @desc Augment the most recent selection extent by (offsetX,offsetY) and scroll if necessary.
* @param {Hypergrid} grid
* @param {number} offsetX - x coordinate to start at
* @param {number} offsetY - y coordinate to start at
*/
moveShiftSelect: function(grid, offsetY) {
this.moveSingleSelect(grid, offsetY, true);
},
/**
* @memberOf RowSelection.prototype
* @desc Replace the most recent row selection with a single cell row selection `offsetY` rows from the previous selection.
* @param {Hypergrid} grid
* @param {number} offsetY - y coordinate to start at
*/
moveSingleSelect: function(grid, offsetY, shift) {
var selections = grid.selectionModel.rowSelectionModel.selection,
lastSelection = selections[selections.length - 1],
top = lastSelection[0],
bottom = lastSelection[1];
if (shift) {
var firstOffsetY = lastSelection.offsetY = lastSelection.offsetY || offsetY;
if (lastSelection.offsetY < 0) {
top += offsetY;
} else {
bottom += offsetY;
}
} else {
top += offsetY;
bottom += offsetY;
}
if (top < 0 || bottom >= grid.getRowCount()) {
return;
}
selections.length -= 1;
if (selections.length) {
lastSelection = selections[selections.length - 1];
delete lastSelection.offsetY;
}
grid.selectRow(top, bottom);
if (shift && top !== bottom) {
lastSelection = selections[selections.length - 1];
lastSelection.offsetY = firstOffsetY;
}
grid.setMouseDown(grid.newPoint(0, top));
grid.setDragExtent(grid.newPoint(0, bottom - top));
grid.scrollToMakeVisible(grid.properties.fixedColumnCount, offsetY < 0 ? top : bottom + 1); // +1 for partial row
moveCellSelection(grid);
grid.fireSyntheticRowSelectionChangedEvent();
grid.repaint();
},
isSingleRowSelection: function() {
return true;
}
});
function moveCellSelection(grid) {
var rows;
if (
grid.properties.collapseCellSelections &&
grid.properties.singleRowSelectionMode && // let's only attempt this when in this mode
!grid.properties.multipleSelections && // and only when in single selection mode
(rows = grid.getSelectedRows()).length && // user just selected a row (must be single row due to mode we're in)
grid.selectionModel.getSelections().length // there was a cell region selected (must be the only one)
) {
var rect = grid.selectionModel.getLastSelection(), // the only cell selection
x = rect.left,
y = rows[0], // we know there's only 1 row selected
width = rect.right - x,
height = 0, // collapse the new region to occupy a single row
fireSelectionChangedEvent = false;
grid.selectionModel.select(x, y, width, height, fireSelectionChangedEvent);
grid.repaint();
}
}
module.exports = RowSelection;