UNPKG

tabulator-tables

Version:

Interactive table generation JavaScript library

2,144 lines (1,657 loc) 614 kB
/* Tabulator v5.1.5 (c) Oliver Folkerd 2022 */ class CoreFeature{ constructor(table){ this.table = table; } ////////////////////////////////////////// /////////////// DataLoad ///////////////// ////////////////////////////////////////// reloadData(data, silent){ return this.table.dataLoader.load(data, undefined, undefined, undefined, silent); } ////////////////////////////////////////// ///////////// 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(){ return this.table.modules.layout.layout(); } ////////////////////////////////////////// /////////////// 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]; } ////////////////////////////////////////// //////////////// Modules ///////////////// ////////////////////////////////////////// module(key){ return this.table.module(key); } } class Module extends CoreFeature{ constructor(table, name){ super(table); this._handler = null; } initialize(){ // setup module when table is initialized, to be overriden 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 Registation /// /////////////////////////////////// registerTableFunction(name, func){ if(typeof this.table[name] === "undefined"){ this.table[name] = func; }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; } refreshData(renderInPosition, handler){ if(!handler){ handler = this._handler; } if(handler){ this.table.rowManager.refreshActiveData(handler, false, renderInPosition); } } } 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 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; } } var defautlAccessors = {}; class Accessor extends Module{ 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, colCompnent; if(column.modules.accessor){ accessor = column.modules.accessor[key] || column.modules.accessor.accessor || false; if(accessor){ value = column.getFieldValue(data); if(value != "undefined"){ colCompnent = column.getComponent(); params = typeof accessor.params === "function" ? accessor.params(value, data, type, colCompnent, rowComponent) : accessor.params; column.setFieldValue(data, accessor.accessor(value, data, type, params, colCompnent, rowComponent)); } } } }); return data; } } //load defaults Accessor.moduleName = "accessor"; Accessor.accessors = defautlAccessors; var defaultConfig = { method: "GET", }; 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; } function serializeParams(params){ var output = generateParamsList(params), encoded = []; output.forEach(function(item){ encoded.push(encodeURIComponent(item.key) + "=" + encodeURIComponent(item.value)); }); return encoded.join("&"); } function defaultURLGenerator(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$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; } 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$1(params), form = new FormData(); output.forEach(function(item){ form.append(item.key, item.value); }); return form; }, }, }; class Ajax extends Module{ 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(params, ajaxParams); } 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(); } } } Ajax.moduleName = "ajax"; //load defaults Ajax.defaultConfig = defaultConfig; Ajax.defaultURLGenerator = defaultURLGenerator; Ajax.defaultLoaderPromise = defaultLoaderPromise; Ajax.contentTypeFormatters = defaultContentTypeFormatters; var defaultPasteActions = { replace:function(rows){ return this.table.setData(rows); }, update:function(rows){ return this.table.updateOrAddData(rows); }, insert:function(rows){ return this.table.addData(rows); }, }; 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; } } }; class Clipboard extends Module{ 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); //DEPRICATED - 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 { var list = this.table.modules.export.generateExportList(this.table.options.clipboardCopyConfig, this.table.options.clipboardCopyStyled, this.rowRange, "clipboard"); html = this.table.modules.export.genereateHTMLTable(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 range, 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.checkPaseOrigin(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; } checkPaseOrigin(e){ var valid = true; if(e.target.tagName != "DIV" || this.table.modules.edit.currentCell){ 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; } } Clipboard.moduleName = "clipboard"; //load defaults Clipboard.pasteActions = defaultPasteActions; Clipboard.pasteParsers = defaultPasteParsers; 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(){ return this._cell.row.getData(); } 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"); this.element = this.element; } _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); }); } //update tooltip on mouse enter if (this.table.options.tooltipGenerationMode === "hover"){ element.addEventListener("mouseenter", (e) => { this._generateTooltip(); }); } 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": case "null": this.element.innerHTML = ""; break; default: this.element.innerHTML = val; } } cellRendered(){ this.dispatch("cell-rendered", this); } //generate tooltip text _generateTooltip(){ var tooltip = this.column.tooltip; if(tooltip){ if(tooltip === true){ tooltip = this.value; }else if(typeof(tooltip) == "function"){ tooltip = tooltip(this.getComponent()); if(tooltip === false){ tooltip = ""; } } if(typeof tooltip === "undefined"){ tooltip = ""; } this.element.setAttribute("title", tooltip); }else { this.element.setAttribute("title", ""); } } //////////////////// 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){ var changed = this.setValueProcessData(value, mutate); 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){ var changed = false; if(this.value !== value){ 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._generateTooltip(); 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; } setHeight(){ this.height = this.row.height; this.element.style.height = 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.parent instanceof Column ? this._column.parent.getComponent() : false; } _getSelf(){ return this._column; } scrollTo(){ return this._column.table.columnManager.scrollToColumn(this._column); } 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.renderer.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, "tooltip": undefined, "cssClass": undefined, "variableHeight": undefined, "headerTooltip": undefined, "headerVertical": undefined, "headerHozAlign": undefined, "editableTitle": undefined, }; class Column extends CoreFeature{ constructor(def, parent){ 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.element = this.createElement(); //column header element this.contentElement = false; this.titleHolderElement = false; this.titleElement = false; this.groupElement = this.createGroupElement(); //column group holder element this.isGroup = false; this.tooltip = false; //hold column tooltip 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 prestyled to improve render efficiency this.maxWidth = null; //column maximum width this.maxWidthStyled = ""; //column maximum prestyled to improve render efficiency this.maxInitialWidth = null; this.minWidth = null; //column minimum width this.minWidthStyled = ""; //column minimum prestyled 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(); this.bindModuleColumns(); } createElement (){ var el = document.createElement("div"); el.classList.add("tabulator-col"); el.setAttribute("role", "columnheader"); el.setAttribute("aria-sort", "none"); 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.registerColumnPosition(this); } } _mapDepricatedFunctionality(){ //all previously deprecated functionality removed in the 5.0 release } setTooltip(){ var def = this.definition; //set header tooltips var tooltip = def.headerTooltip; if(tooltip){ if(tooltip === true){ if(def.field){ this.langBind("columns|" + def.field, (value) => { this.element.setAttribute("title", value || def.title); }); }else { this.element.setAttribute("title", def.title); } }else { if(typeof(tooltip) == "function"){ tooltip = tooltip(this.getComponent()); if(tooltip === false){ tooltip = ""; } } this.element.setAttribute("title", tooltip); } }else { this.element.setAttribute("title", ""); } } //build header element _initialize(){ var def = this.definition; while(this.element.firstChild) this.element.removeChild(this.element.firstChild); if(def.headerVertical){ this.element.classList.add("tabulator-col-vertical"); if(def.headerVertical === "flip"){ this.element.classList.add("tabulator-col-vertical-flip"); } } this.contentElement = this._bindEvents(); this.contentElement = this._buildColumnHeaderContent(); this.element.appendChild(this.contentElement); if(this.isGroup){ this._buildGroupHeader(); }else { this._buildColumnHeader(); } this.setTooltip(); this.dispatch("column-init", this); //update header tooltip on mouse enter this.element.addEventListener("mouseenter", (e) => { this.setTooltip(); }); } _bindEvents(){ var def = this.definition, dblTap, tapHold, tap; //setup header click event bindings if(typeof(def.headerClick) == "function"){ this.element.addEventListener("click", (e) => {def.headerClick(e, this.getComponent());}); } if(typeof(def.headerDblClick) == "function"){ this.element.addEventListener("dblclick", (e) => {def.headerDblClick(e, this.getComponent());}); } if(typeof(def.headerContext) == "function"){ this.element.addEventListener("contextmenu", (e) => {def.headerContext(e, this.getComponent());}); } //setup header tap event bindings if(typeof(def.headerTap) == "function"){ tap = false; this.element.addEventListener("touchstart", (e) => { tap = true; }, {passive: true}); this.element.addEventListener("touchend", (e) => { if(tap){ def.headerTap(e, this.getComponent()); } tap = false; }); } if(typeof(def.headerDblTap) == "function"){ dblTap = null; this.element.addEventListener("touchend", (e) => { if(dblTap){ clearTimeout(dblTap); dblTap = null; def.headerDblTap(e, this.getComponent()); }else { dblTap = setTimeout(() => { clearTimeout(dblTap); dblTap = null; }, 300); } }); } if(typeof(def.headerTapHold) == "function"){ tapHold = null; this.element.addEventListener("touchstart", (e) => { clearTimeout(tapHold); tapHold = setTimeout(function(){ clearTimeout(tapHold); tapHold = null; tap = false; def.headerTapHold(e, this.getComponent()); }, 1000); }, {passive: true}); this.element.addEventListener("touchend", (e) => { clearTimeout(tapHold); tapHold = null; }); } } //build header element for header _buildColumnHeader(){ var def = this.definition, table = this.table; this.dispatch("column-layout", this); //set column visibility if(typeof def.visible != "undefined"){ if(def.visible){ this.show(true); }else { this.hide(true); } } //asign additional css classes to column header if(def.cssClass){ var classeNames = def.cssClass.split(" "); classeNames.forEach((className) => { this.element.classList.add(className); }); } if(def.field){ this.element.setAttribute("tabulator-field", def.field); } //set min width if present this.setMinWidth(parseInt(def.minWidth)); if (def.maxInitialWidth) { this.maxInitialWidth = parseInt(def.maxInitialWidth); } if(def.maxWidth){ this.setMaxWidth(parseInt(def.maxWidth)); } this.reinitializeWidth(); //set tooltip if present this.tooltip = this.definition.tooltip; //set orizontal text alignment this.hozAlign = this.definition.hozAlign; this.vertAlign = this.definition.vertAlign; this.titleElement.style.textAlign = this.definition.headerHozAlign; } _buildColumnHeaderContent(){ var def = this.definition, table = this.table; var contentElement = document.createElement("div"); contentElement.classList.add("tabulator-col-content"); this.titleHolderElement = document.createElement("div"); this.titleHolderElement.classList.add("tabulator-col-title-holder"); contentElement.appendChild(this.titleHolderElement); this.titleElement = this._buildColumnHeaderTitle(); this.titleHolderElement.appendChild(this.titleElement); return contentElement; } //build title element of column _buildColumnHeaderTitle(){ var def = this.definition; var titleHolderElement = document.createElement("div"); titleHolderElement.classList.add("tabulator-col-title"); if(def.editableTitle){ var titleElement = document.createElement("input"); titleElement.classList.add("tabulator-title-editor"); titleElement.addEventListener("click", (e) => { e.stopPropagation(); titleElement.focus(); }); titleElement.addEventListener("change", () => { def.title = titleElement.value; this.dispatchExternal("columnTitleChanged", this.getComponent()); }); titleHolderElement.appendChild(titleElement); if(def.field){ this.langBind("columns|" + def.field, (text) => { titleElement.value = text || (def.title || "&nbsp;"); }); }else { titleElement.value = def.title || "&nbsp;"; } }else { if(def.field){ this.langBind("columns|" + def.field, (text) => { this._formatColumnHeaderTitle(titleHolderElement, text || (def.title || "&nbsp;")); }); }else { this._formatColumnHeaderTitle(titleHolderElement, def.title || "&nbsp;"); } } return titleHolderElement; } _formatColumnHeaderTitle(el, title){ var contents = this.chain("column-format", [this, title, el], null, () => { return title; }); switch(typeof contents){ case "object": if(contents instanceof Node){ el.appendChild(contents); }else { el.innerHTML = ""; console.warn("Format Error - Title formatter has returned a type of object, the only valid formatter object return is an instance of Node, the formatter returned:", contents); } break; case "undefined": case "null": el.innerHTML = ""; break; default: el.innerHTML = contents; } } //build header element for column group _buildGroupHeader(){ this.element.classList.add("tabulator-col-group"); this.element.setAttribute("role", "columngroup"); this.element.setAttribute("aria-title", this.definition.title); //asign additional css classes to column header if(this.definition.cssClass){ var classeNames = this.definition.cssClass.split(" "); classeNames.forEach((className) => { this.element.classList.add(className); }); } this.titleElement.style.textAlign = this.definition.headerHozAlign; this.element.appendChild(this.groupElement); } //flat field lookup _getFlatData(data){ return data[this.field]; } //nested field lookup _getNestedData(data){ var dataObj = data, structure = this.fieldStructure, length = structure.length, output; for(let i = 0; i < length; i++){ dataObj = dataObj[structure[i]]; output = dataObj; if(!dataObj){ break; } } return output; } //flat field set _setFlatData(data, value){ if(this.field){ data[this.field] = value; } } //nested field set _setNestedData(data, value){ var dataObj = data, structure = this.fieldStructure, length = structure.length; for(let i = 0; i < length; i++){ if(i == length -1){ dataObj[structure[i]] = value; }else { if(!dataObj[structure[i]]){ if(typeof value !== "