slickgrid
Version:
A lightning fast JavaScript grid/spreadsheet
375 lines (329 loc) • 12.4 kB
JavaScript
(function ($) {
// register namespace
$.extend(true, window, {
"Slick": {
"CellRangeSelector": CellRangeSelector
}
});
function CellRangeSelector(options) {
var _grid;
var _currentlySelectedRange;
var _canvas;
var _gridOptions;
var _$activeCanvas;
var _dragging;
var _decorator;
var _self = this;
var _handler = new Slick.EventHandler();
var _defaults = {
autoScroll: true,
minIntervalToShowNextCell: 30,
maxIntervalToShowNextCell: 600, // better to a multiple of minIntervalToShowNextCell
accelerateInterval: 5, // increase 5ms when cursor 1px outside the viewport.
selectionCss: {
"border": "2px dashed blue"
}
};
// Frozen row & column variables
var _rowOffset;
var _columnOffset;
var _isRightCanvas;
var _isBottomCanvas;
// autoScroll related variables
var _$activeViewport;
var _viewportWidth;
var _viewportHeight;
var _draggingMouseOffset;
var _moveDistanceForOneCell;
var _autoScrollTimerId;
var _xDelayForNextCell;
var _yDelayForNextCell;
var _isRowMoveRegistered = false;
// Scrollings
var _scrollTop = 0;
var _scrollLeft = 0;
function init(grid) {
options = $.extend(true, {}, _defaults, options);
_decorator = options.cellDecorator || new Slick.CellRangeDecorator(grid, options);
_grid = grid;
_canvas = _grid.getCanvasNode();
_gridOptions = _grid.getOptions();
_handler
.subscribe(_grid.onScroll, handleScroll)
.subscribe(_grid.onDragInit, handleDragInit)
.subscribe(_grid.onDragStart, handleDragStart)
.subscribe(_grid.onDrag, handleDrag)
.subscribe(_grid.onDragEnd, handleDragEnd);
}
function destroy() {
_handler.unsubscribeAll();
_$activeCanvas = null;
_$activeViewport = null;
_canvas = null;
if (_decorator && _decorator.destroy) {
_decorator.destroy();
}
}
function getCellDecorator() {
return _decorator;
}
function handleScroll(e, args) {
_scrollTop = args.scrollTop;
_scrollLeft = args.scrollLeft;
}
function handleDragInit(e, dd) {
// Set the active canvas node because the decorator needs to append its
// box to the correct canvas
_$activeCanvas = $(_grid.getActiveCanvasNode(e));
_$activeViewport = $(_grid.getActiveViewportNode(e));
var scrollbarDimensions = _grid.getDisplayedScrollbarDimensions()
_viewportWidth = _$activeViewport.width() - scrollbarDimensions.width;
_viewportHeight = _$activeViewport.height() - scrollbarDimensions.height;
_moveDistanceForOneCell = {
x: _grid.getAbsoluteColumnMinWidth() / 2,
y: _grid.getOptions().rowHeight / 2
}
_isRowMoveRegistered = hasRowMoveManager();
var c = _$activeCanvas.offset();
_rowOffset = 0;
_columnOffset = 0;
_isBottomCanvas = _$activeCanvas.hasClass('grid-canvas-bottom');
if (_gridOptions.frozenRow > -1 && _isBottomCanvas) {
_rowOffset = (_gridOptions.frozenBottom) ? $('.'+_grid.getUID()+' .grid-canvas-bottom').height() : $('.'+_grid.getUID()+' .grid-canvas-top').height();
}
_isRightCanvas = _$activeCanvas.hasClass('grid-canvas-right');
if (_gridOptions.frozenColumn > -1 && _isRightCanvas) {
_columnOffset = $('.'+_grid.getUID()+' .grid-canvas-left').width();
}
// prevent the grid from cancelling drag'n'drop by default
e.stopImmediatePropagation();
}
function handleDragStart(e, dd) {
var cell = _grid.getCellFromEvent(e);
if (_self.onBeforeCellRangeSelected.notify(cell) !== false) {
if (_grid.canCellBeSelected(cell.row, cell.cell)) {
_dragging = true;
e.stopImmediatePropagation();
}
}
if (!_dragging) {
return;
}
_grid.focus();
var startX = dd.startX - $(_canvas).offset().left;
if (_gridOptions.frozenColumn >= 0 && _isRightCanvas) {
startX += _scrollLeft;
}
var startY = dd.startY - $(_canvas).offset().top;
if (_gridOptions.frozenRow >= 0 && _isBottomCanvas) {
startY += _scrollTop;
}
var start = _grid.getCellFromPoint(startX, startY);
dd.range = { start: start, end: {} };
_currentlySelectedRange = dd.range;
return _decorator.show(new Slick.Range(start.row, start.cell));
}
function handleDrag(e, dd) {
if (!_dragging && !_isRowMoveRegistered) {
return;
}
if (!_isRowMoveRegistered) {
e.stopImmediatePropagation();
}
if (options.autoScroll) {
_draggingMouseOffset = getMouseOffsetViewport(e, dd);
if (_draggingMouseOffset.isOutsideViewport) {
return handleDragOutsideViewport();
}
}
stopIntervalTimer();
handleDragTo(e, dd);
}
function getMouseOffsetViewport(e, dd) {
var viewportLeft = _$activeViewport.scrollLeft();
var viewportTop = _$activeViewport.scrollTop();
var viewportRight = viewportLeft + _viewportWidth;
var viewportBottom = viewportTop + _viewportHeight;
var viewportOffset = _$activeViewport.offset();
var viewportOffsetLeft = viewportOffset.left;
var viewportOffsetTop = viewportOffset.top;
var viewportOffsetRight = viewportOffsetLeft + _viewportWidth;
var viewportOffsetBottom = viewportOffsetTop + _viewportHeight;
var result = {
e: e,
dd: dd,
viewport: {
left: viewportLeft,
top: viewportTop,
right: viewportRight,
bottom: viewportBottom,
offset: {
left: viewportOffsetLeft,
top: viewportOffsetTop,
right: viewportOffsetRight,
bottom: viewportOffsetBottom
}
},
// Consider the viewport as the origin, the `offset` is based on the coordinate system:
// the cursor is on the viewport's left/bottom when it is less than 0, and on the right/top when greater than 0.
offset: {
x: 0,
y: 0
},
isOutsideViewport: false
}
// ... horizontal
if (e.pageX < viewportOffsetLeft) {
result.offset.x = e.pageX - viewportOffsetLeft;
} else if (e.pageX > viewportOffsetRight) {
result.offset.x = e.pageX - viewportOffsetRight;
}
// ... vertical
if (e.pageY < viewportOffsetTop) {
result.offset.y = viewportOffsetTop - e.pageY;
} else if (e.pageY > viewportOffsetBottom) {
result.offset.y = viewportOffsetBottom - e.pageY;
}
result.isOutsideViewport = !!result.offset.x || !!result.offset.y;
return result;
}
function handleDragOutsideViewport() {
_xDelayForNextCell = options.maxIntervalToShowNextCell - Math.abs(_draggingMouseOffset.offset.x) * options.accelerateInterval;
_yDelayForNextCell = options.maxIntervalToShowNextCell - Math.abs(_draggingMouseOffset.offset.y) * options.accelerateInterval;
// only one timer is created to handle the case that cursor outside the viewport
if (!_autoScrollTimerId) {
var xTotalDelay = 0;
var yTotalDelay = 0;
_autoScrollTimerId = setInterval(function () {
var xNeedUpdate = false;
var yNeedUpdate = false;
// ... horizontal
if (_draggingMouseOffset.offset.x) {
xTotalDelay += options.minIntervalToShowNextCell;
xNeedUpdate = xTotalDelay >= _xDelayForNextCell;
} else {
xTotalDelay = 0;
}
// ... vertical
if (_draggingMouseOffset.offset.y) {
yTotalDelay += options.minIntervalToShowNextCell;
yNeedUpdate = yTotalDelay >= _yDelayForNextCell;
} else {
yTotalDelay = 0;
}
if (xNeedUpdate || yNeedUpdate) {
if (xNeedUpdate) {
xTotalDelay = 0;
}
if (yNeedUpdate) {
yTotalDelay = 0;
}
handleDragToNewPosition(xNeedUpdate, yNeedUpdate);
}
}, options.minIntervalToShowNextCell);
}
}
function handleDragToNewPosition(xNeedUpdate, yNeedUpdate) {
var pageX = _draggingMouseOffset.e.pageX;
var pageY = _draggingMouseOffset.e.pageY;
var mouseOffsetX = _draggingMouseOffset.offset.x;
var mouseOffsetY = _draggingMouseOffset.offset.y;
var viewportOffset = _draggingMouseOffset.viewport.offset;
// ... horizontal
if (xNeedUpdate && mouseOffsetX) {
if (mouseOffsetX > 0) {
pageX = viewportOffset.right + _moveDistanceForOneCell.x;
} else {
pageX = viewportOffset.left - _moveDistanceForOneCell.x;
}
}
// ... vertical
if (yNeedUpdate && mouseOffsetY) {
if (mouseOffsetY > 0) {
pageY = viewportOffset.top - _moveDistanceForOneCell.y;
} else {
pageY = viewportOffset.bottom + _moveDistanceForOneCell.y;
}
}
handleDragTo({
pageX: pageX,
pageY: pageY
}, _draggingMouseOffset.dd);
}
function stopIntervalTimer() {
clearInterval(_autoScrollTimerId);
_autoScrollTimerId = null;
}
function handleDragTo(e, dd) {
var end = _grid.getCellFromPoint(
e.pageX - _$activeCanvas.offset().left + _columnOffset,
e.pageY - _$activeCanvas.offset().top + _rowOffset
);
// ... frozen column(s),
if ( _gridOptions.frozenColumn >= 0 && (!_isRightCanvas && (end.cell > _gridOptions.frozenColumn)) || (_isRightCanvas && (end.cell <= _gridOptions.frozenColumn)) ) {
return;
}
// ... or frozen row(s)
if ( _gridOptions.frozenRow >= 0 && (!_isBottomCanvas && (end.row >= _gridOptions.frozenRow)) || (_isBottomCanvas && (end.row < _gridOptions.frozenRow)) ) {
return;
}
// scrolling the viewport to display the target `end` cell if it is not fully displayed
if (options.autoScroll && _draggingMouseOffset) {
var endCellBox = _grid.getCellNodeBox(end.row, end.cell);
if (!endCellBox) {
return;
}
var viewport = _draggingMouseOffset.viewport;
if (endCellBox.left < viewport.left || endCellBox.right > viewport.right
|| endCellBox.top < viewport.top || endCellBox.bottom > viewport.bottom) {
_grid.scrollCellIntoView(end.row, end.cell);
}
}
// ... or regular grid (without any frozen options)
if (!_grid.canCellBeSelected(end.row, end.cell)) {
return;
}
if (dd && dd.range) {
dd.range.end = end;
var range = new Slick.Range(dd.range.start.row, dd.range.start.cell, end.row, end.cell);
_decorator.show(range);
_self.onCellRangeSelecting.notify({
range: range
});
}
}
function hasRowMoveManager() {
return !!(_grid.getPluginByName('RowMoveManager') || _grid.getPluginByName('CrossGridRowMoveManager'));
}
function handleDragEnd(e, dd) {
if (!_dragging) {
return;
}
_dragging = false;
e.stopImmediatePropagation();
stopIntervalTimer();
_decorator.hide();
_self.onCellRangeSelected.notify({
range: new Slick.Range(
dd.range.start.row,
dd.range.start.cell,
dd.range.end.row,
dd.range.end.cell
)
});
}
function getCurrentRange() {
return _currentlySelectedRange;
}
$.extend(this, {
"init": init,
"destroy": destroy,
"pluginName": "CellRangeSelector",
"getCellDecorator": getCellDecorator,
"getCurrentRange": getCurrentRange,
"onBeforeCellRangeSelected": new Slick.Event(),
"onCellRangeSelected": new Slick.Event(),
"onCellRangeSelecting": new Slick.Event()
});
}
})(jQuery);