123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- (function (Handsontable) {
- 'use strict';
- function HandsontableFormula() {
- var formulaRenderer = function (instance, TD, row, col, prop, value, cellProperties) {
- if (instance.formulasEnabled) {
- // translate coordinates into cellId
- var cellId = instance.plugin.utils.translateCellCoords({row: row, col: col}),
- prevFormula = null,
- formula = null,
- needUpdate = false,
- error, result;
- if (!cellId) {
- return;
- }
- // get cell data
- var item = instance.plugin.matrix.getItem(cellId);
- if (item) {
- needUpdate = !!item.needUpdate;
- if (item.error) {
- prevFormula = item.formula;
- error = item.error;
- if (needUpdate) {
- error = null;
- }
- }
- }
- // check if typed formula or cell value should be recalculated
- if ((value && value[0] === '=') || needUpdate) {
- formula = value.substr(1).toUpperCase();
- if (!error || formula !== prevFormula) {
- var currentItem = item;
- if (!currentItem) {
- // define item to rulesJS matrix if not exists
- item = {
- id: cellId,
- formula: formula
- };
- // add item to matrix
- currentItem = instance.plugin.matrix.addItem(item);
- }
- // parse formula
- var newValue = instance.plugin.parse(formula, {row: row, col: col, id: cellId});
- // update item value and error
- instance.plugin.matrix.updateItem(currentItem, {formula: formula, value: newValue.result, error: newValue.error, needUpdate: false});
- error = newValue.error;
- result = newValue.result;
- // update cell value in hot
- value = error || result;
- }
- }
- if (error) {
- // clear cell value
- if (!value) {
- // reset error
- error = null;
- } else {
- // show error
- value = error;
- }
- }
- // change background color
- if (instance.plugin.utils.isSet(error)) {
- Handsontable.Dom.addClass(TD, 'formula-error');
- } else if (instance.plugin.utils.isSet(result)) {
- Handsontable.Dom.removeClass(TD, 'formula-error');
- Handsontable.Dom.addClass(TD, 'formula');
- }
- }
- // apply changes
- textCell.renderer.apply(this, [instance, TD, row, col, prop, value, cellProperties]);
- };
- var afterChange = function (changes, source) {
- var instance = this;
- if (!instance.formulasEnabled) {
- return;
- }
- if (source === 'edit' || source === 'undo' || source === 'autofill') {
- var rerender = false;
- changes.forEach(function (item) {
- var row = item[0],
- col = item[1],
- prevValue = item[2],
- value = item[3];
- var cellId = instance.plugin.utils.translateCellCoords({row: row, col: col});
- // if changed value, all references cells should be recalculated
- if (value[0] !== '=' || prevValue !== value) {
- instance.plugin.matrix.removeItem(cellId);
- // get referenced cells
- var deps = instance.plugin.matrix.getDependencies(cellId);
- // update cells
- deps.forEach(function (itemId) {
- instance.plugin.matrix.updateItem(itemId, {needUpdate: true});
- });
- rerender = true;
- }
- });
- if (rerender) {
- instance.render();
- }
- }
- };
- var beforeAutofillInsidePopulate = function (index, direction, data, deltas, iterators, selected) {
- var instance = this;
- var r = index.row,
- c = index.col,
- value = data[r][c],
- delta = 0,
- rlength = data.length, // rows
- clength = data ? data[0].length : 0; //cols
- if (value[0] === '=') { // formula
- if (['down', 'up'].indexOf(direction) !== -1) {
- delta = rlength * iterators.row;
- } else if (['right', 'left'].indexOf(direction) !== -1) {
- delta = clength * iterators.col;
- }
- return {
- value: instance.plugin.utils.updateFormula(value, direction, delta),
- iterators: iterators
- }
- } else { // other value
- // increment or decrement values for more than 2 selected cells
- if (rlength >= 2 || clength >= 2) {
- var newValue = instance.plugin.helper.number(value),
- ii,
- start;
- if (instance.plugin.utils.isNumber(newValue)) {
- if (['down', 'up'].indexOf(direction) !== -1) {
- delta = deltas[0][c];
- if (direction === 'down') {
- newValue += (delta * rlength * iterators.row);
- } else {
- ii = (selected.row - r) % rlength;
- start = ii > 0 ? rlength - ii : 0;
- newValue = instance.plugin.helper.number(data[start][c]);
- newValue += (delta * rlength * iterators.row);
- // last element in array -> decrement iterator
- // iterator cannot be less than 1
- if (iterators.row > 1 && (start + 1) === rlength) {
- iterators.row--;
- }
- }
- } else if (['right', 'left'].indexOf(direction) !== -1) {
- delta = deltas[r][0];
- if (direction === 'right') {
- newValue += (delta * clength * iterators.col);
- } else {
- ii = (selected.col - c) % clength;
- start = ii > 0 ? clength - ii : 0;
- newValue = instance.plugin.helper.number(data[r][start]);
- newValue += (delta * clength * (iterators.col || 1));
- // last element in array -> decrement iterator
- // iterator cannot be less than 1
- if (iterators.col > 1 && (start + 1) === clength) {
- iterators.col--;
- }
- }
- }
- return {
- value: newValue,
- iterators: iterators
- }
- }
- }
- }
- return {
- value: value,
- iterators: iterators
- };
- };
- var afterCreateRow = function (row, amount, auto) {
- if (auto) {
- return;
- }
- var instance = this;
- var selectedRow = instance.plugin.utils.isArray(instance.getSelected()) ? instance.getSelected()[0] : undefined;
- if (instance.plugin.utils.isUndefined(selectedRow)) {
- return;
- }
- var direction = (selectedRow >= row) ? 'before' : 'after',
- items = instance.plugin.matrix.getRefItemsToRow(row),
- counter = 1,
- changes = [];
- items.forEach(function (id) {
- var item = instance.plugin.matrix.getItem(id),
- formula = instance.plugin.utils.changeFormula(item.formula, 1, {row: row}), // update formula if needed
- newId = id;
- if (formula !== item.formula) { // formula updated
- // change row index and get new coordinates
- if ((direction === 'before' && selectedRow <= item.row) || (direction === 'after' && selectedRow < item.row)) {
- newId = instance.plugin.utils.changeRowIndex(id, counter);
- }
- var cellCoords = instance.plugin.utils.cellCoords(newId);
- if (newId !== id) {
- // remove current item from matrix
- instance.plugin.matrix.removeItem(id);
- }
- // set updated formula in new cell
- changes.push([cellCoords.row, cellCoords.col, '=' + formula]);
- }
- });
- if (items) {
- instance.plugin.matrix.removeItemsBelowRow(row);
- }
- if (changes) {
- instance.setDataAtCell(changes);
- }
- };
- var afterCreateCol = function (col) {
- var instance = this;
- var selectedCol = instance.plugin.utils.isArray(instance.getSelected()) ? instance.getSelected()[1] : undefined;
- if (instance.plugin.utils.isUndefined(selectedCol)) {
- return;
- }
- var items = instance.plugin.matrix.getRefItemsToColumn(col),
- counter = 1,
- direction = (selectedCol >= col) ? 'before' : 'after',
- changes = [];
- items.forEach(function (id) {
- var item = instance.plugin.matrix.getItem(id),
- formula = instance.plugin.utils.changeFormula(item.formula, 1, {col: col}), // update formula if needed
- newId = id;
- if (formula !== item.formula) { // formula updated
- // change col index and get new coordinates
- if ((direction === 'before' && selectedCol <= item.col) || (direction === 'after' && selectedCol < item.col)) {
- newId = instance.plugin.utils.changeColIndex(id, counter);
- }
- var cellCoords = instance.plugin.utils.cellCoords(newId);
- if (newId !== id) {
- // remove current item from matrix if id changed
- instance.plugin.matrix.removeItem(id);
- }
- // set updated formula in new cell
- changes.push([cellCoords.row, cellCoords.col, '=' + formula]);
- }
- });
- if (items) {
- instance.plugin.matrix.removeItemsBelowCol(col);
- }
- if (changes) {
- instance.setDataAtCell(changes);
- }
- };
- var formulaCell = {
- renderer: formulaRenderer,
- editor: Handsontable.editors.TextEditor,
- dataType: 'formula'
- };
- var textCell = {
- renderer: Handsontable.renderers.TextRenderer,
- editor: Handsontable.editors.TextEditor
- };
- this.init = function () {
- var instance = this;
- instance.formulasEnabled = !!instance.getSettings().formulas;
- if (instance.formulasEnabled) {
- var custom = {
- cellValue: instance.getDataAtCell
- };
- instance.plugin = new ruleJS();
- instance.plugin.init();
- instance.plugin.custom = custom;
- Handsontable.cellTypes['formula'] = formulaCell;
- Handsontable.TextCell.renderer = formulaRenderer;
- instance.addHook('afterChange', afterChange);
- instance.addHook('beforeAutofillInsidePopulate', beforeAutofillInsidePopulate);
- instance.addHook('afterCreateRow', afterCreateRow);
- instance.addHook('afterCreateCol', afterCreateCol);
- } else {
- instance.removeHook('afterChange', afterChange);
- instance.removeHook('beforeAutofillInsidePopulate', beforeAutofillInsidePopulate);
- instance.removeHook('afterCreateRow', afterCreateRow);
- instance.removeHook('afterCreateCol', afterCreateCol);
- }
- };
- }
- var htFormula = new HandsontableFormula();
- Handsontable.hooks.add('beforeInit', htFormula.init);
- Handsontable.hooks.add('afterUpdateSettings', function () {
- htFormula.init.call(this, 'afterUpdateSettings')
- });
- })(Handsontable);
|