@reactual/handsontable
Version:
Spreadsheet-like data grid editor
717 lines (590 loc) • 20.3 kB
JavaScript
export function sleep(delay = 100) {
return Promise.resolve({
then: function(resolve) {
setTimeout(resolve, delay);
}
});
};
export function hot() {
return spec().$container.data('handsontable');
};
export function handsontable(options) {
var currentSpec = spec();
currentSpec.$container.handsontable(options);
currentSpec.$container[0].focus(); // otherwise TextEditor tests do not pass in IE8
return currentSpec.$container.data('handsontable');
};
/**
* As for v. 0.11 the only scrolling method is native scroll, which creates copies of main htCore table inside of the container.
* Therefore, simple $(".htCore") will return more than one object. Most of the time, you're interested in the original
* htCore, not the copies made by native scroll.
*
* This method returns the original htCore object
*
* @returns {jqObject} reference to the original htCore
*/
export function getHtCore() {
return spec().$container.find('.htCore').first();
};
export function getTopClone() {
return spec().$container.find('.ht_clone_top');
};
export function getTopLeftClone() {
return spec().$container.find('.ht_clone_top_left_corner');
};
// for compatybility
// var getCornerClone = getTopLeftClone;
export function getLeftClone() {
return spec().$container.find('.ht_clone_left');
};
export function getBottomClone() {
return spec().$container.find('.ht_clone_bottom');
};
export function getBottomLeftClone() {
return spec().$container.find('.ht_clone_bottom_left_corner');
};
// Rename me to countTD
export function countCells() {
return getHtCore().find('tbody td').length;
};
export function isEditorVisible() {
return !!(keyProxy().is(':visible') && keyProxy().parent().is(':visible') && !keyProxy().parent().is('.htHidden'));
};
export function isFillHandleVisible() {
return !!spec().$container.find('.wtBorder.corner:visible').length;
};
export function getCorrespondingOverlay(cell, container) {
var overlay = $(cell).parents('.handsontable');
if (overlay[0] == container[0]) {
return $('.ht_master');
}
return $(overlay[0]);
};
/**
* Shows context menu
*/
export function contextMenu(cell) {
var hot = spec().$container.data('handsontable');
var selected = hot.getSelected();
if (!selected) {
hot.selectCell(0, 0);
selected = hot.getSelected();
}
if (!cell) {
cell = getCell(selected[0], selected[1]);
}
var cellOffset = $(cell).offset();
$(cell).simulate('contextmenu', {
clientX: cellOffset.left - Handsontable.dom.getWindowScrollLeft(),
clientY: cellOffset.top - Handsontable.dom.getWindowScrollTop(),
});
};
export function closeContextMenu() {
$(document).simulate('mousedown');
// $(document).trigger('mousedown');
};
/**
* Shows dropdown menu
*/
export function dropdownMenu(columnIndex) {
var hot = spec().$container.data('handsontable');
var th = hot.view.wt.wtTable.getColumnHeader(columnIndex || 0);
var button = th.querySelector('.changeType');
if (button) {
$(button).simulate('mousedown');
$(button).simulate('click');
}
};
export function closeDropdownMenu() {
$(document).simulate('mousedown');
};
export function dropdownMenuRootElement() {
var plugin = hot().getPlugin('dropdownMenu');
var root;
if (plugin && plugin.menu) {
root = plugin.menu.container;
}
return root;
};
/**
* Returns a function that triggers a mouse event
* @param {String} type Event type
* @return {Function}
*/
export function handsontableMouseTriggerFactory(type, button) {
return function(element) {
if (!(element instanceof jQuery)) {
element = $(element);
}
var ev = $.Event(type);
ev.which = button || 1; // left click by default
element.simulate(type, ev);
};
};
export const mouseDown = handsontableMouseTriggerFactory('mousedown');
export const mouseMove = handsontableMouseTriggerFactory('mousemove');
export const mouseOver = handsontableMouseTriggerFactory('mouseover');
export const mouseUp = handsontableMouseTriggerFactory('mouseup');
export function mouseDoubleClick(element) {
mouseDown(element);
mouseUp(element);
mouseDown(element);
mouseUp(element);
};
export const mouseRightDown = handsontableMouseTriggerFactory('mousedown', 3);
export const mouseRightUp = handsontableMouseTriggerFactory('mouseup', 3);
/**
* Returns a function that triggers a key event
* @param {String} type Event type
* @return {Function}
*/
export function handsontableKeyTriggerFactory(type) {
return function(key, extend) {
var ev = {}; // $.Event(type);
if (typeof key === 'string') {
if (key.indexOf('shift+') > -1) {
key = key.substring(6);
ev.shiftKey = true;
}
if (key.indexOf('ctrl+') > -1) {
key = key.substring(5);
ev.ctrlKey = true;
ev.metaKey = true;
}
switch (key) {
case 'tab':
ev.keyCode = 9;
break;
case 'enter':
ev.keyCode = 13;
break;
case 'esc':
ev.keyCode = 27;
break;
case 'f2':
ev.keyCode = 113;
break;
case 'arrow_left':
ev.keyCode = 37;
break;
case 'arrow_up':
ev.keyCode = 38;
break;
case 'arrow_right':
ev.keyCode = 39;
break;
case 'arrow_down':
ev.keyCode = 40;
break;
case 'ctrl':
ev.keyCode = 17;
break;
case 'shift':
ev.keyCode = 16;
break;
case 'backspace':
ev.keyCode = 8;
break;
case 'delete':
ev.keyCode = 46;
break;
case 'space':
ev.keyCode = 32;
break;
case 'x':
ev.keyCode = 88;
break;
case 'c':
ev.keyCode = 67;
break;
case 'v':
ev.keyCode = 86;
break;
default:
throw new Error(`Unrecognised key name: ${key}`);
}
} else if (typeof key === 'number') {
ev.keyCode = key;
}
// ev.originalEvent = {}; //needed as long Handsontable searches for event.originalEvent
$.extend(ev, extend);
$(document.activeElement).simulate(type, ev);
};
};
export const keyDown = handsontableKeyTriggerFactory('keydown');
export const keyUp = handsontableKeyTriggerFactory('keyup');
/**
* Presses keyDown, then keyUp
*/
export function keyDownUp(key, extend) {
if (typeof key === 'string' && key.indexOf('shift+') > -1) {
keyDown('shift');
}
keyDown(key, extend);
keyUp(key, extend);
if (typeof key === 'string' && key.indexOf('shift+') > -1) {
keyUp('shift');
}
};
/**
* Returns current value of the keyboard proxy textarea
* @return {String}
*/
export function keyProxy() {
return spec().$container.find('textarea.handsontableInput');
};
export function serveImmediatePropagation(event) {
if (event != null && event.isImmediatePropagationEnabled == null) {
event.stopImmediatePropagation = function() {
this.isImmediatePropagationEnabled = false;
this.cancelBubble = true;
};
event.isImmediatePropagationEnabled = true;
event.isImmediatePropagationStopped = function() {
return !this.isImmediatePropagationEnabled;
};
}
return event;
};
export function autocompleteEditor() {
return spec().$container.find('.handsontableInput');
};
/**
* Sets text cursor inside keyboard proxy
*/
export function setCaretPosition(pos) {
var el = keyProxy()[0];
if (el.setSelectionRange) {
el.focus();
el.setSelectionRange(pos, pos);
} else if (el.createTextRange) {
var range = el.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
};
/**
* Returns autocomplete instance
*/
export function autocomplete() {
return spec().$container.find('.autocompleteEditor');
};
/**
* Triggers paste string on current selection
*/
export function triggerPaste(str) {
spec().$container.data('handsontable').getPlugin('CopyPaste').paste(str);
};
/**
* Calls a method in current Handsontable instance, returns its output
* @param method
* @return {Function}
*/
export function handsontableMethodFactory(method) {
return function() {
var instance;
try {
instance = spec().$container.handsontable('getInstance');
} catch (err) {
console.error(err);
}
if (instance) {
if (method === 'destroy') {
spec().$container.removeData();
}
} else {
if (method === 'destroy') {
return; // we can forgive this... maybe it was destroyed in the test
}
throw new Error('Something wrong with the test spec: Handsontable instance not found');
}
return instance[method](...arguments);
};
};
export const addHook = handsontableMethodFactory('addHook');
export const alter = handsontableMethodFactory('alter');
export const colToProp = handsontableMethodFactory('colToProp');
export const countCols = handsontableMethodFactory('countCols');
export const countRows = handsontableMethodFactory('countRows');
export const deselectCell = handsontableMethodFactory('deselectCell');
export const destroy = handsontableMethodFactory('destroy');
export const destroyEditor = handsontableMethodFactory('destroyEditor');
export const getActiveEditor = handsontableMethodFactory('getActiveEditor');
export const getCell = handsontableMethodFactory('getCell');
export const getCellEditor = handsontableMethodFactory('getCellEditor');
export const getCellMeta = handsontableMethodFactory('getCellMeta');
export const getCellMetaAtRow = handsontableMethodFactory('getCellMetaAtRow');
export const getCellRenderer = handsontableMethodFactory('getCellRenderer');
export const getCellsMeta = handsontableMethodFactory('getCellsMeta');
export const getCellValidator = handsontableMethodFactory('getCellValidator');
export const getColHeader = handsontableMethodFactory('getColHeader');
export const getCopyableData = handsontableMethodFactory('getCopyableData');
export const getCopyableText = handsontableMethodFactory('getCopyableText');
export const getData = handsontableMethodFactory('getData');
export const getDataAtCell = handsontableMethodFactory('getDataAtCell');
export const getDataAtCol = handsontableMethodFactory('getDataAtCol');
export const getDataAtRow = handsontableMethodFactory('getDataAtRow');
export const getDataAtRowProp = handsontableMethodFactory('getDataAtRowProp');
export const getDataType = handsontableMethodFactory('getDataType');
export const getInstance = handsontableMethodFactory('getInstance');
export const getRowHeader = handsontableMethodFactory('getRowHeader');
export const getSelected = handsontableMethodFactory('getSelected');
export const getSourceData = handsontableMethodFactory('getSourceData');
export const getSourceDataArray = handsontableMethodFactory('getSourceDataArray');
export const getSourceDataAtCell = handsontableMethodFactory('getSourceDataAtCell');
export const getSourceDataAtCol = handsontableMethodFactory('getSourceDataAtCol');
export const getSourceDataAtRow = handsontableMethodFactory('getSourceDataAtRow');
export const getValue = handsontableMethodFactory('getValue');
export const loadData = handsontableMethodFactory('loadData');
export const populateFromArray = handsontableMethodFactory('populateFromArray');
export const propToCol = handsontableMethodFactory('propToCol');
export const removeCellMeta = handsontableMethodFactory('removeCellMeta');
export const render = handsontableMethodFactory('render');
export const selectCell = handsontableMethodFactory('selectCell');
export const setCellMeta = handsontableMethodFactory('setCellMeta');
export const setDataAtCell = handsontableMethodFactory('setDataAtCell');
export const setDataAtRowProp = handsontableMethodFactory('setDataAtRowProp');
export const spliceCellsMeta = handsontableMethodFactory('spliceCellsMeta');
export const spliceCol = handsontableMethodFactory('spliceCol');
export const spliceRow = handsontableMethodFactory('spliceRow');
export const updateSettings = handsontableMethodFactory('updateSettings');
export const countSourceRows = handsontableMethodFactory('countSourceRows');
export const countSourceCols = handsontableMethodFactory('countSourceCols');
export const countEmptyRows = handsontableMethodFactory('countEmptyRows');
export const countEmptyCols = handsontableMethodFactory('countEmptyCols');
/**
* Returns column width for HOT container
* @param $elem
* @param col
* @returns {Number}
*/
export function colWidth($elem, col) {
var TR = $elem[0].querySelector('TBODY TR');
var cell;
if (TR) {
cell = TR.querySelectorAll('TD')[col];
} else {
cell = $elem[0].querySelector('THEAD TR').querySelectorAll('TH')[col];
}
if (!cell) {
throw new Error(`Cannot find table column of index '${col}'`);
}
return cell.offsetWidth;
}
/**
* Returns row height for HOT container
* @param $elem
* @param row
* @returns {Number}
*/
export function rowHeight($elem, row) {
var TD;
if (row >= 0) {
TD = $elem[0].querySelector(`tbody tr:nth-child(${row + 1}) td`);
} else {
TD = $elem[0].querySelector(`thead tr:nth-child(${Math.abs(row)})`);
}
if (!TD) {
throw new Error(`Cannot find table row of index '${row}'`);
}
return Handsontable.dom.outerHeight(TD);
}
/**
* Returns value that has been rendered in table cell
* @param {Number} trIndex
* @param {Number} tdIndex
* @returns {String}
*/
export function getRenderedValue(trIndex, tdIndex) {
return spec().$container.find('tbody tr').eq(trIndex).find('td').eq(tdIndex).html();
}
/**
* Returns nodes that have been rendered in table cell
* @param {Number} trIndex
* @param {Number} tdIndex
* @returns {String}
*/
export function getRenderedContent(trIndex, tdIndex) {
return spec().$container.find('tbody tr').eq(trIndex).find('td').eq(tdIndex).children();
}
/**
* Create numerical data values for the table
* @param rowCount
* @param colCount
* @returns {Array}
*/
export function createNumericData(rowCount, colCount) {
rowCount = typeof rowCount === 'number' ? rowCount : 100;
colCount = typeof colCount === 'number' ? colCount : 4;
var
rows = [],
i,
j;
for (i = 0; i < rowCount; i++) {
var row = [];
for (j = 0; j < colCount; j++) {
row.push((i + 1));
}
rows.push(row);
}
return rows;
}
/**
* Model factory, which creates object with private properties, accessible by setters and getters.
* Created for the purpose of testing HOT with Backbone-like Models
* @param opts
* @returns {{}}
* @constructor
*/
export function Model(opts) {
var obj = {};
var _data = $.extend({
id: undefined,
name: undefined,
address: undefined
}, opts);
obj.attr = function(name, value) {
if (typeof value === 'undefined') {
return this.get(name);
}
return this.set(name, value);
};
obj.get = function(name) {
return _data[name];
};
obj.set = function(name, value) {
_data[name] = value;
return this;
};
return obj;
}
/**
* Factory which produces an accessor for objects of type "Model" (see above).
* This function should be used to create accessor for a given property name and pass it as `data` option in column
* configuration.
*
* @param name - name of the property for which an accessor function will be created
* @returns {Function}
*/
export function createAccessorForProperty(name) {
return function(obj, value) {
return obj.attr(name, value);
};
}
export function resizeColumn(displayedColumnIndex, width) {
var $container = spec().$container;
var $th = $container.find(`thead tr:eq(0) th:eq(${displayedColumnIndex})`);
$th.simulate('mouseover');
var $resizer = $container.find('.manualColumnResizer');
var resizerPosition = $resizer.position();
$resizer.simulate('mousedown', {
clientX: resizerPosition.left,
});
var delta = width - $th.width() - 2;
var newPosition = resizerPosition.left + delta;
$resizer.simulate('mousemove', {
clientX: newPosition
});
$resizer.simulate('mouseup');
}
export function resizeRow(displayedRowIndex, height) {
var $container = spec().$container;
var $th = $container.find(`tbody tr:eq(${displayedRowIndex}) th:eq(0)`);
$th.simulate('mouseover');
var $resizer = $container.find('.manualRowResizer');
var resizerPosition = $resizer.position();
$resizer.simulate('mousedown', {
clientY: resizerPosition.top
});
var delta = height - $th.height() - 2;
if (delta < 0) {
delta = 0;
}
$resizer.simulate('mousemove', {
clientY: resizerPosition.top + delta
});
$resizer.simulate('mouseup');
}
export function moveSecondDisplayedRowBeforeFirstRow(container, secondDisplayedRowIndex) {
var
$mainContainer = container.parents('.handsontable').not('[class*=clone]').not('[class*=master]').first(),
$rowHeaders = container.find('tbody tr th'),
$firstRowHeader = $rowHeaders.eq(secondDisplayedRowIndex - 1),
$secondRowHeader = $rowHeaders.eq(secondDisplayedRowIndex);
$secondRowHeader.simulate('mouseover');
var $manualRowMover = $mainContainer.find('.manualRowMover');
if ($manualRowMover.length) {
$manualRowMover.simulate('mousedown', {
clientY: $manualRowMover[0].getBoundingClientRect().top
});
$manualRowMover.simulate('mousemove', {
clientY: $manualRowMover[0].getBoundingClientRect().top - 20
});
$firstRowHeader.simulate('mouseover');
$secondRowHeader.simulate('mouseup');
}
}
export function moveFirstDisplayedRowAfterSecondRow(container, firstDisplayedRowIndex) {
var
$mainContainer = container.parents('.handsontable').not('[class*=clone]').not('[class*=master]').first(),
$rowHeaders = container.find('tbody tr th'),
$firstRowHeader = $rowHeaders.eq(firstDisplayedRowIndex),
$secondRowHeader = $rowHeaders.eq(firstDisplayedRowIndex + 1);
$secondRowHeader.simulate('mouseover');
var $manualRowMover = $mainContainer.find('.manualRowMover');
if ($manualRowMover.length) {
$manualRowMover.simulate('mousedown', {
clientY: $manualRowMover[0].getBoundingClientRect().top
});
$manualRowMover.simulate('mousemove', {
clientY: $manualRowMover[0].getBoundingClientRect().top + 20
});
$firstRowHeader.simulate('mouseover');
$secondRowHeader.simulate('mouseup');
}
}
export function swapDisplayedColumns(container, from, to) {
var $mainContainer = container.parents('.handsontable').not('[class*=clone]').not('[class*=master]').first();
var $colHeaders = container.find('thead tr:eq(0) th');
var $to = $colHeaders.eq(to);
var $from = $colHeaders.eq(from);
// Enter the second column header
$from.simulate('mouseover');
var $manualColumnMover = $mainContainer.find('.manualColumnMover');
// Grab the second column
$manualColumnMover.simulate('mousedown', {
pageX: $manualColumnMover[0].getBoundingClientRect().left,
});
// Drag the second column over the first column
$manualColumnMover.simulate('mousemove', {
pageX: $manualColumnMover[0].getBoundingClientRect().left - 20,
});
$to.simulate('mouseover');
// Drop the second column
$from.simulate('mouseup');
}
export function triggerTouchEvent(type, target, pageX, pageY) {
var e = document.createEvent('TouchEvent');
var targetCoords = target.getBoundingClientRect();
var touches;
var targetTouches;
var changedTouches;
if (!pageX && !pageY) {
pageX = parseInt(targetCoords.left + 3, 10);
pageY = parseInt(targetCoords.top + 3, 10);
}
var touch = document.createTouch(window, target, 0, pageX, pageY, pageX, pageY);
if (type == 'touchend') {
touches = document.createTouchList();
targetTouches = document.createTouchList();
changedTouches = document.createTouchList(touch);
} else {
touches = document.createTouchList(touch);
targetTouches = document.createTouchList(touch);
changedTouches = document.createTouchList(touch);
}
e.initTouchEvent(type, true, true, window, null, 0, 0, 0, 0, false, false, false, false, touches, targetTouches, changedTouches, 1, 0);
target.dispatchEvent(e);
};