UNPKG

tabulator-tables

Version:

Interactive table generation JavaScript library

2,214 lines (1,707 loc) 733 kB
/* Tabulator v6.3.1 (c) Oliver Folkerd 2025 */ class CoreFeature{ constructor(table){ this.table = table; } ////////////////////////////////////////// /////////////// DataLoad ///////////////// ////////////////////////////////////////// reloadData(data, silent, columnsChanged){ return this.table.dataLoader.load(data, undefined, undefined, undefined, silent, columnsChanged); } ////////////////////////////////////////// ///////////// Localization /////////////// ////////////////////////////////////////// langText(){ return this.table.modules.localize.getText(...arguments); } langBind(){ return this.table.modules.localize.bind(...arguments); } langLocale(){ return this.table.modules.localize.getLocale(...arguments); } ////////////////////////////////////////// ////////// Inter Table Comms ///////////// ////////////////////////////////////////// commsConnections(){ return this.table.modules.comms.getConnections(...arguments); } commsSend(){ return this.table.modules.comms.send(...arguments); } ////////////////////////////////////////// //////////////// Layout ///////////////// ////////////////////////////////////////// layoutMode(){ return this.table.modules.layout.getMode(); } layoutRefresh(force){ return this.table.modules.layout.layout(force); } ////////////////////////////////////////// /////////////// Event Bus //////////////// ////////////////////////////////////////// subscribe(){ return this.table.eventBus.subscribe(...arguments); } unsubscribe(){ return this.table.eventBus.unsubscribe(...arguments); } subscribed(key){ return this.table.eventBus.subscribed(key); } subscriptionChange(){ return this.table.eventBus.subscriptionChange(...arguments); } dispatch(){ return this.table.eventBus.dispatch(...arguments); } chain(){ return this.table.eventBus.chain(...arguments); } confirm(){ return this.table.eventBus.confirm(...arguments); } dispatchExternal(){ return this.table.externalEvents.dispatch(...arguments); } subscribedExternal(key){ return this.table.externalEvents.subscribed(key); } subscriptionChangeExternal(){ return this.table.externalEvents.subscriptionChange(...arguments); } ////////////////////////////////////////// //////////////// Options ///////////////// ////////////////////////////////////////// options(key){ return this.table.options[key]; } setOption(key, value){ if(typeof value !== "undefined"){ this.table.options[key] = value; } return this.table.options[key]; } ////////////////////////////////////////// /////////// Deprecation Checks /////////// ////////////////////////////////////////// deprecationCheck(oldOption, newOption, convert){ return this.table.deprecationAdvisor.check(oldOption, newOption, convert); } deprecationCheckMsg(oldOption, msg){ return this.table.deprecationAdvisor.checkMsg(oldOption, msg); } deprecationMsg(msg){ return this.table.deprecationAdvisor.msg(msg); } ////////////////////////////////////////// //////////////// Modules ///////////////// ////////////////////////////////////////// module(key){ return this.table.module(key); } } class Helpers{ static elVisible(el){ return !(el.offsetWidth <= 0 && el.offsetHeight <= 0); } static elOffset(el){ var box = el.getBoundingClientRect(); return { top: box.top + window.pageYOffset - document.documentElement.clientTop, left: box.left + window.pageXOffset - document.documentElement.clientLeft }; } static retrieveNestedData(separator, field, data){ var structure = separator ? field.split(separator) : [field], length = structure.length, output; for(let i = 0; i < length; i++){ data = data[structure[i]]; output = data; if(!data){ break; } } return output; } static deepClone(obj, clone, list = []){ var objectProto = {}.__proto__, arrayProto = [].__proto__; if (!clone){ clone = Object.assign(Array.isArray(obj) ? [] : {}, obj); } for(var i in obj) { let subject = obj[i], match, copy; if(subject != null && typeof subject === "object" && (subject.__proto__ === objectProto || subject.__proto__ === arrayProto)){ match = list.findIndex((item) => { return item.subject === subject; }); if(match > -1){ clone[i] = list[match].copy; }else { copy = Object.assign(Array.isArray(subject) ? [] : {}, subject); list.unshift({subject, copy}); clone[i] = this.deepClone(subject, copy, list); } } } return clone; } } let Popup$1 = class Popup extends CoreFeature{ constructor(table, element, parent){ super(table); this.element = element; this.container = this._lookupContainer(); this.parent = parent; this.reversedX = false; this.childPopup = null; this.blurable = false; this.blurCallback = null; this.blurEventsBound = false; this.renderedCallback = null; this.visible = false; this.hideable = true; this.element.classList.add("tabulator-popup-container"); this.blurEvent = this.hide.bind(this, false); this.escEvent = this._escapeCheck.bind(this); this.destroyBinding = this.tableDestroyed.bind(this); this.destroyed = false; } tableDestroyed(){ this.destroyed = true; this.hide(true); } _lookupContainer(){ var container = this.table.options.popupContainer; if(typeof container === "string"){ container = document.querySelector(container); if(!container){ console.warn("Menu Error - no container element found matching selector:", this.table.options.popupContainer , "(defaulting to document body)"); } }else if (container === true){ container = this.table.element; } if(container && !this._checkContainerIsParent(container)){ container = false; console.warn("Menu Error - container element does not contain this table:", this.table.options.popupContainer , "(defaulting to document body)"); } if(!container){ container = document.body; } return container; } _checkContainerIsParent(container, element = this.table.element){ if(container === element){ return true; }else { return element.parentNode ? this._checkContainerIsParent(container, element.parentNode) : false; } } renderCallback(callback){ this.renderedCallback = callback; } containerEventCoords(e){ var touch = !(e instanceof MouseEvent); var x = touch ? e.touches[0].pageX : e.pageX; var y = touch ? e.touches[0].pageY : e.pageY; if(this.container !== document.body){ let parentOffset = Helpers.elOffset(this.container); x -= parentOffset.left; y -= parentOffset.top; } return {x, y}; } elementPositionCoords(element, position = "right"){ var offset = Helpers.elOffset(element), containerOffset, x, y; if(this.container !== document.body){ containerOffset = Helpers.elOffset(this.container); offset.left -= containerOffset.left; offset.top -= containerOffset.top; } switch(position){ case "right": x = offset.left + element.offsetWidth; y = offset.top - 1; break; case "bottom": x = offset.left; y = offset.top + element.offsetHeight; break; case "left": x = offset.left; y = offset.top - 1; break; case "top": x = offset.left; y = offset.top; break; case "center": x = offset.left + (element.offsetWidth / 2); y = offset.top + (element.offsetHeight / 2); break; } return {x, y, offset}; } show(origin, position){ var x, y, parentEl, parentOffset, coords; if(this.destroyed || this.table.destroyed){ return this; } if(origin instanceof HTMLElement){ parentEl = origin; coords = this.elementPositionCoords(origin, position); parentOffset = coords.offset; x = coords.x; y = coords.y; }else if(typeof origin === "number"){ parentOffset = {top:0, left:0}; x = origin; y = position; }else { coords = this.containerEventCoords(origin); x = coords.x; y = coords.y; this.reversedX = false; } this.element.style.top = y + "px"; this.element.style.left = x + "px"; this.container.appendChild(this.element); if(typeof this.renderedCallback === "function"){ this.renderedCallback(); } this._fitToScreen(x, y, parentEl, parentOffset, position); this.visible = true; this.subscribe("table-destroy", this.destroyBinding); this.element.addEventListener("mousedown", (e) => { e.stopPropagation(); }); return this; } _fitToScreen(x, y, parentEl, parentOffset, position){ var scrollTop = this.container === document.body ? document.documentElement.scrollTop : this.container.scrollTop; //move menu to start on right edge if it is too close to the edge of the screen if((x + this.element.offsetWidth) >= this.container.offsetWidth || this.reversedX){ this.element.style.left = ""; if(parentEl){ this.element.style.right = (this.container.offsetWidth - parentOffset.left) + "px"; }else { this.element.style.right = (this.container.offsetWidth - x) + "px"; } this.reversedX = true; } //move menu to start on bottom edge if it is too close to the edge of the screen let offsetHeight = Math.max(this.container.offsetHeight, scrollTop ? this.container.scrollHeight : 0); if((y + this.element.offsetHeight) > offsetHeight) { if(parentEl){ switch(position){ case "bottom": this.element.style.top = (parseInt(this.element.style.top) - this.element.offsetHeight - parentEl.offsetHeight - 1) + "px"; break; default: this.element.style.top = (parseInt(this.element.style.top) - this.element.offsetHeight + parentEl.offsetHeight + 1) + "px"; } }else { this.element.style.height = offsetHeight + "px"; } } } isVisible(){ return this.visible; } hideOnBlur(callback){ this.blurable = true; if(this.visible){ setTimeout(() => { if(this.visible){ this.table.rowManager.element.addEventListener("scroll", this.blurEvent); this.subscribe("cell-editing", this.blurEvent); document.body.addEventListener("click", this.blurEvent); document.body.addEventListener("contextmenu", this.blurEvent); document.body.addEventListener("mousedown", this.blurEvent); window.addEventListener("resize", this.blurEvent); document.body.addEventListener("keydown", this.escEvent); this.blurEventsBound = true; } }, 100); this.blurCallback = callback; } return this; } _escapeCheck(e){ if(e.keyCode == 27){ this.hide(); } } blockHide(){ this.hideable = false; } restoreHide(){ this.hideable = true; } hide(silent = false){ if(this.visible && this.hideable){ if(this.blurable && this.blurEventsBound){ document.body.removeEventListener("keydown", this.escEvent); document.body.removeEventListener("click", this.blurEvent); document.body.removeEventListener("contextmenu", this.blurEvent); document.body.removeEventListener("mousedown", this.blurEvent); window.removeEventListener("resize", this.blurEvent); this.table.rowManager.element.removeEventListener("scroll", this.blurEvent); this.unsubscribe("cell-editing", this.blurEvent); this.blurEventsBound = false; } if(this.childPopup){ this.childPopup.hide(); } if(this.parent){ this.parent.childPopup = null; } if(this.element.parentNode){ this.element.parentNode.removeChild(this.element); } this.visible = false; if(this.blurCallback && !silent){ this.blurCallback(); } this.unsubscribe("table-destroy", this.destroyBinding); } return this; } child(element){ if(this.childPopup){ this.childPopup.hide(); } this.childPopup = new Popup(this.table, element, this); return this.childPopup; } }; class Module extends CoreFeature{ constructor(table, name){ super(table); this._handler = null; } initialize(){ // setup module when table is initialized, to be overridden in module } /////////////////////////////////// ////// Options Registration /////// /////////////////////////////////// registerTableOption(key, value){ this.table.optionsList.register(key, value); } registerColumnOption(key, value){ this.table.columnManager.optionsList.register(key, value); } /////////////////////////////////// /// Public Function Registration /// /////////////////////////////////// registerTableFunction(name, func){ if(typeof this.table[name] === "undefined"){ this.table[name] = (...args) => { this.table.initGuard(name); return func(...args); }; }else { console.warn("Unable to bind table function, name already in use", name); } } registerComponentFunction(component, func, handler){ return this.table.componentFunctionBinder.bind(component, func, handler); } /////////////////////////////////// ////////// Data Pipeline ////////// /////////////////////////////////// registerDataHandler(handler, priority){ this.table.rowManager.registerDataPipelineHandler(handler, priority); this._handler = handler; } registerDisplayHandler(handler, priority){ this.table.rowManager.registerDisplayPipelineHandler(handler, priority); this._handler = handler; } displayRows(adjust){ var index = this.table.rowManager.displayRows.length - 1, lookupIndex; if(this._handler){ lookupIndex = this.table.rowManager.displayPipeline.findIndex((item) => { return item.handler === this._handler; }); if(lookupIndex > -1){ index = lookupIndex; } } if(adjust){ index = index + adjust; } if(this._handler){ if(index > -1){ return this.table.rowManager.getDisplayRows(index); }else { return this.activeRows(); } } } activeRows(){ return this.table.rowManager.activeRows; } refreshData(renderInPosition, handler){ if(!handler){ handler = this._handler; } if(handler){ this.table.rowManager.refreshActiveData(handler, false, renderInPosition); } } /////////////////////////////////// //////// Footer Management //////// /////////////////////////////////// footerAppend(element){ return this.table.footerManager.append(element); } footerPrepend(element){ return this.table.footerManager.prepend(element); } footerRemove(element){ return this.table.footerManager.remove(element); } /////////////////////////////////// //////// Popups Management //////// /////////////////////////////////// popup(menuEl, menuContainer){ return new Popup$1(this.table, menuEl, menuContainer); } /////////////////////////////////// //////// Alert Management //////// /////////////////////////////////// alert(content, type){ return this.table.alertManager.alert(content, type); } clearAlert(){ return this.table.alertManager.clear(); } } var defaultAccessors = { rownum:function(value, data, type, params, column, row){ return row.getPosition(); } }; class Accessor extends Module{ static moduleName = "accessor"; //load defaults static accessors = defaultAccessors; constructor(table){ super(table); this.allowedTypes = ["", "data", "download", "clipboard", "print", "htmlOutput"]; //list of accessor types this.registerColumnOption("accessor"); this.registerColumnOption("accessorParams"); this.registerColumnOption("accessorData"); this.registerColumnOption("accessorDataParams"); this.registerColumnOption("accessorDownload"); this.registerColumnOption("accessorDownloadParams"); this.registerColumnOption("accessorClipboard"); this.registerColumnOption("accessorClipboardParams"); this.registerColumnOption("accessorPrint"); this.registerColumnOption("accessorPrintParams"); this.registerColumnOption("accessorHtmlOutput"); this.registerColumnOption("accessorHtmlOutputParams"); } initialize(){ this.subscribe("column-layout", this.initializeColumn.bind(this)); this.subscribe("row-data-retrieve", this.transformRow.bind(this)); } //initialize column accessor initializeColumn(column){ var match = false, config = {}; this.allowedTypes.forEach((type) => { var key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1)), accessor; if(column.definition[key]){ accessor = this.lookupAccessor(column.definition[key]); if(accessor){ match = true; config[key] = { accessor:accessor, params: column.definition[key + "Params"] || {}, }; } } }); if(match){ column.modules.accessor = config; } } lookupAccessor(value){ var accessor = false; //set column accessor switch(typeof value){ case "string": if(Accessor.accessors[value]){ accessor = Accessor.accessors[value]; }else { console.warn("Accessor Error - No such accessor found, ignoring: ", value); } break; case "function": accessor = value; break; } return accessor; } //apply accessor to row transformRow(row, type){ var key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1)), rowComponent = row.getComponent(); //clone data object with deep copy to isolate internal data from returned result var data = Helpers.deepClone(row.data || {}); this.table.columnManager.traverse(function(column){ var value, accessor, params, colComponent; if(column.modules.accessor){ accessor = column.modules.accessor[key] || column.modules.accessor.accessor || false; if(accessor){ value = column.getFieldValue(data); if(value != "undefined"){ colComponent = column.getComponent(); params = typeof accessor.params === "function" ? accessor.params(value, data, type, colComponent, rowComponent) : accessor.params; column.setFieldValue(data, accessor.accessor(value, data, type, params, colComponent, rowComponent)); } } } }); return data; } } var defaultConfig = { method: "GET", }; function generateParamsList$1(data, prefix){ var output = []; prefix = prefix || ""; if(Array.isArray(data)){ data.forEach((item, i) => { output = output.concat(generateParamsList$1(item, prefix ? prefix + "[" + i + "]" : i)); }); }else if (typeof data === "object"){ for (var key in data){ output = output.concat(generateParamsList$1(data[key], prefix ? prefix + "[" + key + "]" : key)); } }else { output.push({key:prefix, value:data}); } return output; } function serializeParams(params){ var output = generateParamsList$1(params), encoded = []; output.forEach(function(item){ encoded.push(encodeURIComponent(item.key) + "=" + encodeURIComponent(item.value)); }); return encoded.join("&"); } function urlBuilder(url, config, params){ if(url){ if(params && Object.keys(params).length){ if(!config.method || config.method.toLowerCase() == "get"){ config.method = "get"; url += (url.includes("?") ? "&" : "?") + serializeParams(params); } } } return url; } function defaultLoaderPromise(url, config, params){ var contentType; return new Promise((resolve, reject) => { //set url url = this.urlGenerator.call(this.table, url, config, params); //set body content if not GET request if(config.method.toUpperCase() != "GET"){ contentType = typeof this.table.options.ajaxContentType === "object" ? this.table.options.ajaxContentType : this.contentTypeFormatters[this.table.options.ajaxContentType]; if(contentType){ for(var key in contentType.headers){ if(!config.headers){ config.headers = {}; } if(typeof config.headers[key] === "undefined"){ config.headers[key] = contentType.headers[key]; } } config.body = contentType.body.call(this, url, config, params); }else { console.warn("Ajax Error - Invalid ajaxContentType value:", this.table.options.ajaxContentType); } } if(url){ //configure headers if(typeof config.headers === "undefined"){ config.headers = {}; } if(typeof config.headers.Accept === "undefined"){ config.headers.Accept = "application/json"; } if(typeof config.headers["X-Requested-With"] === "undefined"){ config.headers["X-Requested-With"] = "XMLHttpRequest"; } if(typeof config.mode === "undefined"){ config.mode = "cors"; } if(config.mode == "cors"){ if(typeof config.headers["Origin"] === "undefined"){ config.headers["Origin"] = window.location.origin; } if(typeof config.credentials === "undefined"){ config.credentials = 'same-origin'; } }else { if(typeof config.credentials === "undefined"){ config.credentials = 'include'; } } //send request fetch(url, config) .then((response)=>{ if(response.ok) { response.json() .then((data)=>{ resolve(data); }).catch((error)=>{ reject(error); console.warn("Ajax Load Error - Invalid JSON returned", error); }); }else { console.error("Ajax Load Error - Connection Error: " + response.status, response.statusText); reject(response); } }) .catch((error)=>{ console.error("Ajax Load Error - Connection Error: ", error); reject(error); }); }else { console.warn("Ajax Load Error - No URL Set"); resolve([]); } }); } function generateParamsList(data, prefix){ var output = []; prefix = prefix || ""; if(Array.isArray(data)){ data.forEach((item, i) => { output = output.concat(generateParamsList(item, prefix ? prefix + "[" + i + "]" : i)); }); }else if (typeof data === "object"){ for (var key in data){ output = output.concat(generateParamsList(data[key], prefix ? prefix + "[" + key + "]" : key)); } }else { output.push({key:prefix, value:data}); } return output; } var defaultContentTypeFormatters = { "json":{ headers:{ 'Content-Type': 'application/json', }, body:function(url, config, params){ return JSON.stringify(params); }, }, "form":{ headers:{ }, body:function(url, config, params){ var output = generateParamsList(params), form = new FormData(); output.forEach(function(item){ form.append(item.key, item.value); }); return form; }, }, }; class Ajax extends Module{ static moduleName = "ajax"; //load defaults static defaultConfig = defaultConfig; static defaultURLGenerator = urlBuilder; static defaultLoaderPromise = defaultLoaderPromise; static contentTypeFormatters = defaultContentTypeFormatters; constructor(table){ super(table); this.config = {}; //hold config object for ajax request this.url = ""; //request URL this.urlGenerator = false; this.params = false; //request parameters this.loaderPromise = false; this.registerTableOption("ajaxURL", false); //url for ajax loading this.registerTableOption("ajaxURLGenerator", false); this.registerTableOption("ajaxParams", {}); //params for ajax loading this.registerTableOption("ajaxConfig", "get"); //ajax request type this.registerTableOption("ajaxContentType", "form"); //ajax request type this.registerTableOption("ajaxRequestFunc", false); //promise function this.registerTableOption("ajaxRequesting", function(){}); this.registerTableOption("ajaxResponse", false); this.contentTypeFormatters = Ajax.contentTypeFormatters; } //initialize setup options initialize(){ this.loaderPromise = this.table.options.ajaxRequestFunc || Ajax.defaultLoaderPromise; this.urlGenerator = this.table.options.ajaxURLGenerator || Ajax.defaultURLGenerator; if(this.table.options.ajaxURL){ this.setUrl(this.table.options.ajaxURL); } this.setDefaultConfig(this.table.options.ajaxConfig); this.registerTableFunction("getAjaxUrl", this.getUrl.bind(this)); this.subscribe("data-loading", this.requestDataCheck.bind(this)); this.subscribe("data-params", this.requestParams.bind(this)); this.subscribe("data-load", this.requestData.bind(this)); } requestParams(data, config, silent, params){ var ajaxParams = this.table.options.ajaxParams; if(ajaxParams){ if(typeof ajaxParams === "function"){ ajaxParams = ajaxParams.call(this.table); } params = Object.assign(Object.assign({}, ajaxParams), params); } return params; } requestDataCheck(data, params, config, silent){ return !!((!data && this.url) || typeof data === "string"); } requestData(url, params, config, silent, previousData){ var ajaxConfig; if(!previousData && this.requestDataCheck(url)){ if(url){ this.setUrl(url); } ajaxConfig = this.generateConfig(config); return this.sendRequest(this.url, params, ajaxConfig); }else { return previousData; } } setDefaultConfig(config = {}){ this.config = Object.assign({}, Ajax.defaultConfig); if(typeof config == "string"){ this.config.method = config; }else { Object.assign(this.config, config); } } //load config object generateConfig(config = {}){ var ajaxConfig = Object.assign({}, this.config); if(typeof config == "string"){ ajaxConfig.method = config; }else { Object.assign(ajaxConfig, config); } return ajaxConfig; } //set request url setUrl(url){ this.url = url; } //get request url getUrl(){ return this.url; } //send ajax request sendRequest(url, params, config){ if(this.table.options.ajaxRequesting.call(this.table, url, params) !== false){ return this.loaderPromise(url, config, params) .then((data)=>{ if(this.table.options.ajaxResponse){ data = this.table.options.ajaxResponse.call(this.table, url, params, data); } return data; }); }else { return Promise.reject(); } } } var defaultPasteActions = { replace:function(data){ return this.table.setData(data); }, update:function(data){ return this.table.updateOrAddData(data); }, insert:function(data){ return this.table.addData(data); }, }; var defaultPasteParsers = { table:function(clipboard){ var data = [], headerFindSuccess = true, columns = this.table.columnManager.columns, columnMap = [], rows = []; //get data from clipboard into array of columns and rows. clipboard = clipboard.split("\n"); clipboard.forEach(function(row){ data.push(row.split("\t")); }); if(data.length && !(data.length === 1 && data[0].length < 2)){ //check if headers are present by title data[0].forEach(function(value){ var column = columns.find(function(column){ return value && column.definition.title && value.trim() && column.definition.title.trim() === value.trim(); }); if(column){ columnMap.push(column); }else { headerFindSuccess = false; } }); //check if column headers are present by field if(!headerFindSuccess){ headerFindSuccess = true; columnMap = []; data[0].forEach(function(value){ var column = columns.find(function(column){ return value && column.field && value.trim() && column.field.trim() === value.trim(); }); if(column){ columnMap.push(column); }else { headerFindSuccess = false; } }); if(!headerFindSuccess){ columnMap = this.table.columnManager.columnsByIndex; } } //remove header row if found if(headerFindSuccess){ data.shift(); } data.forEach(function(item){ var row = {}; item.forEach(function(value, i){ if(columnMap[i]){ row[columnMap[i].field] = value; } }); rows.push(row); }); return rows; }else { return false; } }, }; var bindings$2 = { copyToClipboard:["ctrl + 67", "meta + 67"], }; var actions$2 = { copyToClipboard:function(e){ if(!this.table.modules.edit.currentCell){ if(this.table.modExists("clipboard", true)){ this.table.modules.clipboard.copy(false, true); } } }, }; var extensions$4 = { keybindings:{ bindings:bindings$2, actions:actions$2 }, }; class Clipboard extends Module{ static moduleName = "clipboard"; static moduleExtensions = extensions$4; //load defaults static pasteActions = defaultPasteActions; static pasteParsers = defaultPasteParsers; constructor(table){ super(table); this.mode = true; this.pasteParser = function(){}; this.pasteAction = function(){}; this.customSelection = false; this.rowRange = false; this.blocked = true; //block copy actions not originating from this command this.registerTableOption("clipboard", false); //enable clipboard this.registerTableOption("clipboardCopyStyled", true); //formatted table data this.registerTableOption("clipboardCopyConfig", false); //clipboard config this.registerTableOption("clipboardCopyFormatter", false); //DEPRECATED - REMOVE in 5.0 this.registerTableOption("clipboardCopyRowRange", "active"); //restrict clipboard to visible rows only this.registerTableOption("clipboardPasteParser", "table"); //convert pasted clipboard data to rows this.registerTableOption("clipboardPasteAction", "insert"); //how to insert pasted data into the table this.registerColumnOption("clipboard"); this.registerColumnOption("titleClipboard"); } initialize(){ this.mode = this.table.options.clipboard; this.rowRange = this.table.options.clipboardCopyRowRange; if(this.mode === true || this.mode === "copy"){ this.table.element.addEventListener("copy", (e) => { var plain, html, list; if(!this.blocked){ e.preventDefault(); if(this.customSelection){ plain = this.customSelection; if(this.table.options.clipboardCopyFormatter){ plain = this.table.options.clipboardCopyFormatter("plain", plain); } }else { list = this.table.modules.export.generateExportList(this.table.options.clipboardCopyConfig, this.table.options.clipboardCopyStyled, this.rowRange, "clipboard"); html = this.table.modules.export.generateHTMLTable(list); plain = html ? this.generatePlainContent(list) : ""; if(this.table.options.clipboardCopyFormatter){ plain = this.table.options.clipboardCopyFormatter("plain", plain); html = this.table.options.clipboardCopyFormatter("html", html); } } if (window.clipboardData && window.clipboardData.setData) { window.clipboardData.setData('Text', plain); } else if (e.clipboardData && e.clipboardData.setData) { e.clipboardData.setData('text/plain', plain); if(html){ e.clipboardData.setData('text/html', html); } } else if (e.originalEvent && e.originalEvent.clipboardData.setData) { e.originalEvent.clipboardData.setData('text/plain', plain); if(html){ e.originalEvent.clipboardData.setData('text/html', html); } } this.dispatchExternal("clipboardCopied", plain, html); this.reset(); } }); } if(this.mode === true || this.mode === "paste"){ this.table.element.addEventListener("paste", (e) => { this.paste(e); }); } this.setPasteParser(this.table.options.clipboardPasteParser); this.setPasteAction(this.table.options.clipboardPasteAction); this.registerTableFunction("copyToClipboard", this.copy.bind(this)); } reset(){ this.blocked = true; this.customSelection = false; } generatePlainContent (list) { var output = []; list.forEach((row) => { var rowData = []; row.columns.forEach((col) => { var value = ""; if(col){ if(row.type === "group"){ col.value = col.component.getKey(); } if(col.value === null){ value = ""; }else { switch(typeof col.value){ case "object": value = JSON.stringify(col.value); break; case "undefined": value = ""; break; default: value = col.value; } } } rowData.push(value); }); output.push(rowData.join("\t")); }); return output.join("\n"); } copy (range, internal) { var sel, textRange; this.blocked = false; this.customSelection = false; if (this.mode === true || this.mode === "copy") { this.rowRange = range || this.table.options.clipboardCopyRowRange; if (typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") { range = document.createRange(); range.selectNodeContents(this.table.element); sel = window.getSelection(); if (sel.toString() && internal) { this.customSelection = sel.toString(); } sel.removeAllRanges(); sel.addRange(range); } else if (typeof document.selection != "undefined" && typeof document.body.createTextRange != "undefined") { textRange = document.body.createTextRange(); textRange.moveToElementText(this.table.element); textRange.select(); } document.execCommand('copy'); if (sel) { sel.removeAllRanges(); } } } //PASTE EVENT HANDLING setPasteAction(action){ switch(typeof action){ case "string": this.pasteAction = Clipboard.pasteActions[action]; if(!this.pasteAction){ console.warn("Clipboard Error - No such paste action found:", action); } break; case "function": this.pasteAction = action; break; } } setPasteParser(parser){ switch(typeof parser){ case "string": this.pasteParser = Clipboard.pasteParsers[parser]; if(!this.pasteParser){ console.warn("Clipboard Error - No such paste parser found:", parser); } break; case "function": this.pasteParser = parser; break; } } paste(e){ var data, rowData, rows; if(this.checkPasteOrigin(e)){ data = this.getPasteData(e); rowData = this.pasteParser.call(this, data); if(rowData){ e.preventDefault(); if(this.table.modExists("mutator")){ rowData = this.mutateData(rowData); } rows = this.pasteAction.call(this, rowData); this.dispatchExternal("clipboardPasted", data, rowData, rows); }else { this.dispatchExternal("clipboardPasteError", data); } } } mutateData(data){ var output = []; if(Array.isArray(data)){ data.forEach((row) => { output.push(this.table.modules.mutator.transformRow(row, "clipboard")); }); }else { output = data; } return output; } checkPasteOrigin(e){ var valid = true; var blocked = this.confirm("clipboard-paste", [e]); if(blocked || !["DIV", "SPAN"].includes(e.target.tagName)){ valid = false; } return valid; } getPasteData(e){ var data; if (window.clipboardData && window.clipboardData.getData) { data = window.clipboardData.getData('Text'); } else if (e.clipboardData && e.clipboardData.getData) { data = e.clipboardData.getData('text/plain'); } else if (e.originalEvent && e.originalEvent.clipboardData.getData) { data = e.originalEvent.clipboardData.getData('text/plain'); } return data; } } class CalcComponent{ constructor (row){ this._row = row; return new Proxy(this, { get: function(target, name, receiver) { if (typeof target[name] !== "undefined") { return target[name]; }else { return target._row.table.componentFunctionBinder.handle("row", target._row, name); } } }); } getData(transform){ return this._row.getData(transform); } getElement(){ return this._row.getElement(); } getTable(){ return this._row.table; } getCells(){ var cells = []; this._row.getCells().forEach(function(cell){ cells.push(cell.getComponent()); }); return cells; } getCell(column){ var cell = this._row.getCell(column); return cell ? cell.getComponent() : false; } _getSelf(){ return this._row; } } //public cell object class CellComponent { constructor (cell){ this._cell = cell; return new Proxy(this, { get: function(target, name, receiver) { if (typeof target[name] !== "undefined") { return target[name]; }else { return target._cell.table.componentFunctionBinder.handle("cell", target._cell, name); } } }); } getValue(){ return this._cell.getValue(); } getOldValue(){ return this._cell.getOldValue(); } getInitialValue(){ return this._cell.initialValue; } getElement(){ return this._cell.getElement(); } getRow(){ return this._cell.row.getComponent(); } getData(transform){ return this._cell.row.getData(transform); } getType(){ return "cell"; } getField(){ return this._cell.column.getField(); } getColumn(){ return this._cell.column.getComponent(); } setValue(value, mutate){ if(typeof mutate == "undefined"){ mutate = true; } this._cell.setValue(value, mutate); } restoreOldValue(){ this._cell.setValueActual(this._cell.getOldValue()); } restoreInitialValue(){ this._cell.setValueActual(this._cell.initialValue); } checkHeight(){ this._cell.checkHeight(); } getTable(){ return this._cell.table; } _getSelf(){ return this._cell; } } class Cell extends CoreFeature{ constructor(column, row){ super(column.table); this.table = column.table; this.column = column; this.row = row; this.element = null; this.value = null; this.initialValue; this.oldValue = null; this.modules = {}; this.height = null; this.width = null; this.minWidth = null; this.component = null; this.loaded = false; //track if the cell has been added to the DOM yet this.build(); } //////////////// Setup Functions ///////////////// //generate element build(){ this.generateElement(); this.setWidth(); this._configureCell(); this.setValueActual(this.column.getFieldValue(this.row.data)); this.initialValue = this.value; } generateElement(){ this.element = document.createElement('div'); this.element.className = "tabulator-cell"; this.element.setAttribute("role", "gridcell"); if(this.column.isRowHeader){ this.element.classList.add("tabulator-row-header"); } } _configureCell(){ var element = this.element, field = this.column.getField(), vertAligns = { top:"flex-start", bottom:"flex-end", middle:"center", }, hozAligns = { left:"flex-start", right:"flex-end", center:"center", }; //set text alignment element.style.textAlign = this.column.hozAlign; if(this.column.vertAlign){ element.style.display = "inline-flex"; element.style.alignItems = vertAligns[this.column.vertAlign] || ""; if(this.column.hozAlign){ element.style.justifyContent = hozAligns[this.column.hozAlign] || ""; } } if(field){ element.setAttribute("tabulator-field", field); } //add class to cell if needed if(this.column.definition.cssClass){ var classNames = this.column.definition.cssClass.split(" "); classNames.forEach((className) => { element.classList.add(className); }); } this.dispatch("cell-init", this); //hide cell if not visible if(!this.column.visible){ this.hide(); } } //generate cell contents _generateContents(){ var val; val = this.chain("cell-format", this, null, () => { return this.element.innerHTML = this.value; }); switch(typeof val){ case "object": if(val instanceof Node){ //clear previous cell contents while(this.element.firstChild) this.element.removeChild(this.element.firstChild); this.element.appendChild(val); }else { this.element.innerHTML = ""; if(val != null){ console.warn("Format Error - Formatter has returned a type of object, the only valid formatter object return is an instance of Node, the formatter returned:", val); } } break; case "undefined": this.element.innerHTML = ""; break; default: this.element.innerHTML = val; } } cellRendered(){ this.dispatch("cell-rendered", this); } //////////////////// Getters //////////////////// getElement(containerOnly){ if(!this.loaded){ this.loaded = true; if(!containerOnly){ this.layoutElement(); } } return this.element; } getValue(){ return this.value; } getOldValue(){ return this.oldValue; } //////////////////// Actions //////////////////// setValue(value, mutate, force){ var changed = this.setValueProcessData(value, mutate, force); if(changed){ this.dispatch("cell-value-updated", this); this.cellRendered(); if(this.column.definition.cellEdited){ this.column.definition.cellEdited.call(this.table, this.getComponent()); } this.dispatchExternal("cellEdited", this.getComponent()); if(this.subscribedExternal("dataChanged")){ this.dispatchExternal("dataChanged", this.table.rowManager.getData()); } } } setValueProcessData(value, mutate, force){ var changed = false; if(this.value !== value || force){ changed = true; if(mutate){ value = this.chain("cell-value-changing", [this, value], null, value); } } this.setValueActual(value); if(changed){ this.dispatch("cell-value-changed", this); } return changed; } setValueActual(value){ this.oldValue = this.value; this.value = value; this.dispatch("cell-value-save-before", this); this.column.setFieldValue(this.row.data, value); this.dispatch("cell-value-save-after", this); if(this.loaded){ this.layoutElement(); } } layoutElement(){ this._generateContents(); this.dispatch("cell-layout", this); } setWidth(){ this.width = this.column.width; this.element.style.width = this.column.widthStyled; } clearWidth(){ this.width = ""; this.element.style.width = ""; } getWidth(){ return this.width || this.element.offsetWidth; } setMinWidth(){ this.minWidth = this.column.minWidth; this.element.style.minWidth = this.column.minWidthStyled; } setMaxWidth(){ this.maxWidth = this.column.maxWidth; this.element.style.maxWidth = this.column.maxWidthStyled; } checkHeight(){ // var height = this.element.css("height"); this.row.reinitializeHeight(); } clearHeight(){ this.element.style.height = ""; this.height = null; this.dispatch("cell-height", this, ""); } setHeight(){ this.height = this.row.height; this.element.style.height = this.row.heightStyled; this.dispatch("cell-height", this, this.row.heightStyled); } getHeight(){ return this.height || this.element.offsetHeight; } show(){ this.element.style.display = this.column.vertAlign ? "inline-flex" : ""; } hide(){ this.element.style.display = "none"; } delete(){ this.dispatch("cell-delete", this); if(!this.table.rowManager.redrawBlock && this.element.parentNode){ this.element.parentNode.removeChild(this.element); } this.element = false; this.column.deleteCell(this); this.row.deleteCell(this); this.calcs = {}; } getIndex(){ return this.row.getCellIndex(this); } //////////////// Object Generation ///////////////// getComponent(){ if(!this.component){ this.component = new CellComponent(this); } return this.component; } } //public column object class ColumnComponent { constructor (column){ this._column = column; this.type = "ColumnComponent"; return new Proxy(this, { get: function(target, name, receiver) { if (typeof target[name] !== "undefined") { return target[name]; }else { return target._column.table.componentFunctionBinder.handle("column", target._column, name); } } }); } getElement(){ return this._column.getElement(); } getDefinition(){ return this._column.getDefinition(); } getField(){ return this._column.getField(); } getTitleDownload() { return this._column.getTitleDownload(); } getCells(){ var cells = []; this._column.cells.forEach(function(cell){ cells.push(cell.getComponent()); }); return cells; } isVisible(){ return this._column.visible; } show(){ if(this._column.isGroup){ this._column.columns.forEach(function(column){ column.show(); }); }else { this._column.show(); } } hide(){ if(this._column.isGroup){ this._column.columns.forEach(function(column){ column.hide(); }); }else { this._column.hide(); } } toggle(){ if(this._column.visible){ this.hide(); }else { this.show(); } } delete(){ return this._column.delete(); } getSubColumns(){ var output = []; if(this._column.columns.length){ this._column.columns.forEach(function(column){ output.push(column.getComponent()); }); } return output; } getParentColumn(){ return this._column.getParentComponent(); } _getSelf(){ return this._column; } scrollTo(position, ifVisible){ return this._column.table.columnManager.scrollToColumn(this._column, position, ifVisible); } getTable(){ return this._column.table; } move(to, after){ var toColumn = this._column.table.columnManager.findColumn(to); if(toColumn){ this._column.table.columnManager.moveColumn(this._column, toColumn, after); }else { console.warn("Move Error - No matching column found:", toColumn); } } getNextColumn(){ var nextCol = this._column.nextColumn(); return nextCol ? nextCol.getComponent() : false; } getPrevColumn(){ var prevCol = this._column.prevColumn(); return prevCol ? prevCol.getComponent() : false; } updateDefinition(updates){ return this._column.updateDefinition(updates); } getWidth(){ return this._column.getWidth(); } setWidth(width){ var result; if(width === true){ result = this._column.reinitializeWidth(true); }else { result = this._column.setWidth(width); } this._column.table.columnManager.rerenderColumns(true); return result; } } var defaultColumnOptions = { "title": undefined, "field": undefined, "columns": undefined, "visible": undefined, "hozAlign": undefined, "vertAlign": undefined, "width": undefined, "minWidth": 40, "maxWidth": undefined, "maxInitialWidth": undefined, "cssClass": undefined, "variableHeight": undefined, "headerVertical": undefined, "headerHozAlign": undefined, "headerWordWrap": false, "editableTitle": undefined, }; class Column extends CoreFeature{ static defaultOptionList = defaultColumnOptions; constructor(def, parent, rowHeader){ super(parent.table); this.definition = def; //column definition this.parent = parent; //hold parent object this.type = "column"; //type of element this.columns = []; //child columns this.cells = []; //cells bound to this column this.isGroup = false; this.isRowHeader = rowHeader; this.element = this.createElement(); //column header element this.contentElement = false; this.titleHolderElement = false; this.titleElement = false; this.groupElement = this.createGroupElement(); //column group holder element this.hozAlign = ""; //horizontal text alignment this.vertAlign = ""; //vert text alignment //multi dimensional filed handling this.field =""; this.fieldStructure = ""; this.getFieldValue = ""; this.setFieldValue = ""; this.titleDownload = null; this.titleFormatterRendered = false; this.mapDefinitions(); this.setField(this.definition.field); this.modules = {}; //hold module variables; this.width = null; //column width this.widthStyled = ""; //column width pre-styled to improve render efficiency this.maxWidth = null; //column maximum width this.maxWidthStyled = ""; //column maximum pre-styled to improve render efficiency this.maxInitialWidth = null; this.minWidth = null; //column minimum width this.minWidthStyled = ""; //column minimum pre-styled to improve render efficiency this.widthFixed = false; //user has specified a width for this column this.visible = true; //default visible state this.component = null; //initialize column if(this.definition.columns){ this.isGroup = true; this.definition.columns.forEach((def, i) => { var newCol = new Column(def, this); this.attachColumn(newCol); }); this.checkColumnVisibility(); }else { parent.registerColumnField(this); } this._initialize(); } createElement (){ var el = document.createElement("div"); el.classList.add("tabulator-col"); el.setAttribute("role", "columnheader"); el.setAttribute("aria-sort", "none"); if(this.isRowHeader){ el.classList.add("tabulator-row-header"); } switch(this.table.options.columnHeaderVertAlign){ case "middle": el.style.justifyContent = "center"; break; case "bottom": el.style.justifyContent = "flex-end"; break; } return el; } createGroupElement (){ var el = document.createElement("div"); el.classList.add("tabulator-col-group-cols"); return el; } mapDefinitions(){ var defaults = this.table.options.columnDefaults; //map columnDefaults onto column definitions if(defaults){ for(let key in defaults){ if(typeof this.definition[key] === "undefined"){ this.definition[key] = defaults[key]; } } } this.definition = this.table.columnManager.optionsList.generate(Column.defaultOptionList, this.definition); } checkDefinition(){ Object.keys(this.definition).forEach((key) => { if(Column.defaultOptionList.indexOf(key) === -1){ console.warn("Invalid column definition option in '" + (this.field || this.definition.title) + "' column:", key); } }); } setField(field){ this.field = field; this.fieldStructure = field ? (this.table.options.nestedFieldSeparator ? field.split(this.table.options.nestedFieldSeparator) : [field]) : []; this.getFieldValue = this.fieldStructure.length > 1 ? this._getNestedData : this._getFlatData; this.setFieldValue = this.fieldStructure.length > 1 ? this._setNestedData : this._setFlatData; } //register column position with column manager registerColumnPosition(column){ this.parent.registerColumnPosition(column); } //register column position with column manager registerColumnField(column){ this.parent.registerColumnField(column); } //trigger position registration reRegisterPosition(){ if(this.isGroup){ this.columns.forEach(function(column){ column.reRegisterPosition(); }); }else { this.registerColu