UNPKG

tabulator-tables

Version:

Interactive table generation JavaScript library

726 lines (560 loc) 18.7 kB
import Module from '../../core/Module.js'; import Helpers from '../../core/tools/Helpers.js'; import defaultEditors from './defaults/editors.js'; class Edit extends Module{ constructor(table){ super(table); this.currentCell = false; //hold currently editing cell this.mouseClick = false; //hold mousedown state to prevent click binding being overridden by editor opening this.recursionBlock = false; //prevent focus recursion this.invalidEdit = false; this.editedCells = []; this.editors = Edit.editors; this.registerColumnOption("editable"); this.registerColumnOption("editor"); this.registerColumnOption("editorParams"); this.registerColumnOption("cellEditing"); this.registerColumnOption("cellEdited"); this.registerColumnOption("cellEditCancelled"); this.registerTableFunction("getEditedCells", this.getEditedCells.bind(this)); this.registerTableFunction("clearCellEdited", this.clearCellEdited.bind(this)); this.registerTableFunction("navigatePrev", this.navigatePrev.bind(this)); this.registerTableFunction("navigateNext", this.navigateNext.bind(this)); this.registerTableFunction("navigateLeft", this.navigateLeft.bind(this)); this.registerTableFunction("navigateRight", this.navigateRight.bind(this)); this.registerTableFunction("navigateUp", this.navigateUp.bind(this)); this.registerTableFunction("navigateDown", this.navigateDown.bind(this)); this.registerComponentFunction("cell", "isEdited", this.cellIsEdited.bind(this)); this.registerComponentFunction("cell", "clearEdited", this.clearEdited.bind(this)); this.registerComponentFunction("cell", "edit", this.editCell.bind(this)); this.registerComponentFunction("cell", "cancelEdit", this.cellCancelEdit.bind(this)); this.registerComponentFunction("cell", "navigatePrev", this.navigatePrev.bind(this)); this.registerComponentFunction("cell", "navigateNext", this.navigateNext.bind(this)); this.registerComponentFunction("cell", "navigateLeft", this.navigateLeft.bind(this)); this.registerComponentFunction("cell", "navigateRight", this.navigateRight.bind(this)); this.registerComponentFunction("cell", "navigateUp", this.navigateUp.bind(this)); this.registerComponentFunction("cell", "navigateDown", this.navigateDown.bind(this)); } initialize(){ this.subscribe("cell-init", this.bindEditor.bind(this)); this.subscribe("cell-delete", this.clearEdited.bind(this)); this.subscribe("cell-value-changed", this.updateCellClass.bind(this)); this.subscribe("column-layout", this.initializeColumnCheck.bind(this)); this.subscribe("column-delete", this.columnDeleteCheck.bind(this)); this.subscribe("row-deleting", this.rowDeleteCheck.bind(this)); this.subscribe("data-refreshing", this.cancelEdit.bind(this)); this.subscribe("keybinding-nav-prev", this.navigatePrev.bind(this, undefined)); this.subscribe("keybinding-nav-next", this.keybindingNavigateNext.bind(this)); this.subscribe("keybinding-nav-left", this.navigateLeft.bind(this, undefined)); this.subscribe("keybinding-nav-right", this.navigateRight.bind(this, undefined)); this.subscribe("keybinding-nav-up", this.navigateUp.bind(this, undefined)); this.subscribe("keybinding-nav-down", this.navigateDown.bind(this, undefined)); } /////////////////////////////////// ////// Keybinding Functions /////// /////////////////////////////////// keybindingNavigateNext(e){ var cell = this.currentCell, newRow = this.options("tabEndNewRow"); if(cell){ if(!this.navigateNext(cell, e)){ if(newRow){ cell.getElement().firstChild.blur(); if(newRow === true){ newRow = this.table.addRow({}); }else{ if(typeof newRow == "function"){ newRow = this.table.addRow(newRow(cell.row.getComponent())); }else{ newRow = this.table.addRow(Object.assign({}, newRow)); } } newRow.then(() => { setTimeout(() => { cell.getComponent().navigateNext(); }); }); } } } } /////////////////////////////////// ///////// Cell Functions ////////// /////////////////////////////////// cellIsEdited(cell){ return !! cell.modules.edit && cell.modules.edit.edited; } cellCancelEdit(cell){ if(cell === this.currentCell){ this.table.modules.edit.cancelEdit(); }else{ console.warn("Cancel Editor Error - This cell is not currently being edited "); } } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// updateCellClass(cell){ if(this.allowEdit(cell)) { cell.getElement().classList.add("tabulator-editable"); } else { cell.getElement().classList.remove("tabulator-editable"); } } clearCellEdited(cells){ if(!cells){ cells = this.table.modules.edit.getEditedCells(); } if(!Array.isArray(cells)){ cells = [cells]; } cells.forEach((cell) => { this.table.modules.edit.clearEdited(cell._getSelf()); }); } navigatePrev(cell = this.currentCell, e){ var nextCell, prevRow; if(cell){ if(e){ e.preventDefault(); } nextCell = this.navigateLeft(); if(nextCell){ return true; }else{ prevRow = this.table.rowManager.prevDisplayRow(cell.row, true); if(prevRow){ nextCell = this.findNextEditableCell(prevRow, prevRow.cells.length); if(nextCell){ nextCell.getComponent().edit(); return true; } } } } return false; } navigateNext(cell = this.currentCell, e){ var nextCell, nextRow; if(cell){ if(e){ e.preventDefault(); } nextCell = this.navigateRight(); if(nextCell){ return true; }else{ nextRow = this.table.rowManager.nextDisplayRow(cell.row, true); if(nextRow){ nextCell = this.findNextEditableCell(nextRow, -1); if(nextCell){ nextCell.getComponent().edit(); return true; } } } } return false; } navigateLeft(cell = this.currentCell, e){ var index, nextCell; if(cell){ if(e){ e.preventDefault(); } index = cell.getIndex(); nextCell = this.findPrevEditableCell(cell.row, index); if(nextCell){ nextCell.getComponent().edit(); return true; } } return false; } navigateRight(cell = this.currentCell, e){ var index, nextCell; if(cell){ if(e){ e.preventDefault(); } index = cell.getIndex(); nextCell = this.findNextEditableCell(cell.row, index); if(nextCell){ nextCell.getComponent().edit(); return true; } } return false; } navigateUp(cell = this.currentCell, e){ var index, nextRow; if(cell){ if(e){ e.preventDefault(); } index = cell.getIndex(); nextRow = this.table.rowManager.prevDisplayRow(cell.row, true); if(nextRow){ nextRow.cells[index].getComponent().edit(); return true; } } return false; } navigateDown(cell = this.currentCell, e){ var index, nextRow; if(cell){ if(e){ e.preventDefault(); } index = cell.getIndex(); nextRow = this.table.rowManager.nextDisplayRow(cell.row, true); if(nextRow){ nextRow.cells[index].getComponent().edit(); return true; } } return false; } findNextEditableCell(row, index){ var nextCell = false; if(index < row.cells.length-1){ for(var i = index+1; i < row.cells.length; i++){ let cell = row.cells[i]; if(cell.column.modules.edit && Helpers.elVisible(cell.getElement())){ let allowEdit = this.allowEdit(cell); if(allowEdit){ nextCell = cell; break; } } } } return nextCell; } findPrevEditableCell(row, index){ var prevCell = false; if(index > 0){ for(var i = index-1; i >= 0; i--){ let cell = row.cells[i]; if(cell.column.modules.edit && Helpers.elVisible(cell.getElement())){ let allowEdit = this.allowEdit(cell); if(allowEdit){ prevCell = cell; break; } } } } return prevCell; } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// initializeColumnCheck(column){ if(typeof column.definition.editor !== "undefined"){ this.initializeColumn(column); } } columnDeleteCheck(column){ if(this.currentCell && this.currentCell.column === column){ this.cancelEdit(); } } rowDeleteCheck(row){ if(this.currentCell && this.currentCell.row === row){ this.cancelEdit(); } } //initialize column editor initializeColumn(column){ var config = { editor:false, blocked:false, check:column.definition.editable, params:column.definition.editorParams || {} }; //set column editor switch(typeof column.definition.editor){ case "string": if(this.editors[column.definition.editor]){ config.editor = this.editors[column.definition.editor]; }else{ console.warn("Editor Error - No such editor found: ", column.definition.editor); } break; case "function": config.editor = column.definition.editor; break; case "boolean": if(column.definition.editor === true){ if(typeof column.definition.formatter !== "function"){ if(this.editors[column.definition.formatter]){ config.editor = this.editors[column.definition.formatter]; }else{ config.editor = this.editors["input"]; } }else{ console.warn("Editor Error - Cannot auto lookup editor for a custom formatter: ", column.definition.formatter); } } break; } if(config.editor){ column.modules.edit = config; } } getCurrentCell(){ return this.currentCell ? this.currentCell.getComponent() : false; } clearEditor(cancel){ var cell = this.currentCell, cellEl; this.invalidEdit = false; if(cell){ this.currentCell = false; cellEl = cell.getElement(); this.dispatch("edit-editor-clear", cell, cancel); cellEl.classList.remove("tabulator-editing"); while(cellEl.firstChild) cellEl.removeChild(cellEl.firstChild); cell.row.getElement().classList.remove("tabulator-editing"); cell.table.element.classList.remove("tabulator-editing"); } } cancelEdit(){ if(this.currentCell){ var cell = this.currentCell; var component = this.currentCell.getComponent(); this.clearEditor(true); cell.setValueActual(cell.getValue()); cell.cellRendered(); if(cell.column.definition.editor == "textarea" || cell.column.definition.variableHeight){ cell.row.normalizeHeight(true); } if(cell.column.definition.cellEditCancelled){ cell.column.definition.cellEditCancelled.call(this.table, component); } this.dispatch("edit-cancelled", cell); this.dispatchExternal("cellEditCancelled", component); } } //return a formatted value for a cell bindEditor(cell){ if(cell.column.modules.edit){ var self = this, element = cell.getElement(true); this.updateCellClass(cell); element.setAttribute("tabindex", 0); element.addEventListener("click", function(e){ if(!element.classList.contains("tabulator-editing")){ element.focus({preventScroll: true}); } }); element.addEventListener("mousedown", function(e){ if (e.button === 2) { e.preventDefault(); }else{ self.mouseClick = true; } }); element.addEventListener("focus", function(e){ if(!self.recursionBlock){ self.edit(cell, e, false); } }); } } focusCellNoEvent(cell, block){ this.recursionBlock = true; if(!(block && this.table.browser === "ie")){ cell.getElement().focus({preventScroll: true}); } this.recursionBlock = false; } editCell(cell, forceEdit){ this.focusCellNoEvent(cell); this.edit(cell, false, forceEdit); } focusScrollAdjust(cell){ if(this.table.rowManager.getRenderMode() == "virtual"){ var topEdge = this.table.rowManager.element.scrollTop, bottomEdge = this.table.rowManager.element.clientHeight + this.table.rowManager.element.scrollTop, rowEl = cell.row.getElement(); if(rowEl.offsetTop < topEdge){ this.table.rowManager.element.scrollTop -= (topEdge - rowEl.offsetTop); }else{ if(rowEl.offsetTop + rowEl.offsetHeight > bottomEdge){ this.table.rowManager.element.scrollTop += (rowEl.offsetTop + rowEl.offsetHeight - bottomEdge); } } var leftEdge = this.table.rowManager.element.scrollLeft, rightEdge = this.table.rowManager.element.clientWidth + this.table.rowManager.element.scrollLeft, cellEl = cell.getElement(); if(this.table.modExists("frozenColumns")){ leftEdge += parseInt(this.table.modules.frozenColumns.leftMargin); rightEdge -= parseInt(this.table.modules.frozenColumns.rightMargin); } if(this.table.options.renderHorizontal === "virtual"){ leftEdge -= parseInt(this.table.columnManager.renderer.vDomPadLeft); rightEdge -= parseInt(this.table.columnManager.renderer.vDomPadLeft); } if(cellEl.offsetLeft < leftEdge){ this.table.rowManager.element.scrollLeft -= (leftEdge - cellEl.offsetLeft); }else{ if(cellEl.offsetLeft + cellEl.offsetWidth > rightEdge){ this.table.rowManager.element.scrollLeft += (cellEl.offsetLeft + cellEl.offsetWidth - rightEdge); } } } } allowEdit(cell) { var check = cell.column.modules.edit ? true : false; if(cell.column.modules.edit){ switch(typeof cell.column.modules.edit.check){ case "function": check = cell.column.modules.edit.check(cell.getComponent()); break; case "string": check = !!cell.row.data[cell.column.modules.edit.check]; break; case "boolean": check = cell.column.modules.edit.check; break; } } return check; } edit(cell, e, forceEdit){ var self = this, allowEdit = true, rendered = function(){}, element = cell.getElement(), cellEditor, component, params; //prevent editing if another cell is refusing to leave focus (eg. validation fail) if(this.currentCell){ if(!this.invalidEdit){ this.cancelEdit(); } return; } //handle successful value change function success(value){ if(self.currentCell === cell){ var valid = self.chain("edit-success", [cell, value], true, true); if(valid === true || self.table.options.validationMode === "highlight"){ self.clearEditor(); if(!cell.modules.edit){ cell.modules.edit = {}; } cell.modules.edit.edited = true; if(self.editedCells.indexOf(cell) == -1){ self.editedCells.push(cell); } cell.setValue(value, true); return valid === true; }else{ self.invalidEdit = true; self.focusCellNoEvent(cell, true); rendered(); return false; } }else{ // console.warn("Edit Success Error - cannot call success on a cell that is no longer being edited"); } } //handle aborted edit function cancel(){ if(self.currentCell === cell){ self.cancelEdit(); }else{ // console.warn("Edit Success Error - cannot call cancel on a cell that is no longer being edited"); } } function onRendered(callback){ rendered = callback; } if(!cell.column.modules.edit.blocked){ if(e){ e.stopPropagation(); } allowEdit = this.allowEdit(cell); if(allowEdit || forceEdit){ self.cancelEdit(); self.currentCell = cell; this.focusScrollAdjust(cell); component = cell.getComponent(); if(this.mouseClick){ this.mouseClick = false; if(cell.column.definition.cellClick){ cell.column.definition.cellClick.call(this.table, e, component); } } if(cell.column.definition.cellEditing){ cell.column.definition.cellEditing.call(this.table, component); } this.dispatch("cell-editing", cell); this.dispatchExternal("cellEditing", component); params = typeof cell.column.modules.edit.params === "function" ? cell.column.modules.edit.params(component) : cell.column.modules.edit.params; cellEditor = cell.column.modules.edit.editor.call(self, component, onRendered, success, cancel, params); //if editor returned, add to DOM, if false, abort edit if(cellEditor !== false){ if(cellEditor instanceof Node){ element.classList.add("tabulator-editing"); cell.row.getElement().classList.add("tabulator-editing"); cell.table.element.classList.add("tabulator-editing"); while(element.firstChild) element.removeChild(element.firstChild); element.appendChild(cellEditor); //trigger onRendered Callback rendered(); //prevent editing from triggering rowClick event var children = element.children; for (var i = 0; i < children.length; i++) { children[i].addEventListener("click", function(e){ e.stopPropagation(); }); } }else{ console.warn("Edit Error - Editor should return an instance of Node, the editor returned:", cellEditor); element.blur(); return false; } }else{ element.blur(); return false; } return true; }else{ this.mouseClick = false; element.blur(); return false; } }else{ this.mouseClick = false; element.blur(); return false; } } getEditedCells(){ var output = []; this.editedCells.forEach((cell) => { output.push(cell.getComponent()); }); return output; } clearEdited(cell){ var editIndex; if(cell.modules.edit && cell.modules.edit.edited){ cell.modules.edit.edited = false; this.dispatch("edit-edited-clear", cell); } editIndex = this.editedCells.indexOf(cell); if(editIndex > -1){ this.editedCells.splice(editIndex, 1); } } } Edit.moduleName = "edit"; //load defaults Edit.editors = defaultEditors; export default Edit;