slickgrid
Version:
A lightning fast JavaScript grid/spreadsheet
281 lines (240 loc) • 9.69 kB
JavaScript
/**
* Row Move Manager options:
* cssClass: A CSS class to be added to the menu item container.
* columnId: Column definition id (defaults to "_move")
* cancelEditOnDrag: Do we want to cancel any Editing while dragging a row (defaults to false)
* disableRowSelection: Do we want to disable the row selection? (defaults to false)
* hideRowMoveShadow: Do we want to hide the row move shadow clone? (defaults to true)
* rowMoveShadowMarginTop: When row move shadow is shown, optional margin-top (defaults to 0)
* rowMoveShadowMarginLeft: When row move shadow is shown, optional margin-left (defaults to 0)
* rowMoveShadowOpacity: When row move shadow is shown, what is its opacity? (defaults to 0.95)
* rowMoveShadowScale: When row move shadow is shown, what is its size scale? (default to 0.75)
* singleRowMove: Do we want a single row move? Setting this to false means that it's a multple row move (defaults to false)
* width: Width of the column
* usabilityOverride: Callback method that user can override the default behavior of the row being moveable or not
*
*/
(function ($) {
// register namespace
$.extend(true, window, {
"Slick": {
"CrossGridRowMoveManager": CrossGridRowMoveManager
}
});
function CrossGridRowMoveManager(options) {
var _grid;
var _canvas;
var _toGrid;
var _toCanvas;
var _dragging;
var _self = this;
var _usabilityOverride = null;
var _handler = new Slick.EventHandler();
var _defaults = {
columnId: "_move",
cssClass: null,
cancelEditOnDrag: false,
disableRowSelection: false,
hideRowMoveShadow: true,
rowMoveShadowMarginTop: 0,
rowMoveShadowMarginLeft: 0,
rowMoveShadowOpacity: 0.95,
rowMoveShadowScale: 0.75,
singleRowMove: false,
width: 40,
};
// user could override the expandable icon logic from within the options or after instantiating the plugin
if (options && typeof options.usabilityOverride === 'function') {
usabilityOverride(options.usabilityOverride);
}
function init(grid) {
options = $.extend(true, {}, _defaults, options);
_grid = grid;
_canvas = _grid.getCanvasNode();
_toGrid = options.toGrid;
_toCanvas = _toGrid.getCanvasNode();
_handler
.subscribe(_grid.onDragInit, handleDragInit)
.subscribe(_grid.onDragStart, handleDragStart)
.subscribe(_grid.onDrag, handleDrag)
.subscribe(_grid.onDragEnd, handleDragEnd);
}
function destroy() {
_handler.unsubscribeAll();
}
function setOptions(newOptions) {
options = $.extend({}, options, newOptions);
}
function handleDragInit(e) {
// prevent the grid from cancelling drag'n'drop by default
e.stopImmediatePropagation();
}
function handleDragStart(e, dd) {
var cell = _grid.getCellFromEvent(e);
var currentRow = cell && cell.row;
var dataContext = _grid.getDataItem(currentRow);
if (!checkUsabilityOverride(currentRow, dataContext, _grid)) {
return;
}
if (options.cancelEditOnDrag && _grid.getEditorLock().isActive()) {
_grid.getEditorLock().cancelCurrentEdit();
}
if (_grid.getEditorLock().isActive() || !isHandlerColumn(cell.cell)) {
return false;
}
_dragging = true;
e.stopImmediatePropagation();
// optionally create a shadow element of the row so that we can see all the time which row exactly we're dragging
if (!options.hideRowMoveShadow) {
var $slickRowElm = $(_grid.getCellNode(cell.row, cell.cell)).closest('.slick-row');
if ($slickRowElm) {
dd.clonedSlickRow = $slickRowElm.clone();
dd.clonedSlickRow.addClass('slick-reorder-shadow-row')
.css("marginTop", options.rowMoveShadowMarginTop || 0)
.css("marginLeft", options.rowMoveShadowMarginLeft || 0)
.css("opacity", options.rowMoveShadowOpacity || 0.95)
.css("transform", "scale(" + options.rowMoveShadowScale + ")")
.hide()
.appendTo(_canvas);
}
}
var selectedRows = options.singleRowMove ? [cell.row] : _grid.getSelectedRows();
if (selectedRows.length === 0 || $.inArray(cell.row, selectedRows) == -1) {
selectedRows = [cell.row];
if (!options.disableRowSelection) {
_grid.setSelectedRows(selectedRows);
}
}
selectedRows.sort(function(a,b) { return a-b; });
var rowHeight = _grid.getOptions().rowHeight;
dd.fromGrid = _grid;
dd.toGrid = _toGrid;
dd.selectedRows = selectedRows;
dd.selectionProxy = $("<div class='slick-reorder-proxy'/>")
.css("position", "absolute")
.css("zIndex", "99999")
.css("width", $(_toCanvas).innerWidth())
.css("height", rowHeight * selectedRows.length)
.hide()
.appendTo(_toCanvas);
dd.guide = $("<div class='slick-reorder-guide'/>")
.css("position", "absolute")
.css("zIndex", "99998")
.css("width", $(_toCanvas).innerWidth())
.css("top", -1000)
.appendTo(_toCanvas);
dd.insertBefore = -1;
}
function handleDrag(e, dd) {
if (!_dragging) {
return;
}
e.stopImmediatePropagation();
var top = e.pageY - $(_toCanvas).offset().top;
dd.selectionProxy.css("top", top - 5).show();
// if the row move shadow is enabled, we'll also make it follow the mouse cursor
if (dd.clonedSlickRow) {
var offsetY = e.pageY - $(_canvas).offset().top;
dd.clonedSlickRow.css("top", offsetY - 6).show();
}
var insertBefore = Math.max(0, Math.min(Math.round(top / _toGrid.getOptions().rowHeight), _toGrid.getDataLength()));
if (insertBefore !== dd.insertBefore) {
var eventData = {
"fromGrid": _grid,
"toGrid": _toGrid,
"rows": dd.selectedRows,
"insertBefore": insertBefore
};
if (_self.onBeforeMoveRows.notify(eventData) === false) {
dd.canMove = false;
} else {
dd.canMove = true;
}
// if there's a UsabilityOverride defined, we also need to verify that the condition is valid
if (_usabilityOverride && dd.canMove) {
var insertBeforeDataContext = _toGrid.getDataItem(insertBefore);
dd.canMove = checkUsabilityOverride(insertBefore, insertBeforeDataContext, _toGrid);
}
// if the new target is possible we'll display the dark blue bar (representin the acceptability) at the target position
// else it won't show up (it will be off the screen)
if (!dd.canMove) {
dd.guide.css("top", -1000);
} else {
dd.guide.css("top", insertBefore * _toGrid.getOptions().rowHeight);
}
dd.insertBefore = insertBefore;
}
}
function handleDragEnd(e, dd) {
if (!_dragging) {
return;
}
_dragging = false;
e.stopImmediatePropagation();
dd.guide.remove();
dd.selectionProxy.remove();
if (dd.clonedSlickRow) {
dd.clonedSlickRow.remove();
dd.clonedSlickRow = null;
}
if (dd.canMove) {
var eventData = {
"fromGrid": _grid,
"toGrid": _toGrid,
"rows": dd.selectedRows,
"insertBefore": dd.insertBefore
};
// TODO: _grid.remapCellCssClasses ?
_self.onMoveRows.notify(eventData);
}
}
function getColumnDefinition() {
return {
id: options.columnId || "_move",
name: "",
field: "move",
width: options.width || 40,
behavior: "selectAndMove",
selectable: false,
resizable: false,
cssClass: options.cssClass,
formatter: moveIconFormatter
};
}
function moveIconFormatter(row, cell, value, columnDef, dataContext, grid) {
if (!checkUsabilityOverride(row, dataContext, grid)) {
return null;
} else {
return { addClasses: "cell-reorder dnd", text: "" };
}
}
function checkUsabilityOverride(row, dataContext, grid) {
if (typeof _usabilityOverride === 'function') {
return _usabilityOverride(row, dataContext, grid);
}
return true;
}
/**
* Method that user can pass to override the default behavior or making every row moveable.
* In order word, user can choose which rows to be an available as moveable (or not) by providing his own logic show/hide icon and usability.
* @param overrideFn: override function callback
*/
function usabilityOverride(overrideFn) {
_usabilityOverride = overrideFn;
}
function isHandlerColumn(columnIndex) {
return /move|selectAndMove/.test(_grid.getColumns()[columnIndex].behavior);
}
$.extend(this, {
"onBeforeMoveRows": new Slick.Event(),
"onMoveRows": new Slick.Event(),
"init": init,
"destroy": destroy,
"getColumnDefinition": getColumnDefinition,
"setOptions": setOptions,
"usabilityOverride": usabilityOverride,
"isHandlerColumn": isHandlerColumn,
"pluginName": "CrossGridRowMoveManager"
});
}
})(jQuery);