forked from Mirrors/apostrophe
3723 lines
110 KiB
JavaScript
3723 lines
110 KiB
JavaScript
import * as dom from './dom.js';
|
|
import * as helper from './helpers.js';
|
|
import numeral from 'numeral';
|
|
import {DataMap} from './dataMap.js';
|
|
import {EditorManager} from './editorManager.js';
|
|
import {eventManager as eventManagerObject} from './eventManager.js';
|
|
import {getPlugin} from './plugins.js';
|
|
import {getRenderer} from './renderers.js';
|
|
import {TableView} from './tableView.js';
|
|
import {WalkontableCellCoords} from './3rdparty/walkontable/src/cell/coords.js';
|
|
import {WalkontableCellRange} from './3rdparty/walkontable/src/cell/range.js';
|
|
import {WalkontableSelection} from './3rdparty/walkontable/src/selection.js';
|
|
|
|
Handsontable.activeGuid = null;
|
|
|
|
/**
|
|
* Handsontable constructor
|
|
*
|
|
* @core
|
|
* @dependencies numeral
|
|
* @constructor Core
|
|
* @description
|
|
*
|
|
* After Handsontable is constructed, you can modify the grid behavior using the available public methods.
|
|
*
|
|
* ---
|
|
* ## How to call methods
|
|
*
|
|
* These are 2 equal ways to call a Handsontable method:
|
|
*
|
|
* ```js
|
|
* // all following examples assume that you constructed Handsontable like this
|
|
* var ht = new Handsontable(document.getElementById('example1'), options);
|
|
*
|
|
* // now, to use setDataAtCell method, you can either:
|
|
* ht.setDataAtCell(0, 0, 'new value');
|
|
* ```
|
|
*
|
|
* Alternatively, you can call the method using jQuery wrapper (__obsolete__, requires initialization using our jQuery guide
|
|
* ```js
|
|
* $('#example1').handsontable('setDataAtCell', 0, 0, 'new value');
|
|
* ```
|
|
* ---
|
|
*/
|
|
Handsontable.Core = function Core(rootElement, userSettings) {
|
|
var priv
|
|
, datamap
|
|
, grid
|
|
, selection
|
|
, editorManager
|
|
, instance = this
|
|
, GridSettings = function() {}
|
|
, eventManager = eventManagerObject(instance);
|
|
|
|
helper.extend(GridSettings.prototype, DefaultSettings.prototype); //create grid settings as a copy of default settings
|
|
helper.extend(GridSettings.prototype, userSettings); //overwrite defaults with user settings
|
|
helper.extend(GridSettings.prototype, expandType(userSettings));
|
|
|
|
this.rootElement = rootElement;
|
|
this.isHotTableEnv = dom.isChildOfWebComponentTable(this.rootElement);
|
|
Handsontable.eventManager.isHotTableEnv = this.isHotTableEnv;
|
|
|
|
this.container = document.createElement('DIV');
|
|
|
|
rootElement.insertBefore(this.container, rootElement.firstChild);
|
|
|
|
this.guid = 'ht_' + helper.randomString(); //this is the namespace for global events
|
|
|
|
if (!this.rootElement.id || this.rootElement.id.substring(0, 3) === "ht_") {
|
|
this.rootElement.id = this.guid; //if root element does not have an id, assign a random id
|
|
}
|
|
priv = {
|
|
cellSettings: [],
|
|
columnSettings: [],
|
|
columnsSettingConflicts: ['data', 'width'],
|
|
settings: new GridSettings(), // current settings instance
|
|
selRange: null, //exposed by public method `getSelectedRange`
|
|
isPopulated: null,
|
|
scrollable: null,
|
|
firstRun: true
|
|
};
|
|
|
|
grid = {
|
|
/**
|
|
* Inserts or removes rows and columns
|
|
*
|
|
* @memberof Core#
|
|
* @function alter
|
|
* @param {String} action Possible values: "insert_row", "insert_col", "remove_row", "remove_col"
|
|
* @param {Number} index
|
|
* @param {Number} amount
|
|
* @param {String} [source] Optional. Source of hook runner.
|
|
* @param {Boolean} [keepEmptyRows] Optional. Flag for preventing deletion of empty rows.
|
|
*/
|
|
alter: function(action, index, amount, source, keepEmptyRows) {
|
|
var delta;
|
|
|
|
amount = amount || 1;
|
|
|
|
switch (action) {
|
|
case "insert_row":
|
|
|
|
if (instance.getSettings().maxRows === instance.countRows()) {
|
|
return;
|
|
}
|
|
|
|
delta = datamap.createRow(index, amount);
|
|
|
|
if (delta) {
|
|
if (selection.isSelected() && priv.selRange.from.row >= index) {
|
|
priv.selRange.from.row = priv.selRange.from.row + delta;
|
|
selection.transformEnd(delta, 0); //will call render() internally
|
|
}
|
|
else {
|
|
selection.refreshBorders(); //it will call render and prepare methods
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "insert_col":
|
|
// //column order may have changes, so we need to translate the selection column index -> source array index
|
|
// index = instance.runHooksAndReturn('modifyCol', index);
|
|
delta = datamap.createCol(index, amount);
|
|
|
|
if (delta) {
|
|
|
|
if (Array.isArray(instance.getSettings().colHeaders)) {
|
|
var spliceArray = [index, 0];
|
|
spliceArray.length += delta; //inserts empty (undefined) elements at the end of an array
|
|
Array.prototype.splice.apply(instance.getSettings().colHeaders, spliceArray); //inserts empty (undefined) elements into the colHeader array
|
|
}
|
|
|
|
if (selection.isSelected() && priv.selRange.from.col >= index) {
|
|
priv.selRange.from.col = priv.selRange.from.col + delta;
|
|
selection.transformEnd(0, delta); //will call render() internally
|
|
}
|
|
else {
|
|
selection.refreshBorders(); //it will call render and prepare methods
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "remove_row":
|
|
//column order may have changes, so we need to translate the selection column index -> source array index
|
|
index = instance.runHooks('modifyCol', index);
|
|
|
|
datamap.removeRow(index, amount);
|
|
priv.cellSettings.splice(index, amount);
|
|
|
|
var fixedRowsTop = instance.getSettings().fixedRowsTop;
|
|
if (fixedRowsTop >= index + 1) {
|
|
instance.getSettings().fixedRowsTop -= Math.min(amount, fixedRowsTop - index);
|
|
}
|
|
|
|
grid.adjustRowsAndCols();
|
|
selection.refreshBorders(); //it will call render and prepare methods
|
|
break;
|
|
|
|
case "remove_col":
|
|
datamap.removeCol(index, amount);
|
|
|
|
for (var row = 0, len = datamap.getAll().length; row < len; row++) {
|
|
if (row in priv.cellSettings) { //if row hasn't been rendered it wouldn't have cellSettings
|
|
priv.cellSettings[row].splice(index, amount);
|
|
}
|
|
}
|
|
|
|
var fixedColumnsLeft = instance.getSettings().fixedColumnsLeft;
|
|
if (fixedColumnsLeft >= index + 1) {
|
|
instance.getSettings().fixedColumnsLeft -= Math.min(amount, fixedColumnsLeft - index);
|
|
}
|
|
|
|
if (Array.isArray(instance.getSettings().colHeaders)) {
|
|
if (typeof index == 'undefined') {
|
|
index = -1;
|
|
}
|
|
instance.getSettings().colHeaders.splice(index, amount);
|
|
}
|
|
|
|
//priv.columnSettings.splice(index, amount);
|
|
|
|
grid.adjustRowsAndCols();
|
|
selection.refreshBorders(); //it will call render and prepare methods
|
|
break;
|
|
|
|
/* jshint ignore:start */
|
|
default:
|
|
throw new Error('There is no such action "' + action + '"');
|
|
break;
|
|
/* jshint ignore:end */
|
|
}
|
|
|
|
if (!keepEmptyRows) {
|
|
grid.adjustRowsAndCols(); //makes sure that we did not add rows that will be removed in next refresh
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Makes sure there are empty rows at the bottom of the table
|
|
*/
|
|
adjustRowsAndCols: function() {
|
|
var r, rlen, emptyRows, emptyCols;
|
|
|
|
//should I add empty rows to data source to meet minRows?
|
|
rlen = instance.countRows();
|
|
if (rlen < priv.settings.minRows) {
|
|
for (r = 0; r < priv.settings.minRows - rlen; r++) {
|
|
datamap.createRow(instance.countRows(), 1, true);
|
|
}
|
|
}
|
|
|
|
emptyRows = instance.countEmptyRows(true);
|
|
|
|
//should I add empty rows to meet minSpareRows?
|
|
if (emptyRows < priv.settings.minSpareRows) {
|
|
for (; emptyRows < priv.settings.minSpareRows && instance.countRows() < priv.settings.maxRows; emptyRows++) {
|
|
datamap.createRow(instance.countRows(), 1, true);
|
|
}
|
|
}
|
|
|
|
//count currently empty cols
|
|
emptyCols = instance.countEmptyCols(true);
|
|
|
|
//should I add empty cols to meet minCols?
|
|
if (!priv.settings.columns && instance.countCols() < priv.settings.minCols) {
|
|
for (; instance.countCols() < priv.settings.minCols; emptyCols++) {
|
|
datamap.createCol(instance.countCols(), 1, true);
|
|
}
|
|
}
|
|
|
|
//should I add empty cols to meet minSpareCols?
|
|
if (!priv.settings.columns && instance.dataType === 'array' && emptyCols < priv.settings.minSpareCols) {
|
|
for (; emptyCols < priv.settings.minSpareCols && instance.countCols() < priv.settings.maxCols; emptyCols++) {
|
|
datamap.createCol(instance.countCols(), 1, true);
|
|
}
|
|
}
|
|
|
|
// if (priv.settings.enterBeginsEditing) {
|
|
// for (; (((priv.settings.minRows || priv.settings.minSpareRows) &&
|
|
// instance.countRows() > priv.settings.minRows) && (priv.settings.minSpareRows && emptyRows > priv.settings.minSpareRows)); emptyRows--) {
|
|
// datamap.removeRow();
|
|
// }
|
|
// }
|
|
|
|
// if (priv.settings.enterBeginsEditing && !priv.settings.columns) {
|
|
// for (; (((priv.settings.minCols || priv.settings.minSpareCols) &&
|
|
// instance.countCols() > priv.settings.minCols) && (priv.settings.minSpareCols && emptyCols > priv.settings.minSpareCols)); emptyCols--) {
|
|
// datamap.removeCol();
|
|
// }
|
|
// }
|
|
|
|
var rowCount = instance.countRows();
|
|
var colCount = instance.countCols();
|
|
|
|
if (rowCount === 0 || colCount === 0) {
|
|
selection.deselect();
|
|
}
|
|
|
|
if (selection.isSelected()) {
|
|
var selectionChanged;
|
|
var fromRow = priv.selRange.from.row;
|
|
var fromCol = priv.selRange.from.col;
|
|
var toRow = priv.selRange.to.row;
|
|
var toCol = priv.selRange.to.col;
|
|
|
|
//if selection is outside, move selection to last row
|
|
if (fromRow > rowCount - 1) {
|
|
fromRow = rowCount - 1;
|
|
selectionChanged = true;
|
|
if (toRow > fromRow) {
|
|
toRow = fromRow;
|
|
}
|
|
} else if (toRow > rowCount - 1) {
|
|
toRow = rowCount - 1;
|
|
selectionChanged = true;
|
|
if (fromRow > toRow) {
|
|
fromRow = toRow;
|
|
}
|
|
}
|
|
|
|
//if selection is outside, move selection to last row
|
|
if (fromCol > colCount - 1) {
|
|
fromCol = colCount - 1;
|
|
selectionChanged = true;
|
|
if (toCol > fromCol) {
|
|
toCol = fromCol;
|
|
}
|
|
} else if (toCol > colCount - 1) {
|
|
toCol = colCount - 1;
|
|
selectionChanged = true;
|
|
if (fromCol > toCol) {
|
|
fromCol = toCol;
|
|
}
|
|
}
|
|
|
|
if (selectionChanged) {
|
|
instance.selectCell(fromRow, fromCol, toRow, toCol);
|
|
}
|
|
}
|
|
if (instance.view) {
|
|
instance.view.wt.wtOverlays.adjustElementsSize();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Populate cells at position with 2d array
|
|
*
|
|
* @memberof Core#
|
|
* @function populateFromArray
|
|
* @param {Object} start Start selection position
|
|
* @param {Array} input 2d array
|
|
* @param {Object} [end] End selection position (only for drag-down mode)
|
|
* @param {String} [source="populateFromArray"]
|
|
* @param {String} [method="overwrite"]
|
|
* @param {String} direction (left|right|up|down)
|
|
* @param {Array} deltas array
|
|
* @returns {Object|undefined} ending td in pasted area (only if any cell was changed)
|
|
*/
|
|
populateFromArray: function(start, input, end, source, method, direction, deltas) {
|
|
var r, rlen, c, clen, setData = [], current = {};
|
|
rlen = input.length;
|
|
if (rlen === 0) {
|
|
return false;
|
|
}
|
|
|
|
var repeatCol
|
|
, repeatRow
|
|
, cmax
|
|
, rmax;
|
|
|
|
// insert data with specified pasteMode method
|
|
switch (method) {
|
|
case 'shift_down' :
|
|
repeatCol = end ? end.col - start.col + 1 : 0;
|
|
repeatRow = end ? end.row - start.row + 1 : 0;
|
|
input = helper.translateRowsToColumns(input);
|
|
for (c = 0, clen = input.length, cmax = Math.max(clen, repeatCol); c < cmax; c++) {
|
|
if (c < clen) {
|
|
for (r = 0, rlen = input[c].length; r < repeatRow - rlen; r++) {
|
|
input[c].push(input[c][r % rlen]);
|
|
}
|
|
input[c].unshift(start.col + c, start.row, 0);
|
|
instance.spliceCol.apply(instance, input[c]);
|
|
}
|
|
else {
|
|
input[c % clen][0] = start.col + c;
|
|
instance.spliceCol.apply(instance, input[c % clen]);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'shift_right' :
|
|
repeatCol = end ? end.col - start.col + 1 : 0;
|
|
repeatRow = end ? end.row - start.row + 1 : 0;
|
|
for (r = 0, rlen = input.length, rmax = Math.max(rlen, repeatRow); r < rmax; r++) {
|
|
if (r < rlen) {
|
|
for (c = 0, clen = input[r].length; c < repeatCol - clen; c++) {
|
|
input[r].push(input[r][c % clen]);
|
|
}
|
|
input[r].unshift(start.row + r, start.col, 0);
|
|
instance.spliceRow.apply(instance, input[r]);
|
|
}
|
|
else {
|
|
input[r % rlen][0] = start.row + r;
|
|
instance.spliceRow.apply(instance, input[r % rlen]);
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* jshint ignore:start */
|
|
case 'overwrite':
|
|
default:
|
|
/* jshint ignore:end */
|
|
// overwrite and other not specified options
|
|
current.row = start.row;
|
|
current.col = start.col;
|
|
|
|
var iterators = {row: 0, col: 0}, // number of packages
|
|
selected = { // selected range
|
|
row: (end && start) ? (end.row - start.row + 1) : 1,
|
|
col: (end && start) ? (end.col - start.col + 1) : 1
|
|
},
|
|
pushData = true;
|
|
|
|
if (['up', 'left'].indexOf(direction) !== -1) {
|
|
iterators = {
|
|
row: Math.ceil(selected.row / rlen) || 1,
|
|
col: Math.ceil(selected.col / input[0].length) || 1
|
|
};
|
|
} else if (['down', 'right'].indexOf(direction) !== -1) {
|
|
iterators = {
|
|
row: 1,
|
|
col: 1
|
|
};
|
|
}
|
|
|
|
|
|
for (r = 0; r < rlen; r++) {
|
|
if ((end && current.row > end.row) || (!priv.settings.allowInsertRow && current.row > instance.countRows() - 1) || (current.row >= priv.settings.maxRows)) {
|
|
break;
|
|
}
|
|
current.col = start.col;
|
|
clen = input[r] ? input[r].length : 0;
|
|
for (c = 0; c < clen; c++) {
|
|
if ((end && current.col > end.col) || (!priv.settings.allowInsertColumn && current.col > instance.countCols() - 1) || (current.col >= priv.settings.maxCols)) {
|
|
break;
|
|
}
|
|
|
|
if (!instance.getCellMeta(current.row, current.col).readOnly) {
|
|
var result,
|
|
value = input[r][c],
|
|
orgValue = instance.getDataAtCell(current.row, current.col),
|
|
index = {
|
|
row: r,
|
|
col: c
|
|
},
|
|
valueSchema,
|
|
orgValueSchema;
|
|
|
|
if (source === 'autofill') {
|
|
result = instance.runHooks('beforeAutofillInsidePopulate', index, direction, input, deltas, iterators, selected);
|
|
|
|
if (result) {
|
|
iterators = typeof(result.iterators) !== 'undefined' ? result.iterators : iterators;
|
|
value = typeof(result.value) !== 'undefined' ? result.value : value;
|
|
}
|
|
}
|
|
if (value !== null && typeof value === 'object') {
|
|
if (orgValue === null || typeof orgValue !== 'object') {
|
|
pushData = false;
|
|
|
|
} else {
|
|
orgValueSchema = Handsontable.helper.duckSchema(orgValue[0] || orgValue);
|
|
valueSchema = Handsontable.helper.duckSchema(value[0] || value);
|
|
|
|
/* jshint -W073 */
|
|
if (Handsontable.helper.isObjectEquals(orgValueSchema, valueSchema)) {
|
|
value = Handsontable.helper.deepClone(value);
|
|
} else {
|
|
pushData = false;
|
|
}
|
|
}
|
|
|
|
} else if (orgValue !== null && typeof orgValue === 'object') {
|
|
pushData = false;
|
|
}
|
|
if (pushData) {
|
|
setData.push([current.row, current.col, value]);
|
|
}
|
|
pushData = true;
|
|
}
|
|
|
|
current.col++;
|
|
|
|
if (end && c === clen - 1) {
|
|
c = -1;
|
|
|
|
if (['down', 'right'].indexOf(direction) !== -1) {
|
|
iterators.col++;
|
|
} else if (['up', 'left'].indexOf(direction) !== -1) {
|
|
if (iterators.col > 1) {
|
|
iterators.col--;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
current.row++;
|
|
iterators.col = 1;
|
|
|
|
if (end && r === rlen - 1) {
|
|
r = -1;
|
|
|
|
if (['down', 'right'].indexOf(direction) !== -1) {
|
|
iterators.row++;
|
|
} else if (['up', 'left'].indexOf(direction) !== -1) {
|
|
if (iterators.row > 1) {
|
|
iterators.row--;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
instance.setDataAtCell(setData, null, null, source || 'populateFromArray');
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
this.selection = selection = { //this public assignment is only temporary
|
|
inProgress: false,
|
|
|
|
selectedHeader: {
|
|
cols: false,
|
|
rows: false
|
|
},
|
|
|
|
/**
|
|
* @param {Number} rows
|
|
* @param {Number} cols
|
|
*/
|
|
setSelectedHeaders: function(rows, cols) {
|
|
instance.selection.selectedHeader.rows = rows;
|
|
instance.selection.selectedHeader.cols = cols;
|
|
},
|
|
|
|
/**
|
|
* Sets inProgress to `true`. This enables onSelectionEnd and onSelectionEndByProp to function as desired.
|
|
*/
|
|
begin: function() {
|
|
instance.selection.inProgress = true;
|
|
},
|
|
|
|
/**
|
|
* Sets inProgress to `false`. Triggers onSelectionEnd and onSelectionEndByProp.
|
|
*/
|
|
finish: function() {
|
|
var sel = instance.getSelected();
|
|
Handsontable.hooks.run(instance, "afterSelectionEnd", sel[0], sel[1], sel[2], sel[3]);
|
|
Handsontable.hooks.run(instance, "afterSelectionEndByProp", sel[0], instance.colToProp(sel[1]), sel[2], instance.colToProp(sel[3]));
|
|
instance.selection.inProgress = false;
|
|
},
|
|
|
|
/**
|
|
* @returns {Boolean}
|
|
*/
|
|
isInProgress: function() {
|
|
return instance.selection.inProgress;
|
|
},
|
|
|
|
/**
|
|
* Starts selection range on given td object.
|
|
*
|
|
* @param {WalkontableCellCoords} coords
|
|
* @param keepEditorOpened
|
|
*/
|
|
setRangeStart: function(coords, keepEditorOpened) {
|
|
Handsontable.hooks.run(instance, "beforeSetRangeStart", coords);
|
|
priv.selRange = new WalkontableCellRange(coords, coords, coords);
|
|
selection.setRangeEnd(coords, null, keepEditorOpened);
|
|
},
|
|
|
|
/**
|
|
* Ends selection range on given td object.
|
|
*
|
|
* @param {WalkontableCellCoords} coords
|
|
* @param {Boolean} [scrollToCell=true] If `true`, viewport will be scrolled to range end
|
|
* @param {Boolean} [keepEditorOpened] If `true`, cell editor will be still opened after changing selection range
|
|
*/
|
|
setRangeEnd: function(coords, scrollToCell, keepEditorOpened) {
|
|
if (priv.selRange === null) {
|
|
return;
|
|
}
|
|
var disableVisualSelection;
|
|
|
|
//trigger handlers
|
|
Handsontable.hooks.run(instance, "beforeSetRangeEnd", coords);
|
|
instance.selection.begin();
|
|
priv.selRange.to = new WalkontableCellCoords(coords.row, coords.col);
|
|
|
|
if (!priv.settings.multiSelect) {
|
|
priv.selRange.from = coords;
|
|
}
|
|
// set up current selection
|
|
instance.view.wt.selections.current.clear();
|
|
|
|
disableVisualSelection = instance.getCellMeta(priv.selRange.highlight.row, priv.selRange.highlight.col).disableVisualSelection;
|
|
|
|
if (typeof disableVisualSelection === 'string') {
|
|
disableVisualSelection = [disableVisualSelection];
|
|
}
|
|
|
|
if (disableVisualSelection === false ||
|
|
Array.isArray(disableVisualSelection) && disableVisualSelection.indexOf('current') === -1) {
|
|
instance.view.wt.selections.current.add(priv.selRange.highlight);
|
|
}
|
|
// set up area selection
|
|
instance.view.wt.selections.area.clear();
|
|
|
|
if ((disableVisualSelection === false ||
|
|
Array.isArray(disableVisualSelection) && disableVisualSelection.indexOf('area') === -1) &&
|
|
selection.isMultiple()) {
|
|
instance.view.wt.selections.area.add(priv.selRange.from);
|
|
instance.view.wt.selections.area.add(priv.selRange.to);
|
|
}
|
|
// set up highlight
|
|
if (priv.settings.currentRowClassName || priv.settings.currentColClassName) {
|
|
instance.view.wt.selections.highlight.clear();
|
|
instance.view.wt.selections.highlight.add(priv.selRange.from);
|
|
instance.view.wt.selections.highlight.add(priv.selRange.to);
|
|
}
|
|
|
|
// trigger handlers
|
|
Handsontable.hooks.run(instance, "afterSelection",
|
|
priv.selRange.from.row, priv.selRange.from.col, priv.selRange.to.row, priv.selRange.to.col);
|
|
Handsontable.hooks.run(instance, "afterSelectionByProp",
|
|
priv.selRange.from.row, datamap.colToProp(priv.selRange.from.col), priv.selRange.to.row, datamap.colToProp(priv.selRange.to.col));
|
|
|
|
if (scrollToCell !== false && instance.view.mainViewIsActive()) {
|
|
if (priv.selRange.from && !selection.isMultiple()) {
|
|
instance.view.scrollViewport(priv.selRange.from);
|
|
} else {
|
|
instance.view.scrollViewport(coords);
|
|
}
|
|
}
|
|
selection.refreshBorders(null, keepEditorOpened);
|
|
},
|
|
|
|
/**
|
|
* Destroys editor, redraws borders around cells, prepares editor.
|
|
*
|
|
* @param {Boolean} [revertOriginal]
|
|
* @param {Boolean} [keepEditor]
|
|
*/
|
|
refreshBorders: function(revertOriginal, keepEditor) {
|
|
if (!keepEditor) {
|
|
editorManager.destroyEditor(revertOriginal);
|
|
}
|
|
instance.view.render();
|
|
|
|
if (selection.isSelected() && !keepEditor) {
|
|
editorManager.prepareEditor();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns information if we have a multiselection.
|
|
*
|
|
* @returns {Boolean}
|
|
*/
|
|
isMultiple: function() {
|
|
var isMultiple = !(priv.selRange.to.col === priv.selRange.from.col && priv.selRange.to.row === priv.selRange.from.row)
|
|
, modifier = Handsontable.hooks.run(instance, 'afterIsMultipleSelection', isMultiple);
|
|
|
|
if (isMultiple) {
|
|
return modifier;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Selects cell relative to current cell (if possible).
|
|
*/
|
|
transformStart: function(rowDelta, colDelta, force, keepEditorOpened) {
|
|
var delta = new WalkontableCellCoords(rowDelta, colDelta),
|
|
rowTransformDir = 0,
|
|
colTransformDir = 0,
|
|
totalRows,
|
|
totalCols,
|
|
coords;
|
|
|
|
instance.runHooks('modifyTransformStart', delta);
|
|
totalRows = instance.countRows();
|
|
totalCols = instance.countCols();
|
|
|
|
/* jshint ignore:start */
|
|
if (priv.selRange.highlight.row + rowDelta > totalRows - 1) {
|
|
if (force && priv.settings.minSpareRows > 0) {
|
|
instance.alter("insert_row", totalRows);
|
|
totalRows = instance.countRows();
|
|
|
|
} else if (priv.settings.autoWrapCol) {
|
|
delta.row = 1 - totalRows;
|
|
delta.col = priv.selRange.highlight.col + delta.col == totalCols - 1 ? 1 - totalCols : 1;
|
|
}
|
|
} else if (priv.settings.autoWrapCol && priv.selRange.highlight.row + delta.row < 0 && priv.selRange.highlight.col + delta.col >= 0) {
|
|
delta.row = totalRows - 1;
|
|
delta.col = priv.selRange.highlight.col + delta.col == 0 ? totalCols - 1 : -1;
|
|
}
|
|
|
|
if (priv.selRange.highlight.col + delta.col > totalCols - 1) {
|
|
if (force && priv.settings.minSpareCols > 0) {
|
|
instance.alter("insert_col", totalCols);
|
|
totalCols = instance.countCols();
|
|
|
|
} else if (priv.settings.autoWrapRow) {
|
|
delta.row = priv.selRange.highlight.row + delta.row == totalRows - 1 ? 1 - totalRows : 1;
|
|
delta.col = 1 - totalCols;
|
|
}
|
|
} else if (priv.settings.autoWrapRow && priv.selRange.highlight.col + delta.col < 0 && priv.selRange.highlight.row + delta.row >= 0) {
|
|
delta.row = priv.selRange.highlight.row + delta.row == 0 ? totalRows - 1 : -1;
|
|
delta.col = totalCols - 1;
|
|
}
|
|
/* jshint ignore:end */
|
|
|
|
coords = new WalkontableCellCoords(priv.selRange.highlight.row + delta.row, priv.selRange.highlight.col + delta.col);
|
|
|
|
if (coords.row < 0) {
|
|
rowTransformDir = -1;
|
|
coords.row = 0;
|
|
|
|
} else if (coords.row > 0 && coords.row >= totalRows) {
|
|
rowTransformDir = 1;
|
|
coords.row = totalRows - 1;
|
|
}
|
|
|
|
if (coords.col < 0) {
|
|
colTransformDir = -1;
|
|
coords.col = 0;
|
|
|
|
} else if (coords.col > 0 && coords.col >= totalCols) {
|
|
colTransformDir = 1;
|
|
coords.col = totalCols - 1;
|
|
}
|
|
instance.runHooks('afterModifyTransformStart', coords, rowTransformDir, colTransformDir);
|
|
selection.setRangeStart(coords, keepEditorOpened);
|
|
},
|
|
|
|
/**
|
|
* Sets selection end cell relative to current selection end cell (if possible).
|
|
*/
|
|
transformEnd: function(rowDelta, colDelta) {
|
|
var delta = new WalkontableCellCoords(rowDelta, colDelta),
|
|
rowTransformDir = 0,
|
|
colTransformDir = 0,
|
|
totalRows,
|
|
totalCols,
|
|
coords;
|
|
|
|
instance.runHooks('modifyTransformEnd', delta);
|
|
|
|
totalRows = instance.countRows();
|
|
totalCols = instance.countCols();
|
|
coords = new WalkontableCellCoords(priv.selRange.to.row + delta.row, priv.selRange.to.col + delta.col);
|
|
|
|
if (coords.row < 0) {
|
|
rowTransformDir = -1;
|
|
coords.row = 0;
|
|
|
|
} else if (coords.row > 0 && coords.row >= totalRows) {
|
|
rowTransformDir = 1;
|
|
coords.row = totalRows - 1;
|
|
}
|
|
|
|
if (coords.col < 0) {
|
|
colTransformDir = -1;
|
|
coords.col = 0;
|
|
|
|
} else if (coords.col > 0 && coords.col >= totalCols) {
|
|
colTransformDir = 1;
|
|
coords.col = totalCols - 1;
|
|
}
|
|
instance.runHooks('afterModifyTransformEnd', coords, rowTransformDir, colTransformDir);
|
|
selection.setRangeEnd(coords, true);
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if currently there is a selection on screen, `false` otherwise.
|
|
*
|
|
* @returns {Boolean}
|
|
*/
|
|
isSelected: function() {
|
|
return (priv.selRange !== null);
|
|
},
|
|
|
|
/**
|
|
* Returns `true` if coords is within current selection coords.
|
|
*
|
|
* @param {WalkontableCellCoords} coords
|
|
* @returns {Boolean}
|
|
*/
|
|
inInSelection: function(coords) {
|
|
if (!selection.isSelected()) {
|
|
return false;
|
|
}
|
|
return priv.selRange.includes(coords);
|
|
},
|
|
|
|
/**
|
|
* Deselects all selected cells
|
|
*/
|
|
deselect: function() {
|
|
if (!selection.isSelected()) {
|
|
return;
|
|
}
|
|
instance.selection.inProgress = false; //needed by HT inception
|
|
priv.selRange = null;
|
|
instance.view.wt.selections.current.clear();
|
|
instance.view.wt.selections.area.clear();
|
|
if (priv.settings.currentRowClassName || priv.settings.currentColClassName) {
|
|
instance.view.wt.selections.highlight.clear();
|
|
}
|
|
editorManager.destroyEditor();
|
|
selection.refreshBorders();
|
|
Handsontable.hooks.run(instance, 'afterDeselect');
|
|
},
|
|
|
|
/**
|
|
* Select all cells
|
|
*/
|
|
selectAll: function() {
|
|
if (!priv.settings.multiSelect) {
|
|
return;
|
|
}
|
|
selection.setRangeStart(new WalkontableCellCoords(0, 0));
|
|
selection.setRangeEnd(new WalkontableCellCoords(instance.countRows() - 1, instance.countCols() - 1), false);
|
|
},
|
|
|
|
/**
|
|
* Deletes data from selected cells
|
|
*/
|
|
empty: function() {
|
|
if (!selection.isSelected()) {
|
|
return;
|
|
}
|
|
var topLeft = priv.selRange.getTopLeftCorner();
|
|
var bottomRight = priv.selRange.getBottomRightCorner();
|
|
var r, c, changes = [];
|
|
for (r = topLeft.row; r <= bottomRight.row; r++) {
|
|
for (c = topLeft.col; c <= bottomRight.col; c++) {
|
|
if (!instance.getCellMeta(r, c).readOnly) {
|
|
changes.push([r, c, '']);
|
|
}
|
|
}
|
|
}
|
|
instance.setDataAtCell(changes);
|
|
}
|
|
};
|
|
|
|
this.init = function() {
|
|
Handsontable.hooks.run(instance, 'beforeInit');
|
|
|
|
if (Handsontable.mobileBrowser) {
|
|
dom.addClass(instance.rootElement, 'mobile');
|
|
}
|
|
|
|
this.updateSettings(priv.settings, true);
|
|
|
|
this.view = new TableView(this);
|
|
editorManager = new EditorManager(instance, priv, selection, datamap);
|
|
|
|
this.forceFullRender = true; //used when data was changed
|
|
this.view.render();
|
|
|
|
if (typeof priv.firstRun === 'object') {
|
|
Handsontable.hooks.run(instance, 'afterChange', priv.firstRun[0], priv.firstRun[1]);
|
|
priv.firstRun = false;
|
|
}
|
|
Handsontable.hooks.run(instance, 'afterInit');
|
|
};
|
|
|
|
function ValidatorsQueue() { //moved this one level up so it can be used in any function here. Probably this should be moved to a separate file
|
|
var resolved = false;
|
|
|
|
return {
|
|
validatorsInQueue: 0,
|
|
addValidatorToQueue: function() {
|
|
this.validatorsInQueue++;
|
|
resolved = false;
|
|
},
|
|
removeValidatorFormQueue: function() {
|
|
this.validatorsInQueue = this.validatorsInQueue - 1 < 0 ? 0 : this.validatorsInQueue - 1;
|
|
this.checkIfQueueIsEmpty();
|
|
},
|
|
onQueueEmpty: function() {
|
|
},
|
|
checkIfQueueIsEmpty: function() {
|
|
/* jshint ignore:start */
|
|
if (this.validatorsInQueue == 0 && resolved == false) {
|
|
resolved = true;
|
|
this.onQueueEmpty();
|
|
}
|
|
/* jshint ignore:end */
|
|
}
|
|
};
|
|
}
|
|
|
|
function validateChanges(changes, source, callback) {
|
|
var waitingForValidator = new ValidatorsQueue();
|
|
waitingForValidator.onQueueEmpty = resolve;
|
|
|
|
for (var i = changes.length - 1; i >= 0; i--) {
|
|
if (changes[i] === null) {
|
|
changes.splice(i, 1);
|
|
}
|
|
else {
|
|
var row = changes[i][0];
|
|
var col = datamap.propToCol(changes[i][1]);
|
|
//column order may have changes, so we need to translate physical col index (stored in datasource) to logical (displayed to user)
|
|
var logicalCol = instance.runHooks('modifyCol', col);
|
|
var cellProperties = instance.getCellMeta(row, logicalCol);
|
|
|
|
if (cellProperties.type === 'numeric' && typeof changes[i][3] === 'string') {
|
|
if (changes[i][3].length > 0 && (/^-?[\d\s]*(\.|\,)?\d*$/.test(changes[i][3]) || cellProperties.format )) {
|
|
var len = changes[i][3].length;
|
|
if (typeof cellProperties.language == 'undefined') {
|
|
numeral.language('en');
|
|
}
|
|
//this input in format XXXX.XX is likely to come from paste. Let's parse it using international rules
|
|
else if (changes[i][3].indexOf(".") === len - 3 && changes[i][3].indexOf(",") === -1) {
|
|
numeral.language('en');
|
|
}
|
|
else {
|
|
numeral.language(cellProperties.language);
|
|
}
|
|
if (numeral.validate(changes[i][3])) {
|
|
changes[i][3] = numeral().unformat(changes[i][3]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* jshint ignore:start */
|
|
if (instance.getCellValidator(cellProperties)) {
|
|
waitingForValidator.addValidatorToQueue();
|
|
instance.validateCell(changes[i][3], cellProperties, (function(i, cellProperties) {
|
|
return function(result) {
|
|
if (typeof result !== 'boolean') {
|
|
throw new Error("Validation error: result is not boolean");
|
|
}
|
|
if (result === false && cellProperties.allowInvalid === false) {
|
|
changes.splice(i, 1); // cancel the change
|
|
cellProperties.valid = true; // we cancelled the change, so cell value is still valid
|
|
--i;
|
|
}
|
|
waitingForValidator.removeValidatorFormQueue();
|
|
};
|
|
})(i, cellProperties)
|
|
, source);
|
|
}
|
|
/* jshint ignore:end */
|
|
}
|
|
}
|
|
waitingForValidator.checkIfQueueIsEmpty();
|
|
|
|
function resolve() {
|
|
var beforeChangeResult;
|
|
|
|
if (changes.length) {
|
|
beforeChangeResult = Handsontable.hooks.run(instance, "beforeChange", changes, source);
|
|
if (typeof beforeChangeResult === 'function') {
|
|
console.warn("Your beforeChange callback returns a function. It's not supported since Handsontable 0.12.1 (and the returned function will not be executed).");
|
|
} else if (beforeChangeResult === false) {
|
|
changes.splice(0, changes.length); //invalidate all changes (remove everything from array)
|
|
}
|
|
}
|
|
callback(); //called when async validators are resolved and beforeChange was not async
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal function to apply changes. Called after validateChanges
|
|
*
|
|
* @private
|
|
* @param {Array} changes Array in form of [row, prop, oldValue, newValue]
|
|
* @param {String} source String that identifies how this change will be described in changes array (useful in onChange callback)
|
|
* @fires Hooks#beforeChangeRender
|
|
* @fires Hooks#afterChange
|
|
*/
|
|
function applyChanges(changes, source) {
|
|
var i = changes.length - 1;
|
|
|
|
if (i < 0) {
|
|
return;
|
|
}
|
|
|
|
for (; 0 <= i; i--) {
|
|
if (changes[i] === null) {
|
|
changes.splice(i, 1);
|
|
continue;
|
|
}
|
|
|
|
if (changes[i][2] == null && changes[i][3] == null) {
|
|
continue;
|
|
}
|
|
|
|
if (priv.settings.allowInsertRow) {
|
|
while (changes[i][0] > instance.countRows() - 1) {
|
|
datamap.createRow();
|
|
}
|
|
}
|
|
|
|
if (instance.dataType === 'array' && priv.settings.allowInsertColumn) {
|
|
while (datamap.propToCol(changes[i][1]) > instance.countCols() - 1) {
|
|
datamap.createCol();
|
|
}
|
|
}
|
|
|
|
datamap.set(changes[i][0], changes[i][1], changes[i][3]);
|
|
}
|
|
|
|
instance.forceFullRender = true; //used when data was changed
|
|
grid.adjustRowsAndCols();
|
|
Handsontable.hooks.run(instance, 'beforeChangeRender', changes, source);
|
|
selection.refreshBorders(null, true);
|
|
Handsontable.hooks.run(instance, 'afterChange', changes, source || 'edit');
|
|
}
|
|
|
|
this.validateCell = function(value, cellProperties, callback, source) {
|
|
var validator = instance.getCellValidator(cellProperties);
|
|
|
|
function done(valid) {
|
|
var col = cellProperties.col,
|
|
row = cellProperties.row,
|
|
td = instance.getCell(row, col, true);
|
|
|
|
if (td) {
|
|
instance.view.wt.wtSettings.settings.cellRenderer(row, col, td);
|
|
}
|
|
callback(valid);
|
|
}
|
|
|
|
if (Object.prototype.toString.call(validator) === '[object RegExp]') {
|
|
validator = (function(validator) {
|
|
return function(value, callback) {
|
|
callback(validator.test(value));
|
|
};
|
|
})(validator);
|
|
}
|
|
|
|
if (typeof validator == 'function') {
|
|
|
|
value = Handsontable.hooks.run(instance, "beforeValidate", value, cellProperties.row, cellProperties.prop, source);
|
|
|
|
// To provide consistent behaviour, validation should be always asynchronous
|
|
instance._registerTimeout(setTimeout(function() {
|
|
validator.call(cellProperties, value, function(valid) {
|
|
valid = Handsontable.hooks.run(instance, "afterValidate", valid, value, cellProperties.row, cellProperties.prop, source);
|
|
cellProperties.valid = valid;
|
|
|
|
done(valid);
|
|
Handsontable.hooks.run(instance, "postAfterValidate", valid, value, cellProperties.row, cellProperties.prop, source);
|
|
});
|
|
}, 0));
|
|
|
|
} else {
|
|
//resolve callback even if validator function was not found
|
|
cellProperties.valid = true;
|
|
done(cellProperties.valid);
|
|
}
|
|
};
|
|
|
|
function setDataInputToArray(row, propOrCol, value) {
|
|
if (typeof row === "object") { //is it an array of changes
|
|
return row;
|
|
}
|
|
else {
|
|
return [
|
|
[row, propOrCol, value]
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @description
|
|
* Set new value to a cell. To change many cells at once, pass an array of `changes` in format `[[row, col, value], ...]` as
|
|
* the only parameter. `col` is the index of __visible__ column (note that if columns were reordered,
|
|
* the current order will be used). `source` is a flag for before/afterChange events. If you pass only array of
|
|
* changes then `source` could be set as second parameter.
|
|
*
|
|
* @memberof Core#
|
|
* @function setDataAtCell
|
|
* @param {Number|Array} row or array of changes in format `[[row, col, value], ...]`
|
|
* @param {Number|String} col or source String
|
|
* @param {String} value
|
|
* @param {String} [source] String that identifies how this change will be described in changes array (useful in onChange callback)
|
|
*/
|
|
this.setDataAtCell = function(row, col, value, source) {
|
|
var input = setDataInputToArray(row, col, value)
|
|
, i
|
|
, ilen
|
|
, changes = []
|
|
, prop;
|
|
|
|
for (i = 0, ilen = input.length; i < ilen; i++) {
|
|
if (typeof input[i] !== 'object') {
|
|
throw new Error('Method `setDataAtCell` accepts row number or changes array of arrays as its first parameter');
|
|
}
|
|
if (typeof input[i][1] !== 'number') {
|
|
throw new Error('Method `setDataAtCell` accepts row and column number as its parameters. If you want to use object property name, use method `setDataAtRowProp`');
|
|
}
|
|
prop = datamap.colToProp(input[i][1]);
|
|
changes.push([
|
|
input[i][0],
|
|
prop,
|
|
datamap.get(input[i][0], prop),
|
|
input[i][2]
|
|
]);
|
|
}
|
|
|
|
if (!source && typeof row === "object") {
|
|
source = col;
|
|
}
|
|
|
|
validateChanges(changes, source, function() {
|
|
applyChanges(changes, source);
|
|
});
|
|
};
|
|
|
|
|
|
/**
|
|
* Same as above, except instead of `col`, you provide name of the object property (e.g. `[0, 'first.name', 'Jennifer']`).
|
|
*
|
|
* @memberof Core#
|
|
* @function setDataAtRowProp
|
|
* @param {Number|Array} row or array of changes in format `[[row, prop, value], ...]`
|
|
* @param {String} prop or source String
|
|
* @param {String} value
|
|
* @param {String} [source] String that identifies how this change will be described in changes array (useful in onChange callback)
|
|
*/
|
|
this.setDataAtRowProp = function(row, prop, value, source) {
|
|
var input = setDataInputToArray(row, prop, value)
|
|
, i
|
|
, ilen
|
|
, changes = [];
|
|
|
|
for (i = 0, ilen = input.length; i < ilen; i++) {
|
|
changes.push([
|
|
input[i][0],
|
|
input[i][1],
|
|
datamap.get(input[i][0], input[i][1]),
|
|
input[i][2]
|
|
]);
|
|
}
|
|
|
|
if (!source && typeof row === "object") {
|
|
source = prop;
|
|
}
|
|
|
|
validateChanges(changes, source, function() {
|
|
applyChanges(changes, source);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Listen to keyboard input on document body.
|
|
*
|
|
* @memberof Core#
|
|
* @function listen
|
|
* @since 0.11
|
|
*/
|
|
this.listen = function() {
|
|
Handsontable.activeGuid = instance.guid;
|
|
|
|
if (document.activeElement && document.activeElement !== document.body) {
|
|
document.activeElement.blur();
|
|
}
|
|
else if (!document.activeElement) { //IE
|
|
document.body.focus();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Stop listening to keyboard input on document body.
|
|
*
|
|
* @memberof Core#
|
|
* @function unlisten
|
|
* @since 0.11
|
|
*/
|
|
this.unlisten = function() {
|
|
Handsontable.activeGuid = null;
|
|
};
|
|
|
|
/**
|
|
* Returns `true` if current Handsontable instance is listening to keyboard input on document body.
|
|
*
|
|
* @memberof Core#
|
|
* @function isListening
|
|
* @since 0.11
|
|
* @returns {Boolean}
|
|
*/
|
|
this.isListening = function() {
|
|
return Handsontable.activeGuid === instance.guid;
|
|
};
|
|
|
|
/**
|
|
* Destroys current editor, renders and selects current cell.
|
|
*
|
|
* @memberof Core#
|
|
* @function destroyEditor
|
|
* @param {Boolean} [revertOriginal] If != `true`, edited data is saved. Otherwise previous value is restored
|
|
*/
|
|
this.destroyEditor = function(revertOriginal) {
|
|
selection.refreshBorders(revertOriginal);
|
|
};
|
|
|
|
/**
|
|
* Populate cells at position with 2D input array (e.g. `[[1, 2], [3, 4]]`).
|
|
* Use `endRow`, `endCol` when you want to cut input when certain row is reached.
|
|
* Optional `source` parameter (default value "populateFromArray") is used to identify this call in the resulting events (beforeChange, afterChange).
|
|
* Optional `populateMethod` parameter (default value "overwrite", possible values "shift_down" and "shift_right")
|
|
* has the same effect as pasteMethod option (see Options page)
|
|
*
|
|
* @memberof Core#
|
|
* @function populateFromArray
|
|
* @since 0.9.0
|
|
* @param {Number} row Start row
|
|
* @param {Number} col Start column
|
|
* @param {Array} input 2d array
|
|
* @param {Number} [endRow] End row (use when you want to cut input when certain row is reached)
|
|
* @param {Number} [endCol] End column (use when you want to cut input when certain column is reached)
|
|
* @param {String} [source="populateFromArray"]
|
|
* @param {String} [method="overwrite"]
|
|
* @param {String} direction edit (left|right|up|down)
|
|
* @param {Array} deltas array
|
|
* @returns {Object|undefined} ending td in pasted area (only if any cell was changed)
|
|
*/
|
|
this.populateFromArray = function(row, col, input, endRow, endCol, source, method, direction, deltas) {
|
|
var c;
|
|
|
|
if (!(typeof input === 'object' && typeof input[0] === 'object')) {
|
|
throw new Error("populateFromArray parameter `input` must be an array of arrays"); //API changed in 0.9-beta2, let's check if you use it correctly
|
|
}
|
|
c = typeof endRow === 'number' ? new WalkontableCellCoords(endRow, endCol) : null;
|
|
|
|
return grid.populateFromArray(new WalkontableCellCoords(row, col), input, c, source, method, direction, deltas);
|
|
};
|
|
|
|
/**
|
|
* Adds/removes data from the column. This function works is modelled after Array.splice.
|
|
* Parameter `col` is the index of column in which do you want to do splice.
|
|
* Parameter `index` is the row index at which to start changing the array.
|
|
* If negative, will begin that many elements from the end. Parameter `amount`, is the number of old array elements to remove.
|
|
* If the amount is 0, no elements are removed. Fourth and further parameters are the `elements` to add to the array.
|
|
* If you don't specify any elements, spliceCol simply removes elements from the array.
|
|
* {@link DataMap#spliceCol}
|
|
*
|
|
* @memberof Core#
|
|
* @function spliceCol
|
|
* @since 0.9-beta2
|
|
* @param {Number} col Index of column in which do you want to do splice.
|
|
* @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
|
|
* @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
|
|
* @param {*} [elements] The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
|
|
*/
|
|
this.spliceCol = function(col, index, amount/*, elements... */) {
|
|
return datamap.spliceCol.apply(datamap, arguments);
|
|
};
|
|
|
|
/**
|
|
* Adds/removes data from the row. This function works is modelled after Array.splice.
|
|
* Parameter `row` is the index of row in which do you want to do splice.
|
|
* Parameter `index` is the column index at which to start changing the array.
|
|
* If negative, will begin that many elements from the end. Parameter `amount`, is the number of old array elements to remove.
|
|
* If the amount is 0, no elements are removed. Fourth and further parameters are the `elements` to add to the array.
|
|
* If you don't specify any elements, spliceCol simply removes elements from the array.
|
|
* {@link DataMap#spliceRow}
|
|
*
|
|
* @memberof Core#
|
|
* @function spliceRow
|
|
* @since 0.11
|
|
* @param {Number} row Index of column in which do you want to do splice.
|
|
* @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
|
|
* @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
|
|
* @param {*} [elements] The elements to add to the array. If you don't specify any elements, spliceCol simply removes elements from the array
|
|
*/
|
|
this.spliceRow = function(row, index, amount/*, elements... */) {
|
|
return datamap.spliceRow.apply(datamap, arguments);
|
|
};
|
|
|
|
/**
|
|
* Return index of the currently selected cells as an array `[startRow, startCol, endRow, endCol]`.
|
|
*
|
|
* Start row and start col are the coordinates of the active cell (where the selection was started).
|
|
*
|
|
* @memberof Core#
|
|
* @function getSelected
|
|
* @returns {Array}
|
|
*/
|
|
this.getSelected = function() { //https://github.com/handsontable/handsontable/issues/44 //cjl
|
|
if (selection.isSelected()) {
|
|
return [priv.selRange.from.row, priv.selRange.from.col, priv.selRange.to.row, priv.selRange.to.col];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns current selection as a WalkontableCellRange object.
|
|
*
|
|
* @memberof Core#
|
|
* @function getSelectedRange
|
|
* @since 0.11
|
|
* @returns {WalkontableCellRange} Returns `undefined` if there is no selection.
|
|
*/
|
|
this.getSelectedRange = function() { //https://github.com/handsontable/handsontable/issues/44 //cjl
|
|
if (selection.isSelected()) {
|
|
return priv.selRange;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Rerender the table.
|
|
*
|
|
* @memberof Core#
|
|
* @function render
|
|
*/
|
|
this.render = function() {
|
|
if (instance.view) {
|
|
instance.forceFullRender = true; //used when data was changed
|
|
selection.refreshBorders(null, true);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Reset all cells in the grid to contain data from the data array.
|
|
*
|
|
* @memberof Core#
|
|
* @function loadData
|
|
* @param {Array} data
|
|
* @fires Hooks#afterLoadData
|
|
* @fires Hooks#afterChange
|
|
*/
|
|
this.loadData = function(data) {
|
|
if (typeof data === 'object' && data !== null) {
|
|
if (!(data.push && data.splice)) { //check if data is array. Must use duck-type check so Backbone Collections also pass it
|
|
//when data is not an array, attempt to make a single-row array of it
|
|
data = [data];
|
|
}
|
|
}
|
|
else if (data === null) {
|
|
data = [];
|
|
var row;
|
|
for (var r = 0, rlen = priv.settings.startRows; r < rlen; r++) {
|
|
row = [];
|
|
for (var c = 0, clen = priv.settings.startCols; c < clen; c++) {
|
|
row.push(null);
|
|
}
|
|
data.push(row);
|
|
}
|
|
}
|
|
else {
|
|
throw new Error("loadData only accepts array of objects or array of arrays (" + typeof data + " given)");
|
|
}
|
|
|
|
priv.isPopulated = false;
|
|
GridSettings.prototype.data = data;
|
|
|
|
if (Array.isArray(priv.settings.dataSchema) || Array.isArray(data[0])) {
|
|
instance.dataType = 'array';
|
|
}
|
|
else if (typeof priv.settings.dataSchema === 'function') {
|
|
instance.dataType = 'function';
|
|
}
|
|
else {
|
|
instance.dataType = 'object';
|
|
}
|
|
|
|
datamap = new DataMap(instance, priv, GridSettings);
|
|
|
|
clearCellSettingCache();
|
|
|
|
grid.adjustRowsAndCols();
|
|
Handsontable.hooks.run(instance, 'afterLoadData');
|
|
|
|
if (priv.firstRun) {
|
|
priv.firstRun = [null, 'loadData'];
|
|
}
|
|
else {
|
|
Handsontable.hooks.run(instance, 'afterChange', null, 'loadData');
|
|
instance.render();
|
|
}
|
|
|
|
priv.isPopulated = true;
|
|
|
|
|
|
function clearCellSettingCache() {
|
|
priv.cellSettings.length = 0;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Return the current data object (the same that was passed by `data` configuration option or `loadData` method).
|
|
* Optionally you can provide cell range `row`, `col`, `row2`, `col2` to get only a fragment of grid data.
|
|
*
|
|
* @memberof Core#
|
|
* @function getData
|
|
* @param {Number} [r] From row
|
|
* @param {Number} [c] From col
|
|
* @param {Number} [r2] To row
|
|
* @param {Number} [c2] To col
|
|
* @returns {Array|Object}
|
|
*/
|
|
this.getData = function(r, c, r2, c2) {
|
|
if (typeof r === 'undefined') {
|
|
return datamap.getAll();
|
|
} else {
|
|
return datamap.getRange(new WalkontableCellCoords(r, c), new WalkontableCellCoords(r2, c2), datamap.DESTINATION_RENDERER);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get value of selected range. Each column is separated by tab, each row is separated by new line character.
|
|
* {@link DataMap#getCopyableText}
|
|
*
|
|
* @memberof Core#
|
|
* @function getCopyableData
|
|
* @since 0.11
|
|
* @param {Number} startRow From row
|
|
* @param {Number} startCol From col
|
|
* @param {Number} endRow To row
|
|
* @param {Number} endCol To col
|
|
* @returns {Array|Object}
|
|
*/
|
|
this.getCopyableData = function(startRow, startCol, endRow, endCol) {
|
|
return datamap.getCopyableText(new WalkontableCellCoords(startRow, startCol), new WalkontableCellCoords(endRow, endCol));
|
|
};
|
|
|
|
/**
|
|
* Get schema provided by constructor settings or if it doesn't exist return schema based on data
|
|
* structure on the first row.
|
|
*
|
|
* @memberof Core#
|
|
* @function getSchema
|
|
* @since 0.13.2
|
|
* @returns {Object}
|
|
*/
|
|
this.getSchema = function() {
|
|
return datamap.getSchema();
|
|
};
|
|
|
|
/**
|
|
* Use it if you need to change configuration after initialization.
|
|
*
|
|
* @memberof Core#
|
|
* @function updateSettings
|
|
* @param {Object} settings Settings to update
|
|
* @param {Boolean} init
|
|
* @fires Hooks#afterCellMetaReset
|
|
* @fires Hooks#afterUpdateSettings
|
|
*/
|
|
this.updateSettings = function(settings, init) {
|
|
var i, clen;
|
|
|
|
if (typeof settings.rows !== "undefined") {
|
|
throw new Error("'rows' setting is no longer supported. do you mean startRows, minRows or maxRows?");
|
|
}
|
|
if (typeof settings.cols !== "undefined") {
|
|
throw new Error("'cols' setting is no longer supported. do you mean startCols, minCols or maxCols?");
|
|
}
|
|
|
|
for (i in settings) {
|
|
if (i === 'data') {
|
|
continue; //loadData will be triggered later
|
|
}
|
|
else {
|
|
if (Handsontable.hooks.getRegistered().indexOf(i) > -1) {
|
|
if (typeof settings[i] === 'function' || Array.isArray(settings[i])) {
|
|
instance.addHook(i, settings[i]);
|
|
}
|
|
}
|
|
else {
|
|
// Update settings
|
|
if (!init && settings.hasOwnProperty(i)) {
|
|
GridSettings.prototype[i] = settings[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load data or create data map
|
|
if (settings.data === void 0 && priv.settings.data === void 0) {
|
|
instance.loadData(null); //data source created just now
|
|
}
|
|
else if (settings.data !== void 0) {
|
|
instance.loadData(settings.data); //data source given as option
|
|
}
|
|
else if (settings.columns !== void 0) {
|
|
datamap.createMap();
|
|
}
|
|
|
|
// Init columns constructors configuration
|
|
clen = instance.countCols();
|
|
|
|
//Clear cellSettings cache
|
|
priv.cellSettings.length = 0;
|
|
|
|
if (clen > 0) {
|
|
var proto, column;
|
|
|
|
for (i = 0; i < clen; i++) {
|
|
priv.columnSettings[i] = helper.columnFactory(GridSettings, priv.columnsSettingConflicts);
|
|
|
|
// shortcut for prototype
|
|
proto = priv.columnSettings[i].prototype;
|
|
|
|
// Use settings provided by user
|
|
if (GridSettings.prototype.columns) {
|
|
column = GridSettings.prototype.columns[i];
|
|
helper.extend(proto, column);
|
|
helper.extend(proto, expandType(column));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (typeof settings.cell !== 'undefined') {
|
|
for (i in settings.cell) {
|
|
if (settings.cell.hasOwnProperty(i)) {
|
|
var cell = settings.cell[i];
|
|
instance.setCellMetaObject(cell.row, cell.col, cell);
|
|
}
|
|
}
|
|
}
|
|
|
|
Handsontable.hooks.run(instance, 'afterCellMetaReset');
|
|
|
|
if (typeof settings.className !== "undefined") {
|
|
if (GridSettings.prototype.className) {
|
|
dom.removeClass(instance.rootElement, GridSettings.prototype.className);
|
|
// instance.rootElement.removeClass(GridSettings.prototype.className);
|
|
}
|
|
if (settings.className) {
|
|
dom.addClass(instance.rootElement, settings.className);
|
|
// instance.rootElement.addClass(settings.className);
|
|
}
|
|
}
|
|
|
|
if (typeof settings.height != 'undefined') {
|
|
var height = settings.height;
|
|
|
|
if (typeof height == 'function') {
|
|
height = height();
|
|
}
|
|
|
|
instance.rootElement.style.height = height + 'px';
|
|
}
|
|
|
|
if (typeof settings.width != 'undefined') {
|
|
var width = settings.width;
|
|
|
|
if (typeof width == 'function') {
|
|
width = width();
|
|
}
|
|
|
|
instance.rootElement.style.width = width + 'px';
|
|
}
|
|
|
|
/* jshint ignore:start */
|
|
if (height) {
|
|
instance.rootElement.style.overflow = 'hidden';
|
|
}
|
|
/* jshint ignore:end */
|
|
|
|
if (!init) {
|
|
Handsontable.hooks.run(instance, 'afterUpdateSettings');
|
|
}
|
|
|
|
grid.adjustRowsAndCols();
|
|
if (instance.view && !priv.firstRun) {
|
|
instance.forceFullRender = true; //used when data was changed
|
|
selection.refreshBorders(null, true);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get value from selected cell.
|
|
*
|
|
* @memberof Core#
|
|
* @function getValue
|
|
* @since 0.11
|
|
* @returns {*} Returns value of selected cell
|
|
*/
|
|
this.getValue = function() {
|
|
var sel = instance.getSelected();
|
|
if (GridSettings.prototype.getValue) {
|
|
if (typeof GridSettings.prototype.getValue === 'function') {
|
|
return GridSettings.prototype.getValue.call(instance);
|
|
}
|
|
else if (sel) {
|
|
return instance.getData()[sel[0]][GridSettings.prototype.getValue];
|
|
}
|
|
}
|
|
else if (sel) {
|
|
return instance.getDataAtCell(sel[0], sel[1]);
|
|
}
|
|
};
|
|
|
|
function expandType(obj) {
|
|
if (!obj.hasOwnProperty('type')) {
|
|
//ignore obj.prototype.type
|
|
return;
|
|
}
|
|
|
|
var type, expandedType = {};
|
|
|
|
if (typeof obj.type === 'object') {
|
|
type = obj.type;
|
|
}
|
|
else if (typeof obj.type === 'string') {
|
|
type = Handsontable.cellTypes[obj.type];
|
|
if (type === void 0) {
|
|
throw new Error('You declared cell type "' + obj.type +
|
|
'" as a string that is not mapped to a known object. Cell type must be an object or a string mapped to an object in Handsontable.cellTypes');
|
|
}
|
|
}
|
|
|
|
|
|
for (var i in type) {
|
|
if (type.hasOwnProperty(i) && !obj.hasOwnProperty(i)) {
|
|
expandedType[i] = type[i];
|
|
}
|
|
}
|
|
|
|
return expandedType;
|
|
|
|
}
|
|
|
|
/**
|
|
* Get object settings.
|
|
*
|
|
* @memberof Core#
|
|
* @function getSettings
|
|
* @returns {Object} Returns an object containing the current grid settings
|
|
*/
|
|
this.getSettings = function() {
|
|
return priv.settings;
|
|
};
|
|
|
|
/**
|
|
* Clears grid.
|
|
*
|
|
* @memberof Core#
|
|
* @function clear
|
|
* @since 0.11
|
|
*/
|
|
this.clear = function() {
|
|
selection.selectAll();
|
|
selection.empty();
|
|
};
|
|
|
|
/**
|
|
* Inserts or removes rows and columns.
|
|
*
|
|
* @memberof Core#
|
|
* @function alter
|
|
* @param {String} action See grid.alter for possible values: `"insert_row"`, `"insert_col"`, `"remove_row"`, `"remove_col"`
|
|
* @param {Number} index
|
|
* @param {Number} amount
|
|
* @param {String} [source] Source of hook runner
|
|
* @param {Boolean} [keepEmptyRows] Flag for preventing deletion of empty rows
|
|
* @description
|
|
*
|
|
* Insert new row(s) above the row at given `index`. If index is `null` or `undefined`, the new row will be
|
|
* added after the current last row. Default `amount` equals 1.
|
|
* ```js
|
|
* var hot = new Handsontable(document.getElementById('example'));
|
|
* hot.alter('insert_row', 10);
|
|
* ```
|
|
*
|
|
* Insert new column(s) before the column at given `index`. If index is `null` or `undefined`, the new column
|
|
* will be added after the current last column. Default `amount` equals 1
|
|
* ```js
|
|
* var hot = new Handsontable(document.getElementById('example'));
|
|
* hot.alter('insert_col', 10);
|
|
* ```
|
|
*
|
|
* Remove the row(s) at given `index`. Default `amount` equals 1
|
|
* ```js
|
|
* var hot = new Handsontable(document.getElementById('example'));
|
|
* hot.alter('remove_row', 10);
|
|
* ```
|
|
*
|
|
* Remove the column(s) at given `index`. Default `amount` equals 1
|
|
* ```js
|
|
* var hot = new Handsontable(document.getElementById('example'));
|
|
* hot.alter('remove_col', 10);
|
|
* ```
|
|
*/
|
|
this.alter = function(action, index, amount, source, keepEmptyRows) {
|
|
grid.alter(action, index, amount, source, keepEmptyRows);
|
|
};
|
|
|
|
/**
|
|
* Returns TD element for given `row`, `col` if it is rendered on screen.
|
|
* Returns `null` if the TD is not rendered on screen (probably because that part of table is not visible).
|
|
*
|
|
* @memberof Core#
|
|
* @function getCell
|
|
* @param {Number} row
|
|
* @param {Number} col
|
|
* @param {Boolean} topmost
|
|
* @returns {Element}
|
|
*/
|
|
this.getCell = function(row, col, topmost) {
|
|
return instance.view.getCellAtCoords(new WalkontableCellCoords(row, col), topmost);
|
|
};
|
|
|
|
/**
|
|
* Returns coordinates for the provided element.
|
|
*
|
|
* @memberof Core#
|
|
* @function getCoords
|
|
* @param {Element} elem
|
|
* @returns {WalkontableCellCoords}
|
|
*/
|
|
this.getCoords = function(elem) {
|
|
return this.view.wt.wtTable.getCoords.call(this.view.wt.wtTable, elem);
|
|
};
|
|
|
|
/**
|
|
* Returns property name that corresponds with the given column index. {@link DataMap#colToProp}
|
|
*
|
|
* @memberof Core#
|
|
* @function colToProp
|
|
* @param {Number} col Column index
|
|
* @returns {String}
|
|
*/
|
|
this.colToProp = function(col) {
|
|
return datamap.colToProp(col);
|
|
};
|
|
|
|
/**
|
|
* Returns column index that corresponds with the given property. {@link DataMap#propToCol}
|
|
*
|
|
* @memberof Core#
|
|
* @function propToCol
|
|
* @param {String} prop
|
|
* @returns {Number}
|
|
*/
|
|
this.propToCol = function(prop) {
|
|
return datamap.propToCol(prop);
|
|
};
|
|
|
|
/**
|
|
* @description
|
|
* Return cell value at `row`, `col`. `row` and `col` are the __visible__ indexes (note that if columns were reordered or sorted,
|
|
* the current order will be used).
|
|
*
|
|
* @memberof Core#
|
|
* @function getDataAtCell
|
|
* @param {Number} row
|
|
* @param {Number} col
|
|
* @returns {*}
|
|
*/
|
|
this.getDataAtCell = function(row, col) {
|
|
return datamap.get(row, datamap.colToProp(col));
|
|
};
|
|
|
|
/**
|
|
* Return value at `row`, `prop`. {@link DataMap#get}
|
|
*
|
|
* @memberof Core#
|
|
* @function getDataAtRowProp
|
|
* @param {Number} row
|
|
* @param {String} prop
|
|
* @returns {*}
|
|
*/
|
|
this.getDataAtRowProp = function(row, prop) {
|
|
return datamap.get(row, prop);
|
|
};
|
|
|
|
/**
|
|
* @description
|
|
* Returns array of column values from the data source. `col` is the __visible__ index of the column.
|
|
*
|
|
* @memberof Core#
|
|
* @function getDataAtCol
|
|
* @since 0.9-beta2
|
|
* @param {Number} col
|
|
* @returns {Array}
|
|
*/
|
|
this.getDataAtCol = function(col) {
|
|
var out = [];
|
|
return out.concat.apply(out, datamap.getRange(
|
|
new WalkontableCellCoords(0, col), new WalkontableCellCoords(priv.settings.data.length - 1, col), datamap.DESTINATION_RENDERER));
|
|
};
|
|
|
|
/**
|
|
* Given the object property name (e.g. `'first.name'`), returns array of column values from the data source.
|
|
*
|
|
* @memberof Core#
|
|
* @function getDataAtProp
|
|
* @since 0.9-beta2
|
|
* @param {String} prop
|
|
* @returns {*}
|
|
*/
|
|
this.getDataAtProp = function(prop) {
|
|
var out = [],
|
|
range;
|
|
|
|
range = datamap.getRange(
|
|
new WalkontableCellCoords(0, datamap.propToCol(prop)),
|
|
new WalkontableCellCoords(priv.settings.data.length - 1, datamap.propToCol(prop)),
|
|
datamap.DESTINATION_RENDERER);
|
|
|
|
return out.concat.apply(out, range);
|
|
};
|
|
|
|
/**
|
|
* Returns array of column values from the data source. `col` is the index of the row in the data source.
|
|
*
|
|
* @memberof Core#
|
|
* @function getSourceDataAtCol
|
|
* @since 0.11.0-beta3
|
|
* @param {Number} col
|
|
* @returns {Array}
|
|
*/
|
|
this.getSourceDataAtCol = function(col) {
|
|
var out = [],
|
|
data = priv.settings.data;
|
|
|
|
for (var i = 0; i < data.length; i++) {
|
|
out.push(data[i][col]);
|
|
}
|
|
|
|
return out;
|
|
};
|
|
|
|
/**
|
|
* Returns a single row of the data (array or object, depending on what you have). `row` is the index of the row in the data source.
|
|
*
|
|
* @memberof Core#
|
|
* @function getSourceDataAtRow
|
|
* @since 0.11.0-beta3
|
|
* @param {Number} row
|
|
* @returns {Array|Object}
|
|
*/
|
|
this.getSourceDataAtRow = function(row) {
|
|
return priv.settings.data[row];
|
|
};
|
|
|
|
/**
|
|
* @description
|
|
* Returns a single row of the data (array or object, depending on what you have). `row` is the __visible__ index of the row.
|
|
*
|
|
* @memberof Core#
|
|
* @function getDataAtRow
|
|
* @param {Number} row
|
|
* @returns {*}
|
|
* @since 0.9-beta2
|
|
*/
|
|
this.getDataAtRow = function(row) {
|
|
var data = datamap.getRange(new WalkontableCellCoords(row, 0), new WalkontableCellCoords(row, this.countCols() - 1), datamap.DESTINATION_RENDERER);
|
|
|
|
return data[0];
|
|
};
|
|
|
|
/**
|
|
* Remove `key` property object from cell meta data corresponding to params `row`, `col`.
|
|
*
|
|
* @memberof Core#
|
|
* @function removeCellMeta
|
|
* @param {Number} row
|
|
* @param {Number} col
|
|
* @param {String} key
|
|
*/
|
|
this.removeCellMeta = function(row, col, key) {
|
|
var cellMeta = instance.getCellMeta(row, col);
|
|
/* jshint ignore:start */
|
|
if (cellMeta[key] != undefined) {
|
|
delete priv.cellSettings[row][col][key];
|
|
}
|
|
/* jshint ignore:end */
|
|
};
|
|
|
|
/**
|
|
* Set cell meta data object `prop` to corresponding params `row`, `col`
|
|
*
|
|
* @memberof Core#
|
|
* @function setCellMetaObject
|
|
* @since 0.11
|
|
* @param {Number} row
|
|
* @param {Number} col
|
|
* @param {Object} prop
|
|
*/
|
|
this.setCellMetaObject = function(row, col, prop) {
|
|
if (typeof prop === 'object') {
|
|
for (var key in prop) {
|
|
if (prop.hasOwnProperty(key)) {
|
|
var value = prop[key];
|
|
this.setCellMeta(row, col, key, value);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sets cell meta data object `key` corresponding to params `row`, `col`.
|
|
*
|
|
* @memberof Core#
|
|
* @function setCellMeta
|
|
* @since 0.11
|
|
* @param {Number} row
|
|
* @param {Number} col
|
|
* @param {String} key
|
|
* @param {String} val
|
|
* @fires Hooks#afterSetCellMeta
|
|
*/
|
|
this.setCellMeta = function(row, col, key, val) {
|
|
if (!priv.cellSettings[row]) {
|
|
priv.cellSettings[row] = [];
|
|
}
|
|
if (!priv.cellSettings[row][col]) {
|
|
priv.cellSettings[row][col] = new priv.columnSettings[col]();
|
|
}
|
|
priv.cellSettings[row][col][key] = val;
|
|
Handsontable.hooks.run(instance, 'afterSetCellMeta', row, col, key, val);
|
|
};
|
|
|
|
/**
|
|
* Return cell properties for given `row`, `col` coordinates.
|
|
*
|
|
* @memberof Core#
|
|
* @function getCellMeta
|
|
* @param {Number} row
|
|
* @param {Number} col
|
|
* @returns {Object}
|
|
* @fires Hooks#beforeGetCellMeta
|
|
* @fires Hooks#afterGetCellMeta
|
|
*/
|
|
this.getCellMeta = function(row, col) {
|
|
var prop = datamap.colToProp(col)
|
|
, cellProperties;
|
|
|
|
row = translateRowIndex(row);
|
|
col = translateColIndex(col);
|
|
|
|
if (!priv.columnSettings[col]) {
|
|
priv.columnSettings[col] = helper.columnFactory(GridSettings, priv.columnsSettingConflicts);
|
|
}
|
|
|
|
if (!priv.cellSettings[row]) {
|
|
priv.cellSettings[row] = [];
|
|
}
|
|
if (!priv.cellSettings[row][col]) {
|
|
priv.cellSettings[row][col] = new priv.columnSettings[col]();
|
|
}
|
|
|
|
cellProperties = priv.cellSettings[row][col]; //retrieve cellProperties from cache
|
|
|
|
cellProperties.row = row;
|
|
cellProperties.col = col;
|
|
cellProperties.prop = prop;
|
|
cellProperties.instance = instance;
|
|
|
|
Handsontable.hooks.run(instance, 'beforeGetCellMeta', row, col, cellProperties);
|
|
helper.extend(cellProperties, expandType(cellProperties)); //for `type` added in beforeGetCellMeta
|
|
|
|
if (cellProperties.cells) {
|
|
var settings = cellProperties.cells.call(cellProperties, row, col, prop);
|
|
|
|
if (settings) {
|
|
helper.extend(cellProperties, settings);
|
|
helper.extend(cellProperties, expandType(settings)); //for `type` added in cells
|
|
}
|
|
}
|
|
|
|
Handsontable.hooks.run(instance, 'afterGetCellMeta', row, col, cellProperties);
|
|
|
|
return cellProperties;
|
|
};
|
|
|
|
/**
|
|
* Checks if the data format and config allows user to modify the column structure.
|
|
* @returns {boolean}
|
|
*/
|
|
this.isColumnModificationAllowed = function() {
|
|
return !(instance.dataType === 'object' || instance.getSettings().columns);
|
|
};
|
|
|
|
/**
|
|
* If displayed rows order is different than the order of rows stored in memory (i.e. sorting is applied)
|
|
* we need to translate logical (stored) row index to physical (displayed) index.
|
|
*
|
|
* @memberof Core#
|
|
* @function translateRowIndex
|
|
* @param {Number} row Original row index
|
|
* @returns {Number} Translated row index
|
|
* @fires Hooks#modifyRow
|
|
*/
|
|
function translateRowIndex(row) {
|
|
return Handsontable.hooks.run(instance, 'modifyRow', row);
|
|
}
|
|
|
|
/**
|
|
* If displayed columns order is different than the order of columns stored in memory (i.e. column were moved using manualColumnMove plugin)
|
|
* we need to translate logical (stored) column index to physical (displayed) index.
|
|
*
|
|
* @memberof Core#
|
|
* @function translateColIndex
|
|
* @param {Number} col Original column index
|
|
* @returns {Number} Translated column index
|
|
* @fires Hooks#modifyCol
|
|
*/
|
|
function translateColIndex(col) {
|
|
// warning: this must be done after datamap.colToProp
|
|
return Handsontable.hooks.run(instance, 'modifyCol', col);
|
|
}
|
|
|
|
var rendererLookup = helper.cellMethodLookupFactory('renderer');
|
|
|
|
/**
|
|
* Get cell renderer type by `row` and `col`.
|
|
*
|
|
* @memberof Core#
|
|
* @function getCellRenderer
|
|
* @since 0.11
|
|
* @param {Number} row
|
|
* @param {Number} col
|
|
* @returns {Function} Returns rederer type
|
|
*/
|
|
this.getCellRenderer = function(row, col) {
|
|
var renderer = rendererLookup.call(this, row, col);
|
|
|
|
return getRenderer(renderer);
|
|
};
|
|
|
|
/**
|
|
* Get cell editor by `row` and `col`.
|
|
*
|
|
* @memberof Core#
|
|
* @function getCellEditor
|
|
* @returns {*}
|
|
*/
|
|
this.getCellEditor = helper.cellMethodLookupFactory('editor');
|
|
|
|
/**
|
|
* Get cell validator by `row` and `col`
|
|
*
|
|
* @memberof Core#
|
|
* @function getCellValidator
|
|
* @returns {*}
|
|
*/
|
|
this.getCellValidator = helper.cellMethodLookupFactory('validator');
|
|
|
|
|
|
/**
|
|
* Validates all cells using their validator functions and calls callback when finished. Does not render the view.
|
|
*
|
|
* @memberof Core#
|
|
* @function validateCells
|
|
* @param {Function} callback
|
|
*/
|
|
this.validateCells = function(callback) {
|
|
var waitingForValidator = new ValidatorsQueue();
|
|
waitingForValidator.onQueueEmpty = callback;
|
|
|
|
/* jshint ignore:start */
|
|
var i = instance.countRows() - 1;
|
|
while (i >= 0) {
|
|
var j = instance.countCols() - 1;
|
|
while (j >= 0) {
|
|
waitingForValidator.addValidatorToQueue();
|
|
instance.validateCell(instance.getDataAtCell(i, j), instance.getCellMeta(i, j), function() {
|
|
waitingForValidator.removeValidatorFormQueue();
|
|
}, 'validateCells');
|
|
j--;
|
|
}
|
|
i--;
|
|
}
|
|
/* jshint ignore:end */
|
|
waitingForValidator.checkIfQueueIsEmpty();
|
|
};
|
|
|
|
/**
|
|
* Returns array of row headers (if they are enabled). If param `row` given, return header at given row as string.
|
|
*
|
|
* @memberof Core#
|
|
* @function getRowHeader
|
|
* @param {Number} [row]
|
|
* @returns {Array|String}
|
|
*/
|
|
this.getRowHeader = function(row) {
|
|
if (row === void 0) {
|
|
var out = [];
|
|
for (var i = 0, ilen = instance.countRows(); i < ilen; i++) {
|
|
out.push(instance.getRowHeader(i));
|
|
}
|
|
return out;
|
|
}
|
|
else if (Array.isArray(priv.settings.rowHeaders) && priv.settings.rowHeaders[row] !== void 0) {
|
|
return priv.settings.rowHeaders[row];
|
|
}
|
|
else if (typeof priv.settings.rowHeaders === 'function') {
|
|
return priv.settings.rowHeaders(row);
|
|
}
|
|
else if (priv.settings.rowHeaders && typeof priv.settings.rowHeaders !== 'string' && typeof priv.settings.rowHeaders !== 'number') {
|
|
return row + 1;
|
|
}
|
|
else {
|
|
return priv.settings.rowHeaders;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns information of this table is configured to display row headers.
|
|
*
|
|
* @memberof Core#
|
|
* @function hasRowHeaders
|
|
* @returns {Boolean}
|
|
* @since 0.11
|
|
*/
|
|
this.hasRowHeaders = function() {
|
|
return !!priv.settings.rowHeaders;
|
|
};
|
|
|
|
/**
|
|
* Returns information of this table is configured to display column headers.
|
|
*
|
|
* @memberof Core#
|
|
* @function hasColHeaders
|
|
* @since 0.11
|
|
* @returns {Boolean}
|
|
*/
|
|
this.hasColHeaders = function() {
|
|
if (priv.settings.colHeaders !== void 0 && priv.settings.colHeaders !== null) { //Polymer has empty value = null
|
|
return !!priv.settings.colHeaders;
|
|
}
|
|
for (var i = 0, ilen = instance.countCols(); i < ilen; i++) {
|
|
if (instance.getColHeader(i)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Return array of column headers (if they are enabled). If param `col` given, return header at given column as string
|
|
*
|
|
* @memberof Core#
|
|
* @function getColHeader
|
|
* @param {Number} [col] Column index
|
|
* @returns {Array|String}
|
|
*/
|
|
this.getColHeader = function(col) {
|
|
if (col === void 0) {
|
|
var out = [];
|
|
for (var i = 0, ilen = instance.countCols(); i < ilen; i++) {
|
|
out.push(instance.getColHeader(i));
|
|
}
|
|
return out;
|
|
}
|
|
else {
|
|
var baseCol = col;
|
|
|
|
col = Handsontable.hooks.run(instance, 'modifyCol', col);
|
|
|
|
if (priv.settings.columns && priv.settings.columns[col] && priv.settings.columns[col].title) {
|
|
return priv.settings.columns[col].title;
|
|
}
|
|
else if (Array.isArray(priv.settings.colHeaders) && priv.settings.colHeaders[col] !== void 0) {
|
|
return priv.settings.colHeaders[col];
|
|
}
|
|
else if (typeof priv.settings.colHeaders === 'function') {
|
|
return priv.settings.colHeaders(col);
|
|
}
|
|
else if (priv.settings.colHeaders && typeof priv.settings.colHeaders !== 'string' && typeof priv.settings.colHeaders !== 'number') {
|
|
return helper.spreadsheetColumnLabel(baseCol); //see #1458
|
|
}
|
|
else {
|
|
return priv.settings.colHeaders;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Return column width from settings (no guessing). Private use intended.
|
|
*
|
|
* @private
|
|
* @memberof Core#
|
|
* @function _getColWidthFromSettings
|
|
* @param {Number} col
|
|
* @returns {Number}
|
|
*/
|
|
this._getColWidthFromSettings = function(col) {
|
|
var cellProperties = instance.getCellMeta(0, col);
|
|
var width = cellProperties.width;
|
|
|
|
if (width === void 0 || width === priv.settings.width) {
|
|
width = cellProperties.colWidths;
|
|
}
|
|
if (width !== void 0 && width !== null) {
|
|
switch (typeof width) {
|
|
case 'object': //array
|
|
width = width[col];
|
|
break;
|
|
|
|
case 'function':
|
|
width = width(col);
|
|
break;
|
|
}
|
|
if (typeof width === 'string') {
|
|
width = parseInt(width, 10);
|
|
}
|
|
}
|
|
|
|
return width;
|
|
};
|
|
|
|
/**
|
|
* Return column width
|
|
*
|
|
* @memberof Core#
|
|
* @function getColWidth
|
|
* @since 0.11
|
|
* @param {Number} col
|
|
* @returns {Number}
|
|
* @fires Hooks#modifyColWidth
|
|
*/
|
|
this.getColWidth = function(col) {
|
|
var width = instance._getColWidthFromSettings(col);
|
|
|
|
if (!width) {
|
|
width = 50;
|
|
}
|
|
width = Handsontable.hooks.run(instance, 'modifyColWidth', width, col);
|
|
|
|
return width;
|
|
};
|
|
|
|
/**
|
|
* Return row height from settings (no guessing). Private use intended.
|
|
*
|
|
* @private
|
|
* @memberof Core#
|
|
* @function _getRowHeightFromSettings
|
|
* @param {Number} row
|
|
* @returns {Number}
|
|
*/
|
|
this._getRowHeightFromSettings = function(row) {
|
|
var height = priv.settings.rowHeights; //only uses grid settings
|
|
|
|
if (height !== void 0 && height !== null) {
|
|
switch (typeof height) {
|
|
case 'object': //array
|
|
height = height[row];
|
|
break;
|
|
|
|
case 'function':
|
|
height = height(row);
|
|
break;
|
|
}
|
|
if (typeof height === 'string') {
|
|
height = parseInt(height, 10);
|
|
}
|
|
}
|
|
|
|
return height;
|
|
};
|
|
|
|
/**
|
|
* Return row height.
|
|
*
|
|
* @memberof Core#
|
|
* @function getRowHeight
|
|
* @since 0.11
|
|
* @param {Number} row
|
|
* @returns {Number}
|
|
* @fires Hooks#modifyRowHeight
|
|
*/
|
|
this.getRowHeight = function(row) {
|
|
var height = instance._getRowHeightFromSettings(row);
|
|
|
|
height = Handsontable.hooks.run(instance, 'modifyRowHeight', height, row);
|
|
|
|
return height;
|
|
};
|
|
|
|
/**
|
|
* Returns total number of rows in the grid.
|
|
*
|
|
* @memberof Core#
|
|
* @function countRows
|
|
* @returns {Number} Total number in rows the grid
|
|
*/
|
|
this.countRows = function() {
|
|
return priv.settings.data.length;
|
|
};
|
|
|
|
/**
|
|
* Returns total number of columns in the grid.
|
|
*
|
|
* @memberof Core#
|
|
* @function countCols
|
|
* @returns {Number} Total number of columns
|
|
*/
|
|
this.countCols = function() {
|
|
if (instance.dataType === 'object' || instance.dataType === 'function') {
|
|
if (priv.settings.columns && priv.settings.columns.length) {
|
|
return priv.settings.columns.length;
|
|
}
|
|
else {
|
|
return datamap.colToPropCache.length;
|
|
}
|
|
}
|
|
else if (instance.dataType === 'array') {
|
|
if (priv.settings.columns && priv.settings.columns.length) {
|
|
return priv.settings.columns.length;
|
|
}
|
|
else if (priv.settings.data && priv.settings.data[0] && priv.settings.data[0].length) {
|
|
return priv.settings.data[0].length;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get index of first visible row.
|
|
*
|
|
* @memberof Core#
|
|
* @function rowOffset
|
|
* @returns {Number} Returns index of first visible row
|
|
*/
|
|
this.rowOffset = function() {
|
|
return instance.view.wt.wtTable.getFirstRenderedRow();
|
|
};
|
|
|
|
/**
|
|
* Get index of first visible column.
|
|
*
|
|
* @memberof Core#
|
|
* @function colOffset
|
|
* @returns {Number} Return index of first visible column.
|
|
*/
|
|
this.colOffset = function() {
|
|
return instance.view.wt.wtTable.getFirstRenderedColumn();
|
|
};
|
|
|
|
/**
|
|
* Return number of rendered rows (including rows partially or fully rendered outside viewport).
|
|
*
|
|
* @memberof Core#
|
|
* @function countRenderedRows
|
|
* @returns {Number} Returns -1 if table is not visible
|
|
*/
|
|
this.countRenderedRows = function() {
|
|
return instance.view.wt.drawn ? instance.view.wt.wtTable.getRenderedRowsCount() : -1;
|
|
};
|
|
|
|
/**
|
|
* Return number of visible rows (rendered rows that fully fit inside viewport).
|
|
*
|
|
* @memberof Core#
|
|
* @function countVisibleRows
|
|
* @returns {Number} Returns -1 if table is not visible
|
|
*/
|
|
this.countVisibleRows = function() {
|
|
return instance.view.wt.drawn ? instance.view.wt.wtTable.getVisibleRowsCount() : -1;
|
|
};
|
|
|
|
/**
|
|
* Return number of visible columns.
|
|
*
|
|
* @memberof Core#
|
|
* @function countRenderedCols
|
|
* @returns {Number} Returns -1 if table is not visible
|
|
*/
|
|
this.countRenderedCols = function() {
|
|
return instance.view.wt.drawn ? instance.view.wt.wtTable.getRenderedColumnsCount() : -1;
|
|
};
|
|
|
|
/**
|
|
* Return number of visible columns. Returns -1 if table is not visible
|
|
*
|
|
* @memberof Core#
|
|
* @function countVisibleCols
|
|
* @return {Number}
|
|
*/
|
|
this.countVisibleCols = function() {
|
|
return instance.view.wt.drawn ? instance.view.wt.wtTable.getVisibleColumnsCount() : -1;
|
|
};
|
|
|
|
/**
|
|
* Returns number of empty rows. If the optional ending parameter is 1, returns
|
|
* number of empty rows at the bottom of the table.
|
|
*
|
|
* @memberof Core#
|
|
* @function countEmptyRows
|
|
* @param {Boolean} [ending] If `true`, will only count empty rows at the end of the data source
|
|
* @returns {Number} Count empty rows
|
|
* @fires Hooks#modifyRow
|
|
*/
|
|
this.countEmptyRows = function(ending) {
|
|
var i = instance.countRows() - 1,
|
|
empty = 0,
|
|
row;
|
|
|
|
while (i >= 0) {
|
|
row = Handsontable.hooks.run(this, 'modifyRow', i);
|
|
|
|
if (instance.isEmptyRow(row)) {
|
|
empty++;
|
|
|
|
} else if (ending) {
|
|
break;
|
|
}
|
|
i--;
|
|
}
|
|
|
|
return empty;
|
|
};
|
|
|
|
/**
|
|
* Returns number of empty columns. If the optional `ending` parameter is `true`, returns number of empty
|
|
* columns at right hand edge of the table.
|
|
*
|
|
* @memberof Core#
|
|
* @function countEmptyCols
|
|
* @param {Boolean} [ending] If `true`, will only count empty columns at the end of the data source row
|
|
* @returns {Number} Count empty cols
|
|
*/
|
|
this.countEmptyCols = function(ending) {
|
|
if (instance.countRows() < 1) {
|
|
return 0;
|
|
}
|
|
|
|
var i = instance.countCols() - 1
|
|
, empty = 0;
|
|
while (i >= 0) {
|
|
if (instance.isEmptyCol(i)) {
|
|
empty++;
|
|
}
|
|
else if (ending) {
|
|
break;
|
|
}
|
|
i--;
|
|
}
|
|
return empty;
|
|
};
|
|
|
|
/**
|
|
* Check is `row` is empty.
|
|
*
|
|
* @memberof Core#
|
|
* @function isEmptyRow
|
|
* @param {Number} row Row index
|
|
* @returns {Boolean} Return `true` if the row at the given `row` is empty, `false` otherwise.
|
|
*/
|
|
this.isEmptyRow = function(row) {
|
|
return priv.settings.isEmptyRow.call(instance, row);
|
|
};
|
|
|
|
/**
|
|
* Check is `col` is empty.
|
|
*
|
|
* @memberof Core#
|
|
* @function isEmptyCol
|
|
* @param {Number} col Column index
|
|
* @returns {Boolean} Return `true` if the column at the given `col` is empty, `false` otherwise.
|
|
*/
|
|
this.isEmptyCol = function(col) {
|
|
return priv.settings.isEmptyCol.call(instance, col);
|
|
};
|
|
|
|
/**
|
|
* Select cell `row`, `col` or range of cells finishing at `endRow`, `endCol`.
|
|
* By default, viewport will be scrolled to selection and after `selectCell` call instance will be listening
|
|
* to keyboard input on document.
|
|
*
|
|
* @memberof Core#
|
|
* @function selectCell
|
|
* @param {Number} row
|
|
* @param {Number} col
|
|
* @param {Number} [endRow]
|
|
* @param {Number} [endCol]
|
|
* @param {Boolean} [scrollToCell=true] If `true`, viewport will be scrolled to the selection
|
|
* @param {Boolean} [changeListener=true] If `false`, Handsontable will not change keyboard events listener to
|
|
* himself (default `true`)
|
|
* @returns {Boolean}
|
|
*/
|
|
this.selectCell = function(row, col, endRow, endCol, scrollToCell, changeListener) {
|
|
var coords;
|
|
|
|
changeListener = typeof changeListener === 'undefined' || changeListener === true;
|
|
|
|
if (typeof row !== 'number' || row < 0 || row >= instance.countRows()) {
|
|
return false;
|
|
}
|
|
if (typeof col !== 'number' || col < 0 || col >= instance.countCols()) {
|
|
return false;
|
|
}
|
|
if (typeof endRow !== 'undefined') {
|
|
if (typeof endRow !== 'number' || endRow < 0 || endRow >= instance.countRows()) {
|
|
return false;
|
|
}
|
|
if (typeof endCol !== 'number' || endCol < 0 || endCol >= instance.countCols()) {
|
|
return false;
|
|
}
|
|
}
|
|
coords = new WalkontableCellCoords(row, col);
|
|
priv.selRange = new WalkontableCellRange(coords, coords, coords);
|
|
|
|
if (document.activeElement && document.activeElement !== document.documentElement &&
|
|
document.activeElement !== document.body) {
|
|
// needed or otherwise prepare won't focus the cell. selectionSpec tests this (should move focus to selected cell)
|
|
document.activeElement.blur();
|
|
}
|
|
if (changeListener) {
|
|
instance.listen();
|
|
}
|
|
|
|
if (typeof endRow === 'undefined') {
|
|
selection.setRangeEnd(priv.selRange.from, scrollToCell);
|
|
|
|
} else {
|
|
selection.setRangeEnd(new WalkontableCellCoords(endRow, endCol), scrollToCell);
|
|
}
|
|
instance.selection.finish();
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Select cell `row`, `prop` or range finishing at `endRow`, `endProp`. By default, viewport will be scrolled to selection.
|
|
*
|
|
* @memberof Core#
|
|
* @function selectCellByProp
|
|
* @param {Number} row
|
|
* @param {Object} prop
|
|
* @param {Number} [endRow]
|
|
* @param {Object} [endProp]
|
|
* @param {Boolean} [scrollToCell=true] If `true`, viewport will be scrolled to the selection
|
|
* @returns {Boolean}
|
|
*/
|
|
this.selectCellByProp = function(row, prop, endRow, endProp, scrollToCell) {
|
|
/* jshint ignore:start */
|
|
arguments[1] = datamap.propToCol(arguments[1]);
|
|
if (typeof arguments[3] !== "undefined") {
|
|
arguments[3] = datamap.propToCol(arguments[3]);
|
|
}
|
|
return instance.selectCell.apply(instance, arguments);
|
|
/* jshint ignore:end */
|
|
};
|
|
|
|
/**
|
|
* Deselects current cell selection on grid.
|
|
*
|
|
* @memberof Core#
|
|
* @function deselectCell
|
|
*/
|
|
this.deselectCell = function() {
|
|
selection.deselect();
|
|
};
|
|
|
|
/**
|
|
* Remove grid from DOM.
|
|
*
|
|
* @memberof Core#
|
|
* @function destroy
|
|
* @fires Hooks#afterDestroy
|
|
*/
|
|
this.destroy = function() {
|
|
|
|
instance._clearTimeouts();
|
|
if (instance.view) { //in case HT is destroyed before initialization has finished
|
|
instance.view.destroy();
|
|
}
|
|
|
|
|
|
dom.empty(instance.rootElement);
|
|
eventManager.clear();
|
|
|
|
Handsontable.hooks.run(instance, 'afterDestroy');
|
|
Handsontable.hooks.destroy(instance);
|
|
|
|
for (var i in instance) {
|
|
if (instance.hasOwnProperty(i)) {
|
|
//replace instance methods with post mortem
|
|
if (typeof instance[i] === "function") {
|
|
if (i !== "runHooks") {
|
|
instance[i] = postMortem;
|
|
}
|
|
}
|
|
//replace instance properties with null (restores memory)
|
|
//it should not be necessary but this prevents a memory leak side effects that show itself in Jasmine tests
|
|
else if (i !== "guid") {
|
|
instance[i] = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//replace private properties with null (restores memory)
|
|
//it should not be necessary but this prevents a memory leak side effects that show itself in Jasmine tests
|
|
priv = null;
|
|
datamap = null;
|
|
grid = null;
|
|
selection = null;
|
|
editorManager = null;
|
|
instance = null;
|
|
GridSettings = null;
|
|
};
|
|
|
|
/**
|
|
* Replacement for all methods after Handsotnable was destroyed.
|
|
*
|
|
* @private
|
|
*/
|
|
function postMortem() {
|
|
throw new Error("This method cannot be called because this Handsontable instance has been destroyed");
|
|
}
|
|
|
|
/**
|
|
* Returns active editor object. {@link Handsontable.EditorManager#getActiveEditor}
|
|
*
|
|
* @memberof Core#
|
|
* @function getActiveEditor
|
|
* @returns {Object}
|
|
*/
|
|
this.getActiveEditor = function() {
|
|
return editorManager.getActiveEditor();
|
|
};
|
|
|
|
/**
|
|
* Returns plugin instance by plugin name
|
|
*
|
|
* @memberof Core#
|
|
* @function getPlugin
|
|
* @param {String} pluginName
|
|
* @returns {*}
|
|
* @since 0.15.0
|
|
*/
|
|
this.getPlugin = function(pluginName) {
|
|
return getPlugin(this, pluginName);
|
|
};
|
|
|
|
/**
|
|
* Return Handsontable instance.
|
|
*
|
|
* @memberof Core#
|
|
* @function getInstance
|
|
* @returns {Handsontable}
|
|
*/
|
|
this.getInstance = function() {
|
|
return instance;
|
|
};
|
|
|
|
/**
|
|
* Adds listener to specified hook name and only for this Handsontable instance.
|
|
*
|
|
* @memberof Core#
|
|
* @function addHook
|
|
* @see Hooks#add
|
|
* @param {String} key Hook name
|
|
* @param {Function|Array} callback Function or array of Functions
|
|
*
|
|
* @example
|
|
* ```js
|
|
* hot.addHook('beforeInit', myCallback);
|
|
* ```
|
|
*/
|
|
this.addHook = function(key, callback) {
|
|
Handsontable.hooks.add(key, callback, instance);
|
|
};
|
|
|
|
/**
|
|
* Adds listener to specified hook name and only for this Handsontable instance. After hook runs this
|
|
* listener will be automatically removed.
|
|
*
|
|
* @memberof Core#
|
|
* @function addHookOnce
|
|
* @see Hooks#once
|
|
* @param {String} key Hook name
|
|
* @param {Function|Array} callback Function or array of Functions
|
|
*
|
|
* @example
|
|
* ```js
|
|
* hot.addHookOnce('beforeInit', myCallback);
|
|
* ```
|
|
*/
|
|
this.addHookOnce = function(key, callback) {
|
|
Handsontable.hooks.once(key, callback, instance);
|
|
};
|
|
|
|
/**
|
|
* Removes the hook listener previously registered with {@link Core#addHook}.
|
|
*
|
|
* @memberof Core#
|
|
* @function removeHook
|
|
* @see Hooks#remove
|
|
* @param {String} key Hook name
|
|
* @param {Function} callback Function which have been registered via {@link Core#addHook}
|
|
*
|
|
* @example
|
|
* ```js
|
|
* hot.removeHook('beforeInit', myCallback);
|
|
* ```
|
|
*/
|
|
this.removeHook = function(key, callback) {
|
|
Handsontable.hooks.remove(key, callback, instance);
|
|
};
|
|
|
|
/**
|
|
* @memberof Core#
|
|
* @function runHooks
|
|
* @see Hooks#run
|
|
* @param {String} key Hook name
|
|
* @param {*} [p1]
|
|
* @param {*} [p2]
|
|
* @param {*} [p3]
|
|
* @param {*} [p4]
|
|
* @param {*} [p5]
|
|
* @param {*} [p6]
|
|
* @returns {*}
|
|
*
|
|
* @example
|
|
* ```js
|
|
* hot.runHooks('beforeInit');
|
|
* ```
|
|
*/
|
|
this.runHooks = function(key, p1, p2, p3, p4, p5, p6) {
|
|
return Handsontable.hooks.run(instance, key, p1, p2, p3, p4, p5, p6);
|
|
};
|
|
|
|
this.timeouts = [];
|
|
|
|
/**
|
|
* Sets timeout. Purpose of this method is to clear all known timeouts when `destroy` method is called.
|
|
*
|
|
* @param {*} handle
|
|
* @private
|
|
*/
|
|
this._registerTimeout = function(handle) {
|
|
this.timeouts.push(handle);
|
|
};
|
|
|
|
/**
|
|
* Clears all known timeouts.
|
|
*
|
|
* @private
|
|
*/
|
|
this._clearTimeouts = function() {
|
|
for (var i = 0, ilen = this.timeouts.length; i < ilen; i++) {
|
|
clearTimeout(this.timeouts[i]);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handsontable version
|
|
*
|
|
* @type {String}
|
|
*/
|
|
this.version = Handsontable.version;
|
|
};
|
|
|
|
/**
|
|
* @alias Options
|
|
* @constructor
|
|
* @description
|
|
|
|
* ## Constructor options
|
|
*
|
|
* Constructor options are applied using an object literal passed as a first argument to the Handsontable constructor.
|
|
*
|
|
* ```js
|
|
* var hot = new Handsontable(document.getElementById('example1'), {
|
|
* data: myArray,
|
|
* width: 400,
|
|
* height: 300
|
|
* })
|
|
* ```
|
|
*
|
|
* ---
|
|
* ## Cascading configuration
|
|
*
|
|
* Handsontable 0.9 and newer is using *Cascading Configuration*, which is fast way to provide configuration options
|
|
* for whole table, its columns and particular cells.
|
|
*
|
|
* Consider the following example:
|
|
* ```js
|
|
* var hot = new Handsontable(document.getElementById('example'), {
|
|
* readOnly: true,
|
|
* columns: [
|
|
* {readOnly: false},
|
|
* {},
|
|
* {}
|
|
* ],
|
|
* cells: function (row, col, prop) {
|
|
* var cellProperties = {};
|
|
*
|
|
* if (row === 0 && col === 0) {
|
|
* cellProperties.readOnly = true;
|
|
* }
|
|
*
|
|
* return cellProperties;
|
|
* }
|
|
* });
|
|
* ```
|
|
*
|
|
* The above notation will result in all TDs being *read only*, except for first column TDs which will be *editable*, except for the TD in top left corner which will still be *read only*.
|
|
*
|
|
* ### The Cascading Configuration model
|
|
*
|
|
* ##### 1. Constructor
|
|
*
|
|
* Configuration options that are provided using first-level `handsontable(container, {option: "value"})` and `updateSettings` method.
|
|
*
|
|
* ##### 2. Columns
|
|
*
|
|
* Configuration options that are provided using second-level object `handsontable(container, {columns: {option: "value"}]})`
|
|
*
|
|
* ##### 3. Cells
|
|
*
|
|
* Configuration options that are provided using second-level function `handsontable(container, {cells: function: (row, col, prop){ }})`
|
|
*
|
|
* ---
|
|
* ## Architecture performance
|
|
*
|
|
* The Cascading Configuration model is based on prototypical inheritance. It is much faster and memory efficient compared
|
|
* to the previous model that used jQuery extend. See: [http://jsperf.com/extending-settings](http://jsperf.com/extending-settings).
|
|
*
|
|
* ---
|
|
* __Important notice:__ In order for the data separation to work properly, make sure that each instance of Handsontable has a unique `id`.
|
|
*/
|
|
var DefaultSettings = function() {
|
|
};
|
|
|
|
DefaultSettings.prototype = {
|
|
/**
|
|
* @description
|
|
* Initial data source that will be bound to the data grid __by reference__ (editing data grid alters the data source).
|
|
* Can be Array of Array, Array of Objects or Function.
|
|
*
|
|
* See [Understanding binding as reference](http://handsontable.com/demo/understanding_reference.html).
|
|
*
|
|
* @type {Array|Function}
|
|
* @default undefined
|
|
*/
|
|
data: void 0,
|
|
|
|
/**
|
|
* @description
|
|
* Defines the structure of a new row when data source is an object.
|
|
* Default like the first data row Array or Object.
|
|
*
|
|
* See [demo/datasources.html](http://handsontable.com/demo/datasources.html) for examples.
|
|
*
|
|
* @type {Object}
|
|
* @default undefined
|
|
*/
|
|
dataSchema: void 0,
|
|
|
|
/**
|
|
* Width of the grid. Can be a number or a function that returns a number.
|
|
*
|
|
* @type {Number|Function}
|
|
* @default undefined
|
|
*/
|
|
width: void 0,
|
|
|
|
/**
|
|
* Height of the grid. Can be a number or a function that returns a number.
|
|
*
|
|
* @type {Number|Function}
|
|
* @default undefined
|
|
*/
|
|
height: void 0,
|
|
|
|
/**
|
|
* @description
|
|
* Initial number of rows.
|
|
*
|
|
* __Notice:__ This option only has effect in Handsontable constructor and only if `data` option is not provided
|
|
*
|
|
* @type {Number}
|
|
* @default 5
|
|
*/
|
|
startRows: 5,
|
|
|
|
/**
|
|
* @description
|
|
* Initial number of columns.
|
|
*
|
|
* __Notice:__ This option only has effect in Handsontable constructor and only if `data` option is not provided
|
|
*
|
|
* @type {Number}
|
|
* @default 5
|
|
*/
|
|
startCols: 5,
|
|
|
|
/**
|
|
* Setting `true` or `false` will enable or disable the default row headers (1, 2, 3).
|
|
* You can also define an array `['One', 'Two', 'Three', ...]` or a function to define the headers.
|
|
* If a function is set the index of the row is passed as a parameter.
|
|
*
|
|
* @type {Boolean|Array|Function}
|
|
* @default null
|
|
* @example
|
|
* ```js
|
|
* ...
|
|
* // as boolean
|
|
* rowHeaders: true,
|
|
* ...
|
|
*
|
|
* ...
|
|
* // as array
|
|
* rowHeaders: [1, 2, 3],
|
|
* ...
|
|
*
|
|
* ...
|
|
* // as function
|
|
* rowHeaders: function(index) {
|
|
* return index + ': AB';
|
|
* },
|
|
* ...
|
|
* ```
|
|
*/
|
|
rowHeaders: null,
|
|
|
|
/**
|
|
* Setting `true` or `false` will enable or disable the default column headers (A, B, C).
|
|
* You can also define an array `['One', 'Two', 'Three', ...]` or a function to define the headers.
|
|
* If a function is set the index of the column is passed as a parameter.
|
|
*
|
|
* @type {Boolean|Array|Function}
|
|
* @default null
|
|
* @example
|
|
* ```js
|
|
* ...
|
|
* // as boolean
|
|
* colHeaders: true,
|
|
* ...
|
|
*
|
|
* ...
|
|
* // as array
|
|
* colHeaders: ['A', 'B', 'C'],
|
|
* ...
|
|
*
|
|
* ...
|
|
* // as function
|
|
* colHeaders: function(index) {
|
|
* return index + ': AB';
|
|
* },
|
|
* ...
|
|
* ```
|
|
*/
|
|
colHeaders: null,
|
|
|
|
/**
|
|
* Defines column widths in pixels. Accepts number, string (that will be converted to number),
|
|
* array of numbers (if you want to define column width separately for each column) or a
|
|
* function (if you want to set column width dynamically on each render).
|
|
*
|
|
* @type {Array|Function|Number|String}
|
|
* @default undefined
|
|
*/
|
|
colWidths: void 0,
|
|
|
|
/**
|
|
* @description
|
|
* Defines the cell properties and data binding for certain columns.
|
|
*
|
|
* __Notice:__ Using this option sets a fixed number of columns (options `startCols`, `minCols`, `maxCols` will be ignored).
|
|
*
|
|
* See [demo/datasources.html](http://handsontable.com/demo/datasources.html) for examples.
|
|
*
|
|
* @type {Array}
|
|
* @default undefined
|
|
* @example
|
|
* ```js
|
|
* ...
|
|
* var exampleContainer = document.getElementById('example');
|
|
* var hot = new Handsontable(exampleContainer, {
|
|
* columns: [
|
|
* {
|
|
* // column options for the first column
|
|
* type: 'numeric',
|
|
* format: '0,0.00 $'
|
|
* },
|
|
* {
|
|
* // column options for the second column
|
|
* type: 'text',
|
|
* readOnly: true
|
|
* }
|
|
* ]
|
|
* });
|
|
* ...
|
|
* ```
|
|
*/
|
|
columns: void 0,
|
|
|
|
/**
|
|
* @description
|
|
* Defines the cell properties for given `row`, `col`, `prop` coordinates.
|
|
* Any constructor or column option may be overwritten for a particular cell (row/column combination), using `cell`
|
|
* array passed to the Handsontable constructor. Or using `cells` function property to the Handsontable constructor.
|
|
*
|
|
* @type {Function}
|
|
* @default undefined
|
|
* @example
|
|
* ```js
|
|
* ...
|
|
* var hot = new Handsontable(document.getElementById('example'), {
|
|
* cells: function (row, col, prop) {
|
|
* var cellProperties = {};
|
|
*
|
|
* if (row === 0 && col === 0) {
|
|
* cellProperties.readOnly = true;
|
|
* }
|
|
*
|
|
* return cellProperties;
|
|
* }
|
|
* });
|
|
* ...
|
|
* ```
|
|
*/
|
|
cells: void 0,
|
|
|
|
/**
|
|
* Any constructor or column option may be overwritten for a particular cell (row/column combination), using `cell`
|
|
* array passed to the Handsontable constructor.
|
|
*
|
|
* @type {Array}
|
|
* @default []
|
|
* @example
|
|
* ```js
|
|
* ...
|
|
* var hot = new Handsontable(document.getElementById('example'), {
|
|
* cell: [
|
|
* {row: 0, col: 0, readOnly: true}
|
|
* ]
|
|
* });
|
|
* ...
|
|
* ```
|
|
*/
|
|
cell: [],
|
|
|
|
/**
|
|
* @description
|
|
* If `true`, enables Comments plugin, which enables applying cell comments through the context menu
|
|
* (configurable with context menu keys commentsAddEdit, commentsRemove).
|
|
*
|
|
* To initialize Handsontable with predefined comments, provide cell coordinates and comment texts in form of an array.
|
|
*
|
|
* See [Comments](http://handsontable.com/demo/comments.html) demo for examples.
|
|
*
|
|
* @since 0.11.0
|
|
* @type {Boolean|Array}
|
|
* @default false
|
|
* @example
|
|
* ```js
|
|
* ...
|
|
* var hot = new Handsontable(document.getElementById('example'), {
|
|
* comments: [{row: 1, col: 1, comment: "Test comment"}]
|
|
* });
|
|
* ...
|
|
* ```
|
|
*/
|
|
comments: false,
|
|
|
|
/**
|
|
* @description
|
|
* If `true`, enables Custom Borders plugin, which enables applying custom borders through the context menu (configurable with context menu key borders).
|
|
*
|
|
* To initialize Handsontable with predefined custom borders, provide cell coordinates and border styles in form of an array.
|
|
*
|
|
* See [Custom Borders](http://handsontable.com/demo/custom_borders.html) demo for examples.
|
|
*
|
|
* @since 0.11.0
|
|
* @type {Boolean|Array}
|
|
* @default false
|
|
* @example
|
|
* ```js
|
|
* ...
|
|
* var hot = new Handsontable(document.getElementById('example'), {
|
|
* customBorders: [
|
|
* {range: {
|
|
* from: {row: 1, col: 1},
|
|
* to: {row: 3, col: 4}},
|
|
* left: {},
|
|
* right: {},
|
|
* top: {},
|
|
* bottom: {}
|
|
* }
|
|
* ],
|
|
* });
|
|
* ...
|
|
*
|
|
* // or
|
|
* ...
|
|
* var hot = new Handsontable(document.getElementById('example'), {
|
|
* customBorders: [
|
|
* {row: 2, col: 2, left: {width: 2, color: 'red'},
|
|
* right: {width: 1, color: 'green'}, top: '', bottom: ''}
|
|
* ],
|
|
* });
|
|
* ...
|
|
* ```
|
|
*/
|
|
customBorders: false,
|
|
|
|
/**
|
|
* Minimum number of rows. At least that many of rows will be created during initialization.
|
|
*
|
|
* @type {Number}
|
|
* @default 0
|
|
*/
|
|
minRows: 0,
|
|
|
|
/**
|
|
* Minimum number of columns. At least that many of columns will be created during initialization.
|
|
*
|
|
* @type {Number}
|
|
* @default 0
|
|
*/
|
|
minCols: 0,
|
|
|
|
/**
|
|
* Maximum number of rows.
|
|
*
|
|
* @type {Number}
|
|
* @default Infinity
|
|
*/
|
|
maxRows: Infinity,
|
|
|
|
/**
|
|
* Maximum number of cols.
|
|
*
|
|
* @type {Number}
|
|
* @default Infinity
|
|
*/
|
|
maxCols: Infinity,
|
|
|
|
/**
|
|
* When set to 1 (or more), Handsontable will add a new row at the end of grid if there are no more empty rows.
|
|
*
|
|
* @type {Number}
|
|
* @default 0
|
|
*/
|
|
minSpareRows: 0,
|
|
|
|
/**
|
|
* When set to 1 (or more), Handsontable will add a new column at the end of grid if there are no more empty columns.
|
|
*
|
|
* @type {Number}
|
|
* @default 0
|
|
*/
|
|
minSpareCols: 0,
|
|
|
|
/**
|
|
* @type {Boolean}
|
|
* @default true
|
|
*/
|
|
allowInsertRow: true,
|
|
|
|
/**
|
|
* @type {Boolean}
|
|
* @default true
|
|
*/
|
|
allowInsertColumn: true,
|
|
|
|
/**
|
|
* @type {Boolean}
|
|
* @default true
|
|
*/
|
|
allowRemoveRow: true,
|
|
|
|
/**
|
|
* @type {Boolean}
|
|
* @default true
|
|
*/
|
|
allowRemoveColumn: true,
|
|
|
|
/**
|
|
* If true, selection of multiple cells using keyboard or mouse is allowed.
|
|
*
|
|
* @type {Boolean}
|
|
* @default true
|
|
*/
|
|
multiSelect: true,
|
|
|
|
/**
|
|
* Enables the fill handle (drag-down and copy-down) functionality, which shows the small rectangle in bottom
|
|
* right corner of the selected area, that let's you expand values to the adjacent cells.
|
|
*
|
|
* Possible values: `true` (to enable in all directions), `"vertical"` or `"horizontal"` (to enable in one direction),
|
|
* `false` (to disable completely). Setting to `true` enables the fillHandle plugin.
|
|
*
|
|
* @type {Boolean|String}
|
|
* @default true
|
|
*/
|
|
fillHandle: true,
|
|
|
|
/**
|
|
* Allows to specify the number of rows fixed (aka freezed) on the top of the table.
|
|
*
|
|
* @type {Number}
|
|
* @default 0
|
|
*/
|
|
fixedRowsTop: 0,
|
|
|
|
/**
|
|
* Allows to specify the number of columns fixed (aka freezed) on the left side of the table.
|
|
*
|
|
* @type {Number}
|
|
* @default 0
|
|
*/
|
|
fixedColumnsLeft: 0,
|
|
|
|
/**
|
|
* If `true`, mouse click outside the grid will deselect the current selection.
|
|
*
|
|
* @type {Boolean}
|
|
* @default true
|
|
*/
|
|
outsideClickDeselects: true,
|
|
|
|
/**
|
|
* If `true`, <kbd>ENTER</kbd> begins editing mode (like Google Docs). If `false`, <kbd>ENTER</kbd> moves to next
|
|
* row (like Excel) and adds new row if necessary. <kbd>TAB</kbd> adds new column if necessary.
|
|
*
|
|
* @type {Boolean}
|
|
* @default true
|
|
*/
|
|
enterBeginsEditing: true,
|
|
|
|
/**
|
|
* Defines cursor move after <kbd>ENTER</kbd> is pressed (<kbd>SHIFT</kbd> + <kbd>ENTER</kbd> uses negative vector).
|
|
* Can be an object or a function that returns an object. The event argument passed to the function
|
|
* is a DOM Event object received after a <kbd>ENTER</kbd> key has been pressed. This event object can be used to check
|
|
* whether user pressed <kbd>ENTER</kbd> or <kbd>SHIFT</kbd> + <kbd>ENTER</kbd>.
|
|
*
|
|
* @type {Object|Function}
|
|
* @default {row: 1, col: 0}
|
|
*/
|
|
enterMoves: {row: 1, col: 0},
|
|
|
|
/**
|
|
* Defines cursor move after <kbd>TAB</kbd> is pressed (<kbd>SHIFT</kbd> + <kbd>TAB</kbd> uses negative vector).
|
|
* Can be an object or a function that returns an object. The event argument passed to the function
|
|
* is a DOM Event object received after a <kbd>TAB</kbd> key has been pressed. This event object can be used to check
|
|
* whether user pressed <kbd>TAB</kbd> or <kbd>SHIFT</kbd> + <kbd>TAB</kbd>.
|
|
*
|
|
* @type {Object}
|
|
* @default {row: 0, col: 1}
|
|
*/
|
|
tabMoves: {row: 0, col: 1},
|
|
|
|
/**
|
|
* If `true`, pressing <kbd>TAB</kbd> or right arrow in the last column will move to first column in next row
|
|
*
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
autoWrapRow: false,
|
|
|
|
/**
|
|
* If `true`, pressing <kbd>ENTER</kbd> or down arrow in the last row will move to first row in next column
|
|
*
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
autoWrapCol: false,
|
|
|
|
/**
|
|
* Maximum number of rows than can be copied to clipboard using <kbd>CTRL</kbd> + <kbd>C</kbd>.
|
|
*
|
|
* @type {Number}
|
|
* @default 1000
|
|
*/
|
|
copyRowsLimit: 1000,
|
|
|
|
/**
|
|
* Maximum number of columns than can be copied to clipboard using <kbd>CTRL</kbd> + <kbd>C</kbd>.
|
|
*
|
|
* @type {Number}
|
|
* @default 1000
|
|
*/
|
|
copyColsLimit: 1000,
|
|
|
|
/**
|
|
* Defines paste (<kbd>CTRL</kbd> + <kbd>V</kbd>) behavior. Default value `"overwrite"` will paste clipboard value over current selection.
|
|
* When set to `"shift_down"`, clipboard data will be pasted in place of current selection, while all selected cells are moved down.
|
|
* When set to `"shift_right"`, clipboard data will be pasted in place of current selection, while all selected cells are moved right.
|
|
*
|
|
* @type {String}
|
|
* @default 'overwrite'
|
|
*/
|
|
pasteMode: 'overwrite',
|
|
|
|
/**
|
|
* @description
|
|
* Turn on saving the state of column sorting, columns positions and columns sizes in local storage.
|
|
*
|
|
* You can save any sort of data in local storage in to preserve table state between page reloads.
|
|
* In order to enable data storage mechanism, `persistentState` option must be set to `true` (you can set it
|
|
* either during Handsontable initialization or using the `updateSettings` method). When `persistentState` is enabled it exposes 3 hooks:
|
|
*
|
|
* __persistentStateSave__ (key: String, value: Mixed)
|
|
*
|
|
* * Saves value under given key in browser local storage.
|
|
*
|
|
* __persistentStateLoad__ (key: String, valuePlaceholder: Object)
|
|
*
|
|
* * Loads `value`, saved under given key, form browser local storage. The loaded `value` will be saved in `valuePlaceholder.value`
|
|
* (this is due to specific behaviour of `Hooks.run()` method). If no value have been saved under key `valuePlaceholder.value`
|
|
* will be `undefined`.
|
|
*
|
|
* __persistentStateReset__ (key: String)
|
|
*
|
|
* * Clears the value saved under `key`. If no `key` is given, all values associated with table will be cleared.
|
|
*
|
|
* __Note:__ The main reason behind using `persistentState` hooks rather than regular LocalStorage API is that it
|
|
* ensures separation of data stored by multiple Handsontable instances. In other words, if you have two (or more)
|
|
* instances of Handsontable on one page, data saved by one instance won't be accessible by the second instance.
|
|
* Those two instances can store data under the same key and no data would be overwritten.
|
|
*
|
|
* __Important:__ In order for the data separation to work properly, make sure that each instance of Handsontable has a unique `id`.
|
|
*
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
persistentState: false,
|
|
|
|
/**
|
|
* Class name for all visible rows in current selection.
|
|
*
|
|
* @type {String}
|
|
* @default undefined
|
|
*/
|
|
currentRowClassName: void 0,
|
|
|
|
/**
|
|
* Class name for all visible columns in current selection.
|
|
*
|
|
* @type {String}
|
|
* @default undefined
|
|
*/
|
|
currentColClassName: void 0,
|
|
|
|
/**
|
|
* @description
|
|
* [Column stretching](http://handsontable.com/demo/scroll.html) mode. Possible values: `"none"`, `"last"`, `"all"`.
|
|
*
|
|
* @type {String}
|
|
* @default 'none'
|
|
*/
|
|
stretchH: 'none',
|
|
|
|
/**
|
|
* Lets you overwrite the default `isEmptyRow` method.
|
|
*
|
|
* @type {Function}
|
|
* @param {Number} row
|
|
* @returns {Boolean}
|
|
*/
|
|
isEmptyRow: function(row) {
|
|
var col, colLen, value, meta;
|
|
|
|
for (col = 0, colLen = this.countCols(); col < colLen; col++) {
|
|
value = this.getDataAtCell(row, col);
|
|
|
|
if (value !== '' && value !== null && typeof value !== 'undefined') {
|
|
if (typeof value === 'object') {
|
|
meta = this.getCellMeta(row, col);
|
|
|
|
return helper.isObjectEquals(this.getSchema()[meta.prop], value);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Lets you overwrite the default `isEmptyCol` method.
|
|
*
|
|
* @type {Function}
|
|
* @param {Number} col
|
|
* @returns {Boolean}
|
|
*/
|
|
isEmptyCol: function(col) {
|
|
var row, rowLen, value;
|
|
|
|
for (row = 0, rowLen = this.countRows(); row < rowLen; row++) {
|
|
value = this.getDataAtCell(row, col);
|
|
|
|
if (value !== '' && value !== null && typeof value !== 'undefined') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* When set to `true`, the table is rerendered when it is detected that it was made visible in DOM.
|
|
*
|
|
* @type {Boolean}
|
|
* @default true
|
|
*/
|
|
observeDOMVisibility: true,
|
|
|
|
/**
|
|
* If set to `true`, cells will accept value that is marked as invalid by cell `validator`, with a background color
|
|
* automatically applied using CSS class `htInvalid`. If set to `false`, cells will not accept invalid value.
|
|
*
|
|
* @type {Boolean}
|
|
* @default true
|
|
* @since 0.9.5
|
|
*/
|
|
allowInvalid: true,
|
|
|
|
/**
|
|
* CSS class name for cells that did not pass validation.
|
|
*
|
|
* @type {String}
|
|
* @default 'htInvalid'
|
|
*/
|
|
invalidCellClassName: 'htInvalid',
|
|
|
|
/**
|
|
* When set to an non-empty string, displayed as the cell content for empty cells.
|
|
*
|
|
* @type {Boolean|String}
|
|
* @default false
|
|
*/
|
|
placeholder: false,
|
|
|
|
/**
|
|
* CSS class name for cells that have a placeholder in use.
|
|
*
|
|
* @type {String}
|
|
* @default 'htPlaceholder'
|
|
*/
|
|
placeholderCellClassName: 'htPlaceholder',
|
|
|
|
/**
|
|
* CSS class name for read-only cells.
|
|
*
|
|
* @type {String}
|
|
* @default 'htDimmed'
|
|
*/
|
|
readOnlyCellClassName: 'htDimmed',
|
|
|
|
/**
|
|
* String or rendering function.
|
|
*
|
|
* String may be one of the following predefined values: `autocomplete`, `checkbox`, `text`, `numeric`. Function will
|
|
* receive the following arguments: `function(instance, TD, row, col, prop, value, cellProperties) {}`.
|
|
* You can map your own function to a string like this: `Handsontable.cellLookup.renderer.myRenderer = myRenderer;`
|
|
*
|
|
* @type {String|Function}
|
|
* @default undefined
|
|
*/
|
|
renderer: void 0,
|
|
|
|
/**
|
|
* @type {String}
|
|
* @default 'htCommentCell'
|
|
*/
|
|
commentedCellClassName: 'htCommentCell',
|
|
|
|
/**
|
|
* Setting to `true` enables selecting just a fragment of the text within a single cell or between adjacent cells.
|
|
*
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
fragmentSelection: false,
|
|
|
|
/**
|
|
* @description
|
|
* Make cell [read only](http://handsontable.com/demo/readonly.html).
|
|
*
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
readOnly: false,
|
|
|
|
/**
|
|
* @description
|
|
* Setting to true enables the search plugin (see [demo](http://handsontable.com/demo/search.html)).
|
|
*
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
search: false,
|
|
|
|
/**
|
|
* @description
|
|
* Shortcut to define combination of cell renderer and editor for the column.
|
|
*
|
|
* Possible values:
|
|
* * text
|
|
* * [numeric](http://handsontable.com/demo/numeric.html)
|
|
* * [date](http://handsontable.com/demo/date.html)
|
|
* * [checkbox](http://handsontable.com/demo/checkbox.html)
|
|
* * [autocomplete](http://handsontable.com/demo/autocomplete.html)
|
|
* * [handsontable](http://handsontable.com/demo/handsontable.html)
|
|
*
|
|
* @type {String}
|
|
* @default 'text'
|
|
*/
|
|
type: 'text',
|
|
|
|
/**
|
|
* @description
|
|
* Make cell copyable (pressing <kbd>CTRL</kbd> + <kbd>C</kbd> on your keyboard moves its value to system clipboard).
|
|
*
|
|
* __Note:__ this setting is `false` by default for cells with type `password`.
|
|
*
|
|
* @type {Boolean}
|
|
* @default true
|
|
* @since 0.10.2
|
|
*/
|
|
copyable: true,
|
|
|
|
/**
|
|
* String or rendering function.
|
|
* String may be one of the following predefined values: `autocomplete`, `checkbox`, `text`, `date`, `handsontable`, `mobile`.
|
|
*
|
|
* @type {String|Function|Boolean}
|
|
* @default undefined
|
|
*/
|
|
editor: void 0,
|
|
|
|
/**
|
|
* @description
|
|
* Autocomplete definitions. See [demo/autocomplete.html](http://handsontable.com/demo/autocomplete.html) for examples and definitions.
|
|
*
|
|
* @type {Array}
|
|
* @default undefined
|
|
*/
|
|
autoComplete: void 0,
|
|
|
|
/**
|
|
* Setting to true enables the debug mode, currently used to test the correctness of the row and column
|
|
* header fixed positioning on a layer above the master table.
|
|
*
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
debug: false,
|
|
|
|
/**
|
|
* When set to `true`, the text of the cell content is wrapped if it does not fit in the fixed column width.
|
|
*
|
|
* @type {Boolean}
|
|
* @default true
|
|
* @since 0.11.0
|
|
*/
|
|
wordWrap: true,
|
|
|
|
/**
|
|
* CSS class name added to cells with cell meta `wordWrap: false`.
|
|
*
|
|
* @type {String}
|
|
* @default 'htNoWrap'
|
|
* @since 0.11.0
|
|
*/
|
|
noWordWrapClassName: 'htNoWrap',
|
|
|
|
/**
|
|
* @description
|
|
* Defines if the right-click context menu should be enabled. Context menu allows to create new row or
|
|
* column at any place in the grid. Possible values: `true` (to enable basic options), `false` (to disable completely)
|
|
* or array of any available strings: `["row_above", "row_below", "col_left", "col_right",
|
|
* "remove_row", "remove_col", "undo", "redo", "sep1", "sep2", "sep3"]`.
|
|
*
|
|
* See [demo/contextmenu.html](http://handsontable.com/demo/contextmenu.html) for examples.
|
|
*
|
|
* @type {Boolean|Array}
|
|
* @default undefined
|
|
*/
|
|
contextMenu: void 0,
|
|
|
|
/**
|
|
* If `true`, undo/redo functionality is enabled.
|
|
*
|
|
* @type {Boolean}
|
|
* @default undefined
|
|
*/
|
|
undo: void 0,
|
|
|
|
/**
|
|
* @description
|
|
* Turn on [Column sorting](http://handsontable.com/demo/sorting.html).
|
|
*
|
|
* @type {Boolean|Object}
|
|
* @default undefined
|
|
*/
|
|
columnSorting: void 0,
|
|
|
|
/**
|
|
* @description
|
|
* Turn on [Manual column move](http://handsontable.com/demo/column_move.html), if set to a boolean or define initial
|
|
* column order, if set to an array of column indexes.
|
|
*
|
|
* @type {Boolean|Array}
|
|
* @default undefined
|
|
*/
|
|
manualColumnMove: void 0,
|
|
|
|
/**
|
|
* @description
|
|
* Turn on [Manual column resize](http://handsontable.com/demo/column_resize.html), if set to a boolean or define initial
|
|
* column resized widths, if set to an array of numbers.
|
|
*
|
|
* @type {Boolean|Array}
|
|
* @default undefined
|
|
*/
|
|
manualColumnResize: void 0,
|
|
|
|
/**
|
|
* @description
|
|
* Turn on [Manual row move](http://handsontable.com/demo/column_move.html), if set to a boolean or define initial
|
|
* row order, if set to an array of row indexes.
|
|
*
|
|
* @type {Boolean|Array}
|
|
* @default undefined
|
|
* @since 0.11.0
|
|
*/
|
|
manualRowMove: void 0,
|
|
|
|
/**
|
|
* @description
|
|
* Turn on [Manual row resize](http://handsontable.com/demo/column_resize.html), if set to a boolean or define initial
|
|
* row resized heights, if set to an array of numbers.
|
|
*
|
|
* @type {Boolean|Array}
|
|
* @default undefined
|
|
* @since 0.11.0
|
|
*/
|
|
manualRowResize: void 0,
|
|
|
|
/**
|
|
* @description
|
|
* Setting to true or array enables the mergeCells plugin, which enables the merging of the cells. (see [demo](http://handsontable.com/demo/merge_cells.html)).
|
|
* You can provide the merged cells on the pageload if you feed the mergeCells option with an array.
|
|
*
|
|
* @type {Boolean|Array}
|
|
* @default false
|
|
*/
|
|
mergeCells: false,
|
|
|
|
/**
|
|
* Number of rows to be prerendered before and after the viewport is changed.
|
|
*
|
|
* @type {Number}
|
|
* @default 10
|
|
*/
|
|
viewportRowRenderingOffset: 10,
|
|
|
|
/**
|
|
* Number of columns to be prerendered before and after the viewport is changed.
|
|
*
|
|
* @type {Number}
|
|
* @default 10
|
|
*/
|
|
viewportColumnRenderingOffset: 10,
|
|
|
|
/**
|
|
* @description
|
|
* If `true`, enables Grouping plugin, which enables applying expandable row and column groups.
|
|
* To initialize Handsontable with predefined groups, provide row or column group start and end coordinates in form of an array.
|
|
*
|
|
* See [Grouping](http://handsontable.com/demo/grouping.html) demo for examples.
|
|
*
|
|
* @type {Boolean|Array}
|
|
* @default undefined
|
|
* @since 0.11.4
|
|
* @example
|
|
* ```js
|
|
* ...
|
|
* // as boolean
|
|
* groups: true,
|
|
* ...
|
|
*
|
|
* ...
|
|
* // as array
|
|
* groups: [{cols: [0, 2]}, {cols: [5, 15], rows: [0, 5]}],
|
|
* ...
|
|
* ```
|
|
*/
|
|
groups: void 0,
|
|
|
|
/**
|
|
* A usually small function or regular expression that validates the input.
|
|
* After you determine if the input is valid, execute `callback(true)` or `callback(false)` to proceed with the execution.
|
|
* In function, `this` binds to cellProperties.
|
|
*
|
|
* @type {Function|RegExp}
|
|
* @default undefined
|
|
* @since 0.9.5
|
|
*/
|
|
validator: void 0,
|
|
|
|
/**
|
|
* @description
|
|
* Disable visual cells selection.
|
|
*
|
|
* Possible values:
|
|
* * `true` - Disables any type of visual selection (current and area selection),
|
|
* * `false` - Enables any type of visual selection. This is default value.
|
|
* * `current` - Disables to appear only current selected cell.
|
|
* * `area` - Disables to appear only multiple selected cells.
|
|
*
|
|
* @type {Boolean|String|Array}
|
|
* @default false
|
|
* @since 0.13.2
|
|
* @example
|
|
* ```js
|
|
* ...
|
|
* // as boolean
|
|
* disableVisualSelection: true,
|
|
* ...
|
|
*
|
|
* ...
|
|
* // as string ('current' or 'area')
|
|
* disableVisualSelection: 'current',
|
|
* ...
|
|
*
|
|
* ...
|
|
* // as array
|
|
* disableVisualSelection: ['current', 'area'],
|
|
* ...
|
|
* ```
|
|
*/
|
|
disableVisualSelection: false,
|
|
|
|
/**
|
|
* @description
|
|
* Set whether to display the current sorting indicator (a triangle icon in the column header, specifying the sorting order).
|
|
*
|
|
* @type {Boolean}
|
|
* @default false
|
|
* @since 0.15.0-beta3
|
|
*/
|
|
sortIndicator: false,
|
|
manualColumnFreeze: void 0,
|
|
trimWhitespace: true,
|
|
settings: void 0,
|
|
source: void 0,
|
|
title: void 0,
|
|
checkedTemplate: void 0,
|
|
uncheckedTemplate: void 0,
|
|
format: void 0,
|
|
className: void 0
|
|
};
|
|
Handsontable.DefaultSettings = DefaultSettings;
|