/* Tabulator v5.4.4 (c) Oliver Folkerd 2023 */ 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){ return this.table.deprecationAdvisor.check(oldOption, newOption); } 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 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; } } 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 if((y + this.element.offsetHeight) > Math.max(this.container.offsetHeight, scrollTop ? this.container.scrollHeight : 0)) { 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.top = (parseInt(this.element.style.top) - this.element.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(this.table, menuEl, menuContainer); } /////////////////////////////////// //////// Alert Management //////// /////////////////////////////////// alert(content, type){ return this.table.alertManager.alert(content, type); } clearAlert(){ return this.table.alertManager.clear(); } } var defaultAccessors = {}; 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, 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; } } //load defaults Accessor.moduleName = "accessor"; Accessor.accessors = defaultAccessors; 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 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$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 = urlBuilder; 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); //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.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"); } _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.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.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{ 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.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"); 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.registerColumnPosition(this); } } //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._buildColumnHeaderContent(); this.element.appendChild(this.contentElement); if(this.isGroup){ this._buildGroupHeader(); }else { this._buildColumnHeader(); } this.dispatch("column-init", this); } //build header element for header _buildColumnHeader(){ var def = this.definition; this.dispatch("column-layout", this); //set column visibility if(typeof def.visible != "undefined"){ if(def.visible){ this.show(true); }else { this.hide(true); } } //assign additional css classes to column header if(def.cssClass){ var classNames = def.cssClass.split(" "); classNames.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 horizontal text alignment this.hozAlign = this.definition.hozAlign; this.vertAlign = this.definition.vertAlign; this.titleElement.style.textAlign = this.definition.headerHozAlign; } _buildColumnHeaderContent(){ 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.headerWordWrap){ titleHolderElement.classList.add("tabulator-col-title-wrap"); } 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 || " "); }); }else { titleElement.value = def.title || " "; } }else { if(def.field){ this.langBind("columns|" + def.field, (text) => { this._formatColumnHeaderTitle(titleHolderElement, text || (def.title || " ")); }); }else { this._formatColumnHeaderTitle(titleHolderElement, def.title || " "); } } 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": 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 classNames = this.definition.cssClass.split(" "); classNames.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 !== "undefined"){ dataObj[structure[i]] = {}; }else { break; } } dataObj = dataObj[structure[i]]; } } } //attach column to this group attachColumn(column){ if(this.groupElement){ this.columns.push(column); this.groupElement.appendChild(column.getElement()); column.columnRendered(); }else { console.warn("Column Warning - Column being attached to another column instead of column group"); } } //vertically align header in column verticalAlign(alignment, height){ //calculate height of column header and group holder element var parentHeight = this.parent.isGroup ? this.parent.getGroupElement().clientHeight : (height || this.parent.getHeadersElement().clientHeight); // var parentHeight = this.parent.isGroup ? this.parent.getGroupElement().clientHeight : this.parent.getHeadersElement().clientHeight; this.element.style.height = parentHeight + "px"; this.dispatch("column-height", this, this.element.style.height); if(this.isGroup){ this.groupElement.style.minHeight = (parentHeight - this.contentElement.offsetHeight) + "px"; } //vertically align cell contents // if(!this.isGroup && alignment !== "top"){ // if(alignment === "bottom"){ // this.element.style.paddingTop = (this.element.clientHeight - this.contentElement.offsetHeight) + "px"; // }else{ // this.element.style.paddingTop = ((this.element.clientHeight - this.contentElement.offsetHeight) / 2) + "px"; // } // } this.columns.forEach(function(column){ column.verticalAlign(alignment); }); } //clear vertical alignment clearVerticalAlign(){ this.element.style.paddingTop = ""; this.element.style.height = ""; this.element.style.minHeight = ""; this.groupElement.style.minHeight = ""; this.columns.forEach(function(column){ column.clearVerticalAlign(); }); this.dispatch("column-height", this, ""); } //// Retrieve Column Information //// //return column header element getElement(){ return this.element; } //return column group element getGroupElement(){ return this.groupElement; } //return field name getField(){ return this.field; } getTitleDownload() { return this.titleDownload; } //return the first column in a group getFirstColumn(){ if(!this.isGroup){ return this; }else { if(this.columns.length){ return this.columns[0].getFirstColumn(); }else { return false; } } } //return the last column in a group getLastColumn(){ if(!this.isGroup){ return this; }else { if(this.columns.length){ return this.columns[this.columns.length -1].getLastColumn(); }else { return false; } } } //return all columns in a group getColumns(traverse){ var columns = []; if(traverse){ this.columns.forEach((column) => { columns.push(column); columns = columns.concat(column.getColumns(true)); }); }else { columns = this.columns; } return columns; } //return all columns in a group getCells(){ return this.cells; } //retrieve the top column in a group of columns getTopColumn(){ if(this.parent.isGroup){ return this.parent.getTopColumn(); }else { return this; } } //return column definition object getDefinition(updateBranches){ var colDefs = []; if(this.isGroup && updateBranches){ this.columns.forEach(function(column){ colDefs.push(column.getDefinition(true)); }); this.definition.columns = colDefs; } return this.definition; } //////////////////// Actions //////////////////// checkColumnVisibility(){ var visible = false; this.columns.forEach(function(column){ if(column.visible){ visible = true; } }); if(visible){ this.show(); this.dispatchExternal("columnVisibilityChanged", this.getComponent(), false); }else { this.hide(); } } //show column show(silent, responsiveToggle){ if(!this.visible){ this.visible = true; this.element.style.display = ""; if(this.parent.isGroup){ this.parent.checkColumnVisibility(); } this.cells.forEach(function(cell){ cell.show(); }); if(!this.isGroup && this.width === null){ this.reinitializeWidth(); } this.table.columnManager.verticalAlignHeaders(); this.dispatch("column-show", this, responsiveToggle); if(!silent){ this.dispatchExternal("columnVisibilityChanged", this.getComponent(), true); } if(this.parent.isGroup){ this.parent.matchChildWidths(); } if(!this.silent){ this.table.columnManager.rerenderColumns(); } } } //hide column hide(silent, responsiveToggle){ if(this.visible){ this.visible = false; this.element.style.display = "none"; this.table.columnManager.verticalAlignHeaders(); if(this.parent.isGroup){ this.parent.checkColumnVisibility(); } this.cells.forEach(function(cell){ cell.hide(); }); this.dispatch("column-hide", this, responsiveToggle); if(!silent){ this.dispatchExternal("columnVisibilityChanged", this.getComponent(), false); } if(this.parent.isGroup){ this.parent.matchChildWidths(); } if(!this.silent){ this.table.columnManager.rerenderColumns(); } } } matchChildWidths(){ var childWidth = 0; if(this.contentElement && this.columns.length){ this.columns.forEach(function(column){ if(column.visible){ childWidth += column.getWidth(); } }); this.contentElement.style.maxWidth = (childWidth - 1) + "px"; if(this.parent.isGroup){ this.parent.matchChildWidths(); } } } removeChild(child){ var index = this.columns.indexOf(child); if(index > -1){ this.columns.splice(index, 1); } if(!this.columns.length){ this.delete(); } } setWidth(width){ this.widthFixed = true; this.setWidthActual(width); } setWidthActual(width){ if(isNaN(width)){ width = Math.floor((this.table.element.clientWidth/100) * parseInt(width)); } width = Math.max(this.minWidth, width); if(this.maxWidth){ width = Math.min(this.maxWidth, width); } this.width = width; this.widthStyled = width ? width + "px" : ""; this.element.style.width = this.widthStyled; if(!this.isGroup){ this.cells.forEach(function(cell){ cell.setWidth(); }); } if(this.parent.isGroup){ this.parent.matchChildWidths(); } this.dispatch("column-width", this); } checkCellHeights(){ var rows = []; this.cells.forEach(function(cell){ if(cell.row.heightInitialized){ if(cell.row.getElement().offsetParent !== null){ rows.push(cell.row); cell.row.clearCellHeight(); }else { cell.row.heightInitialized = false; } } }); rows.forEach(function(row){ row.calcHeight(); }); rows.forEach(function(row){ row.setCellHeight(); }); } getWidth(){ var width = 0; if(this.isGroup){ this.columns.forEach(function(column){ if(column.visible){ width += column.getWidth(); } }); }else { width = this.width; } return width; } getLeftOffset(){ var offset = this.element.offsetLeft; if(this.parent.isGroup){ offset += this.parent.getLeftOffset(); } return offset; } getHeight(){ return Math.ceil(this.element.getBoundingClientRect().height); } setMinWidth(minWidth){ if(this.maxWidth && minWidth > this.maxWidth){ minWidth = this.maxWidth; console.warn("the minWidth ("+ minWidth + "px) for column '" + this.field + "' cannot be bigger that its maxWidth ("+ this.maxWidthStyled + ")"); } this.minWidth = minWidth; this.minWidthStyled = minWidth ? minWidth + "px" : ""; this.element.style.minWidth = this.minWidthStyled; this.cells.forEach(function(cell){ cell.setMinWidth(); }); } setMaxWidth(maxWidth){ if(this.minWidth && maxWidth < this.minWidth){ maxWidth = this.minWidth; console.warn("the maxWidth ("+ maxWidth + "px) for column '" + this.field + "' cannot be smaller that its minWidth ("+ this.minWidthStyled + ")"); } this.maxWidth = maxWidth; this.maxWidthStyled = maxWidth ? maxWidth + "px" : ""; this.element.style.maxWidth = this.maxWidthStyled; this.cells.forEach(function(cell){ cell.setMaxWidth(); }); } delete(){ return new Promise((resolve, reject) => { if(this.isGroup){ this.columns.forEach(function(column){ column.delete(); }); } this.dispatch("column-delete", this); var cellCount = this.cells.length; for(let i = 0; i < cellCount; i++){ this.cells[0].delete(); } if(this.element.parentNode){ this.element.parentNode.removeChild(this.element); } this.element = false; this.contentElement = false; this.titleElement = false; this.groupElement = false; if(this.parent.isGroup){ this.parent.removeChild(this); } this.table.columnManager.deregisterColumn(this); this.table.columnManager.rerenderColumns(true); resolve(); }); } columnRendered(){ if(this.titleFormatterRendered){ this.titleFormatterRendered(); } this.dispatch("column-rendered", this); } //////////////// Cell Management ///////////////// //generate cell for this column generateCell(row){ var cell = new Cell(this, row); this.cells.push(cell); return cell; } nextColumn(){ var index = this.table.columnManager.findColumnIndex(this); return index > -1 ? this._nextVisibleColumn(index + 1) : false; } _nextVisibleColumn(index){ var column = this.table.columnManager.getColumnByIndex(index); return !column || column.visible ? column : this._nextVisibleColumn(index + 1); } prevColumn(){ var index = this.table.columnManager.findColumnIndex(this); return index > -1 ? this._prevVisibleColumn(index - 1) : false; } _prevVisibleColumn(index){ var column = this.table.columnManager.getColumnByIndex(index); return !column || column.visible ? column : this._prevVisibleColumn(index - 1); } reinitializeWidth(force){ this.widthFixed = false; //set width if present if(typeof this.definition.width !== "undefined" && !force){ // maxInitialWidth ignored here as width specified this.setWidth(this.definition.width); } this.dispatch("column-width-fit-before", this); this.fitToData(force); this.dispatch("column-width-fit-after", this); } //set column width to maximum cell width for non group columns fitToData(force){ if(this.isGroup){ return; } if(!this.widthFixed){ this.element.style.width = ""; this.cells.forEach((cell) => { cell.clearWidth(); }); } var maxWidth = this.element.offsetWidth; if(!this.width || !this.widthFixed){ this.cells.forEach((cell) => { var width = cell.getWidth(); if(width > maxWidth){ maxWidth = width; } }); if(maxWidth){ var setTo = maxWidth + 1; if (this.maxInitialWidth && !force) { setTo = Math.min(setTo, this.maxInitialWidth); } this.setWidthActual(setTo); } } } updateDefinition(updates){ var definition; if(!this.isGroup){ if(!this.parent.isGroup){ definition = Object.assign({}, this.getDefinition()); definition = Object.assign(definition, updates); return this.table.columnManager.addColumn(definition, false, this) .then((column) => { if(definition.field == this.field){ this.field = false; //clear field name to prevent deletion of duplicate column from arrays } return this.delete() .then(() => { return column.getComponent(); }); }); }else { console.error("Column Update Error - The updateDefinition function is only available on ungrouped columns"); return Promise.reject("Column Update Error - The updateDefinition function is only available on columns, not column groups"); } }else { console.error("Column Update Error - The updateDefinition function is only available on ungrouped columns"); return Promise.reject("Column Update Error - The updateDefinition function is only available on columns, not column groups"); } } deleteCell(cell){ var index = this.cells.indexOf(cell); if(index > -1){ this.cells.splice(index, 1); } } //////////////// Object Generation ///////////////// getComponent(){ if(!this.component){ this.component = new ColumnComponent(this); } return this.component; } } Column.defaultOptionList = defaultColumnOptions; //public row object class RowComponent { 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(); } 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; } getIndex(){ return this._row.getData("data")[this._row.table.options.index]; } getPosition(){ return this._row.getPosition(); } watchPosition(callback){ return this._row.watchPosition(callback); } delete(){ return this._row.delete(); } scrollTo(){ return this._row.table.rowManager.scrollToRow(this._row); } move(to, after){ this._row.moveToRow(to, after); } update(data){ return this._row.updateData(data); } normalizeHeight(){ this._row.normalizeHeight(true); } _getSelf(){ return this._row; } reformat(){ return this._row.reinitialize(); } getTable(){ return this._row.table; } getNextRow(){ var row = this._row.nextRow(); return row ? row.getComponent() : row; } getPrevRow(){ var row = this._row.prevRow(); return row ? row.getComponent() : row; } } class Row extends CoreFeature{ constructor (data, parent, type = "row"){ super(parent.table); this.parent = parent; this.data = {}; this.type = type; //type of element this.element = false; this.modules = {}; //hold module variables; this.cells = []; this.height = 0; //hold element height this.heightStyled = ""; //hold element height pre-styled to improve render efficiency this.manualHeight = false; //user has manually set row height this.outerHeight = 0; //hold elements outer height this.initialized = false; //element has been rendered this.heightInitialized = false; //element has resized cells to fit this.position = 0; //store position of element in row list this.positionWatchers = []; this.component = null; this.created = false; this.setData(data); } create(){ if(!this.created){ this.created = true; this.generateElement(); } } createElement (){ var el = document.createElement("div"); el.classList.add("tabulator-row"); el.setAttribute("role", "row"); this.element = el; } getElement(){ this.create(); return this.element; } detachElement(){ if (this.element && this.element.parentNode){ this.element.parentNode.removeChild(this.element); } } generateElement(){ this.createElement(); this.dispatch("row-init", this); } generateCells(){ this.cells = this.table.columnManager.generateCells(this); } //functions to setup on first render initialize(force){ this.create(); if(!this.initialized || force){ this.deleteCells(); while(this.element.firstChild) this.element.removeChild(this.element.firstChild); this.dispatch("row-layout-before", this); this.generateCells(); this.initialized = true; this.table.columnManager.renderer.renderRowCells(this); if(force){ this.normalizeHeight(); } this.dispatch("row-layout", this); if(this.table.options.rowFormatter){ this.table.options.rowFormatter(this.getComponent()); } this.dispatch("row-layout-after", this); }else { this.table.columnManager.renderer.rerenderRowCells(this); } } reinitializeHeight(){ this.heightInitialized = false; if(this.element && this.element.offsetParent !== null){ this.normalizeHeight(true); } } deinitialize(){ this.initialized = false; } deinitializeHeight(){ this.heightInitialized = false; } reinitialize(children){ this.initialized = false; this.heightInitialized = false; if(!this.manualHeight){ this.height = 0; this.heightStyled = ""; } if(this.element && this.element.offsetParent !== null){ this.initialize(true); } this.dispatch("row-relayout", this); } //get heights when doing bulk row style calcs in virtual DOM calcHeight(force){ var maxHeight = 0, minHeight; if(this.table.options.rowHeight){ this.height = this.table.options.rowHeight; }else { minHeight = this.table.options.resizableRows ? this.element.clientHeight : 0; this.cells.forEach(function(cell){ var height = cell.getHeight(); if(height > maxHeight){ maxHeight = height; } }); if(force){ this.height = Math.max(maxHeight, minHeight); }else { this.height = this.manualHeight ? this.height : Math.max(maxHeight, minHeight); } } this.heightStyled = this.height ? this.height + "px" : ""; this.outerHeight = this.element.offsetHeight; } //set of cells setCellHeight(){ this.cells.forEach(function(cell){ cell.setHeight(); }); this.heightInitialized = true; } clearCellHeight(){ this.cells.forEach(function(cell){ cell.clearHeight(); }); } //normalize the height of elements in the row normalizeHeight(force){ if(force && !this.table.options.rowHeight){ this.clearCellHeight(); } this.calcHeight(force); this.setCellHeight(); } //set height of rows setHeight(height, force){ if(this.height != height || force){ this.manualHeight = true; this.height = height; this.heightStyled = height ? height + "px" : ""; this.setCellHeight(); // this.outerHeight = this.element.outerHeight(); this.outerHeight = this.element.offsetHeight; } } //return rows outer height getHeight(){ return this.outerHeight; } //return rows outer Width getWidth(){ return this.element.offsetWidth; } //////////////// Cell Management ///////////////// deleteCell(cell){ var index = this.cells.indexOf(cell); if(index > -1){ this.cells.splice(index, 1); } } //////////////// Data Management ///////////////// setData(data){ this.data = this.chain("row-data-init-before", [this, data], undefined, data); this.dispatch("row-data-init-after", this); } //update the rows data updateData(updatedData){ var visible = this.element && Helpers.elVisible(this.element), tempData = {}, newRowData; return new Promise((resolve, reject) => { if(typeof updatedData === "string"){ updatedData = JSON.parse(updatedData); } this.dispatch("row-data-save-before", this); if(this.subscribed("row-data-changing")){ tempData = Object.assign(tempData, this.data); tempData = Object.assign(tempData, updatedData); } newRowData = this.chain("row-data-changing", [this, tempData, updatedData], null, updatedData); //set data for (let attrname in newRowData) { this.data[attrname] = newRowData[attrname]; } this.dispatch("row-data-save-after", this); //update affected cells only for (let attrname in updatedData) { let columns = this.table.columnManager.getColumnsByFieldRoot(attrname); columns.forEach((column) => { let cell = this.getCell(column.getField()); if(cell){ let value = column.getFieldValue(newRowData); if(cell.getValue() !== value){ cell.setValueProcessData(value); if(visible){ cell.cellRendered(); } } } }); } //Partial reinitialization if visible if(visible){ this.normalizeHeight(true); if(this.table.options.rowFormatter){ this.table.options.rowFormatter(this.getComponent()); } }else { this.initialized = false; this.height = 0; this.heightStyled = ""; } this.dispatch("row-data-changed", this, visible, updatedData); //this.reinitialize(); this.dispatchExternal("rowUpdated", this.getComponent()); if(this.subscribedExternal("dataChanged")){ this.dispatchExternal("dataChanged", this.table.rowManager.getData()); } resolve(); }); } getData(transform){ if(transform){ return this.chain("row-data-retrieve", [this, transform], null, this.data); } return this.data; } getCell(column){ var match = false; column = this.table.columnManager.findColumn(column); if(!this.initialized && this.cells.length === 0){ this.generateCells(); } match = this.cells.find(function(cell){ return cell.column === column; }); return match; } getCellIndex(findCell){ return this.cells.findIndex(function(cell){ return cell === findCell; }); } findCell(subject){ return this.cells.find((cell) => { return cell.element === subject; }); } getCells(){ if(!this.initialized && this.cells.length === 0){ this.generateCells(); } return this.cells; } nextRow(){ var row = this.table.rowManager.nextDisplayRow(this, true); return row || false; } prevRow(){ var row = this.table.rowManager.prevDisplayRow(this, true); return row || false; } moveToRow(to, before){ var toRow = this.table.rowManager.findRow(to); if(toRow){ this.table.rowManager.moveRowActual(this, toRow, !before); this.table.rowManager.refreshActiveData("display", false, true); }else { console.warn("Move Error - No matching row found:", to); } } ///////////////////// Actions ///////////////////// delete(){ this.dispatch("row-delete", this); this.deleteActual(); return Promise.resolve(); } deleteActual(blockRedraw){ this.detachModules(); this.table.rowManager.deleteRow(this, blockRedraw); this.deleteCells(); this.initialized = false; this.heightInitialized = false; this.element = false; this.dispatch("row-deleted", this); } detachModules(){ this.dispatch("row-deleting", this); } deleteCells(){ var cellCount = this.cells.length; for(let i = 0; i < cellCount; i++){ this.cells[0].delete(); } } wipe(){ this.detachModules(); this.deleteCells(); if(this.element){ while(this.element.firstChild) this.element.removeChild(this.element.firstChild); if(this.element.parentNode){ this.element.parentNode.removeChild(this.element); } } this.element = false; this.modules = {}; } isDisplayed(){ return this.table.rowManager.getDisplayRows().includes(this); } getPosition(){ return this.isDisplayed() ? this.position : false; } setPosition(position){ if(position != this.position){ this.position = position; this.positionWatchers.forEach((callback) => { callback(this.position); }); } } watchPosition(callback){ this.positionWatchers.push(callback); callback(this.position); } getGroup(){ return this.modules.group || false; } //////////////// Object Generation ///////////////// getComponent(){ if(!this.component){ this.component = new RowComponent(this); } return this.component; } } var defaultCalculations = { "avg":function(values, data, calcParams){ var output = 0, precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : 2; if(values.length){ output = values.reduce(function(sum, value){ return Number(sum) + Number(value); }); output = output / values.length; output = precision !== false ? output.toFixed(precision) : output; } return parseFloat(output).toString(); }, "max":function(values, data, calcParams){ var output = null, precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false; values.forEach(function(value){ value = Number(value); if(value > output || output === null){ output = value; } }); return output !== null ? (precision !== false ? output.toFixed(precision) : output) : ""; }, "min":function(values, data, calcParams){ var output = null, precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false; values.forEach(function(value){ value = Number(value); if(value < output || output === null){ output = value; } }); return output !== null ? (precision !== false ? output.toFixed(precision) : output) : ""; }, "sum":function(values, data, calcParams){ var output = 0, precision = typeof calcParams.precision !== "undefined" ? calcParams.precision : false; if(values.length){ values.forEach(function(value){ value = Number(value); output += !isNaN(value) ? Number(value) : 0; }); } return precision !== false ? output.toFixed(precision) : output; }, "concat":function(values, data, calcParams){ var output = 0; if(values.length){ output = values.reduce(function(sum, value){ return String(sum) + String(value); }); } return output; }, "count":function(values, data, calcParams){ var output = 0; if(values.length){ values.forEach(function(value){ if(value){ output ++; } }); } return output; }, }; class ColumnCalcs extends Module{ constructor(table){ super(table); this.topCalcs = []; this.botCalcs = []; this.genColumn = false; this.topElement = this.createElement(); this.botElement = this.createElement(); this.topRow = false; this.botRow = false; this.topInitialized = false; this.botInitialized = false; this.blocked = false; this.recalcAfterBlock = false; this.registerTableOption("columnCalcs", true); this.registerColumnOption("topCalc"); this.registerColumnOption("topCalcParams"); this.registerColumnOption("topCalcFormatter"); this.registerColumnOption("topCalcFormatterParams"); this.registerColumnOption("bottomCalc"); this.registerColumnOption("bottomCalcParams"); this.registerColumnOption("bottomCalcFormatter"); this.registerColumnOption("bottomCalcFormatterParams"); } createElement (){ var el = document.createElement("div"); el.classList.add("tabulator-calcs-holder"); return el; } initialize(){ this.genColumn = new Column({field:"value"}, this); this.subscribe("cell-value-changed", this.cellValueChanged.bind(this)); this.subscribe("column-init", this.initializeColumnCheck.bind(this)); this.subscribe("row-deleted", this.rowsUpdated.bind(this)); this.subscribe("scroll-horizontal", this.scrollHorizontal.bind(this)); this.subscribe("row-added", this.rowsUpdated.bind(this)); this.subscribe("column-moved", this.recalcActiveRows.bind(this)); this.subscribe("column-add", this.recalcActiveRows.bind(this)); this.subscribe("data-refreshed", this.recalcActiveRowsRefresh.bind(this)); this.subscribe("table-redraw", this.tableRedraw.bind(this)); this.subscribe("rows-visible", this.visibleRows.bind(this)); this.subscribe("scrollbar-vertical", this.adjustForScrollbar.bind(this)); this.subscribe("redraw-blocked", this.blockRedraw.bind(this)); this.subscribe("redraw-restored", this.restoreRedraw.bind(this)); this.subscribe("table-redrawing", this.resizeHolderWidth.bind(this)); this.subscribe("column-resized", this.resizeHolderWidth.bind(this)); this.subscribe("column-show", this.resizeHolderWidth.bind(this)); this.subscribe("column-hide", this.resizeHolderWidth.bind(this)); this.registerTableFunction("getCalcResults", this.getResults.bind(this)); this.registerTableFunction("recalc", this.userRecalc.bind(this)); this.resizeHolderWidth(); } resizeHolderWidth(){ this.topElement.style.minWidth = this.table.columnManager.headersElement.offsetWidth + "px"; } tableRedraw(force){ this.recalc(this.table.rowManager.activeRows); if(force){ this.redraw(); } } blockRedraw(){ this.blocked = true; this.recalcAfterBlock = false; } restoreRedraw(){ this.blocked = false; if(this.recalcAfterBlock){ this.recalcAfterBlock = false; this.recalcActiveRowsRefresh(); } } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// userRecalc(){ this.recalc(this.table.rowManager.activeRows); } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// blockCheck(){ if(this.blocked){ this.recalcAfterBlock = true; } return this.blocked; } visibleRows(viewable, rows){ if(this.topRow){ rows.unshift(this.topRow); } if(this.botRow){ rows.push(this.botRow); } return rows; } rowsUpdated(row){ if(this.table.options.groupBy){ this.recalcRowGroup(row); }else { this.recalcActiveRows(); } } recalcActiveRowsRefresh(){ if(this.table.options.groupBy && this.table.options.dataTreeStartExpanded && this.table.options.dataTree){ this.recalcAll(); }else { this.recalcActiveRows(); } } recalcActiveRows(){ this.recalc(this.table.rowManager.activeRows); } cellValueChanged(cell){ if(cell.column.definition.topCalc || cell.column.definition.bottomCalc){ if(this.table.options.groupBy){ if(this.table.options.columnCalcs == "table" || this.table.options.columnCalcs == "both"){ this.recalcActiveRows(); } if(this.table.options.columnCalcs != "table"){ this.recalcRowGroup(cell.row); } }else { this.recalcActiveRows(); } } } initializeColumnCheck(column){ if(column.definition.topCalc || column.definition.bottomCalc){ this.initializeColumn(column); } } //initialize column calcs initializeColumn(column){ var def = column.definition; var config = { topCalcParams:def.topCalcParams || {}, botCalcParams:def.bottomCalcParams || {}, }; if(def.topCalc){ switch(typeof def.topCalc){ case "string": if(ColumnCalcs.calculations[def.topCalc]){ config.topCalc = ColumnCalcs.calculations[def.topCalc]; }else { console.warn("Column Calc Error - No such calculation found, ignoring: ", def.topCalc); } break; case "function": config.topCalc = def.topCalc; break; } if(config.topCalc){ column.modules.columnCalcs = config; this.topCalcs.push(column); if(this.table.options.columnCalcs != "group"){ this.initializeTopRow(); } } } if(def.bottomCalc){ switch(typeof def.bottomCalc){ case "string": if(ColumnCalcs.calculations[def.bottomCalc]){ config.botCalc = ColumnCalcs.calculations[def.bottomCalc]; }else { console.warn("Column Calc Error - No such calculation found, ignoring: ", def.bottomCalc); } break; case "function": config.botCalc = def.bottomCalc; break; } if(config.botCalc){ column.modules.columnCalcs = config; this.botCalcs.push(column); if(this.table.options.columnCalcs != "group"){ this.initializeBottomRow(); } } } } //dummy functions to handle being mock column manager registerColumnField(){} removeCalcs(){ var changed = false; if(this.topInitialized){ this.topInitialized = false; this.topElement.parentNode.removeChild(this.topElement); changed = true; } if(this.botInitialized){ this.botInitialized = false; this.footerRemove(this.botElement); changed = true; } if(changed){ this.table.rowManager.adjustTableSize(); } } reinitializeCalcs(){ if(this.topCalcs.length){ this.initializeTopRow(); } if(this.botCalcs.length){ this.initializeBottomRow(); } } initializeTopRow(){ if(!this.topInitialized){ this.table.columnManager.getContentsElement().insertBefore(this.topElement, this.table.columnManager.headersElement.nextSibling); this.topInitialized = true; } } initializeBottomRow(){ if(!this.botInitialized){ this.footerPrepend(this.botElement); this.botInitialized = true; } } scrollHorizontal(left){ if(this.botInitialized && this.botRow){ this.botElement.scrollLeft = left; } } recalc(rows){ var data, row; if(!this.blockCheck()){ if(this.topInitialized || this.botInitialized){ data = this.rowsToData(rows); if(this.topInitialized){ if(this.topRow){ this.topRow.deleteCells(); } row = this.generateRow("top", data); this.topRow = row; while(this.topElement.firstChild) this.topElement.removeChild(this.topElement.firstChild); this.topElement.appendChild(row.getElement()); row.initialize(true); } if(this.botInitialized){ if(this.botRow){ this.botRow.deleteCells(); } row = this.generateRow("bottom", data); this.botRow = row; while(this.botElement.firstChild) this.botElement.removeChild(this.botElement.firstChild); this.botElement.appendChild(row.getElement()); row.initialize(true); } this.table.rowManager.adjustTableSize(); //set resizable handles if(this.table.modExists("frozenColumns")){ this.table.modules.frozenColumns.layout(); } } } } recalcRowGroup(row){ this.recalcGroup(this.table.modules.groupRows.getRowGroup(row)); } recalcAll(){ if(this.topCalcs.length || this.botCalcs.length){ if(this.table.options.columnCalcs !== "group"){ this.recalcActiveRows(); } if(this.table.options.groupBy && this.table.options.columnCalcs !== "table"){ var groups = this.table.modules.groupRows.getChildGroups(); groups.forEach((group) => { this.recalcGroup(group); }); } } } recalcGroup(group){ var data, rowData; if(!this.blockCheck()){ if(group){ if(group.calcs){ if(group.calcs.bottom){ data = this.rowsToData(group.rows); rowData = this.generateRowData("bottom", data); group.calcs.bottom.updateData(rowData); group.calcs.bottom.reinitialize(); } if(group.calcs.top){ data = this.rowsToData(group.rows); rowData = this.generateRowData("top", data); group.calcs.top.updateData(rowData); group.calcs.top.reinitialize(); } } } } } //generate top stats row generateTopRow(rows){ return this.generateRow("top", this.rowsToData(rows)); } //generate bottom stats row generateBottomRow(rows){ return this.generateRow("bottom", this.rowsToData(rows)); } rowsToData(rows){ var data = []; rows.forEach((row) => { data.push(row.getData()); if(this.table.options.dataTree && this.table.options.dataTreeChildColumnCalcs){ if(row.modules.dataTree && row.modules.dataTree.open){ var children = this.rowsToData(this.table.modules.dataTree.getFilteredTreeChildren(row)); data = data.concat(children); } } }); return data; } //generate stats row generateRow(pos, data){ var rowData = this.generateRowData(pos, data), row; if(this.table.modExists("mutator")){ this.table.modules.mutator.disable(); } row = new Row(rowData, this, "calc"); if(this.table.modExists("mutator")){ this.table.modules.mutator.enable(); } row.getElement().classList.add("tabulator-calcs", "tabulator-calcs-" + pos); row.component = false; row.getComponent = () => { if(!row.component){ row.component = new CalcComponent(row); } return row.component; }; row.generateCells = () => { var cells = []; this.table.columnManager.columnsByIndex.forEach((column) => { //set field name of mock column this.genColumn.setField(column.getField()); this.genColumn.hozAlign = column.hozAlign; if(column.definition[pos + "CalcFormatter"] && this.table.modExists("format")){ this.genColumn.modules.format = { formatter: this.table.modules.format.getFormatter(column.definition[pos + "CalcFormatter"]), params: column.definition[pos + "CalcFormatterParams"] || {}, }; }else { this.genColumn.modules.format = { formatter: this.table.modules.format.getFormatter("plaintext"), params:{} }; } //ensure css class definition is replicated to calculation cell this.genColumn.definition.cssClass = column.definition.cssClass; //generate cell and assign to correct column var cell = new Cell(this.genColumn, row); cell.getElement(); cell.column = column; cell.setWidth(); column.cells.push(cell); cells.push(cell); if(!column.visible){ cell.hide(); } }); row.cells = cells; }; return row; } //generate stats row generateRowData(pos, data){ var rowData = {}, calcs = pos == "top" ? this.topCalcs : this.botCalcs, type = pos == "top" ? "topCalc" : "botCalc", params, paramKey; calcs.forEach(function(column){ var values = []; if(column.modules.columnCalcs && column.modules.columnCalcs[type]){ data.forEach(function(item){ values.push(column.getFieldValue(item)); }); paramKey = type + "Params"; params = typeof column.modules.columnCalcs[paramKey] === "function" ? column.modules.columnCalcs[paramKey](values, data) : column.modules.columnCalcs[paramKey]; column.setFieldValue(rowData, column.modules.columnCalcs[type](values, data, params)); } }); return rowData; } hasTopCalcs(){ return !!(this.topCalcs.length); } hasBottomCalcs(){ return !!(this.botCalcs.length); } //handle table redraw redraw(){ if(this.topRow){ this.topRow.normalizeHeight(true); } if(this.botRow){ this.botRow.normalizeHeight(true); } } //return the calculated getResults(){ var results = {}, groups; if(this.table.options.groupBy && this.table.modExists("groupRows")){ groups = this.table.modules.groupRows.getGroups(true); groups.forEach((group) => { results[group.getKey()] = this.getGroupResults(group); }); }else { results = { top: this.topRow ? this.topRow.getData() : {}, bottom: this.botRow ? this.botRow.getData() : {}, }; } return results; } //get results from a group getGroupResults(group){ var groupObj = group._getSelf(), subGroups = group.getSubGroups(), subGroupResults = {}, results = {}; subGroups.forEach((subgroup) => { subGroupResults[subgroup.getKey()] = this.getGroupResults(subgroup); }); results = { top: groupObj.calcs.top ? groupObj.calcs.top.getData() : {}, bottom: groupObj.calcs.bottom ? groupObj.calcs.bottom.getData() : {}, groups: subGroupResults, }; return results; } adjustForScrollbar(width){ if(this.botRow){ if(this.table.rtl){ this.botElement.style.paddingLeft = width + "px"; }else { this.botElement.style.paddingRight = width + "px"; } } } } ColumnCalcs.moduleName = "columnCalcs"; //load defaults ColumnCalcs.calculations = defaultCalculations; class DataTree extends Module{ constructor(table){ super(table); this.indent = 10; this.field = ""; this.collapseEl = null; this.expandEl = null; this.branchEl = null; this.elementField = false; this.startOpen = function(){}; this.registerTableOption("dataTree", false); //enable data tree this.registerTableOption("dataTreeFilter", true); //filter child rows this.registerTableOption("dataTreeSort", true); //sort child rows this.registerTableOption("dataTreeElementColumn", false); this.registerTableOption("dataTreeBranchElement", true);//show data tree branch element this.registerTableOption("dataTreeChildIndent", 9); //data tree child indent in px this.registerTableOption("dataTreeChildField", "_children");//data tre column field to look for child rows this.registerTableOption("dataTreeCollapseElement", false);//data tree row collapse element this.registerTableOption("dataTreeExpandElement", false);//data tree row expand element this.registerTableOption("dataTreeStartExpanded", false); this.registerTableOption("dataTreeChildColumnCalcs", false);//include visible data tree rows in column calculations this.registerTableOption("dataTreeSelectPropagate", false);//selecting a parent row selects its children //register component functions this.registerComponentFunction("row", "treeCollapse", this.collapseRow.bind(this)); this.registerComponentFunction("row", "treeExpand", this.expandRow.bind(this)); this.registerComponentFunction("row", "treeToggle", this.toggleRow.bind(this)); this.registerComponentFunction("row", "getTreeParent", this.getTreeParent.bind(this)); this.registerComponentFunction("row", "getTreeChildren", this.getRowChildren.bind(this)); this.registerComponentFunction("row", "addTreeChild", this.addTreeChildRow.bind(this)); this.registerComponentFunction("row", "isTreeExpanded", this.isRowExpanded.bind(this)); } initialize(){ if(this.table.options.dataTree){ var dummyEl = null, options = this.table.options; this.field = options.dataTreeChildField; this.indent = options.dataTreeChildIndent; if(this.options("movableRows")){ console.warn("The movableRows option is not available with dataTree enabled, moving of child rows could result in unpredictable behavior"); } if(options.dataTreeBranchElement){ if(options.dataTreeBranchElement === true){ this.branchEl = document.createElement("div"); this.branchEl.classList.add("tabulator-data-tree-branch"); }else { if(typeof options.dataTreeBranchElement === "string"){ dummyEl = document.createElement("div"); dummyEl.innerHTML = options.dataTreeBranchElement; this.branchEl = dummyEl.firstChild; }else { this.branchEl = options.dataTreeBranchElement; } } } if(options.dataTreeCollapseElement){ if(typeof options.dataTreeCollapseElement === "string"){ dummyEl = document.createElement("div"); dummyEl.innerHTML = options.dataTreeCollapseElement; this.collapseEl = dummyEl.firstChild; }else { this.collapseEl = options.dataTreeCollapseElement; } }else { this.collapseEl = document.createElement("div"); this.collapseEl.classList.add("tabulator-data-tree-control"); this.collapseEl.tabIndex = 0; this.collapseEl.innerHTML = "
"; } if(options.dataTreeExpandElement){ if(typeof options.dataTreeExpandElement === "string"){ dummyEl = document.createElement("div"); dummyEl.innerHTML = options.dataTreeExpandElement; this.expandEl = dummyEl.firstChild; }else { this.expandEl = options.dataTreeExpandElement; } }else { this.expandEl = document.createElement("div"); this.expandEl.classList.add("tabulator-data-tree-control"); this.expandEl.tabIndex = 0; this.expandEl.innerHTML = "
"; } switch(typeof options.dataTreeStartExpanded){ case "boolean": this.startOpen = function(row, index){ return options.dataTreeStartExpanded; }; break; case "function": this.startOpen = options.dataTreeStartExpanded; break; default: this.startOpen = function(row, index){ return options.dataTreeStartExpanded[index]; }; break; } this.subscribe("row-init", this.initializeRow.bind(this)); this.subscribe("row-layout-after", this.layoutRow.bind(this)); this.subscribe("row-deleted", this.rowDelete.bind(this),0); this.subscribe("row-data-changed", this.rowDataChanged.bind(this), 10); this.subscribe("cell-value-updated", this.cellValueChanged.bind(this)); this.subscribe("edit-cancelled", this.cellValueChanged.bind(this)); this.subscribe("column-moving-rows", this.columnMoving.bind(this)); this.subscribe("table-built", this.initializeElementField.bind(this)); this.subscribe("table-redrawing", this.tableRedrawing.bind(this)); this.registerDisplayHandler(this.getRows.bind(this), 30); } } tableRedrawing(force){ var rows; if(force){ rows = this.table.rowManager.getRows(); rows.forEach((row) => { this.reinitializeRowChildren(row); }); } } initializeElementField(){ var firstCol = this.table.columnManager.getFirstVisibleColumn(); this.elementField = this.table.options.dataTreeElementColumn || (firstCol ? firstCol.field : false); } getRowChildren(row){ return this.getTreeChildren(row, true); } columnMoving(){ var rows = []; this.table.rowManager.rows.forEach((row) => { rows = rows.concat(this.getTreeChildren(row, false, true)); }); return rows; } rowDataChanged(row, visible, updatedData){ if(this.redrawNeeded(updatedData)){ this.initializeRow(row); if(visible){ this.layoutRow(row); this.refreshData(true); } } } cellValueChanged(cell){ var field = cell.column.getField(); if(field === this.elementField){ this.layoutRow(cell.row); } } initializeRow(row){ var childArray = row.getData()[this.field]; var isArray = Array.isArray(childArray); var children = isArray || (!isArray && typeof childArray === "object" && childArray !== null); if(!children && row.modules.dataTree && row.modules.dataTree.branchEl){ row.modules.dataTree.branchEl.parentNode.removeChild(row.modules.dataTree.branchEl); } if(!children && row.modules.dataTree && row.modules.dataTree.controlEl){ row.modules.dataTree.controlEl.parentNode.removeChild(row.modules.dataTree.controlEl); } row.modules.dataTree = { index: row.modules.dataTree ? row.modules.dataTree.index : 0, open: children ? (row.modules.dataTree ? row.modules.dataTree.open : this.startOpen(row.getComponent(), 0)) : false, controlEl: row.modules.dataTree && children ? row.modules.dataTree.controlEl : false, branchEl: row.modules.dataTree && children ? row.modules.dataTree.branchEl : false, parent: row.modules.dataTree ? row.modules.dataTree.parent : false, children:children, }; } reinitializeRowChildren(row){ var children = this.getTreeChildren(row, false, true); children.forEach(function(child){ child.reinitialize(true); }); } layoutRow(row){ var cell = this.elementField ? row.getCell(this.elementField) : row.getCells()[0], el = cell.getElement(), config = row.modules.dataTree; if(config.branchEl){ if(config.branchEl.parentNode){ config.branchEl.parentNode.removeChild(config.branchEl); } config.branchEl = false; } if(config.controlEl){ if(config.controlEl.parentNode){ config.controlEl.parentNode.removeChild(config.controlEl); } config.controlEl = false; } this.generateControlElement(row, el); row.getElement().classList.add("tabulator-tree-level-" + config.index); if(config.index){ if(this.branchEl){ config.branchEl = this.branchEl.cloneNode(true); el.insertBefore(config.branchEl, el.firstChild); if(this.table.rtl){ config.branchEl.style.marginRight = (((config.branchEl.offsetWidth + config.branchEl.style.marginLeft) * (config.index - 1)) + (config.index * this.indent)) + "px"; }else { config.branchEl.style.marginLeft = (((config.branchEl.offsetWidth + config.branchEl.style.marginRight) * (config.index - 1)) + (config.index * this.indent)) + "px"; } }else { if(this.table.rtl){ el.style.paddingRight = parseInt(window.getComputedStyle(el, null).getPropertyValue('padding-right')) + (config.index * this.indent) + "px"; }else { el.style.paddingLeft = parseInt(window.getComputedStyle(el, null).getPropertyValue('padding-left')) + (config.index * this.indent) + "px"; } } } } generateControlElement(row, el){ var config = row.modules.dataTree, oldControl = config.controlEl; el = el || row.getCells()[0].getElement(); if(config.children !== false){ if(config.open){ config.controlEl = this.collapseEl.cloneNode(true); config.controlEl.addEventListener("click", (e) => { e.stopPropagation(); this.collapseRow(row); }); }else { config.controlEl = this.expandEl.cloneNode(true); config.controlEl.addEventListener("click", (e) => { e.stopPropagation(); this.expandRow(row); }); } config.controlEl.addEventListener("mousedown", (e) => { e.stopPropagation(); }); if(oldControl && oldControl.parentNode === el){ oldControl.parentNode.replaceChild(config.controlEl,oldControl); }else { el.insertBefore(config.controlEl, el.firstChild); } } } getRows(rows){ var output = []; rows.forEach((row, i) => { var config, children; output.push(row); if(row instanceof Row){ row.create(); config = row.modules.dataTree.children; if(!config.index && config.children !== false){ children = this.getChildren(row); children.forEach((child) => { child.create(); output.push(child); }); } } }); return output; } getChildren(row, allChildren){ var config = row.modules.dataTree, children = [], output = []; if(config.children !== false && (config.open || allChildren)){ if(!Array.isArray(config.children)){ config.children = this.generateChildren(row); } if(this.table.modExists("filter") && this.table.options.dataTreeFilter){ children = this.table.modules.filter.filter(config.children); }else { children = config.children; } if(this.table.modExists("sort") && this.table.options.dataTreeSort){ this.table.modules.sort.sort(children); } children.forEach((child) => { output.push(child); var subChildren = this.getChildren(child); subChildren.forEach((sub) => { output.push(sub); }); }); } return output; } generateChildren(row){ var children = []; var childArray = row.getData()[this.field]; if(!Array.isArray(childArray)){ childArray = [childArray]; } childArray.forEach((childData) => { var childRow = new Row(childData || {}, this.table.rowManager); childRow.create(); childRow.modules.dataTree.index = row.modules.dataTree.index + 1; childRow.modules.dataTree.parent = row; if(childRow.modules.dataTree.children){ childRow.modules.dataTree.open = this.startOpen(childRow.getComponent(), childRow.modules.dataTree.index); } children.push(childRow); }); return children; } expandRow(row, silent){ var config = row.modules.dataTree; if(config.children !== false){ config.open = true; row.reinitialize(); this.refreshData(true); this.dispatchExternal("dataTreeRowExpanded", row.getComponent(), row.modules.dataTree.index); } } collapseRow(row){ var config = row.modules.dataTree; if(config.children !== false){ config.open = false; row.reinitialize(); this.refreshData(true); this.dispatchExternal("dataTreeRowCollapsed", row.getComponent(), row.modules.dataTree.index); } } toggleRow(row){ var config = row.modules.dataTree; if(config.children !== false){ if(config.open){ this.collapseRow(row); }else { this.expandRow(row); } } } isRowExpanded(row){ return row.modules.dataTree.open; } getTreeParent(row){ return row.modules.dataTree.parent ? row.modules.dataTree.parent.getComponent() : false; } getTreeParentRoot(row){ return row.modules.dataTree && row.modules.dataTree.parent ? this.getTreeParentRoot(row.modules.dataTree.parent) : row; } getFilteredTreeChildren(row){ var config = row.modules.dataTree, output = [], children; if(config.children){ if(!Array.isArray(config.children)){ config.children = this.generateChildren(row); } if(this.table.modExists("filter") && this.table.options.dataTreeFilter){ children = this.table.modules.filter.filter(config.children); }else { children = config.children; } children.forEach((childRow) => { if(childRow instanceof Row){ output.push(childRow); } }); } return output; } rowDelete(row){ var parent = row.modules.dataTree.parent, childIndex; if(parent){ childIndex = this.findChildIndex(row, parent); if(childIndex !== false){ parent.data[this.field].splice(childIndex, 1); } if(!parent.data[this.field].length){ delete parent.data[this.field]; } this.initializeRow(parent); this.layoutRow(parent); } this.refreshData(true); } addTreeChildRow(row, data, top, index){ var childIndex = false; if(typeof data === "string"){ data = JSON.parse(data); } if(!Array.isArray(row.data[this.field])){ row.data[this.field] = []; row.modules.dataTree.open = this.startOpen(row.getComponent(), row.modules.dataTree.index); } if(typeof index !== "undefined"){ childIndex = this.findChildIndex(index, row); if(childIndex !== false){ row.data[this.field].splice((top ? childIndex : childIndex + 1), 0, data); } } if(childIndex === false){ if(top){ row.data[this.field].unshift(data); }else { row.data[this.field].push(data); } } this.initializeRow(row); this.layoutRow(row); this.refreshData(true); } findChildIndex(subject, parent){ var match = false; if(typeof subject == "object"){ if(subject instanceof Row){ //subject is row element match = subject.data; }else if(subject instanceof RowComponent){ //subject is public row component match = subject._getSelf().data; }else if(typeof HTMLElement !== "undefined" && subject instanceof HTMLElement){ if(parent.modules.dataTree){ match = parent.modules.dataTree.children.find((childRow) => { return childRow instanceof Row ? childRow.element === subject : false; }); if(match){ match = match.data; } } }else if(subject === null){ match = false; } }else if(typeof subject == "undefined"){ match = false; }else { //subject should be treated as the index of the row match = parent.data[this.field].find((row) => { return row.data[this.table.options.index] == subject; }); } if(match){ if(Array.isArray(parent.data[this.field])){ match = parent.data[this.field].indexOf(match); } if(match == -1){ match = false; } } //catch all for any other type of input return match; } getTreeChildren(row, component, recurse){ var config = row.modules.dataTree, output = []; if(config.children){ if(!Array.isArray(config.children)){ config.children = this.generateChildren(row); } config.children.forEach((childRow) => { if(childRow instanceof Row){ output.push(component ? childRow.getComponent() : childRow); if(recurse){ output = output.concat(this.getTreeChildren(childRow, component, recurse)); } } }); } return output; } getChildField(){ return this.field; } redrawNeeded(data){ return (this.field ? typeof data[this.field] !== "undefined" : false) || (this.elementField ? typeof data[this.elementField] !== "undefined" : false); } } DataTree.moduleName = "dataTree"; function csv(list, options = {}, setFileContents){ var delimiter = options.delimiter ? options.delimiter : ",", fileContents = [], headers = []; list.forEach((row) => { var item = []; switch(row.type){ case "group": console.warn("Download Warning - CSV downloader cannot process row groups"); break; case "calc": console.warn("Download Warning - CSV downloader cannot process column calculations"); break; case "header": row.columns.forEach((col, i) => { if(col && col.depth === 1){ headers[i] = typeof col.value == "undefined" || col.value === null ? "" : ('"' + String(col.value).split('"').join('""') + '"'); } }); break; case "row": row.columns.forEach((col) => { if(col){ switch(typeof col.value){ case "object": col.value = col.value !== null ? JSON.stringify(col.value) : ""; break; case "undefined": col.value = ""; break; } item.push('"' + String(col.value).split('"').join('""') + '"'); } }); fileContents.push(item.join(delimiter)); break; } }); if(headers.length){ fileContents.unshift(headers.join(delimiter)); } fileContents = fileContents.join("\n"); if(options.bom){ fileContents = "\ufeff" + fileContents; } setFileContents(fileContents, "text/csv"); } function json(list, options, setFileContents){ var fileContents = []; list.forEach((row) => { var item = {}; switch(row.type){ case "header": break; case "group": console.warn("Download Warning - JSON downloader cannot process row groups"); break; case "calc": console.warn("Download Warning - JSON downloader cannot process column calculations"); break; case "row": row.columns.forEach((col) => { if(col){ item[col.component.getTitleDownload() || col.component.getField()] = col.value; } }); fileContents.push(item); break; } }); fileContents = JSON.stringify(fileContents, null, '\t'); setFileContents(fileContents, "application/json"); } function pdf(list, options = {}, setFileContents){ var header = [], body = [], autoTableParams = {}, rowGroupStyles = options.rowGroupStyles || { fontStyle: "bold", fontSize: 12, cellPadding: 6, fillColor: 220, }, rowCalcStyles = options.rowCalcStyles || { fontStyle: "bold", fontSize: 10, cellPadding: 4, fillColor: 232, }, jsPDFParams = options.jsPDF || {}, title = options.title ? options.title : ""; if(!jsPDFParams.orientation){ jsPDFParams.orientation = options.orientation || "landscape"; } if(!jsPDFParams.unit){ jsPDFParams.unit = "pt"; } //parse row list list.forEach((row) => { switch(row.type){ case "header": header.push(parseRow(row)); break; case "group": body.push(parseRow(row, rowGroupStyles)); break; case "calc": body.push(parseRow(row, rowCalcStyles)); break; case "row": body.push(parseRow(row)); break; } }); function parseRow(row, styles){ var rowData = []; row.columns.forEach((col) =>{ var cell; if(col){ switch(typeof col.value){ case "object": col.value = col.value !== null ? JSON.stringify(col.value) : ""; break; case "undefined": col.value = ""; break; } cell = { content:col.value, colSpan:col.width, rowSpan:col.height, }; if(styles){ cell.styles = styles; } rowData.push(cell); } }); return rowData; } //configure PDF var doc = new jspdf.jsPDF(jsPDFParams); //set document to landscape, better for most tables if(options.autoTable){ if(typeof options.autoTable === "function"){ autoTableParams = options.autoTable(doc) || {}; }else { autoTableParams = options.autoTable; } } if(title){ autoTableParams.didDrawPage = function(data) { doc.text(title, 40, 30); }; } autoTableParams.head = header; autoTableParams.body = body; doc.autoTable(autoTableParams); if(options.documentProcessing){ options.documentProcessing(doc); } setFileContents(doc.output("arraybuffer"), "application/pdf"); } function xlsx(list, options, setFileContents){ var self = this, sheetName = options.sheetName || "Sheet1", workbook = XLSX.utils.book_new(), tableFeatures = new CoreFeature(this), compression = 'compress' in options ? options.compress : true, output; workbook.SheetNames = []; workbook.Sheets = {}; function generateSheet(){ var rows = [], merges = [], worksheet = {}, range = {s: {c:0, r:0}, e: {c:(list[0] ? list[0].columns.reduce((a, b) => a + (b && b.width ? b.width : 1), 0) : 0), r:list.length }}; //parse row list list.forEach((row, i) => { var rowData = []; row.columns.forEach(function(col, j){ if(col){ rowData.push(!(col.value instanceof Date) && typeof col.value === "object" ? JSON.stringify(col.value) : col.value); if(col.width > 1 || col.height > -1){ if(col.height > 1 || col.width > 1){ merges.push({s:{r:i,c:j},e:{r:i + col.height - 1,c:j + col.width - 1}}); } } }else { rowData.push(""); } }); rows.push(rowData); }); //convert rows to worksheet XLSX.utils.sheet_add_aoa(worksheet, rows); worksheet['!ref'] = XLSX.utils.encode_range(range); if(merges.length){ worksheet["!merges"] = merges; } return worksheet; } if(options.sheetOnly){ setFileContents(generateSheet()); return; } if(options.sheets){ for(var sheet in options.sheets){ if(options.sheets[sheet] === true){ workbook.SheetNames.push(sheet); workbook.Sheets[sheet] = generateSheet(); }else { workbook.SheetNames.push(sheet); tableFeatures.commsSend(options.sheets[sheet], "download", "intercept",{ type:"xlsx", options:{sheetOnly:true}, active:self.active, intercept:function(data){ workbook.Sheets[sheet] = data; } }); } } }else { workbook.SheetNames.push(sheetName); workbook.Sheets[sheetName] = generateSheet(); } if(options.documentProcessing){ workbook = options.documentProcessing(workbook); } //convert workbook to binary array function s2ab(s) { var buf = new ArrayBuffer(s.length); var view = new Uint8Array(buf); for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; return buf; } output = XLSX.write(workbook, {bookType:'xlsx', bookSST:true, type: 'binary', compression }); setFileContents(s2ab(output), "application/octet-stream"); } function html(list, options, setFileContents){ if(this.modExists("export", true)){ setFileContents(this.modules.export.generateHTMLTable(list), "text/html"); } } function jsonLines (list, options, setFileContents) { const fileContents = []; list.forEach((row) => { const item = {}; switch (row.type) { case "header": break; case "group": console.warn("Download Warning - JSON downloader cannot process row groups"); break; case "calc": console.warn("Download Warning - JSON downloader cannot process column calculations"); break; case "row": row.columns.forEach((col) => { if (col) { item[col.component.getTitleDownload() || col.component.getField()] = col.value; } }); fileContents.push(JSON.stringify(item)); break; } }); setFileContents(fileContents.join("\n"), "application/x-ndjson"); } var defaultDownloaders = { csv:csv, json:json, jsonLines:jsonLines, pdf:pdf, xlsx:xlsx, html:html, }; class Download extends Module{ constructor(table){ super(table); this.registerTableOption("downloadEncoder", function(data, mimeType){ return new Blob([data],{type:mimeType}); }); //function to manipulate download data this.registerTableOption("downloadReady", undefined); //warn of function deprecation this.registerTableOption("downloadConfig", {}); //download config this.registerTableOption("downloadRowRange", "active"); //restrict download to active rows only this.registerColumnOption("download"); this.registerColumnOption("titleDownload"); } initialize(){ this.deprecatedOptionsCheck(); this.registerTableFunction("download", this.download.bind(this)); this.registerTableFunction("downloadToTab", this.downloadToTab.bind(this)); } deprecatedOptionsCheck(){ this.deprecationCheck("downloadReady", "downloadEncoder"); } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// downloadToTab(type, filename, options, active){ this.download(type, filename, options, active, true); } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// //trigger file download download(type, filename, options, range, interceptCallback){ var downloadFunc = false; function buildLink(data, mime){ if(interceptCallback){ if(interceptCallback === true){ this.triggerDownload(data, mime, type, filename, true); }else { interceptCallback(data); } }else { this.triggerDownload(data, mime, type, filename); } } if(typeof type == "function"){ downloadFunc = type; }else { if(Download.downloaders[type]){ downloadFunc = Download.downloaders[type]; }else { console.warn("Download Error - No such download type found: ", type); } } if(downloadFunc){ var list = this.generateExportList(range); downloadFunc.call(this.table, list , options || {}, buildLink.bind(this)); } } generateExportList(range){ var list = this.table.modules.export.generateExportList(this.table.options.downloadConfig, false, range || this.table.options.downloadRowRange, "download"); //assign group header formatter var groupHeader = this.table.options.groupHeaderDownload; if(groupHeader && !Array.isArray(groupHeader)){ groupHeader = [groupHeader]; } list.forEach((row) => { var group; if(row.type === "group"){ group = row.columns[0]; if(groupHeader && groupHeader[row.indent]){ group.value = groupHeader[row.indent](group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component); } } }); return list; } triggerDownload(data, mime, type, filename, newTab){ var element = document.createElement('a'), blob = this.table.options.downloadEncoder(data, mime); if(blob){ if(newTab){ window.open(window.URL.createObjectURL(blob)); }else { filename = filename || "Tabulator." + (typeof type === "function" ? "txt" : type); if(navigator.msSaveOrOpenBlob){ navigator.msSaveOrOpenBlob(blob, filename); }else { element.setAttribute('href', window.URL.createObjectURL(blob)); //set file title element.setAttribute('download', filename); //trigger download element.style.display = 'none'; document.body.appendChild(element); element.click(); //remove temporary link element document.body.removeChild(element); } } this.dispatchExternal("downloadComplete"); } } commsReceived(table, action, data){ switch(action){ case "intercept": this.download(data.type, "", data.options, data.active, data.intercept); break; } } } Download.moduleName = "download"; //load defaults Download.downloaders = defaultDownloaders; function maskInput(el, options){ var mask = options.mask, maskLetter = typeof options.maskLetterChar !== "undefined" ? options.maskLetterChar : "A", maskNumber = typeof options.maskNumberChar !== "undefined" ? options.maskNumberChar : "9", maskWildcard = typeof options.maskWildcardChar !== "undefined" ? options.maskWildcardChar : "*"; function fillSymbols(index){ var symbol = mask[index]; if(typeof symbol !== "undefined" && symbol !== maskWildcard && symbol !== maskLetter && symbol !== maskNumber){ el.value = el.value + "" + symbol; fillSymbols(index+1); } } el.addEventListener("keydown", (e) => { var index = el.value.length, char = e.key; if(e.keyCode > 46 && !e.ctrlKey && !e.metaKey){ if(index >= mask.length){ e.preventDefault(); e.stopPropagation(); return false; }else { switch(mask[index]){ case maskLetter: if(char.toUpperCase() == char.toLowerCase()){ e.preventDefault(); e.stopPropagation(); return false; } break; case maskNumber: if(isNaN(char)){ e.preventDefault(); e.stopPropagation(); return false; } break; case maskWildcard: break; default: if(char !== mask[index]){ e.preventDefault(); e.stopPropagation(); return false; } } } } return; }); el.addEventListener("keyup", (e) => { if(e.keyCode > 46){ if(options.maskAutoFill){ fillSymbols(el.value.length); } } }); if(!el.placeholder){ el.placeholder = mask; } if(options.maskAutoFill){ fillSymbols(el.value.length); } } //input element function input(cell, onRendered, success, cancel, editorParams){ //create and style input var cellValue = cell.getValue(), input = document.createElement("input"); input.setAttribute("type", editorParams.search ? "search" : "text"); input.style.padding = "4px"; input.style.width = "100%"; input.style.boxSizing = "border-box"; if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ for (let key in editorParams.elementAttributes){ if(key.charAt(0) == "+"){ key = key.slice(1); input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); }else { input.setAttribute(key, editorParams.elementAttributes[key]); } } } input.value = typeof cellValue !== "undefined" ? cellValue : ""; onRendered(function(){ if(cell._getSelf){ input.focus({preventScroll: true}); input.style.height = "100%"; if(editorParams.selectContents){ input.select(); } } }); function onChange(e){ if(((cellValue === null || typeof cellValue === "undefined") && input.value !== "") || input.value !== cellValue){ if(success(input.value)){ cellValue = input.value; //persist value if successfully validated incase editor is used as header filter } }else { cancel(); } } //submit new value on blur or change input.addEventListener("change", onChange); input.addEventListener("blur", onChange); //submit new value on enter input.addEventListener("keydown", function(e){ switch(e.keyCode){ // case 9: case 13: onChange(); break; case 27: cancel(); break; case 35: case 36: e.stopPropagation(); break; } }); if(editorParams.mask){ maskInput(input, editorParams); } return input; } //resizable text area element function textarea(cell, onRendered, success, cancel, editorParams){ var cellValue = cell.getValue(), vertNav = editorParams.verticalNavigation || "hybrid", value = String(cellValue !== null && typeof cellValue !== "undefined" ? cellValue : ""), input = document.createElement("textarea"), scrollHeight = 0; //create and style input input.style.display = "block"; input.style.padding = "2px"; input.style.height = "100%"; input.style.width = "100%"; input.style.boxSizing = "border-box"; input.style.whiteSpace = "pre-wrap"; input.style.resize = "none"; if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ for (let key in editorParams.elementAttributes){ if(key.charAt(0) == "+"){ key = key.slice(1); input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); }else { input.setAttribute(key, editorParams.elementAttributes[key]); } } } input.value = value; onRendered(function(){ if(cell._getSelf){ input.focus({preventScroll: true}); input.style.height = "100%"; input.scrollHeight; input.style.height = input.scrollHeight + "px"; cell.getRow().normalizeHeight(); if(editorParams.selectContents){ input.select(); } } }); function onChange(e){ if(((cellValue === null || typeof cellValue === "undefined") && input.value !== "") || input.value !== cellValue){ if(success(input.value)){ cellValue = input.value; //persist value if successfully validated incase editor is used as header filter } setTimeout(function(){ cell.getRow().normalizeHeight(); },300); }else { cancel(); } } //submit new value on blur or change input.addEventListener("change", onChange); input.addEventListener("blur", onChange); input.addEventListener("keyup", function(){ input.style.height = ""; var heightNow = input.scrollHeight; input.style.height = heightNow + "px"; if(heightNow != scrollHeight){ scrollHeight = heightNow; cell.getRow().normalizeHeight(); } }); input.addEventListener("keydown", function(e){ switch(e.keyCode){ case 13: if(e.shiftKey && editorParams.shiftEnterSubmit){ onChange(); } break; case 27: cancel(); break; case 38: //up arrow if(vertNav == "editor" || (vertNav == "hybrid" && input.selectionStart)){ e.stopImmediatePropagation(); e.stopPropagation(); } break; case 40: //down arrow if(vertNav == "editor" || (vertNav == "hybrid" && input.selectionStart !== input.value.length)){ e.stopImmediatePropagation(); e.stopPropagation(); } break; case 35: case 36: e.stopPropagation(); break; } }); if(editorParams.mask){ maskInput(input, editorParams); } return input; } //input element with type of number function number(cell, onRendered, success, cancel, editorParams){ var cellValue = cell.getValue(), vertNav = editorParams.verticalNavigation || "editor", input = document.createElement("input"); input.setAttribute("type", "number"); if(typeof editorParams.max != "undefined"){ input.setAttribute("max", editorParams.max); } if(typeof editorParams.min != "undefined"){ input.setAttribute("min", editorParams.min); } if(typeof editorParams.step != "undefined"){ input.setAttribute("step", editorParams.step); } //create and style input input.style.padding = "4px"; input.style.width = "100%"; input.style.boxSizing = "border-box"; if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ for (let key in editorParams.elementAttributes){ if(key.charAt(0) == "+"){ key = key.slice(1); input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); }else { input.setAttribute(key, editorParams.elementAttributes[key]); } } } input.value = cellValue; var blurFunc = function(e){ onChange(); }; onRendered(function () { if(cell._getSelf){ //submit new value on blur input.removeEventListener("blur", blurFunc); input.focus({preventScroll: true}); input.style.height = "100%"; //submit new value on blur input.addEventListener("blur", blurFunc); if(editorParams.selectContents){ input.select(); } } }); function onChange(){ var value = input.value; if(!isNaN(value) && value !==""){ value = Number(value); } if(value !== cellValue){ if(success(value)){ cellValue = value; //persist value if successfully validated incase editor is used as header filter } }else { cancel(); } } //submit new value on enter input.addEventListener("keydown", function(e){ switch(e.keyCode){ case 13: // case 9: onChange(); break; case 27: cancel(); break; case 38: //up arrow case 40: //down arrow if(vertNav == "editor"){ e.stopImmediatePropagation(); e.stopPropagation(); } break; case 35: case 36: e.stopPropagation(); break; } }); if(editorParams.mask){ maskInput(input, editorParams); } return input; } //input element with type of number function range(cell, onRendered, success, cancel, editorParams){ var cellValue = cell.getValue(), input = document.createElement("input"); input.setAttribute("type", "range"); if (typeof editorParams.max != "undefined") { input.setAttribute("max", editorParams.max); } if (typeof editorParams.min != "undefined") { input.setAttribute("min", editorParams.min); } if (typeof editorParams.step != "undefined") { input.setAttribute("step", editorParams.step); } //create and style input input.style.padding = "4px"; input.style.width = "100%"; input.style.boxSizing = "border-box"; if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ for (let key in editorParams.elementAttributes){ if(key.charAt(0) == "+"){ key = key.slice(1); input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); }else { input.setAttribute(key, editorParams.elementAttributes[key]); } } } input.value = cellValue; onRendered(function () { if(cell._getSelf){ input.focus({preventScroll: true}); input.style.height = "100%"; } }); function onChange(){ var value = input.value; if(!isNaN(value) && value !==""){ value = Number(value); } if(value != cellValue){ if(success(value)){ cellValue = value; //persist value if successfully validated incase editor is used as header filter } }else { cancel(); } } //submit new value on blur input.addEventListener("blur", function(e){ onChange(); }); //submit new value on enter input.addEventListener("keydown", function(e){ switch(e.keyCode){ case 13: // case 9: onChange(); break; case 27: cancel(); break; } }); return input; } //input element function date(cell, onRendered, success, cancel, editorParams){ var inputFormat = editorParams.format, vertNav = editorParams.verticalNavigation || "editor", DT = inputFormat ? (window.DateTime || luxon.DateTime) : null; //create and style input var cellValue = cell.getValue(), input = document.createElement("input"); function convertDate(value){ var newDatetime; if(DT.isDateTime(value)){ newDatetime = value; }else if(inputFormat === "iso"){ newDatetime = DT.fromISO(String(value)); }else { newDatetime = DT.fromFormat(String(value), inputFormat); } return newDatetime.toFormat("yyyy-MM-dd"); } input.type = "date"; input.style.padding = "4px"; input.style.width = "100%"; input.style.boxSizing = "border-box"; if(editorParams.max){ input.setAttribute("max", inputFormat ? convertDate(editorParams.max) : editorParams.max); } if(editorParams.min){ input.setAttribute("min", inputFormat ? convertDate(editorParams.min) : editorParams.min); } if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ for (let key in editorParams.elementAttributes){ if(key.charAt(0) == "+"){ key = key.slice(1); input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); }else { input.setAttribute(key, editorParams.elementAttributes[key]); } } } cellValue = typeof cellValue !== "undefined" ? cellValue : ""; if(inputFormat){ if(DT){ cellValue = convertDate(cellValue); }else { console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js"); } } input.value = cellValue; onRendered(function(){ if(cell._getSelf){ input.focus({preventScroll: true}); input.style.height = "100%"; if(editorParams.selectContents){ input.select(); } } }); function onChange(){ var value = input.value, luxDate; if(((cellValue === null || typeof cellValue === "undefined") && value !== "") || value !== cellValue){ if(value && inputFormat){ luxDate = DT.fromFormat(String(value), "yyyy-MM-dd"); switch(inputFormat){ case true: value = luxDate; break; case "iso": value = luxDate.toISO(); break; default: value = luxDate.toFormat(inputFormat); } } if(success(value)){ cellValue = input.value; //persist value if successfully validated incase editor is used as header filter } }else { cancel(); } } //submit new value on blur input.addEventListener("blur", function(e) { if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input) { onChange(); // only on a "true" blur; not when focusing browser's date/time picker } }); //submit new value on enter input.addEventListener("keydown", function(e){ switch(e.keyCode){ // case 9: case 13: onChange(); break; case 27: cancel(); break; case 35: case 36: e.stopPropagation(); break; case 38: //up arrow case 40: //down arrow if(vertNav == "editor"){ e.stopImmediatePropagation(); e.stopPropagation(); } break; } }); return input; } //input element function time(cell, onRendered, success, cancel, editorParams){ var inputFormat = editorParams.format, vertNav = editorParams.verticalNavigation || "editor", DT = inputFormat ? (window.DateTime || luxon.DateTime) : null, newDatetime; //create and style input var cellValue = cell.getValue(), input = document.createElement("input"); input.type = "time"; input.style.padding = "4px"; input.style.width = "100%"; input.style.boxSizing = "border-box"; if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ for (let key in editorParams.elementAttributes){ if(key.charAt(0) == "+"){ key = key.slice(1); input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); }else { input.setAttribute(key, editorParams.elementAttributes[key]); } } } cellValue = typeof cellValue !== "undefined" ? cellValue : ""; if(inputFormat){ if(DT){ if(DT.isDateTime(cellValue)){ newDatetime = cellValue; }else if(inputFormat === "iso"){ newDatetime = DT.fromISO(String(cellValue)); }else { newDatetime = DT.fromFormat(String(cellValue), inputFormat); } cellValue = newDatetime.toFormat("hh:mm"); }else { console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js"); } } input.value = cellValue; onRendered(function(){ if(cell._getSelf){ input.focus({preventScroll: true}); input.style.height = "100%"; if(editorParams.selectContents){ input.select(); } } }); function onChange(){ var value = input.value, luxTime; if(((cellValue === null || typeof cellValue === "undefined") && value !== "") || value !== cellValue){ if(value && inputFormat){ luxTime = DT.fromFormat(String(value), "hh:mm"); switch(inputFormat){ case true: value = luxTime; break; case "iso": value = luxTime.toISO(); break; default: value = luxTime.toFormat(inputFormat); } } if(success(value)){ cellValue = input.value; //persist value if successfully validated incase editor is used as header filter } }else { cancel(); } } //submit new value on blur input.addEventListener("blur", function(e) { if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input) { onChange(); // only on a "true" blur; not when focusing browser's date/time picker } }); //submit new value on enter input.addEventListener("keydown", function(e){ switch(e.keyCode){ // case 9: case 13: onChange(); break; case 27: cancel(); break; case 35: case 36: e.stopPropagation(); break; case 38: //up arrow case 40: //down arrow if(vertNav == "editor"){ e.stopImmediatePropagation(); e.stopPropagation(); } break; } }); return input; } //input element function datetime(cell, onRendered, success, cancel, editorParams){ var inputFormat = editorParams.format, vertNav = editorParams.verticalNavigation || "editor", DT = inputFormat ? (window.DateTime || luxon.DateTime) : null, newDatetime; //create and style input var cellValue = cell.getValue(), input = document.createElement("input"); input.type = "datetime-local"; input.style.padding = "4px"; input.style.width = "100%"; input.style.boxSizing = "border-box"; if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ for (let key in editorParams.elementAttributes){ if(key.charAt(0) == "+"){ key = key.slice(1); input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); }else { input.setAttribute(key, editorParams.elementAttributes[key]); } } } cellValue = typeof cellValue !== "undefined" ? cellValue : ""; if(inputFormat){ if(DT){ if(DT.isDateTime(cellValue)){ newDatetime = cellValue; }else if(inputFormat === "iso"){ newDatetime = DT.fromISO(String(cellValue)); }else { newDatetime = DT.fromFormat(String(cellValue), inputFormat); } cellValue = newDatetime.toFormat("yyyy-MM-dd") + "T" + newDatetime.toFormat("hh:mm"); }else { console.error("Editor Error - 'date' editor 'format' param is dependant on luxon.js"); } } input.value = cellValue; onRendered(function(){ if(cell._getSelf){ input.focus({preventScroll: true}); input.style.height = "100%"; if(editorParams.selectContents){ input.select(); } } }); function onChange(){ var value = input.value, luxDateTime; if(((cellValue === null || typeof cellValue === "undefined") && value !== "") || value !== cellValue){ if(value && inputFormat){ luxDateTime = DT.fromISO(String(value)); switch(inputFormat){ case true: value = luxDateTime; break; case "iso": value = luxDateTime.toISO(); break; default: value = luxDateTime.toFormat(inputFormat); } } if(success(value)){ cellValue = input.value; //persist value if successfully validated incase editor is used as header filter } }else { cancel(); } } //submit new value on blur input.addEventListener("blur", function(e) { if (e.relatedTarget || e.rangeParent || e.explicitOriginalTarget !== input) { onChange(); // only on a "true" blur; not when focusing browser's date/time picker } }); //submit new value on enter input.addEventListener("keydown", function(e){ switch(e.keyCode){ // case 9: case 13: onChange(); break; case 27: cancel(); break; case 35: case 36: e.stopPropagation(); break; case 38: //up arrow case 40: //down arrow if(vertNav == "editor"){ e.stopImmediatePropagation(); e.stopPropagation(); } break; } }); return input; } class Edit{ constructor(editor, cell, onRendered, success, cancel, editorParams){ this.edit = editor; this.table = editor.table; this.cell = cell; this.params = this._initializeParams(editorParams); this.data = []; this.displayItems = []; this.currentItems = []; this.focusedItem = null; this.input = this._createInputElement(); this.listEl = this._createListElement(); this.initialValues = null; this.isFilter = !cell._getSelf; this.filterTimeout = null; this.filtered = false; this.typing = false; this.values = []; this.popup = null; this.listIteration = 0; this.lastAction=""; this.filterTerm=""; this.blurable = true; this.actions = { success:success, cancel:cancel }; this._deprecatedOptionsCheck(); this._initializeValue(); onRendered(this._onRendered.bind(this)); } _deprecatedOptionsCheck(){ if(this.params.listItemFormatter){ this.cell.getTable().deprecationAdvisor.msg("The listItemFormatter editor param has been deprecated, please see the latest editor documentation for updated options"); } if(this.params.sortValuesList){ this.cell.getTable().deprecationAdvisor.msg("The sortValuesList editor param has been deprecated, please see the latest editor documentation for updated options"); } if(this.params.searchFunc){ this.cell.getTable().deprecationAdvisor.msg("The searchFunc editor param has been deprecated, please see the latest editor documentation for updated options"); } if(this.params.searchingPlaceholder){ this.cell.getTable().deprecationAdvisor.msg("The searchingPlaceholder editor param has been deprecated, please see the latest editor documentation for updated options"); } } _initializeValue(){ var initialValue = this.cell.getValue(); if(typeof initialValue === "undefined" && typeof this.params.defaultValue !== "undefined"){ initialValue = this.params.defaultValue; } this.initialValues = this.params.multiselect ? initialValue : [initialValue]; if(this.isFilter){ this.input.value = this.initialValues ? this.initialValues.join(",") : ""; this.headerFilterInitialListGen(); } } _onRendered(){ var cellEl = this.cell.getElement(); function clickStop(e){ e.stopPropagation(); } if(!this.isFilter){ this.input.style.height = "100%"; this.input.focus({preventScroll: true}); } cellEl.addEventListener("click", clickStop); setTimeout(() => { cellEl.removeEventListener("click", clickStop); }, 1000); this.input.addEventListener("mousedown", this._preventPopupBlur.bind(this)); } _createListElement(){ var listEl = document.createElement("div"); listEl.classList.add("tabulator-edit-list"); listEl.addEventListener("mousedown", this._preventBlur.bind(this)); listEl.addEventListener("keydown", this._inputKeyDown.bind(this)); return listEl; } _setListWidth(){ var element = this.isFilter ? this.input : this.cell.getElement(); this.listEl.style.minWidth = element.offsetWidth + "px"; if(this.params.maxWidth){ if(this.params.maxWidth === true){ this.listEl.style.maxWidth = element.offsetWidth + "px"; }else if(typeof this.params.maxWidth === "number"){ this.listEl.style.maxWidth = this.params.maxWidth + "px"; }else { this.listEl.style.maxWidth = this.params.maxWidth; } } } _createInputElement(){ var attribs = this.params.elementAttributes; var input = document.createElement("input"); input.setAttribute("type", this.params.clearable ? "search" : "text"); input.style.padding = "4px"; input.style.width = "100%"; input.style.boxSizing = "border-box"; if(!this.params.autocomplete){ input.style.cursor = "default"; input.style.caretColor = "transparent"; // input.readOnly = (this.edit.currentCell != false); } if(attribs && typeof attribs == "object"){ for (let key in attribs){ if(key.charAt(0) == "+"){ key = key.slice(1); input.setAttribute(key, input.getAttribute(key) + attribs["+" + key]); }else { input.setAttribute(key, attribs[key]); } } } if(this.params.mask){ maskInput(input, this.params); } this._bindInputEvents(input); return input; } _initializeParams(params){ var valueKeys = ["values", "valuesURL", "valuesLookup"], valueCheck; params = Object.assign({}, params); params.verticalNavigation = params.verticalNavigation || "editor"; params.placeholderLoading = typeof params.placeholderLoading === "undefined" ? "Searching ..." : params.placeholderLoading; params.placeholderEmpty = typeof params.placeholderEmpty === "undefined" ? "No Results Found" : params.placeholderEmpty; params.filterDelay = typeof params.filterDelay === "undefined" ? 300 : params.filterDelay; params.emptyValue = Object.keys(params).includes("emptyValue") ? params.emptyValue : ""; valueCheck = Object.keys(params).filter(key => valueKeys.includes(key)).length; if(!valueCheck){ console.warn("list editor config error - either the values, valuesURL, or valuesLookup option must be set"); }else if(valueCheck > 1){ console.warn("list editor config error - only one of the values, valuesURL, or valuesLookup options can be set on the same editor"); } if(params.autocomplete){ if(params.multiselect){ params.multiselect = false; console.warn("list editor config error - multiselect option is not available when autocomplete is enabled"); } }else { if(params.freetext){ params.freetext = false; console.warn("list editor config error - freetext option is only available when autocomplete is enabled"); } if(params.filterFunc){ params.filterFunc = false; console.warn("list editor config error - filterFunc option is only available when autocomplete is enabled"); } if(params.filterRemote){ params.filterRemote = false; console.warn("list editor config error - filterRemote option is only available when autocomplete is enabled"); } if(params.mask){ params.mask = false; console.warn("list editor config error - mask option is only available when autocomplete is enabled"); } if(params.allowEmpty){ params.allowEmpty = false; console.warn("list editor config error - allowEmpty option is only available when autocomplete is enabled"); } if(params.listOnEmpty){ params.listOnEmpty = false; console.warn("list editor config error - listOnEmpty option is only available when autocomplete is enabled"); } } if(params.filterRemote && !(typeof params.valuesLookup === "function" || params.valuesURL)){ params.filterRemote = false; console.warn("list editor config error - filterRemote option should only be used when values list is populated from a remote source"); } return params; } ////////////////////////////////////// ////////// Event Handling //////////// ////////////////////////////////////// _bindInputEvents(input){ input.addEventListener("focus", this._inputFocus.bind(this)); input.addEventListener("click", this._inputClick.bind(this)); input.addEventListener("blur", this._inputBlur.bind(this)); input.addEventListener("keydown", this._inputKeyDown.bind(this)); input.addEventListener("search", this._inputSearch.bind(this)); if(this.params.autocomplete){ input.addEventListener("keyup", this._inputKeyUp.bind(this)); } } _inputFocus(e){ this.rebuildOptionsList(); } _filter(){ if(this.params.filterRemote){ clearTimeout(this.filterTimeout); this.filterTimeout = setTimeout(() => { this.rebuildOptionsList(); }, this.params.filterDelay); }else { this._filterList(); } } _inputClick(e){ e.stopPropagation(); } _inputBlur(e){ if(this.blurable){ if(this.popup){ this.popup.hide(); }else { this._resolveValue(true); } } } _inputSearch(){ this._clearChoices(); } _inputKeyDown(e){ switch(e.keyCode){ case 38: //up arrow this._keyUp(e); break; case 40: //down arrow this._keyDown(e); break; case 37: //left arrow case 39: //right arrow this._keySide(e); break; case 13: //enter this._keyEnter(); break; case 27: //escape this._keyEsc(); break; case 36: //home case 35: //end this._keyHomeEnd(e); break; case 9: //tab break; default: this._keySelectLetter(e); } } _inputKeyUp(e){ switch(e.keyCode){ case 38: //up arrow case 37: //left arrow case 39: //up arrow case 40: //right arrow case 13: //enter case 27: //escape break; default: this._keyAutoCompLetter(e); } } _preventPopupBlur(){ if(this.popup){ this.popup.blockHide(); } setTimeout(() =>{ if(this.popup){ this.popup.restoreHide(); } }, 10); } _preventBlur(){ this.blurable = false; setTimeout(() =>{ this.blurable = true; }, 10); } ////////////////////////////////////// //////// Keyboard Navigation ///////// ////////////////////////////////////// _keyUp(e){ var index = this.displayItems.indexOf(this.focusedItem); if(this.params.verticalNavigation == "editor" || (this.params.verticalNavigation == "hybrid" && index)){ e.stopImmediatePropagation(); e.stopPropagation(); e.preventDefault(); if(index > 0){ this._focusItem(this.displayItems[index - 1]); } } } _keyDown(e){ var index = this.displayItems.indexOf(this.focusedItem); if(this.params.verticalNavigation == "editor" || (this.params.verticalNavigation == "hybrid" && index < this.displayItems.length - 1)){ e.stopImmediatePropagation(); e.stopPropagation(); e.preventDefault(); if(index < this.displayItems.length - 1){ if(index == -1){ this._focusItem(this.displayItems[0]); }else { this._focusItem(this.displayItems[index + 1]); } } } } _keySide(e){ if(!this.params.autocomplete){ e.stopImmediatePropagation(); e.stopPropagation(); e.preventDefault(); } } _keyEnter(e){ if(this.params.autocomplete && this.lastAction === "typing"){ this._resolveValue(true); }else { if(this.focusedItem){ this._chooseItem(this.focusedItem); } } } _keyEsc(e){ this._cancel(); } _keyHomeEnd(e){ if(this.params.autocomplete){ //prevent table navigation while using input element e.stopImmediatePropagation(); } } _keySelectLetter(e){ if(!this.params.autocomplete){ // if(this.edit.currentCell === false){ e.preventDefault(); // } if(e.keyCode >= 38 && e.keyCode <= 90){ this._scrollToValue(e.keyCode); } } } _keyAutoCompLetter(e){ this._filter(); this.lastAction = "typing"; this.typing = true; } _scrollToValue(char){ clearTimeout(this.filterTimeout); var character = String.fromCharCode(char).toLowerCase(); this.filterTerm += character.toLowerCase(); var match = this.displayItems.find((item) => { return typeof item.label !== "undefined" && item.label.toLowerCase().startsWith(this.filterTerm); }); if(match){ this._focusItem(match); } this.filterTimeout = setTimeout(() => { this.filterTerm = ""; }, 800); } _focusItem(item){ this.lastAction = "focus"; if(this.focusedItem && this.focusedItem.element){ this.focusedItem.element.classList.remove("focused"); } this.focusedItem = item; if(item && item.element){ item.element.classList.add("focused"); item.element.scrollIntoView({behavior: 'smooth', block: 'nearest', inline: 'start'}); } } ////////////////////////////////////// /////// Data List Generation ///////// ////////////////////////////////////// headerFilterInitialListGen(){ this._generateOptions(true); } rebuildOptionsList(){ this._generateOptions() .then(this._sortOptions.bind(this)) .then(this._buildList.bind(this)) .then(this._showList.bind(this)) .catch((e) => { if(!Number.isInteger(e)){ console.error("List generation error", e); } }); } _filterList(){ this._buildList(this._filterOptions()); this._showList(); } _generateOptions(silent){ var values = []; var iteration = ++ this.listIteration; this.filtered = false; if(this.params.values){ values = this.params.values; }else if (this.params.valuesURL){ values = this._ajaxRequest(this.params.valuesURL, this.input.value); }else { if(typeof this.params.valuesLookup === "function"){ values = this.params.valuesLookup(this.cell, this.input.value); }else if(this.params.valuesLookup){ values = this._uniqueColumnValues(this.params.valuesLookupField); } } if(values instanceof Promise){ if(!silent){ this._addPlaceholder(this.params.placeholderLoading); } return values.then() .then((responseValues) => { if(this.listIteration === iteration){ return this._parseList(responseValues); }else { return Promise.reject(iteration); } }); }else { return Promise.resolve(this._parseList(values)); } } _addPlaceholder(contents){ var placeholder = document.createElement("div"); if(typeof contents === "function"){ contents = contents(this.cell.getComponent(), this.listEl); } if(contents){ this._clearList(); if(contents instanceof HTMLElement){ placeholder = contents; }else { placeholder.classList.add("tabulator-edit-list-placeholder"); placeholder.innerHTML = contents; } this.listEl.appendChild(placeholder); this._showList(); } } _ajaxRequest(url, term){ var params = this.params.filterRemote ? {term:term} : {}; url = urlBuilder(url, {}, params); return fetch(url) .then((response)=>{ if(response.ok) { return response.json() .catch((error)=>{ console.warn("List Ajax Load Error - Invalid JSON returned", error); return Promise.reject(error); }); }else { console.error("List Ajax Load Error - Connection Error: " + response.status, response.statusText); return Promise.reject(response); } }) .catch((error)=>{ console.error("List Ajax Load Error - Connection Error: ", error); return Promise.reject(error); }); } _uniqueColumnValues(field){ var output = {}, data = this.table.getData(this.params.valuesLookup), column; if(field){ column = this.table.columnManager.getColumnByField(field); }else { column = this.cell.getColumn()._getSelf(); } if(column){ data.forEach((row) => { var val = column.getFieldValue(row); if(val !== null && typeof val !== "undefined" && val !== ""){ output[val] = true; } }); }else { console.warn("unable to find matching column to create select lookup list:", field); output = []; } return Object.keys(output); } _parseList(inputValues){ var data = []; if(!Array.isArray(inputValues)){ inputValues = Object.entries(inputValues).map(([key, value]) => { return { label:value, value:key, }; }); } inputValues.forEach((value) => { if(typeof value !== "object"){ value = { label:value, value:value, }; } this._parseListItem(value, data, 0); }); if(!this.currentItems.length && this.params.freetext){ this.input.value = this.initialValues; this.typing = true; this.lastAction = "typing"; } this.data = data; return data; } _parseListItem(option, data, level){ var item = {}; if(option.options){ item = this._parseListGroup(option, level + 1); }else { item = { label:option.label, value:option.value, itemParams:option.itemParams, elementAttributes: option.elementAttributes, element:false, selected:false, visible:true, level:level, original:option, }; if(this.initialValues && this.initialValues.indexOf(option.value) > -1){ this._chooseItem(item, true); } } data.push(item); } _parseListGroup(option, level){ var item = { label:option.label, group:true, itemParams:option.itemParams, elementAttributes:option.elementAttributes, element:false, visible:true, level:level, options:[], original:option, }; option.options.forEach((child) => { this._parseListItem(child, item.options, level); }); return item; } _sortOptions(options){ var sorter; if(this.params.sort){ sorter = typeof this.params.sort === "function" ? this.params.sort : this._defaultSortFunction.bind(this); this._sortGroup(sorter, options); } return options; } _sortGroup(sorter, options){ options.sort((a,b) => { return sorter(a.label, b.label, a.value, b.value, a.original, b.original); }); options.forEach((option) => { if(option.group){ this._sortGroup(sorter, option.options); } }); } _defaultSortFunction(as, bs){ var a, b, a1, b1, i= 0, L, rx = /(\d+)|(\D+)/g, rd = /\d/; var emptyAlign = 0; if(this.params.sort === "desc"){ [as, bs] = [bs, as]; } //handle empty values if(!as && as!== 0){ emptyAlign = !bs && bs!== 0 ? 0 : -1; }else if(!bs && bs!== 0){ emptyAlign = 1; }else { if(isFinite(as) && isFinite(bs)) return as - bs; a = String(as).toLowerCase(); b = String(bs).toLowerCase(); if(a === b) return 0; if(!(rd.test(a) && rd.test(b))) return a > b ? 1 : -1; a = a.match(rx); b = b.match(rx); L = a.length > b.length ? b.length : a.length; while(i < L){ a1= a[i]; b1= b[i++]; if(a1 !== b1){ if(isFinite(a1) && isFinite(b1)){ if(a1.charAt(0) === "0") a1 = "." + a1; if(b1.charAt(0) === "0") b1 = "." + b1; return a1 - b1; } else return a1 > b1 ? 1 : -1; } } return a.length > b.length; } return emptyAlign; } _filterOptions(){ var filterFunc = this.params.filterFunc || this._defaultFilterFunc, term = this.input.value; if(term){ this.filtered = true; this.data.forEach((item) => { this._filterItem(filterFunc, term, item); }); }else { this.filtered = false; } return this.data; } _filterItem(func, term, item){ var matches = false; if(!item.group){ item.visible = func(term, item.label, item.value, item.original); }else { item.options.forEach((option) => { if(this._filterItem(func, term, option)){ matches = true; } }); item.visible = matches; } return item.visible; } _defaultFilterFunc(term, label, value, item){ term = String(term).toLowerCase(); if(label !== null && typeof label !== "undefined"){ if(String(label).toLowerCase().indexOf(term) > -1 || String(value).toLowerCase().indexOf(term) > -1){ return true; } } return false; } ////////////////////////////////////// /////////// Display List ///////////// ////////////////////////////////////// _clearList(){ while(this.listEl.firstChild) this.listEl.removeChild(this.listEl.firstChild); this.displayItems = []; } _buildList(data){ this._clearList(); data.forEach((option) => { this._buildItem(option); }); if(!this.displayItems.length){ this._addPlaceholder(this.params.placeholderEmpty); } } _buildItem(item){ var el = item.element, contents; if(!this.filtered || item.visible){ if(!el){ el = document.createElement("div"); el.tabIndex = 0; contents = this.params.itemFormatter ? this.params.itemFormatter(item.label, item.value, item.original, el) : item.label; if(contents instanceof HTMLElement){ el.appendChild(contents); }else { el.innerHTML = contents; } if(item.group){ el.classList.add("tabulator-edit-list-group"); }else { el.classList.add("tabulator-edit-list-item"); } el.classList.add("tabulator-edit-list-group-level-" + item.level); if(item.elementAttributes && typeof item.elementAttributes == "object"){ for (let key in item.elementAttributes){ if(key.charAt(0) == "+"){ key = key.slice(1); el.setAttribute(key, this.input.getAttribute(key) + item.elementAttributes["+" + key]); }else { el.setAttribute(key, item.elementAttributes[key]); } } } if(item.group){ el.addEventListener("click", this._groupClick.bind(this, item)); }else { el.addEventListener("click", this._itemClick.bind(this, item)); } el.addEventListener("mousedown", this._preventBlur.bind(this)); item.element = el; } this._styleItem(item); this.listEl.appendChild(el); if(item.group){ item.options.forEach((option) => { this._buildItem(option); }); }else { this.displayItems.push(item); } } } _showList(){ var startVis = this.popup && this.popup.isVisible(); if(this.input.parentNode){ if(this.params.autocomplete && this.input.value === "" && !this.params.listOnEmpty){ if(this.popup){ this.popup.hide(true); } return; } this._setListWidth(); if(!this.popup){ this.popup = this.edit.popup(this.listEl); } this.popup.show(this.cell.getElement(), "bottom"); if(!startVis){ setTimeout(() => { this.popup.hideOnBlur(this._resolveValue.bind(this, true)); }, 10); } } } _styleItem(item){ if(item && item.element){ if(item.selected){ item.element.classList.add("active"); }else { item.element.classList.remove("active"); } } } ////////////////////////////////////// ///////// User Interaction /////////// ////////////////////////////////////// _itemClick(item, e){ e.stopPropagation(); this._chooseItem(item); } _groupClick(item, e){ e.stopPropagation(); } ////////////////////////////////////// ////// Current Item Management /////// ////////////////////////////////////// _cancel(){ this.popup.hide(true); this.actions.cancel(); } _clearChoices(){ this.typing = true; this.currentItems.forEach((item) => { item.selected = false; this._styleItem(item); }); this.currentItems = []; this.focusedItem = null; } _chooseItem(item, silent){ var index; this.typing = false; if(this.params.multiselect){ index = this.currentItems.indexOf(item); if(index > -1){ this.currentItems.splice(index, 1); item.selected = false; }else { this.currentItems.push(item); item.selected = true; } this.input.value = this.currentItems.map(item => item.label).join(","); this._styleItem(item); }else { this.currentItems = [item]; item.selected = true; this.input.value = item.label; this._styleItem(item); if(!silent){ this._resolveValue(); } } this._focusItem(item); } _resolveValue(blur){ var output, initialValue; if(this.popup){ this.popup.hide(true); } if(this.params.multiselect){ output = this.currentItems.map(item => item.value); }else { if(blur && this.params.autocomplete && this.typing){ if(this.params.freetext || (this.params.allowEmpty && this.input.value === "")){ output = this.input.value; }else { this.actions.cancel(); return; } }else { if(this.currentItems[0]){ output = this.currentItems[0].value; }else { initialValue = Array.isArray(this.initialValues) ? this.initialValues[0] : this.initialValues; if(initialValue === null || typeof initialValue === "undefined" || initialValue === ""){ output = initialValue; }else { output = this.params.emptyValue; } } } } if(output === ""){ output = this.params.emptyValue; } this.actions.success(output); if(this.isFilter){ this.initialValues = output && !Array.isArray(output) ? [output] : output; this.currentItems = []; } } } function select(cell, onRendered, success, cancel, editorParams){ this.deprecationMsg("The select editor has been deprecated, please use the new list editor"); var list = new Edit(this, cell, onRendered, success, cancel, editorParams); return list.input; } function list(cell, onRendered, success, cancel, editorParams){ var list = new Edit(this, cell, onRendered, success, cancel, editorParams); return list.input; } function autocomplete(cell, onRendered, success, cancel, editorParams){ this.deprecationMsg("The autocomplete editor has been deprecated, please use the new list editor with the 'autocomplete' editorParam"); editorParams.autocomplete = true; var list = new Edit(this, cell, onRendered, success, cancel, editorParams); return list.input; } //star rating function star(cell, onRendered, success, cancel, editorParams){ var self = this, element = cell.getElement(), value = cell.getValue(), maxStars = element.getElementsByTagName("svg").length || 5, size = element.getElementsByTagName("svg")[0] ? element.getElementsByTagName("svg")[0].getAttribute("width") : 14, stars = [], starsHolder = document.createElement("div"), star = document.createElementNS('http://www.w3.org/2000/svg', "svg"); //change star type function starChange(val){ stars.forEach(function(star, i){ if(i < val){ if(self.table.browser == "ie"){ star.setAttribute("class", "tabulator-star-active"); }else { star.classList.replace("tabulator-star-inactive", "tabulator-star-active"); } star.innerHTML = ''; }else { if(self.table.browser == "ie"){ star.setAttribute("class", "tabulator-star-inactive"); }else { star.classList.replace("tabulator-star-active", "tabulator-star-inactive"); } star.innerHTML = ''; } }); } //build stars function buildStar(i){ var starHolder = document.createElement("span"); var nextStar = star.cloneNode(true); stars.push(nextStar); starHolder.addEventListener("mouseenter", function(e){ e.stopPropagation(); e.stopImmediatePropagation(); starChange(i); }); starHolder.addEventListener("mousemove", function(e){ e.stopPropagation(); e.stopImmediatePropagation(); }); starHolder.addEventListener("click", function(e){ e.stopPropagation(); e.stopImmediatePropagation(); success(i); element.blur(); }); starHolder.appendChild(nextStar); starsHolder.appendChild(starHolder); } //handle keyboard navigation value change function changeValue(val){ value = val; starChange(val); } //style cell element.style.whiteSpace = "nowrap"; element.style.overflow = "hidden"; element.style.textOverflow = "ellipsis"; //style holding element starsHolder.style.verticalAlign = "middle"; starsHolder.style.display = "inline-block"; starsHolder.style.padding = "4px"; //style star star.setAttribute("width", size); star.setAttribute("height", size); star.setAttribute("viewBox", "0 0 512 512"); star.setAttribute("xml:space", "preserve"); star.style.padding = "0 1px"; if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ for (let key in editorParams.elementAttributes){ if(key.charAt(0) == "+"){ key = key.slice(1); starsHolder.setAttribute(key, starsHolder.getAttribute(key) + editorParams.elementAttributes["+" + key]); }else { starsHolder.setAttribute(key, editorParams.elementAttributes[key]); } } } //create correct number of stars for(var i=1;i<= maxStars;i++){ buildStar(i); } //ensure value does not exceed number of stars value = Math.min(parseInt(value), maxStars); // set initial styling of stars starChange(value); starsHolder.addEventListener("mousemove", function(e){ starChange(0); }); starsHolder.addEventListener("click", function(e){ success(0); }); element.addEventListener("blur", function(e){ cancel(); }); //allow key based navigation element.addEventListener("keydown", function(e){ switch(e.keyCode){ case 39: //right arrow changeValue(value + 1); break; case 37: //left arrow changeValue(value - 1); break; case 13: //enter success(value); break; case 27: //escape cancel(); break; } }); return starsHolder; } //draggable progress bar function progress(cell, onRendered, success, cancel, editorParams){ var element = cell.getElement(), max = typeof editorParams.max === "undefined" ? ((element.getElementsByTagName("div")[0] && element.getElementsByTagName("div")[0].getAttribute("max")) || 100) : editorParams.max, min = typeof editorParams.min === "undefined" ? ((element.getElementsByTagName("div")[0] && element.getElementsByTagName("div")[0].getAttribute("min")) || 0) : editorParams.min, percent = (max - min) / 100, value = cell.getValue() || 0, handle = document.createElement("div"), bar = document.createElement("div"), mouseDrag, mouseDragWidth; //set new value function updateValue(){ var style = window.getComputedStyle(element, null); var calcVal = (percent * Math.round(bar.offsetWidth / ((element.clientWidth - parseInt(style.getPropertyValue("padding-left")) - parseInt(style.getPropertyValue("padding-right")))/100))) + min; success(calcVal); element.setAttribute("aria-valuenow", calcVal); element.setAttribute("aria-label", value); } //style handle handle.style.position = "absolute"; handle.style.right = "0"; handle.style.top = "0"; handle.style.bottom = "0"; handle.style.width = "5px"; handle.classList.add("tabulator-progress-handle"); //style bar bar.style.display = "inline-block"; bar.style.position = "relative"; // bar.style.top = "8px"; // bar.style.bottom = "8px"; // bar.style.left = "4px"; // bar.style.marginRight = "4px"; bar.style.height = "100%"; bar.style.backgroundColor = "#488CE9"; bar.style.maxWidth = "100%"; bar.style.minWidth = "0%"; if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ for (let key in editorParams.elementAttributes){ if(key.charAt(0) == "+"){ key = key.slice(1); bar.setAttribute(key, bar.getAttribute(key) + editorParams.elementAttributes["+" + key]); }else { bar.setAttribute(key, editorParams.elementAttributes[key]); } } } //style cell element.style.padding = "4px 4px"; //make sure value is in range value = Math.min(parseFloat(value), max); value = Math.max(parseFloat(value), min); //workout percentage value = Math.round((value - min) / percent); // bar.style.right = value + "%"; bar.style.width = value + "%"; element.setAttribute("aria-valuemin", min); element.setAttribute("aria-valuemax", max); bar.appendChild(handle); handle.addEventListener("mousedown", function(e){ mouseDrag = e.screenX; mouseDragWidth = bar.offsetWidth; }); handle.addEventListener("mouseover", function(){ handle.style.cursor = "ew-resize"; }); element.addEventListener("mousemove", function(e){ if(mouseDrag){ bar.style.width = (mouseDragWidth + e.screenX - mouseDrag) + "px"; } }); element.addEventListener("mouseup", function(e){ if(mouseDrag){ e.stopPropagation(); e.stopImmediatePropagation(); mouseDrag = false; mouseDragWidth = false; updateValue(); } }); //allow key based navigation element.addEventListener("keydown", function(e){ switch(e.keyCode){ case 39: //right arrow e.preventDefault(); bar.style.width = (bar.clientWidth + element.clientWidth/100) + "px"; break; case 37: //left arrow e.preventDefault(); bar.style.width = (bar.clientWidth - element.clientWidth/100) + "px"; break; case 9: //tab case 13: //enter updateValue(); break; case 27: //escape cancel(); break; } }); element.addEventListener("blur", function(){ cancel(); }); return bar; } //checkbox function tickCross(cell, onRendered, success, cancel, editorParams){ var value = cell.getValue(), input = document.createElement("input"), tristate = editorParams.tristate, indetermValue = typeof editorParams.indeterminateValue === "undefined" ? null : editorParams.indeterminateValue, indetermState = false, trueValueSet = Object.keys(editorParams).includes("trueValue"), falseValueSet = Object.keys(editorParams).includes("falseValue"); input.setAttribute("type", "checkbox"); input.style.marginTop = "5px"; input.style.boxSizing = "border-box"; if(editorParams.elementAttributes && typeof editorParams.elementAttributes == "object"){ for (let key in editorParams.elementAttributes){ if(key.charAt(0) == "+"){ key = key.slice(1); input.setAttribute(key, input.getAttribute(key) + editorParams.elementAttributes["+" + key]); }else { input.setAttribute(key, editorParams.elementAttributes[key]); } } } input.value = value; if(tristate && (typeof value === "undefined" || value === indetermValue || value === "")){ indetermState = true; input.indeterminate = true; } if(this.table.browser != "firefox" && this.table.browser != "safari"){ //prevent blur issue on mac firefox onRendered(function(){ if(cell._getSelf){ input.focus({preventScroll: true}); } }); } input.checked = trueValueSet ? value === editorParams.trueValue : (value === true || value === "true" || value === "True" || value === 1); function setValue(blur){ var checkedValue = input.checked; if(trueValueSet && checkedValue){ checkedValue = editorParams.trueValue; }else if(falseValueSet && !checkedValue){ checkedValue = editorParams.falseValue; } if(tristate){ if(!blur){ if(input.checked && !indetermState){ input.checked = false; input.indeterminate = true; indetermState = true; return indetermValue; }else { indetermState = false; return checkedValue; } }else { if(indetermState){ return indetermValue; }else { return checkedValue; } } }else { return checkedValue; } } //submit new value on blur input.addEventListener("change", function(e){ success(setValue()); }); input.addEventListener("blur", function(e){ success(setValue(true)); }); //submit new value on enter input.addEventListener("keydown", function(e){ if(e.keyCode == 13){ success(setValue()); } if(e.keyCode == 27){ cancel(); } }); return input; } var defaultEditors = { input:input, textarea:textarea, number:number, range:range, date:date, time:time, datetime:datetime, select:select, list:list, autocomplete:autocomplete, star:star, progress:progress, tickCross:tickCross, }; class Edit$1 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$1.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("row-layout", this.rowEditableCheck.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.findPrevEditableCell(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(); } } rowEditableCheck(row){ row.getCells().forEach((cell) => { if(cell.column.modules.edit && typeof cell.column.modules.edit.check === "function"){ this.updateCellClass(cell); } }); } //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": if(cell.row.initialized){ 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.currentCell !== cell){ 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; } } } //handle aborted edit function cancel(){ if(self.currentCell === cell){ self.cancelEdit(); } } 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(this.currentCell && 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$1.moduleName = "edit"; //load defaults Edit$1.editors = defaultEditors; class ExportRow{ constructor(type, columns, component, indent){ this.type = type; this.columns = columns; this.component = component || false; this.indent = indent || 0; } } class ExportColumn{ constructor(value, component, width, height, depth){ this.value = value; this.component = component || false; this.width = width; this.height = height; this.depth = depth; } } class Export extends Module{ constructor(table){ super(table); this.config = {}; this.cloneTableStyle = true; this.colVisProp = ""; this.registerTableOption("htmlOutputConfig", false); //html output config this.registerColumnOption("htmlOutput"); this.registerColumnOption("titleHtmlOutput"); } initialize(){ this.registerTableFunction("getHtml", this.getHtml.bind(this)); } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// generateExportList(config, style, range, colVisProp){ this.cloneTableStyle = style; this.config = config || {}; this.colVisProp = colVisProp; var headers = this.config.columnHeaders !== false ? this.headersToExportRows(this.generateColumnGroupHeaders()) : []; var body = this.bodyToExportRows(this.rowLookup(range)); return headers.concat(body); } generateTable(config, style, range, colVisProp){ var list = this.generateExportList(config, style, range, colVisProp); return this.generateTableElement(list); } rowLookup(range){ var rows = []; if(typeof range == "function"){ range.call(this.table).forEach((row) =>{ row = this.table.rowManager.findRow(row); if(row){ rows.push(row); } }); }else { switch(range){ case true: case "visible": rows = this.table.rowManager.getVisibleRows(false, true); break; case "all": rows = this.table.rowManager.rows; break; case "selected": rows = this.table.modules.selectRow.selectedRows; break; case "active": default: if(this.table.options.pagination){ rows = this.table.rowManager.getDisplayRows(this.table.rowManager.displayRows.length - 2); }else { rows = this.table.rowManager.getDisplayRows(); } } } return Object.assign([], rows); } generateColumnGroupHeaders(){ var output = []; var columns = this.config.columnGroups !== false ? this.table.columnManager.columns : this.table.columnManager.columnsByIndex; columns.forEach((column) => { var colData = this.processColumnGroup(column); if(colData){ output.push(colData); } }); return output; } processColumnGroup(column){ var subGroups = column.columns, maxDepth = 0, title = column.definition["title" + (this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1))] || column.definition.title; var groupData = { title:title, column:column, depth:1, }; if(subGroups.length){ groupData.subGroups = []; groupData.width = 0; subGroups.forEach((subGroup) => { var subGroupData = this.processColumnGroup(subGroup); if(subGroupData){ groupData.width += subGroupData.width; groupData.subGroups.push(subGroupData); if(subGroupData.depth > maxDepth){ maxDepth = subGroupData.depth; } } }); groupData.depth += maxDepth; if(!groupData.width){ return false; } }else { if(this.columnVisCheck(column)){ groupData.width = 1; }else { return false; } } return groupData; } columnVisCheck(column){ var visProp = column.definition[this.colVisProp]; if(typeof visProp === "function"){ visProp = visProp.call(this.table, column.getComponent()); } return visProp !== false && (column.visible || (!column.visible && visProp)); } headersToExportRows(columns){ var headers = [], headerDepth = 0, exportRows = []; function parseColumnGroup(column, level){ var depth = headerDepth - level; if(typeof headers[level] === "undefined"){ headers[level] = []; } column.height = column.subGroups ? 1 : (depth - column.depth) + 1; headers[level].push(column); if(column.height > 1){ for(let i = 1; i < column.height; i ++){ if(typeof headers[level + i] === "undefined"){ headers[level + i] = []; } headers[level + i].push(false); } } if(column.width > 1){ for(let i = 1; i < column.width; i ++){ headers[level].push(false); } } if(column.subGroups){ column.subGroups.forEach(function(subGroup){ parseColumnGroup(subGroup, level+1); }); } } //calculate maximum header depth columns.forEach(function(column){ if(column.depth > headerDepth){ headerDepth = column.depth; } }); columns.forEach(function(column){ parseColumnGroup(column,0); }); headers.forEach((header) => { var columns = []; header.forEach((col) => { if(col){ let title = typeof col.title === "undefined" ? "" : col.title; columns.push(new ExportColumn(title, col.column.getComponent(), col.width, col.height, col.depth)); }else { columns.push(null); } }); exportRows.push(new ExportRow("header", columns)); }); return exportRows; } bodyToExportRows(rows){ var columns = []; var exportRows = []; this.table.columnManager.columnsByIndex.forEach((column) => { if (this.columnVisCheck(column)) { columns.push(column.getComponent()); } }); if(this.config.columnCalcs !== false && this.table.modExists("columnCalcs")){ if(this.table.modules.columnCalcs.topInitialized){ rows.unshift(this.table.modules.columnCalcs.topRow); } if(this.table.modules.columnCalcs.botInitialized){ rows.push(this.table.modules.columnCalcs.botRow); } } rows = rows.filter((row) => { switch(row.type){ case "group": return this.config.rowGroups !== false; case "calc": return this.config.columnCalcs !== false; case "row": return !(this.table.options.dataTree && this.config.dataTree === false && row.modules.dataTree.parent); } return true; }); rows.forEach((row, i) => { var rowData = row.getData(this.colVisProp); var exportCols = []; var indent = 0; switch(row.type){ case "group": indent = row.level; exportCols.push(new ExportColumn(row.key, row.getComponent(), columns.length, 1)); break; case "calc" : case "row" : columns.forEach((col) => { exportCols.push(new ExportColumn(col._column.getFieldValue(rowData), col, 1, 1)); }); if(this.table.options.dataTree && this.config.dataTree !== false){ indent = row.modules.dataTree.index; } break; } exportRows.push(new ExportRow(row.type, exportCols, row.getComponent(), indent)); }); return exportRows; } generateTableElement(list){ var table = document.createElement("table"), headerEl = document.createElement("thead"), bodyEl = document.createElement("tbody"), styles = this.lookupTableStyles(), rowFormatter = this.table.options["rowFormatter" + (this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1))], setup = {}; setup.rowFormatter = rowFormatter !== null ? rowFormatter : this.table.options.rowFormatter; if(this.table.options.dataTree &&this.config.dataTree !== false && this.table.modExists("columnCalcs")){ setup.treeElementField = this.table.modules.dataTree.elementField; } //assign group header formatter setup.groupHeader = this.table.options["groupHeader" + (this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1))]; if(setup.groupHeader && !Array.isArray(setup.groupHeader)){ setup.groupHeader = [setup.groupHeader]; } table.classList.add("tabulator-print-table"); this.mapElementStyles(this.table.columnManager.getHeadersElement(), headerEl, ["border-top", "border-left", "border-right", "border-bottom", "background-color", "color", "font-weight", "font-family", "font-size"]); if(list.length > 1000){ console.warn("It may take a long time to render an HTML table with more than 1000 rows"); } list.forEach((row, i) => { let rowEl; switch(row.type){ case "header": headerEl.appendChild(this.generateHeaderElement(row, setup, styles)); break; case "group": bodyEl.appendChild(this.generateGroupElement(row, setup, styles)); break; case "calc": bodyEl.appendChild(this.generateCalcElement(row, setup, styles)); break; case "row": rowEl = this.generateRowElement(row, setup, styles); this.mapElementStyles(((i % 2) && styles.evenRow) ? styles.evenRow : styles.oddRow, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]); bodyEl.appendChild(rowEl); break; } }); if(headerEl.innerHTML){ table.appendChild(headerEl); } table.appendChild(bodyEl); this.mapElementStyles(this.table.element, table, ["border-top", "border-left", "border-right", "border-bottom"]); return table; } lookupTableStyles(){ var styles = {}; //lookup row styles if(this.cloneTableStyle && window.getComputedStyle){ styles.oddRow = this.table.element.querySelector(".tabulator-row-odd:not(.tabulator-group):not(.tabulator-calcs)"); styles.evenRow = this.table.element.querySelector(".tabulator-row-even:not(.tabulator-group):not(.tabulator-calcs)"); styles.calcRow = this.table.element.querySelector(".tabulator-row.tabulator-calcs"); styles.firstRow = this.table.element.querySelector(".tabulator-row:not(.tabulator-group):not(.tabulator-calcs)"); styles.firstGroup = this.table.element.getElementsByClassName("tabulator-group")[0]; if(styles.firstRow){ styles.styleCells = styles.firstRow.getElementsByClassName("tabulator-cell"); styles.firstCell = styles.styleCells[0]; styles.lastCell = styles.styleCells[styles.styleCells.length - 1]; } } return styles; } generateHeaderElement(row, setup, styles){ var rowEl = document.createElement("tr"); row.columns.forEach((column) => { if(column){ var cellEl = document.createElement("th"); var classNames = column.component._column.definition.cssClass ? column.component._column.definition.cssClass.split(" ") : []; cellEl.colSpan = column.width; cellEl.rowSpan = column.height; cellEl.innerHTML = column.value; if(this.cloneTableStyle){ cellEl.style.boxSizing = "border-box"; } classNames.forEach(function(className) { cellEl.classList.add(className); }); this.mapElementStyles(column.component.getElement(), cellEl, ["text-align", "border-top", "border-left", "border-right", "border-bottom", "background-color", "color", "font-weight", "font-family", "font-size"]); this.mapElementStyles(column.component._column.contentElement, cellEl, ["padding-top", "padding-left", "padding-right", "padding-bottom"]); if(column.component._column.visible){ this.mapElementStyles(column.component.getElement(), cellEl, ["width"]); }else { if(column.component._column.definition.width){ cellEl.style.width = column.component._column.definition.width + "px"; } } if(column.component._column.parent){ this.mapElementStyles(column.component._column.parent.groupElement, cellEl, ["border-top"]); } rowEl.appendChild(cellEl); } }); return rowEl; } generateGroupElement(row, setup, styles){ var rowEl = document.createElement("tr"), cellEl = document.createElement("td"), group = row.columns[0]; rowEl.classList.add("tabulator-print-table-row"); if(setup.groupHeader && setup.groupHeader[row.indent]){ group.value = setup.groupHeader[row.indent](group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component); }else { if(setup.groupHeader !== false){ group.value = row.component._group.generator(group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component); } } cellEl.colSpan = group.width; cellEl.innerHTML = group.value; rowEl.classList.add("tabulator-print-table-group"); rowEl.classList.add("tabulator-group-level-" + row.indent); if(group.component.isVisible()){ rowEl.classList.add("tabulator-group-visible"); } this.mapElementStyles(styles.firstGroup, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]); this.mapElementStyles(styles.firstGroup, cellEl, ["padding-top", "padding-left", "padding-right", "padding-bottom"]); rowEl.appendChild(cellEl); return rowEl; } generateCalcElement(row, setup, styles){ var rowEl = this.generateRowElement(row, setup, styles); rowEl.classList.add("tabulator-print-table-calcs"); this.mapElementStyles(styles.calcRow, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]); return rowEl; } generateRowElement(row, setup, styles){ var rowEl = document.createElement("tr"); rowEl.classList.add("tabulator-print-table-row"); row.columns.forEach((col, i) => { if(col){ var cellEl = document.createElement("td"), column = col.component._column, index = this.table.columnManager.findColumnIndex(column), value = col.value, cellStyle; var cellWrapper = { modules:{}, getValue:function(){ return value; }, getField:function(){ return column.definition.field; }, getElement:function(){ return cellEl; }, getColumn:function(){ return column.getComponent(); }, getData:function(){ return row.component.getData(); }, getRow:function(){ return row.component; }, getComponent:function(){ return cellWrapper; }, column:column, }; var classNames = column.definition.cssClass ? column.definition.cssClass.split(" ") : []; classNames.forEach(function(className) { cellEl.classList.add(className); }); if(this.table.modExists("format") && this.config.formatCells !== false){ value = this.table.modules.format.formatExportValue(cellWrapper, this.colVisProp); }else { switch(typeof value){ case "object": value = value !== null ? JSON.stringify(value) : ""; break; case "undefined": value = ""; break; } } if(value instanceof Node){ cellEl.appendChild(value); }else { cellEl.innerHTML = value; } cellStyle = styles.styleCells && styles.styleCells[index] ? styles.styleCells[index] : styles.firstCell; if(cellStyle){ this.mapElementStyles(cellStyle, cellEl, ["padding-top", "padding-left", "padding-right", "padding-bottom", "border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "text-align"]); if(column.definition.align){ cellEl.style.textAlign = column.definition.align; } } if(this.table.options.dataTree && this.config.dataTree !== false){ if((setup.treeElementField && setup.treeElementField == column.field) || (!setup.treeElementField && i == 0)){ if(row.component._row.modules.dataTree.controlEl){ cellEl.insertBefore(row.component._row.modules.dataTree.controlEl.cloneNode(true), cellEl.firstChild); } if(row.component._row.modules.dataTree.branchEl){ cellEl.insertBefore(row.component._row.modules.dataTree.branchEl.cloneNode(true), cellEl.firstChild); } } } rowEl.appendChild(cellEl); if(cellWrapper.modules.format && cellWrapper.modules.format.renderedCallback){ cellWrapper.modules.format.renderedCallback(); } } }); if(setup.rowFormatter && row.type === "row" && this.config.formatCells !== false){ let formatComponent = Object.assign(row.component); formatComponent.getElement = function(){return rowEl;}; setup.rowFormatter(row.component); } return rowEl; } generateHTMLTable(list){ var holder = document.createElement("div"); holder.appendChild(this.generateTableElement(list)); return holder.innerHTML; } getHtml(visible, style, config, colVisProp){ var list = this.generateExportList(config || this.table.options.htmlOutputConfig, style, visible, colVisProp || "htmlOutput"); return this.generateHTMLTable(list); } mapElementStyles(from, to, props){ if(this.cloneTableStyle && from && to){ var lookup = { "background-color" : "backgroundColor", "color" : "fontColor", "width" : "width", "font-weight" : "fontWeight", "font-family" : "fontFamily", "font-size" : "fontSize", "text-align" : "textAlign", "border-top" : "borderTop", "border-left" : "borderLeft", "border-right" : "borderRight", "border-bottom" : "borderBottom", "padding-top" : "paddingTop", "padding-left" : "paddingLeft", "padding-right" : "paddingRight", "padding-bottom" : "paddingBottom", }; if(window.getComputedStyle){ var fromStyle = window.getComputedStyle(from); props.forEach(function(prop){ if(!to.style[lookup[prop]]){ to.style[lookup[prop]] = fromStyle.getPropertyValue(prop); } }); } } } } Export.moduleName = "export"; var defaultFilters = { //equal to "=":function(filterVal, rowVal, rowData, filterParams){ return rowVal == filterVal ? true : false; }, //less than "<":function(filterVal, rowVal, rowData, filterParams){ return rowVal < filterVal ? true : false; }, //less than or equal to "<=":function(filterVal, rowVal, rowData, filterParams){ return rowVal <= filterVal ? true : false; }, //greater than ">":function(filterVal, rowVal, rowData, filterParams){ return rowVal > filterVal ? true : false; }, //greater than or equal to ">=":function(filterVal, rowVal, rowData, filterParams){ return rowVal >= filterVal ? true : false; }, //not equal to "!=":function(filterVal, rowVal, rowData, filterParams){ return rowVal != filterVal ? true : false; }, "regex":function(filterVal, rowVal, rowData, filterParams){ if(typeof filterVal == "string"){ filterVal = new RegExp(filterVal); } return filterVal.test(rowVal); }, //contains the string "like":function(filterVal, rowVal, rowData, filterParams){ if(filterVal === null || typeof filterVal === "undefined"){ return rowVal === filterVal ? true : false; }else { if(typeof rowVal !== 'undefined' && rowVal !== null){ return String(rowVal).toLowerCase().indexOf(filterVal.toLowerCase()) > -1; } else { return false; } } }, //contains the keywords "keywords":function(filterVal, rowVal, rowData, filterParams){ var keywords = filterVal.toLowerCase().split(typeof filterParams.separator === "undefined" ? " " : filterParams.separator), value = String(rowVal === null || typeof rowVal === "undefined" ? "" : rowVal).toLowerCase(), matches = []; keywords.forEach((keyword) =>{ if(value.includes(keyword)){ matches.push(true); } }); return filterParams.matchAll ? matches.length === keywords.length : !!matches.length; }, //starts with the string "starts":function(filterVal, rowVal, rowData, filterParams){ if(filterVal === null || typeof filterVal === "undefined"){ return rowVal === filterVal ? true : false; }else { if(typeof rowVal !== 'undefined' && rowVal !== null){ return String(rowVal).toLowerCase().startsWith(filterVal.toLowerCase()); } else { return false; } } }, //ends with the string "ends":function(filterVal, rowVal, rowData, filterParams){ if(filterVal === null || typeof filterVal === "undefined"){ return rowVal === filterVal ? true : false; }else { if(typeof rowVal !== 'undefined' && rowVal !== null){ return String(rowVal).toLowerCase().endsWith(filterVal.toLowerCase()); } else { return false; } } }, //in array "in":function(filterVal, rowVal, rowData, filterParams){ if(Array.isArray(filterVal)){ return filterVal.length ? filterVal.indexOf(rowVal) > -1 : true; }else { console.warn("Filter Error - filter value is not an array:", filterVal); return false; } }, }; class Filter extends Module{ constructor(table){ super(table); this.filterList = []; //hold filter list this.headerFilters = {}; //hold column filters this.headerFilterColumns = []; //hold columns that use header filters this.prevHeaderFilterChangeCheck = ""; this.prevHeaderFilterChangeCheck = "{}"; this.changed = false; //has filtering changed since last render this.tableInitialized = false; this.registerTableOption("filterMode", "local"); //local or remote filtering this.registerTableOption("initialFilter", false); //initial filtering criteria this.registerTableOption("initialHeaderFilter", false); //initial header filtering criteria this.registerTableOption("headerFilterLiveFilterDelay", 300); //delay before updating column after user types in header filter this.registerColumnOption("headerFilter"); this.registerColumnOption("headerFilterPlaceholder"); this.registerColumnOption("headerFilterParams"); this.registerColumnOption("headerFilterEmptyCheck"); this.registerColumnOption("headerFilterFunc"); this.registerColumnOption("headerFilterFuncParams"); this.registerColumnOption("headerFilterLiveFilter"); this.registerTableFunction("searchRows", this.searchRows.bind(this)); this.registerTableFunction("searchData", this.searchData.bind(this)); this.registerTableFunction("setFilter", this.userSetFilter.bind(this)); this.registerTableFunction("refreshFilter", this.userRefreshFilter.bind(this)); this.registerTableFunction("addFilter", this.userAddFilter.bind(this)); this.registerTableFunction("getFilters", this.getFilters.bind(this)); this.registerTableFunction("setHeaderFilterFocus", this.userSetHeaderFilterFocus.bind(this)); this.registerTableFunction("getHeaderFilterValue", this.userGetHeaderFilterValue.bind(this)); this.registerTableFunction("setHeaderFilterValue", this.userSetHeaderFilterValue.bind(this)); this.registerTableFunction("getHeaderFilters", this.getHeaderFilters.bind(this)); this.registerTableFunction("removeFilter", this.userRemoveFilter.bind(this)); this.registerTableFunction("clearFilter", this.userClearFilter.bind(this)); this.registerTableFunction("clearHeaderFilter", this.userClearHeaderFilter.bind(this)); this.registerComponentFunction("column", "headerFilterFocus", this.setHeaderFilterFocus.bind(this)); this.registerComponentFunction("column", "reloadHeaderFilter", this.reloadHeaderFilter.bind(this)); this.registerComponentFunction("column", "getHeaderFilterValue", this.getHeaderFilterValue.bind(this)); this.registerComponentFunction("column", "setHeaderFilterValue", this.setHeaderFilterValue.bind(this)); } initialize(){ this.subscribe("column-init", this.initializeColumnHeaderFilter.bind(this)); this.subscribe("column-width-fit-before", this.hideHeaderFilterElements.bind(this)); this.subscribe("column-width-fit-after", this.showHeaderFilterElements.bind(this)); this.subscribe("table-built", this.tableBuilt.bind(this)); if(this.table.options.filterMode === "remote"){ this.subscribe("data-params", this.remoteFilterParams.bind(this)); } this.registerDataHandler(this.filter.bind(this), 10); } tableBuilt(){ if(this.table.options.initialFilter){ this.setFilter(this.table.options.initialFilter); } if(this.table.options.initialHeaderFilter){ this.table.options.initialHeaderFilter.forEach((item) => { var column = this.table.columnManager.findColumn(item.field); if(column){ this.setHeaderFilterValue(column, item.value); }else { console.warn("Column Filter Error - No matching column found:", item.field); return false; } }); } this.tableInitialized = true; } remoteFilterParams(data, config, silent, params){ params.filter = this.getFilters(true, true); return params; } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// //set standard filters userSetFilter(field, type, value, params){ this.setFilter(field, type, value, params); this.refreshFilter(); } //set standard filters userRefreshFilter(){ this.refreshFilter(); } //add filter to array userAddFilter(field, type, value, params){ this.addFilter(field, type, value, params); this.refreshFilter(); } userSetHeaderFilterFocus(field){ var column = this.table.columnManager.findColumn(field); if(column){ this.setHeaderFilterFocus(column); }else { console.warn("Column Filter Focus Error - No matching column found:", field); return false; } } userGetHeaderFilterValue(field) { var column = this.table.columnManager.findColumn(field); if(column){ return this.getHeaderFilterValue(column); }else { console.warn("Column Filter Error - No matching column found:", field); } } userSetHeaderFilterValue(field, value){ var column = this.table.columnManager.findColumn(field); if(column){ this.setHeaderFilterValue(column, value); }else { console.warn("Column Filter Error - No matching column found:", field); return false; } } //remove filter from array userRemoveFilter(field, type, value){ this.removeFilter(field, type, value); this.refreshFilter(); } //clear filters userClearFilter(all){ this.clearFilter(all); this.refreshFilter(); } //clear header filters userClearHeaderFilter(){ this.clearHeaderFilter(); this.refreshFilter(); } //search for specific row components searchRows(field, type, value){ return this.search("rows", field, type, value); } //search for specific data searchData(field, type, value){ return this.search("data", field, type, value); } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// initializeColumnHeaderFilter(column){ var def = column.definition; if(def.headerFilter){ this.initializeColumn(column); } } //initialize column header filter initializeColumn(column, value){ var self = this, field = column.getField(); //handle successfully value change function success(value){ var filterType = (column.modules.filter.tagType == "input" && column.modules.filter.attrType == "text") || column.modules.filter.tagType == "textarea" ? "partial" : "match", type = "", filterChangeCheck = "", filterFunc; if(typeof column.modules.filter.prevSuccess === "undefined" || column.modules.filter.prevSuccess !== value){ column.modules.filter.prevSuccess = value; if(!column.modules.filter.emptyFunc(value)){ column.modules.filter.value = value; switch(typeof column.definition.headerFilterFunc){ case "string": if(Filter.filters[column.definition.headerFilterFunc]){ type = column.definition.headerFilterFunc; filterFunc = function(data){ var params = column.definition.headerFilterFuncParams || {}; var fieldVal = column.getFieldValue(data); params = typeof params === "function" ? params(value, fieldVal, data) : params; return Filter.filters[column.definition.headerFilterFunc](value, fieldVal, data, params); }; }else { console.warn("Header Filter Error - Matching filter function not found: ", column.definition.headerFilterFunc); } break; case "function": filterFunc = function(data){ var params = column.definition.headerFilterFuncParams || {}; var fieldVal = column.getFieldValue(data); params = typeof params === "function" ? params(value, fieldVal, data) : params; return column.definition.headerFilterFunc(value, fieldVal, data, params); }; type = filterFunc; break; } if(!filterFunc){ switch(filterType){ case "partial": filterFunc = function(data){ var colVal = column.getFieldValue(data); if(typeof colVal !== 'undefined' && colVal !== null){ return String(colVal).toLowerCase().indexOf(String(value).toLowerCase()) > -1; }else { return false; } }; type = "like"; break; default: filterFunc = function(data){ return column.getFieldValue(data) == value; }; type = "="; } } self.headerFilters[field] = {value:value, func:filterFunc, type:type}; }else { delete self.headerFilters[field]; } column.modules.filter.value = value; filterChangeCheck = JSON.stringify(self.headerFilters); if(self.prevHeaderFilterChangeCheck !== filterChangeCheck){ self.prevHeaderFilterChangeCheck = filterChangeCheck; self.trackChanges(); self.refreshFilter(); } } return true; } column.modules.filter = { success:success, attrType:false, tagType:false, emptyFunc:false, }; this.generateHeaderFilterElement(column); } generateHeaderFilterElement(column, initialValue, reinitialize){ var self = this, success = column.modules.filter.success, field = column.getField(), filterElement, editor, editorElement, cellWrapper, typingTimer, searchTrigger, params, onRenderedCallback; column.modules.filter.value = initialValue; //handle aborted edit function cancel(){} function onRendered(callback){ onRenderedCallback = callback; } if(column.modules.filter.headerElement && column.modules.filter.headerElement.parentNode){ column.contentElement.removeChild(column.modules.filter.headerElement.parentNode); } if(field){ //set empty value function column.modules.filter.emptyFunc = column.definition.headerFilterEmptyCheck || function(value){ return !value && value !== 0; }; filterElement = document.createElement("div"); filterElement.classList.add("tabulator-header-filter"); //set column editor switch(typeof column.definition.headerFilter){ case "string": if(self.table.modules.edit.editors[column.definition.headerFilter]){ editor = self.table.modules.edit.editors[column.definition.headerFilter]; if((column.definition.headerFilter === "tick" || column.definition.headerFilter === "tickCross") && !column.definition.headerFilterEmptyCheck){ column.modules.filter.emptyFunc = function(value){ return value !== true && value !== false; }; } }else { console.warn("Filter Error - Cannot build header filter, No such editor found: ", column.definition.editor); } break; case "function": editor = column.definition.headerFilter; break; case "boolean": if(column.modules.edit && column.modules.edit.editor){ editor = column.modules.edit.editor; }else { if(column.definition.formatter && self.table.modules.edit.editors[column.definition.formatter]){ editor = self.table.modules.edit.editors[column.definition.formatter]; if((column.definition.formatter === "tick" || column.definition.formatter === "tickCross") && !column.definition.headerFilterEmptyCheck){ column.modules.filter.emptyFunc = function(value){ return value !== true && value !== false; }; } }else { editor = self.table.modules.edit.editors["input"]; } } break; } if(editor){ cellWrapper = { getValue:function(){ return typeof initialValue !== "undefined" ? initialValue : ""; }, getField:function(){ return column.definition.field; }, getElement:function(){ return filterElement; }, getColumn:function(){ return column.getComponent(); }, getTable:() => { return this.table; }, getRow:function(){ return { normalizeHeight:function(){ } }; } }; params = column.definition.headerFilterParams || {}; params = typeof params === "function" ? params.call(self.table, cellWrapper) : params; editorElement = editor.call(this.table.modules.edit, cellWrapper, onRendered, success, cancel, params); if(!editorElement){ console.warn("Filter Error - Cannot add filter to " + field + " column, editor returned a value of false"); return; } if(!(editorElement instanceof Node)){ console.warn("Filter Error - Cannot add filter to " + field + " column, editor should return an instance of Node, the editor returned:", editorElement); return; } //set Placeholder Text self.langBind("headerFilters|columns|" + column.definition.field, function(value){ editorElement.setAttribute("placeholder", typeof value !== "undefined" && value ? value : (column.definition.headerFilterPlaceholder || self.langText("headerFilters|default"))); }); //focus on element on click editorElement.addEventListener("click", function(e){ e.stopPropagation(); editorElement.focus(); }); editorElement.addEventListener("focus", (e) => { var left = this.table.columnManager.contentsElement.scrollLeft; var headerPos = this.table.rowManager.element.scrollLeft; if(left !== headerPos){ this.table.rowManager.scrollHorizontal(left); this.table.columnManager.scrollHorizontal(left); } }); //live update filters as user types typingTimer = false; searchTrigger = function(e){ if(typingTimer){ clearTimeout(typingTimer); } typingTimer = setTimeout(function(){ success(editorElement.value); },self.table.options.headerFilterLiveFilterDelay); }; column.modules.filter.headerElement = editorElement; column.modules.filter.attrType = editorElement.hasAttribute("type") ? editorElement.getAttribute("type").toLowerCase() : "" ; column.modules.filter.tagType = editorElement.tagName.toLowerCase(); if(column.definition.headerFilterLiveFilter !== false){ if ( !( column.definition.headerFilter === 'autocomplete' || column.definition.headerFilter === 'tickCross' || ((column.definition.editor === 'autocomplete' || column.definition.editor === 'tickCross') && column.definition.headerFilter === true) ) ) { editorElement.addEventListener("keyup", searchTrigger); editorElement.addEventListener("search", searchTrigger); //update number filtered columns on change if(column.modules.filter.attrType == "number"){ editorElement.addEventListener("change", function(e){ success(editorElement.value); }); } //change text inputs to search inputs to allow for clearing of field if(column.modules.filter.attrType == "text" && this.table.browser !== "ie"){ editorElement.setAttribute("type", "search"); // editorElement.off("change blur"); //prevent blur from triggering filter and preventing selection click } } //prevent input and select elements from propagating click to column sorters etc if(column.modules.filter.tagType == "input" || column.modules.filter.tagType == "select" || column.modules.filter.tagType == "textarea"){ editorElement.addEventListener("mousedown",function(e){ e.stopPropagation(); }); } } filterElement.appendChild(editorElement); column.contentElement.appendChild(filterElement); if(!reinitialize){ self.headerFilterColumns.push(column); } if(onRenderedCallback){ onRenderedCallback(); } } }else { console.warn("Filter Error - Cannot add header filter, column has no field set:", column.definition.title); } } //hide all header filter elements (used to ensure correct column widths in "fitData" layout mode) hideHeaderFilterElements(){ this.headerFilterColumns.forEach(function(column){ if(column.modules.filter && column.modules.filter.headerElement){ column.modules.filter.headerElement.style.display = 'none'; } }); } //show all header filter elements (used to ensure correct column widths in "fitData" layout mode) showHeaderFilterElements(){ this.headerFilterColumns.forEach(function(column){ if(column.modules.filter && column.modules.filter.headerElement){ column.modules.filter.headerElement.style.display = ''; } }); } //programmatically set focus of header filter setHeaderFilterFocus(column){ if(column.modules.filter && column.modules.filter.headerElement){ column.modules.filter.headerElement.focus(); }else { console.warn("Column Filter Focus Error - No header filter set on column:", column.getField()); } } //programmatically get value of header filter getHeaderFilterValue(column){ if(column.modules.filter && column.modules.filter.headerElement){ return column.modules.filter.value; } else { console.warn("Column Filter Error - No header filter set on column:", column.getField()); } } //programmatically set value of header filter setHeaderFilterValue(column, value){ if (column){ if(column.modules.filter && column.modules.filter.headerElement){ this.generateHeaderFilterElement(column, value, true); column.modules.filter.success(value); }else { console.warn("Column Filter Error - No header filter set on column:", column.getField()); } } } reloadHeaderFilter(column){ if (column){ if(column.modules.filter && column.modules.filter.headerElement){ this.generateHeaderFilterElement(column, column.modules.filter.value, true); }else { console.warn("Column Filter Error - No header filter set on column:", column.getField()); } } } refreshFilter(){ if(this.tableInitialized){ if(this.table.options.filterMode === "remote"){ this.reloadData(null, false, false); }else { this.refreshData(true); } } //TODO - Persist left position of row manager // left = this.scrollLeft; // this.scrollHorizontal(left); } //check if the filters has changed since last use trackChanges(){ this.changed = true; this.dispatch("filter-changed"); } //check if the filters has changed since last use hasChanged(){ var changed = this.changed; this.changed = false; return changed; } //set standard filters setFilter(field, type, value, params){ this.filterList = []; if(!Array.isArray(field)){ field = [{field:field, type:type, value:value, params:params}]; } this.addFilter(field); } //add filter to array addFilter(field, type, value, params){ var changed = false; if(!Array.isArray(field)){ field = [{field:field, type:type, value:value, params:params}]; } field.forEach((filter) => { filter = this.findFilter(filter); if(filter){ this.filterList.push(filter); changed = true; } }); if(changed){ this.trackChanges(); } } findFilter(filter){ var column; if(Array.isArray(filter)){ return this.findSubFilters(filter); } var filterFunc = false; if(typeof filter.field == "function"){ filterFunc = function(data){ return filter.field(data, filter.type || {});// pass params to custom filter function }; }else { if(Filter.filters[filter.type]){ column = this.table.columnManager.getColumnByField(filter.field); if(column){ filterFunc = function(data){ return Filter.filters[filter.type](filter.value, column.getFieldValue(data), data, filter.params || {}); }; }else { filterFunc = function(data){ return Filter.filters[filter.type](filter.value, data[filter.field], data, filter.params || {}); }; } }else { console.warn("Filter Error - No such filter type found, ignoring: ", filter.type); } } filter.func = filterFunc; return filter.func ? filter : false; } findSubFilters(filters){ var output = []; filters.forEach((filter) => { filter = this.findFilter(filter); if(filter){ output.push(filter); } }); return output.length ? output : false; } //get all filters getFilters(all, ajax){ var output = []; if(all){ output = this.getHeaderFilters(); } if(ajax){ output.forEach(function(item){ if(typeof item.type == "function"){ item.type = "function"; } }); } output = output.concat(this.filtersToArray(this.filterList, ajax)); return output; } //filter to Object filtersToArray(filterList, ajax){ var output = []; filterList.forEach((filter) => { var item; if(Array.isArray(filter)){ output.push(this.filtersToArray(filter, ajax)); }else { item = {field:filter.field, type:filter.type, value:filter.value}; if(ajax){ if(typeof item.type == "function"){ item.type = "function"; } } output.push(item); } }); return output; } //get all filters getHeaderFilters(){ var output = []; for(var key in this.headerFilters){ output.push({field:key, type:this.headerFilters[key].type, value:this.headerFilters[key].value}); } return output; } //remove filter from array removeFilter(field, type, value){ if(!Array.isArray(field)){ field = [{field:field, type:type, value:value}]; } field.forEach((filter) => { var index = -1; if(typeof filter.field == "object"){ index = this.filterList.findIndex((element) => { return filter === element; }); }else { index = this.filterList.findIndex((element) => { return filter.field === element.field && filter.type === element.type && filter.value === element.value; }); } if(index > -1){ this.filterList.splice(index, 1); }else { console.warn("Filter Error - No matching filter type found, ignoring: ", filter.type); } }); this.trackChanges(); } //clear filters clearFilter(all){ this.filterList = []; if(all){ this.clearHeaderFilter(); } this.trackChanges(); } //clear header filters clearHeaderFilter(){ this.headerFilters = {}; this.prevHeaderFilterChangeCheck = "{}"; this.headerFilterColumns.forEach((column) => { if(typeof column.modules.filter.value !== "undefined"){ delete column.modules.filter.value; } column.modules.filter.prevSuccess = undefined; this.reloadHeaderFilter(column); }); this.trackChanges(); } //search data and return matching rows search (searchType, field, type, value){ var activeRows = [], filterList = []; if(!Array.isArray(field)){ field = [{field:field, type:type, value:value}]; } field.forEach((filter) => { filter = this.findFilter(filter); if(filter){ filterList.push(filter); } }); this.table.rowManager.rows.forEach((row) => { var match = true; filterList.forEach((filter) => { if(!this.filterRecurse(filter, row.getData())){ match = false; } }); if(match){ activeRows.push(searchType === "data" ? row.getData("data") : row.getComponent()); } }); return activeRows; } //filter row array filter(rowList, filters){ var activeRows = [], activeRowComponents = []; if(this.subscribedExternal("dataFiltering")){ this.dispatchExternal("dataFiltering", this.getFilters(true)); } if(this.table.options.filterMode !== "remote" && (this.filterList.length || Object.keys(this.headerFilters).length)){ rowList.forEach((row) => { if(this.filterRow(row)){ activeRows.push(row); } }); }else { activeRows = rowList.slice(0); } if(this.subscribedExternal("dataFiltered")){ activeRows.forEach((row) => { activeRowComponents.push(row.getComponent()); }); this.dispatchExternal("dataFiltered", this.getFilters(true), activeRowComponents); } return activeRows; } //filter individual row filterRow(row, filters){ var match = true, data = row.getData(); this.filterList.forEach((filter) => { if(!this.filterRecurse(filter, data)){ match = false; } }); for(var field in this.headerFilters){ if(!this.headerFilters[field].func(data)){ match = false; } } return match; } filterRecurse(filter, data){ var match = false; if(Array.isArray(filter)){ filter.forEach((subFilter) => { if(this.filterRecurse(subFilter, data)){ match = true; } }); }else { match = filter.func(data); } return match; } } Filter.moduleName = "filter"; //load defaults Filter.filters = defaultFilters; function plaintext(cell, formatterParams, onRendered){ return this.emptyToSpace(this.sanitizeHTML(cell.getValue())); } function html$1(cell, formatterParams, onRendered){ return cell.getValue(); } function textarea$1(cell, formatterParams, onRendered){ cell.getElement().style.whiteSpace = "pre-wrap"; return this.emptyToSpace(this.sanitizeHTML(cell.getValue())); } function money(cell, formatterParams, onRendered){ var floatVal = parseFloat(cell.getValue()), sign = "", number, integer, decimal, rgx; var decimalSym = formatterParams.decimal || "."; var thousandSym = formatterParams.thousand || ","; var negativeSign = formatterParams.negativeSign || "-"; var symbol = formatterParams.symbol || ""; var after = !!formatterParams.symbolAfter; var precision = typeof formatterParams.precision !== "undefined" ? formatterParams.precision : 2; if(isNaN(floatVal)){ return this.emptyToSpace(this.sanitizeHTML(cell.getValue())); } if(floatVal < 0){ floatVal = Math.abs(floatVal); sign = negativeSign; } number = precision !== false ? floatVal.toFixed(precision) : floatVal; number = String(number).split("."); integer = number[0]; decimal = number.length > 1 ? decimalSym + number[1] : ""; if (formatterParams.thousand !== false) { rgx = /(\d+)(\d{3})/; while (rgx.test(integer)){ integer = integer.replace(rgx, "$1" + thousandSym + "$2"); } } return after ? sign + integer + decimal + symbol : sign + symbol + integer + decimal; } function link(cell, formatterParams, onRendered){ var value = cell.getValue(), urlPrefix = formatterParams.urlPrefix || "", download = formatterParams.download, label = value, el = document.createElement("a"), data; function labelTraverse(path, data){ var item = path.shift(), value = data[item]; if(path.length && typeof value === "object"){ return labelTraverse(path, value); } return value; } if(formatterParams.labelField){ data = cell.getData(); label = labelTraverse(formatterParams.labelField.split(this.table.options.nestedFieldSeparator), data); } if(formatterParams.label){ switch(typeof formatterParams.label){ case "string": label = formatterParams.label; break; case "function": label = formatterParams.label(cell); break; } } if(label){ if(formatterParams.urlField){ data = cell.getData(); value = data[formatterParams.urlField]; } if(formatterParams.url){ switch(typeof formatterParams.url){ case "string": value = formatterParams.url; break; case "function": value = formatterParams.url(cell); break; } } el.setAttribute("href", urlPrefix + value); if(formatterParams.target){ el.setAttribute("target", formatterParams.target); } if(formatterParams.download){ if(typeof download == "function"){ download = download(cell); }else { download = download === true ? "" : download; } el.setAttribute("download", download); } el.innerHTML = this.emptyToSpace(this.sanitizeHTML(label)); return el; }else { return " "; } } function image(cell, formatterParams, onRendered){ var el = document.createElement("img"), src = cell.getValue(); if(formatterParams.urlPrefix){ src = formatterParams.urlPrefix + cell.getValue(); } if(formatterParams.urlSuffix){ src = src + formatterParams.urlSuffix; } el.setAttribute("src", src); switch(typeof formatterParams.height){ case "number": el.style.height = formatterParams.height + "px"; break; case "string": el.style.height = formatterParams.height; break; } switch(typeof formatterParams.width){ case "number": el.style.width = formatterParams.width + "px"; break; case "string": el.style.width = formatterParams.width; break; } el.addEventListener("load", function(){ cell.getRow().normalizeHeight(); }); return el; } function tickCross$1(cell, formatterParams, onRendered){ var value = cell.getValue(), element = cell.getElement(), empty = formatterParams.allowEmpty, truthy = formatterParams.allowTruthy, trueValueSet = Object.keys(formatterParams).includes("trueValue"), tick = typeof formatterParams.tickElement !== "undefined" ? formatterParams.tickElement : '', cross = typeof formatterParams.crossElement !== "undefined" ? formatterParams.crossElement : ''; if((trueValueSet && value === formatterParams.trueValue) || (!trueValueSet && ((truthy && value) || (value === true || value === "true" || value === "True" || value === 1 || value === "1")))){ element.setAttribute("aria-checked", true); return tick || ""; }else { if(empty && (value === "null" || value === "" || value === null || typeof value === "undefined")){ element.setAttribute("aria-checked", "mixed"); return ""; }else { element.setAttribute("aria-checked", false); return cross || ""; } } } function datetime$1(cell, formatterParams, onRendered){ var DT = window.DateTime || luxon.DateTime; var inputFormat = formatterParams.inputFormat || "yyyy-MM-dd HH:mm:ss"; var outputFormat = formatterParams.outputFormat || "dd/MM/yyyy HH:mm:ss"; var invalid = typeof formatterParams.invalidPlaceholder !== "undefined" ? formatterParams.invalidPlaceholder : ""; var value = cell.getValue(); if(typeof DT != "undefined"){ var newDatetime; if(DT.isDateTime(value)){ newDatetime = value; }else if(inputFormat === "iso"){ newDatetime = DT.fromISO(String(value)); }else { newDatetime = DT.fromFormat(String(value), inputFormat); } if(newDatetime.isValid){ if(formatterParams.timezone){ newDatetime = newDatetime.setZone(formatterParams.timezone); } return newDatetime.toFormat(outputFormat); }else { if(invalid === true || !value){ return value; }else if(typeof invalid === "function"){ return invalid(value); }else { return invalid; } } }else { console.error("Format Error - 'datetime' formatter is dependant on luxon.js"); } } function datetimediff (cell, formatterParams, onRendered) { var DT = window.DateTime || luxon.DateTime; var inputFormat = formatterParams.inputFormat || "yyyy-MM-dd HH:mm:ss"; var invalid = typeof formatterParams.invalidPlaceholder !== "undefined" ? formatterParams.invalidPlaceholder : ""; var suffix = typeof formatterParams.suffix !== "undefined" ? formatterParams.suffix : false; var unit = typeof formatterParams.unit !== "undefined" ? formatterParams.unit : "days"; var humanize = typeof formatterParams.humanize !== "undefined" ? formatterParams.humanize : false; var date = typeof formatterParams.date !== "undefined" ? formatterParams.date : DT.now(); var value = cell.getValue(); if(typeof DT != "undefined"){ var newDatetime; if(DT.isDateTime(value)){ newDatetime = value; }else if(inputFormat === "iso"){ newDatetime = DT.fromISO(String(value)); }else { newDatetime = DT.fromFormat(String(value), inputFormat); } if (newDatetime.isValid){ if(humanize){ return newDatetime.diff(date, unit).toHuman() + (suffix ? " " + suffix : ""); }else { return parseInt(newDatetime.diff(date, unit)[unit]) + (suffix ? " " + suffix : ""); } } else { if (invalid === true) { return value; } else if (typeof invalid === "function") { return invalid(value); } else { return invalid; } } }else { console.error("Format Error - 'datetimediff' formatter is dependant on luxon.js"); } } function lookup (cell, formatterParams, onRendered) { var value = cell.getValue(); if (typeof formatterParams[value] === "undefined") { console.warn('Missing display value for ' + value); return value; } return formatterParams[value]; } function star$1(cell, formatterParams, onRendered){ var value = cell.getValue(), element = cell.getElement(), maxStars = formatterParams && formatterParams.stars ? formatterParams.stars : 5, stars = document.createElement("span"), star = document.createElementNS('http://www.w3.org/2000/svg', "svg"), starActive = '', starInactive = ''; //style stars holder stars.style.verticalAlign = "middle"; //style star star.setAttribute("width", "14"); star.setAttribute("height", "14"); star.setAttribute("viewBox", "0 0 512 512"); star.setAttribute("xml:space", "preserve"); star.style.padding = "0 1px"; value = value && !isNaN(value) ? parseInt(value) : 0; value = Math.max(0, Math.min(value, maxStars)); for(var i=1;i<= maxStars;i++){ var nextStar = star.cloneNode(true); nextStar.innerHTML = i <= value ? starActive : starInactive; stars.appendChild(nextStar); } element.style.whiteSpace = "nowrap"; element.style.overflow = "hidden"; element.style.textOverflow = "ellipsis"; element.setAttribute("aria-label", value); return stars; } function traffic(cell, formatterParams, onRendered){ var value = this.sanitizeHTML(cell.getValue()) || 0, el = document.createElement("span"), max = formatterParams && formatterParams.max ? formatterParams.max : 100, min = formatterParams && formatterParams.min ? formatterParams.min : 0, colors = formatterParams && typeof formatterParams.color !== "undefined" ? formatterParams.color : ["red", "orange", "green"], color = "#666666", percent, percentValue; if(isNaN(value) || typeof cell.getValue() === "undefined"){ return; } el.classList.add("tabulator-traffic-light"); //make sure value is in range percentValue = parseFloat(value) <= max ? parseFloat(value) : max; percentValue = parseFloat(percentValue) >= min ? parseFloat(percentValue) : min; //workout percentage percent = (max - min) / 100; percentValue = Math.round((percentValue - min) / percent); //set color switch(typeof colors){ case "string": color = colors; break; case "function": color = colors(value); break; case "object": if(Array.isArray(colors)){ var unit = 100 / colors.length; var index = Math.floor(percentValue / unit); index = Math.min(index, colors.length - 1); index = Math.max(index, 0); color = colors[index]; break; } } el.style.backgroundColor = color; return el; } function progress$1(cell, formatterParams = {}, onRendered){ //progress bar var value = this.sanitizeHTML(cell.getValue()) || 0, element = cell.getElement(), max = formatterParams.max ? formatterParams.max : 100, min = formatterParams.min ? formatterParams.min : 0, legendAlign = formatterParams.legendAlign ? formatterParams.legendAlign : "center", percent, percentValue, color, legend, legendColor; //make sure value is in range percentValue = parseFloat(value) <= max ? parseFloat(value) : max; percentValue = parseFloat(percentValue) >= min ? parseFloat(percentValue) : min; //workout percentage percent = (max - min) / 100; percentValue = Math.round((percentValue - min) / percent); //set bar color switch(typeof formatterParams.color){ case "string": color = formatterParams.color; break; case "function": color = formatterParams.color(value); break; case "object": if(Array.isArray(formatterParams.color)){ let unit = 100 / formatterParams.color.length; let index = Math.floor(percentValue / unit); index = Math.min(index, formatterParams.color.length - 1); index = Math.max(index, 0); color = formatterParams.color[index]; break; } default: color = "#2DC214"; } //generate legend switch(typeof formatterParams.legend){ case "string": legend = formatterParams.legend; break; case "function": legend = formatterParams.legend(value); break; case "boolean": legend = value; break; default: legend = false; } //set legend color switch(typeof formatterParams.legendColor){ case "string": legendColor = formatterParams.legendColor; break; case "function": legendColor = formatterParams.legendColor(value); break; case "object": if(Array.isArray(formatterParams.legendColor)){ let unit = 100 / formatterParams.legendColor.length; let index = Math.floor(percentValue / unit); index = Math.min(index, formatterParams.legendColor.length - 1); index = Math.max(index, 0); legendColor = formatterParams.legendColor[index]; } break; default: legendColor = "#000"; } element.style.minWidth = "30px"; element.style.position = "relative"; element.setAttribute("aria-label", percentValue); var barEl = document.createElement("div"); barEl.style.display = "inline-block"; barEl.style.width = percentValue + "%"; barEl.style.backgroundColor = color; barEl.style.height = "100%"; barEl.setAttribute('data-max', max); barEl.setAttribute('data-min', min); var barContainer = document.createElement("div"); barContainer.style.position = "relative"; barContainer.style.width = "100%"; barContainer.style.height = "100%"; if(legend){ var legendEl = document.createElement("div"); legendEl.style.position = "absolute"; legendEl.style.top = 0; legendEl.style.left = 0; legendEl.style.textAlign = legendAlign; legendEl.style.width = "100%"; legendEl.style.color = legendColor; legendEl.innerHTML = legend; } onRendered(function(){ //handle custom element needed if formatter is to be included in printed/downloaded output if(!(cell instanceof CellComponent)){ var holderEl = document.createElement("div"); holderEl.style.position = "absolute"; holderEl.style.top = "4px"; holderEl.style.bottom = "4px"; holderEl.style.left = "4px"; holderEl.style.right = "4px"; element.appendChild(holderEl); element = holderEl; } element.appendChild(barContainer); barContainer.appendChild(barEl); if(legend){ barContainer.appendChild(legendEl); } }); return ""; } function color(cell, formatterParams, onRendered){ cell.getElement().style.backgroundColor = this.sanitizeHTML(cell.getValue()); return ""; } function buttonTick(cell, formatterParams, onRendered){ return ''; } function buttonCross(cell, formatterParams, onRendered){ return ''; } function rownum(cell, formatterParams, onRendered){ var content = document.createElement("span"); var row = cell.getRow(); row.watchPosition((position) => { content.innerText = position; }); return content; } function handle(cell, formatterParams, onRendered){ cell.getElement().classList.add("tabulator-row-handle"); return "
"; } function responsiveCollapse(cell, formatterParams, onRendered){ var el = document.createElement("div"), config = cell.getRow()._row.modules.responsiveLayout; el.classList.add("tabulator-responsive-collapse-toggle"); el.innerHTML = ` `; cell.getElement().classList.add("tabulator-row-handle"); function toggleList(isOpen){ var collapseEl = config.element; config.open = isOpen; if(collapseEl){ if(config.open){ el.classList.add("open"); collapseEl.style.display = ''; }else { el.classList.remove("open"); collapseEl.style.display = 'none'; } } } el.addEventListener("click", function(e){ e.stopImmediatePropagation(); toggleList(!config.open); cell.getTable().rowManager.adjustTableSize(); }); toggleList(config.open); return el; } function rowSelection(cell, formatterParams, onRendered){ var checkbox = document.createElement("input"); var blocked = false; checkbox.type = 'checkbox'; checkbox.setAttribute("aria-label", "Select Row"); if(this.table.modExists("selectRow", true)){ checkbox.addEventListener("click", (e) => { e.stopPropagation(); }); if(typeof cell.getRow == 'function'){ var row = cell.getRow(); if(row instanceof RowComponent){ checkbox.addEventListener("change", (e) => { if(this.table.options.selectableRangeMode === "click"){ if(!blocked){ row.toggleSelect(); }else { blocked = false; } }else { row.toggleSelect(); } }); if(this.table.options.selectableRangeMode === "click"){ checkbox.addEventListener("click", (e) => { blocked = true; this.table.modules.selectRow.handleComplexRowClick(row._row, e); }); } checkbox.checked = row.isSelected && row.isSelected(); this.table.modules.selectRow.registerRowSelectCheckbox(row, checkbox); }else { checkbox = ""; } }else { checkbox.addEventListener("change", (e) => { if(this.table.modules.selectRow.selectedRows.length){ this.table.deselectRow(); }else { this.table.selectRow(formatterParams.rowRange); } }); this.table.modules.selectRow.registerHeaderSelectCheckbox(checkbox); } } return checkbox; } var defaultFormatters = { plaintext:plaintext, html:html$1, textarea:textarea$1, money:money, link:link, image:image, tickCross:tickCross$1, datetime:datetime$1, datetimediff:datetimediff, lookup:lookup, star:star$1, traffic:traffic, progress:progress$1, color:color, buttonTick:buttonTick, buttonCross:buttonCross, rownum:rownum, handle:handle, responsiveCollapse:responsiveCollapse, rowSelection:rowSelection, }; class Format extends Module{ constructor(table){ super(table); this.registerColumnOption("formatter"); this.registerColumnOption("formatterParams"); this.registerColumnOption("formatterPrint"); this.registerColumnOption("formatterPrintParams"); this.registerColumnOption("formatterClipboard"); this.registerColumnOption("formatterClipboardParams"); this.registerColumnOption("formatterHtmlOutput"); this.registerColumnOption("formatterHtmlOutputParams"); this.registerColumnOption("titleFormatter"); this.registerColumnOption("titleFormatterParams"); } initialize(){ this.subscribe("cell-format", this.formatValue.bind(this)); this.subscribe("cell-rendered", this.cellRendered.bind(this)); this.subscribe("column-layout", this.initializeColumn.bind(this)); this.subscribe("column-format", this.formatHeader.bind(this)); } //initialize column formatter initializeColumn(column){ column.modules.format = this.lookupFormatter(column, ""); if(typeof column.definition.formatterPrint !== "undefined"){ column.modules.format.print = this.lookupFormatter(column, "Print"); } if(typeof column.definition.formatterClipboard !== "undefined"){ column.modules.format.clipboard = this.lookupFormatter(column, "Clipboard"); } if(typeof column.definition.formatterHtmlOutput !== "undefined"){ column.modules.format.htmlOutput = this.lookupFormatter(column, "HtmlOutput"); } } lookupFormatter(column, type){ var config = {params:column.definition["formatter" + type + "Params"] || {}}, formatter = column.definition["formatter" + type]; //set column formatter switch(typeof formatter){ case "string": if(Format.formatters[formatter]){ config.formatter = Format.formatters[formatter]; }else { console.warn("Formatter Error - No such formatter found: ", formatter); config.formatter = Format.formatters.plaintext; } break; case "function": config.formatter = formatter; break; default: config.formatter = Format.formatters.plaintext; break; } return config; } cellRendered(cell){ if(cell.modules.format && cell.modules.format.renderedCallback && !cell.modules.format.rendered){ cell.modules.format.renderedCallback(); cell.modules.format.rendered = true; } } //return a formatted value for a column header formatHeader(column, title, el){ var formatter, params, onRendered, mockCell; if(column.definition.titleFormatter){ formatter = this.getFormatter(column.definition.titleFormatter); onRendered = (callback) => { column.titleFormatterRendered = callback; }; mockCell = { getValue:function(){ return title; }, getElement:function(){ return el; }, getColumn:function(){ return column.getComponent(); }, getTable:() => { return this.table; } }; params = column.definition.titleFormatterParams || {}; params = typeof params === "function" ? params() : params; return formatter.call(this, mockCell, params, onRendered); }else { return title; } } //return a formatted value for a cell formatValue(cell){ var component = cell.getComponent(), params = typeof cell.column.modules.format.params === "function" ? cell.column.modules.format.params(component) : cell.column.modules.format.params; function onRendered(callback){ if(!cell.modules.format){ cell.modules.format = {}; } cell.modules.format.renderedCallback = callback; cell.modules.format.rendered = false; } return cell.column.modules.format.formatter.call(this, component, params, onRendered); } formatExportValue(cell, type){ var formatter = cell.column.modules.format[type], params; if(formatter){ params = typeof formatter.params === "function" ? formatter.params(cell.getComponent()) : formatter.params; function onRendered(callback){ if(!cell.modules.format){ cell.modules.format = {}; } cell.modules.format.renderedCallback = callback; cell.modules.format.rendered = false; } return formatter.formatter.call(this, cell.getComponent(), params, onRendered); }else { return this.formatValue(cell); } } sanitizeHTML(value){ if(value){ var entityMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', '`': '`', '=': '=' }; return String(value).replace(/[&<>"'`=/]/g, function (s) { return entityMap[s]; }); }else { return value; } } emptyToSpace(value){ return value === null || typeof value === "undefined" || value === "" ? " " : value; } //get formatter for cell getFormatter(formatter){ switch(typeof formatter){ case "string": if(Format.formatters[formatter]){ formatter = Format.formatters[formatter]; }else { console.warn("Formatter Error - No such formatter found: ", formatter); formatter = Format.formatters.plaintext; } break; case "function": //Custom formatter Function, do nothing break; default: formatter = Format.formatters.plaintext; break; } return formatter; } } Format.moduleName = "format"; //load defaults Format.formatters = defaultFormatters; class FrozenColumns extends Module{ constructor(table){ super(table); this.leftColumns = []; this.rightColumns = []; this.initializationMode = "left"; this.active = false; this.blocked = true; this.registerColumnOption("frozen"); } //reset initial state reset(){ this.initializationMode = "left"; this.leftColumns = []; this.rightColumns = []; this.active = false; } initialize(){ this.subscribe("cell-layout", this.layoutCell.bind(this)); this.subscribe("column-init", this.initializeColumn.bind(this)); this.subscribe("column-width", this.layout.bind(this)); this.subscribe("row-layout-after", this.layoutRow.bind(this)); this.subscribe("table-layout", this.layout.bind(this)); this.subscribe("columns-loading", this.reset.bind(this)); this.subscribe("column-add", this.reinitializeColumns.bind(this)); this.subscribe("column-delete", this.reinitializeColumns.bind(this)); this.subscribe("table-redraw", this.layout.bind(this)); this.subscribe("layout-refreshing", this.blockLayout.bind(this)); this.subscribe("layout-refreshed", this.unblockLayout.bind(this)); this.subscribe("scrollbar-vertical", this.adjustForScrollbar.bind(this)); } blockLayout(){ this.blocked = true; } unblockLayout(){ this.blocked = false; } layoutCell(cell){ this.layoutElement(cell.element, cell.column); } reinitializeColumns(){ this.reset(); this.table.columnManager.columnsByIndex.forEach((column) => { this.initializeColumn(column); }); } //initialize specific column initializeColumn(column){ var config = {margin:0, edge:false}; if(!column.isGroup){ if(this.frozenCheck(column)){ config.position = this.initializationMode; if(this.initializationMode == "left"){ this.leftColumns.push(column); }else { this.rightColumns.unshift(column); } this.active = true; column.modules.frozen = config; }else { this.initializationMode = "right"; } } } frozenCheck(column){ if(column.parent.isGroup && column.definition.frozen){ console.warn("Frozen Column Error - Parent column group must be frozen, not individual columns or sub column groups"); } if(column.parent.isGroup){ return this.frozenCheck(column.parent); }else { return column.definition.frozen; } } //layout calculation rows layoutCalcRows(){ if(this.table.modExists("columnCalcs")){ if(this.table.modules.columnCalcs.topInitialized && this.table.modules.columnCalcs.topRow){ this.layoutRow(this.table.modules.columnCalcs.topRow); } if(this.table.modules.columnCalcs.botInitialized && this.table.modules.columnCalcs.botRow){ this.layoutRow(this.table.modules.columnCalcs.botRow); } if(this.table.modExists("groupRows")){ this.layoutGroupCalcs(this.table.modules.groupRows.getGroups()); } } } layoutGroupCalcs(groups){ groups.forEach((group) => { if(group.calcs.top){ this.layoutRow(group.calcs.top); } if(group.calcs.bottom){ this.layoutRow(group.calcs.bottom); } if(group.groupList && group.groupList.length){ this.layoutGroupCalcs(group.groupList); } }); } //calculate column positions and layout headers layoutColumnPosition(allCells){ var leftParents = []; var leftMargin = 0; var rightMargin = 0; this.leftColumns.forEach((column, i) => { column.modules.frozen.marginValue = leftMargin; column.modules.frozen.margin = column.modules.frozen.marginValue + "px"; if(column.visible){ leftMargin += column.getWidth(); } if(i == this.leftColumns.length - 1){ column.modules.frozen.edge = true; }else { column.modules.frozen.edge = false; } if(column.parent.isGroup){ var parentEl = this.getColGroupParentElement(column); if(!leftParents.includes(parentEl)){ this.layoutElement(parentEl, column); leftParents.push(parentEl); } if(column.modules.frozen.edge){ parentEl.classList.add("tabulator-frozen-" + column.modules.frozen.position); } }else { this.layoutElement(column.getElement(), column); } if(allCells){ column.cells.forEach((cell) => { this.layoutElement(cell.getElement(true), column); }); } }); this.rightColumns.forEach((column, i) => { column.modules.frozen.marginValue = rightMargin; column.modules.frozen.margin = column.modules.frozen.marginValue + "px"; if(column.visible){ rightMargin += column.getWidth(); } if(i == this.rightColumns.length - 1){ column.modules.frozen.edge = true; }else { column.modules.frozen.edge = false; } if(column.parent.isGroup){ this.layoutElement(this.getColGroupParentElement(column), column); }else { this.layoutElement(column.getElement(), column); } if(allCells){ column.cells.forEach((cell) => { this.layoutElement(cell.getElement(true), column); }); } }); } getColGroupParentElement(column){ return column.parent.isGroup ? this.getColGroupParentElement(column.parent) : column.getElement(); } //layout columns appropriately layout(){ if(this.active && !this.blocked){ //calculate left columns this.layoutColumnPosition(); this.reinitializeRows(); this.layoutCalcRows(); } } reinitializeRows(){ var visibleRows = this.table.rowManager.getVisibleRows(true); var otherRows = this.table.rowManager.getRows().filter(row => !visibleRows.includes(row)); otherRows.forEach((row) =>{ row.deinitialize(); }); visibleRows.forEach((row) =>{ if(row.type === "row"){ this.layoutRow(row); } }); } layoutRow(row){ if(this.table.options.layout === "fitDataFill" && this.rightColumns.length){ this.table.rowManager.getTableElement().style.minWidth = "calc(100% - " + this.rightMargin + ")"; } this.leftColumns.forEach((column) => { var cell = row.getCell(column); if(cell){ this.layoutElement(cell.getElement(true), column); } }); this.rightColumns.forEach((column) => { var cell = row.getCell(column); if(cell){ this.layoutElement(cell.getElement(true), column); } }); } layoutElement(element, column){ var position; if(column.modules.frozen){ element.style.position = "sticky"; if(this.table.rtl){ position = column.modules.frozen.position === "left" ? "right" : "left"; }else { position = column.modules.frozen.position; } element.style[position] = column.modules.frozen.margin; element.classList.add("tabulator-frozen"); if(column.modules.frozen.edge){ element.classList.add("tabulator-frozen-" + column.modules.frozen.position); } } } adjustForScrollbar(width){ if(this.rightColumns.length){ this.table.columnManager.getContentsElement().style.width = "calc(100% - " + width + "px)"; } } _calcSpace(columns, index){ var width = 0; for (let i = 0; i < index; i++){ if(columns[i].visible){ width += columns[i].getWidth(); } } return width; } } FrozenColumns.moduleName = "frozenColumns"; class FrozenRows extends Module{ constructor(table){ super(table); this.topElement = document.createElement("div"); this.rows = []; //register component functions this.registerComponentFunction("row", "freeze", this.freezeRow.bind(this)); this.registerComponentFunction("row", "unfreeze", this.unfreezeRow.bind(this)); this.registerComponentFunction("row", "isFrozen", this.isRowFrozen.bind(this)); //register table options this.registerTableOption("frozenRowsField", "id"); //field to choose frozen rows by this.registerTableOption("frozenRows", false); //holder for frozen row identifiers } initialize(){ this.rows = []; this.topElement.classList.add("tabulator-frozen-rows-holder"); // this.table.columnManager.element.append(this.topElement); this.table.columnManager.getContentsElement().insertBefore(this.topElement, this.table.columnManager.headersElement.nextSibling); this.subscribe("row-deleting", this.detachRow.bind(this)); this.subscribe("rows-visible", this.visibleRows.bind(this)); this.registerDisplayHandler(this.getRows.bind(this), 10); if(this.table.options.frozenRows){ this.subscribe("data-processed", this.initializeRows.bind(this)); this.subscribe("row-added", this.initializeRow.bind(this)); this.subscribe("table-redrawing", this.resizeHolderWidth.bind(this)); this.subscribe("column-resized", this.resizeHolderWidth.bind(this)); this.subscribe("column-show", this.resizeHolderWidth.bind(this)); this.subscribe("column-hide", this.resizeHolderWidth.bind(this)); } this.resizeHolderWidth(); } resizeHolderWidth(){ this.topElement.style.minWidth = this.table.columnManager.headersElement.offsetWidth + "px"; } initializeRows(){ this.table.rowManager.getRows().forEach((row) => { this.initializeRow(row); }); } initializeRow(row){ var frozenRows = this.table.options.frozenRows, rowType = typeof frozenRows; if(rowType === "number"){ if(row.getPosition() && (row.getPosition() + this.rows.length) <= frozenRows){ this.freezeRow(row); } }else if(rowType === "function"){ if(frozenRows.call(this.table, row.getComponent())){ this.freezeRow(row); } }else if(Array.isArray(frozenRows)){ if(frozenRows.includes(row.data[this.options("frozenRowsField")])){ this.freezeRow(row); } } } isRowFrozen(row){ var index = this.rows.indexOf(row); return index > -1; } isFrozen(){ return !!this.rows.length; } visibleRows(viewable, rows){ this.rows.forEach((row) => { rows.push(row); }); return rows; } //filter frozen rows out of display data getRows(rows){ var output = rows.slice(0); this.rows.forEach(function(row){ var index = output.indexOf(row); if(index > -1){ output.splice(index, 1); } }); return output; } freezeRow(row){ if(!row.modules.frozen){ row.modules.frozen = true; this.topElement.appendChild(row.getElement()); row.initialize(); row.normalizeHeight(); this.rows.push(row); this.refreshData(false, "display"); this.table.rowManager.adjustTableSize(); this.styleRows(); }else { console.warn("Freeze Error - Row is already frozen"); } } unfreezeRow(row){ if(row.modules.frozen){ row.modules.frozen = false; this.detachRow(row); this.table.rowManager.adjustTableSize(); this.refreshData(false, "display"); if(this.rows.length){ this.styleRows(); } }else { console.warn("Freeze Error - Row is already unfrozen"); } } detachRow(row){ var index = this.rows.indexOf(row); if(index > -1){ var rowEl = row.getElement(); if(rowEl.parentNode){ rowEl.parentNode.removeChild(rowEl); } this.rows.splice(index, 1); } } styleRows(row){ this.rows.forEach((row, i) => { this.table.rowManager.styleRow(row, i); }); } } FrozenRows.moduleName = "frozenRows"; //public group object class GroupComponent { constructor (group){ this._group = group; this.type = "GroupComponent"; return new Proxy(this, { get: function(target, name, receiver) { if (typeof target[name] !== "undefined") { return target[name]; }else { return target._group.groupManager.table.componentFunctionBinder.handle("group", target._group, name); } } }); } getKey(){ return this._group.key; } getField(){ return this._group.field; } getElement(){ return this._group.element; } getRows(){ return this._group.getRows(true); } getSubGroups(){ return this._group.getSubGroups(true); } getParentGroup(){ return this._group.parent ? this._group.parent.getComponent() : false; } isVisible(){ return this._group.visible; } show(){ this._group.show(); } hide(){ this._group.hide(); } toggle(){ this._group.toggleVisibility(); } _getSelf(){ return this._group; } getTable(){ return this._group.groupManager.table; } } //Group functions class Group{ constructor(groupManager, parent, level, key, field, generator, oldGroup){ this.groupManager = groupManager; this.parent = parent; this.key = key; this.level = level; this.field = field; this.hasSubGroups = level < (groupManager.groupIDLookups.length - 1); this.addRow = this.hasSubGroups ? this._addRowToGroup : this._addRow; this.type = "group"; //type of element this.old = oldGroup; this.rows = []; this.groups = []; this.groupList = []; this.generator = generator; this.element = false; this.elementContents = false; this.height = 0; this.outerHeight = 0; this.initialized = false; this.calcs = {}; this.initialized = false; this.modules = {}; this.arrowElement = false; this.visible = oldGroup ? oldGroup.visible : (typeof groupManager.startOpen[level] !== "undefined" ? groupManager.startOpen[level] : groupManager.startOpen[0]); this.component = null; this.createElements(); this.addBindings(); this.createValueGroups(); } wipe(elementsOnly){ if(!elementsOnly){ if(this.groupList.length){ this.groupList.forEach(function(group){ group.wipe(); }); }else { this.rows.forEach((row) => { if(row.modules){ delete row.modules.group; } }); } } this.element = false; this.arrowElement = false; this.elementContents = false; } createElements(){ var arrow = document.createElement("div"); arrow.classList.add("tabulator-arrow"); this.element = document.createElement("div"); this.element.classList.add("tabulator-row"); this.element.classList.add("tabulator-group"); this.element.classList.add("tabulator-group-level-" + this.level); this.element.setAttribute("role", "rowgroup"); this.arrowElement = document.createElement("div"); this.arrowElement.classList.add("tabulator-group-toggle"); this.arrowElement.appendChild(arrow); //setup movable rows if(this.groupManager.table.options.movableRows !== false && this.groupManager.table.modExists("moveRow")){ this.groupManager.table.modules.moveRow.initializeGroupHeader(this); } } createValueGroups(){ var level = this.level + 1; if(this.groupManager.allowedValues && this.groupManager.allowedValues[level]){ this.groupManager.allowedValues[level].forEach((value) => { this._createGroup(value, level); }); } } addBindings(){ var toggleElement; if(this.groupManager.table.options.groupToggleElement){ toggleElement = this.groupManager.table.options.groupToggleElement == "arrow" ? this.arrowElement : this.element; toggleElement.addEventListener("click", (e) => { e.stopPropagation(); e.stopImmediatePropagation(); this.toggleVisibility(); }); } } _createGroup(groupID, level){ var groupKey = level + "_" + groupID; var group = new Group(this.groupManager, this, level, groupID, this.groupManager.groupIDLookups[level].field, this.groupManager.headerGenerator[level] || this.groupManager.headerGenerator[0], this.old ? this.old.groups[groupKey] : false); this.groups[groupKey] = group; this.groupList.push(group); } _addRowToGroup(row){ var level = this.level + 1; if(this.hasSubGroups){ var groupID = this.groupManager.groupIDLookups[level].func(row.getData()), groupKey = level + "_" + groupID; if(this.groupManager.allowedValues && this.groupManager.allowedValues[level]){ if(this.groups[groupKey]){ this.groups[groupKey].addRow(row); } }else { if(!this.groups[groupKey]){ this._createGroup(groupID, level); } this.groups[groupKey].addRow(row); } } } _addRow(row){ this.rows.push(row); row.modules.group = this; } insertRow(row, to, after){ var data = this.conformRowData({}); row.updateData(data); var toIndex = this.rows.indexOf(to); if(toIndex > -1){ if(after){ this.rows.splice(toIndex+1, 0, row); }else { this.rows.splice(toIndex, 0, row); } }else { if(after){ this.rows.push(row); }else { this.rows.unshift(row); } } row.modules.group = this; // this.generateGroupHeaderContents(); if(this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.options.columnCalcs != "table"){ this.groupManager.table.modules.columnCalcs.recalcGroup(this); } this.groupManager.updateGroupRows(true); } scrollHeader(left){ if(this.arrowElement){ this.arrowElement.style.marginLeft = left; this.groupList.forEach(function(child){ child.scrollHeader(left); }); } } getRowIndex(row){} //update row data to match grouping constraints conformRowData(data){ if(this.field){ data[this.field] = this.key; }else { console.warn("Data Conforming Error - Cannot conform row data to match new group as groupBy is a function"); } if(this.parent){ data = this.parent.conformRowData(data); } return data; } removeRow(row){ var index = this.rows.indexOf(row); var el = row.getElement(); if(index > -1){ this.rows.splice(index, 1); } if(!this.groupManager.table.options.groupValues && !this.rows.length){ if(this.parent){ this.parent.removeGroup(this); }else { this.groupManager.removeGroup(this); } this.groupManager.updateGroupRows(true); }else { if(el.parentNode){ el.parentNode.removeChild(el); } if(!this.groupManager.blockRedraw){ this.generateGroupHeaderContents(); if(this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.options.columnCalcs != "table"){ this.groupManager.table.modules.columnCalcs.recalcGroup(this); } } } } removeGroup(group){ var groupKey = group.level + "_" + group.key, index; if(this.groups[groupKey]){ delete this.groups[groupKey]; index = this.groupList.indexOf(group); if(index > -1){ this.groupList.splice(index, 1); } if(!this.groupList.length){ if(this.parent){ this.parent.removeGroup(this); }else { this.groupManager.removeGroup(this); } } } } getHeadersAndRows(){ var output = []; output.push(this); this._visSet(); if(this.calcs.top){ this.calcs.top.detachElement(); this.calcs.top.deleteCells(); } if(this.calcs.bottom){ this.calcs.bottom.detachElement(); this.calcs.bottom.deleteCells(); } if(this.visible){ if(this.groupList.length){ this.groupList.forEach(function(group){ output = output.concat(group.getHeadersAndRows()); }); }else { if(this.groupManager.table.options.columnCalcs != "table" && this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.modules.columnCalcs.hasTopCalcs()){ this.calcs.top = this.groupManager.table.modules.columnCalcs.generateTopRow(this.rows); output.push(this.calcs.top); } output = output.concat(this.rows); if(this.groupManager.table.options.columnCalcs != "table" && this.groupManager.table.modExists("columnCalcs") && this.groupManager.table.modules.columnCalcs.hasBottomCalcs()){ this.calcs.bottom = this.groupManager.table.modules.columnCalcs.generateBottomRow(this.rows); output.push(this.calcs.bottom); } } }else { if(!this.groupList.length && this.groupManager.table.options.columnCalcs != "table"){ if(this.groupManager.table.modExists("columnCalcs")){ if(this.groupManager.table.modules.columnCalcs.hasTopCalcs()){ if(this.groupManager.table.options.groupClosedShowCalcs){ this.calcs.top = this.groupManager.table.modules.columnCalcs.generateTopRow(this.rows); output.push(this.calcs.top); } } if(this.groupManager.table.modules.columnCalcs.hasBottomCalcs()){ if(this.groupManager.table.options.groupClosedShowCalcs){ this.calcs.bottom = this.groupManager.table.modules.columnCalcs.generateBottomRow(this.rows); output.push(this.calcs.bottom); } } } } } return output; } getData(visible, transform){ var output = []; this._visSet(); if(!visible || (visible && this.visible)){ this.rows.forEach((row) => { output.push(row.getData(transform || "data")); }); } return output; } getRowCount(){ var count = 0; if(this.groupList.length){ this.groupList.forEach((group) => { count += group.getRowCount(); }); }else { count = this.rows.length; } return count; } toggleVisibility(){ if(this.visible){ this.hide(); }else { this.show(); } } hide(){ this.visible = false; if(this.groupManager.table.rowManager.getRenderMode() == "basic" && !this.groupManager.table.options.pagination){ this.element.classList.remove("tabulator-group-visible"); if(this.groupList.length){ this.groupList.forEach((group) => { var rows = group.getHeadersAndRows(); rows.forEach((row) => { row.detachElement(); }); }); }else { this.rows.forEach((row) => { var rowEl = row.getElement(); rowEl.parentNode.removeChild(rowEl); }); } this.groupManager.updateGroupRows(true); }else { this.groupManager.updateGroupRows(true); } this.groupManager.table.externalEvents.dispatch("groupVisibilityChanged", this.getComponent(), false); } show(){ this.visible = true; if(this.groupManager.table.rowManager.getRenderMode() == "basic" && !this.groupManager.table.options.pagination){ this.element.classList.add("tabulator-group-visible"); var prev = this.generateElement(); if(this.groupList.length){ this.groupList.forEach((group) => { var rows = group.getHeadersAndRows(); rows.forEach((row) => { var rowEl = row.getElement(); prev.parentNode.insertBefore(rowEl, prev.nextSibling); row.initialize(); prev = rowEl; }); }); }else { this.rows.forEach((row) => { var rowEl = row.getElement(); prev.parentNode.insertBefore(rowEl, prev.nextSibling); row.initialize(); prev = rowEl; }); } this.groupManager.updateGroupRows(true); }else { this.groupManager.updateGroupRows(true); } this.groupManager.table.externalEvents.dispatch("groupVisibilityChanged", this.getComponent(), true); } _visSet(){ var data = []; if(typeof this.visible == "function"){ this.rows.forEach(function(row){ data.push(row.getData()); }); this.visible = this.visible(this.key, this.getRowCount(), data, this.getComponent()); } } getRowGroup(row){ var match = false; if(this.groupList.length){ this.groupList.forEach(function(group){ var result = group.getRowGroup(row); if(result){ match = result; } }); }else { if(this.rows.find(function(item){ return item === row; })){ match = this; } } return match; } getSubGroups(component){ var output = []; this.groupList.forEach(function(child){ output.push(component ? child.getComponent() : child); }); return output; } getRows(component){ var output = []; this.rows.forEach(function(row){ output.push(component ? row.getComponent() : row); }); return output; } generateGroupHeaderContents(){ var data = []; this.rows.forEach(function(row){ data.push(row.getData()); }); this.elementContents = this.generator(this.key, this.getRowCount(), data, this.getComponent()); while(this.element.firstChild) this.element.removeChild(this.element.firstChild); if(typeof this.elementContents === "string"){ this.element.innerHTML = this.elementContents; }else { this.element.appendChild(this.elementContents); } this.element.insertBefore(this.arrowElement, this.element.firstChild); } getPath(path = []) { path.unshift(this.key); if(this.parent) { this.parent.getPath(path); } return path; } ////////////// Standard Row Functions ////////////// getElement(){ return this.elementContents ? this.element : this.generateElement(); } generateElement(){ this.addBindings = false; this._visSet(); if(this.visible){ this.element.classList.add("tabulator-group-visible"); }else { this.element.classList.remove("tabulator-group-visible"); } for(var i = 0; i < this.element.childNodes.length; ++i){ this.element.childNodes[i].parentNode.removeChild(this.element.childNodes[i]); } this.generateGroupHeaderContents(); // this.addBindings(); return this.element; } detachElement(){ if (this.element && this.element.parentNode){ this.element.parentNode.removeChild(this.element); } } //normalize the height of elements in the row normalizeHeight(){ this.setHeight(this.element.clientHeight); } initialize(force){ if(!this.initialized || force){ this.normalizeHeight(); this.initialized = true; } } reinitialize(){ this.initialized = false; this.height = 0; if(Helpers.elVisible(this.element)){ this.initialize(true); } } setHeight(height){ if(this.height != height){ this.height = height; this.outerHeight = this.element.offsetHeight; } } //return rows outer height getHeight(){ return this.outerHeight; } getGroup(){ return this; } reinitializeHeight(){} calcHeight(){} setCellHeight(){} clearCellHeight(){} deinitializeHeight(){} //////////////// Object Generation ///////////////// getComponent(){ if(!this.component){ this.component = new GroupComponent(this); } return this.component; } } class GroupRows extends Module{ constructor(table){ super(table); this.groupIDLookups = false; //enable table grouping and set field to group by this.startOpen = [function(){return false;}]; //starting state of group this.headerGenerator = [function(){return "";}]; this.groupList = []; //ordered list of groups this.allowedValues = false; this.groups = {}; //hold row groups this.displayHandler = this.getRows.bind(this); this.blockRedraw = false; //register table options this.registerTableOption("groupBy", false); //enable table grouping and set field to group by this.registerTableOption("groupStartOpen", true); //starting state of group this.registerTableOption("groupValues", false); this.registerTableOption("groupUpdateOnCellEdit", false); this.registerTableOption("groupHeader", false); //header generation function this.registerTableOption("groupHeaderPrint", null); this.registerTableOption("groupHeaderClipboard", null); this.registerTableOption("groupHeaderHtmlOutput", null); this.registerTableOption("groupHeaderDownload", null); this.registerTableOption("groupToggleElement", "arrow"); this.registerTableOption("groupClosedShowCalcs", false); //register table functions this.registerTableFunction("setGroupBy", this.setGroupBy.bind(this)); this.registerTableFunction("setGroupValues", this.setGroupValues.bind(this)); this.registerTableFunction("setGroupStartOpen", this.setGroupStartOpen.bind(this)); this.registerTableFunction("setGroupHeader", this.setGroupHeader.bind(this)); this.registerTableFunction("getGroups", this.userGetGroups.bind(this)); this.registerTableFunction("getGroupedData", this.userGetGroupedData.bind(this)); //register component functions this.registerComponentFunction("row", "getGroup", this.rowGetGroup.bind(this)); } //initialize group configuration initialize(){ this.subscribe("table-destroy", this._blockRedrawing.bind(this)); this.subscribe("rows-wipe", this._blockRedrawing.bind(this)); this.subscribe("rows-wiped", this._restore_redrawing.bind(this)); if(this.table.options.groupBy){ if(this.table.options.groupUpdateOnCellEdit){ this.subscribe("cell-value-updated", this.cellUpdated.bind(this)); this.subscribe("row-data-changed", this.reassignRowToGroup.bind(this), 0); } this.subscribe("table-built", this.configureGroupSetup.bind(this)); this.subscribe("row-deleting", this.rowDeleting.bind(this)); this.subscribe("row-deleted", this.rowsUpdated.bind(this)); this.subscribe("scroll-horizontal", this.scrollHeaders.bind(this)); this.subscribe("rows-wipe", this.wipe.bind(this)); this.subscribe("rows-added", this.rowsUpdated.bind(this)); this.subscribe("row-moving", this.rowMoving.bind(this)); this.subscribe("row-adding-index", this.rowAddingIndex.bind(this)); this.subscribe("rows-sample", this.rowSample.bind(this)); this.subscribe("render-virtual-fill", this.virtualRenderFill.bind(this)); this.registerDisplayHandler(this.displayHandler, 20); this.initialized = true; } } _blockRedrawing(){ this.blockRedraw = true; } _restore_redrawing(){ this.blockRedraw = false; } configureGroupSetup(){ if(this.table.options.groupBy){ var groupBy = this.table.options.groupBy, startOpen = this.table.options.groupStartOpen, groupHeader = this.table.options.groupHeader; this.allowedValues = this.table.options.groupValues; if(Array.isArray(groupBy) && Array.isArray(groupHeader) && groupBy.length > groupHeader.length){ console.warn("Error creating group headers, groupHeader array is shorter than groupBy array"); } this.headerGenerator = [function(){return "";}]; this.startOpen = [function(){return false;}]; //starting state of group this.langBind("groups|item", (langValue, lang) => { this.headerGenerator[0] = (value, count, data) => { //header layout function return (typeof value === "undefined" ? "" : value) + "(" + count + " " + ((count === 1) ? langValue : lang.groups.items) + ")"; }; }); this.groupIDLookups = []; if(groupBy){ if(this.table.modExists("columnCalcs") && this.table.options.columnCalcs != "table" && this.table.options.columnCalcs != "both"){ this.table.modules.columnCalcs.removeCalcs(); } }else { if(this.table.modExists("columnCalcs") && this.table.options.columnCalcs != "group"){ var cols = this.table.columnManager.getRealColumns(); cols.forEach((col) => { if(col.definition.topCalc){ this.table.modules.columnCalcs.initializeTopRow(); } if(col.definition.bottomCalc){ this.table.modules.columnCalcs.initializeBottomRow(); } }); } } if(!Array.isArray(groupBy)){ groupBy = [groupBy]; } groupBy.forEach((group, i) => { var lookupFunc, column; if(typeof group == "function"){ lookupFunc = group; }else { column = this.table.columnManager.getColumnByField(group); if(column){ lookupFunc = function(data){ return column.getFieldValue(data); }; }else { lookupFunc = function(data){ return data[group]; }; } } this.groupIDLookups.push({ field: typeof group === "function" ? false : group, func:lookupFunc, values:this.allowedValues ? this.allowedValues[i] : false, }); }); if(startOpen){ if(!Array.isArray(startOpen)){ startOpen = [startOpen]; } startOpen.forEach((level) => { }); this.startOpen = startOpen; } if(groupHeader){ this.headerGenerator = Array.isArray(groupHeader) ? groupHeader : [groupHeader]; } }else { this.groupList = []; this.groups = {}; } } rowSample(rows, prevValue){ if(this.table.options.groupBy){ var group = this.getGroups(false)[0]; prevValue.push(group.getRows(false)[0]); } return prevValue; } virtualRenderFill(){ var el = this.table.rowManager.tableElement; var rows = this.table.rowManager.getVisibleRows(); if(this.table.options.groupBy){ rows = rows.filter((row) => { return row.type !== "group"; }); el.style.minWidth = !rows.length ? this.table.columnManager.getWidth() + "px" : ""; }else { return rows; } } rowAddingIndex(row, index, top){ if(this.table.options.groupBy){ this.assignRowToGroup(row); var groupRows = row.modules.group.rows; if(groupRows.length > 1){ if(!index || (index && groupRows.indexOf(index) == -1)){ if(top){ if(groupRows[0] !== row){ index = groupRows[0]; this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top); } }else { if(groupRows[groupRows.length -1] !== row){ index = groupRows[groupRows.length -1]; this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top); } } }else { this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top); } } return index; } } trackChanges(){ this.dispatch("group-changed"); } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// setGroupBy(groups){ this.table.options.groupBy = groups; if(!this.initialized){ this.initialize(); } this.configureGroupSetup(); if(!groups && this.table.modExists("columnCalcs") && this.table.options.columnCalcs === true){ this.table.modules.columnCalcs.reinitializeCalcs(); } this.refreshData(); this.trackChanges(); } setGroupValues(groupValues){ this.table.options.groupValues = groupValues; this.configureGroupSetup(); this.refreshData(); this.trackChanges(); } setGroupStartOpen(values){ this.table.options.groupStartOpen = values; this.configureGroupSetup(); if(this.table.options.groupBy){ this.refreshData(); this.trackChanges(); }else { console.warn("Grouping Update - cant refresh view, no groups have been set"); } } setGroupHeader(values){ this.table.options.groupHeader = values; this.configureGroupSetup(); if(this.table.options.groupBy){ this.refreshData(); this.trackChanges(); }else { console.warn("Grouping Update - cant refresh view, no groups have been set"); } } userGetGroups(values){ return this.getGroups(true); } // get grouped table data in the same format as getData() userGetGroupedData(){ return this.table.options.groupBy ? this.getGroupedData() : this.getData(); } /////////////////////////////////////// ///////// Component Functions ///////// /////////////////////////////////////// rowGetGroup(row){ return row.modules.group ? row.modules.group.getComponent() : false; } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// rowMoving(from, to, after){ if(this.table.options.groupBy){ if(!after && to instanceof Group){ to = this.table.rowManager.prevDisplayRow(from) || to; } var toGroup = to instanceof Group ? to : to.modules.group; var fromGroup = from instanceof Group ? from : from.modules.group; if(toGroup === fromGroup){ this.table.rowManager.moveRowInArray(toGroup.rows, from, to, after); }else { if(fromGroup){ fromGroup.removeRow(from); } toGroup.insertRow(from, to, after); } } } rowDeleting(row){ //remove from group if(this.table.options.groupBy && row.modules.group){ row.modules.group.removeRow(row); } } rowsUpdated(row){ if(this.table.options.groupBy){ this.updateGroupRows(true); } } cellUpdated(cell){ if(this.table.options.groupBy){ this.reassignRowToGroup(cell.row); } } //return appropriate rows with group headers getRows(rows){ if(this.table.options.groupBy && this.groupIDLookups.length){ this.dispatchExternal("dataGrouping"); this.generateGroups(rows); if(this.subscribedExternal("dataGrouped")){ this.dispatchExternal("dataGrouped", this.getGroups(true)); } return this.updateGroupRows(); }else { return rows.slice(0); } } getGroups(component){ var groupComponents = []; this.groupList.forEach(function(group){ groupComponents.push(component ? group.getComponent() : group); }); return groupComponents; } getChildGroups(group){ var groupComponents = []; if(!group){ group = this; } group.groupList.forEach((child) => { if(child.groupList.length){ groupComponents = groupComponents.concat(this.getChildGroups(child)); }else { groupComponents.push(child); } }); return groupComponents; } wipe(){ if(this.table.options.groupBy){ this.groupList.forEach(function(group){ group.wipe(); }); this.groupList = []; this.groups = {}; } } pullGroupListData(groupList) { var groupListData = []; groupList.forEach((group) => { var groupHeader = {}; groupHeader.level = 0; groupHeader.rowCount = 0; groupHeader.headerContent = ""; var childData = []; if (group.hasSubGroups) { childData = this.pullGroupListData(group.groupList); groupHeader.level = group.level; groupHeader.rowCount = childData.length - group.groupList.length; // data length minus number of sub-headers groupHeader.headerContent = group.generator(group.key, groupHeader.rowCount, group.rows, group); groupListData.push(groupHeader); groupListData = groupListData.concat(childData); } else { groupHeader.level = group.level; groupHeader.headerContent = group.generator(group.key, group.rows.length, group.rows, group); groupHeader.rowCount = group.getRows().length; groupListData.push(groupHeader); group.getRows().forEach((row) => { groupListData.push(row.getData("data")); }); } }); return groupListData; } getGroupedData(){ return this.pullGroupListData(this.groupList); } getRowGroup(row){ var match = false; if(this.options("dataTree")){ row = this.table.modules.dataTree.getTreeParentRoot(row); } this.groupList.forEach((group) => { var result = group.getRowGroup(row); if(result){ match = result; } }); return match; } countGroups(){ return this.groupList.length; } generateGroups(rows){ var oldGroups = this.groups; this.groups = {}; this.groupList = []; if(this.allowedValues && this.allowedValues[0]){ this.allowedValues[0].forEach((value) => { this.createGroup(value, 0, oldGroups); }); rows.forEach((row) => { this.assignRowToExistingGroup(row, oldGroups); }); }else { rows.forEach((row) => { this.assignRowToGroup(row, oldGroups); }); } Object.values(oldGroups).forEach((group) => { group.wipe(true); }); } createGroup(groupID, level, oldGroups){ var groupKey = level + "_" + groupID, group; oldGroups = oldGroups || []; group = new Group(this, false, level, groupID, this.groupIDLookups[0].field, this.headerGenerator[0], oldGroups[groupKey]); this.groups[groupKey] = group; this.groupList.push(group); } assignRowToExistingGroup(row, oldGroups){ var groupID = this.groupIDLookups[0].func(row.getData()), groupKey = "0_" + groupID; if(this.groups[groupKey]){ this.groups[groupKey].addRow(row); } } assignRowToGroup(row, oldGroups){ var groupID = this.groupIDLookups[0].func(row.getData()), newGroupNeeded = !this.groups["0_" + groupID]; if(newGroupNeeded){ this.createGroup(groupID, 0, oldGroups); } this.groups["0_" + groupID].addRow(row); return !newGroupNeeded; } reassignRowToGroup(row){ if(row.type === "row"){ var oldRowGroup = row.modules.group, oldGroupPath = oldRowGroup.getPath(), newGroupPath = this.getExpectedPath(row), samePath; // figure out if new group path is the same as old group path samePath = (oldGroupPath.length == newGroupPath.length) && oldGroupPath.every((element, index) => { return element === newGroupPath[index]; }); // refresh if they new path and old path aren't the same (aka the row's groupings have changed) if(!samePath) { oldRowGroup.removeRow(row); this.assignRowToGroup(row, this.groups); this.refreshData(true); } } } getExpectedPath(row) { var groupPath = [], rowData = row.getData(); this.groupIDLookups.forEach((groupId) => { groupPath.push(groupId.func(rowData)); }); return groupPath; } updateGroupRows(force){ var output = []; if(!this.blockRedraw){ this.groupList.forEach((group) => { output = output.concat(group.getHeadersAndRows()); }); if(force){ this.refreshData(true); } } return output; } scrollHeaders(left){ if(this.table.options.groupBy){ if(this.table.options.renderHorizontal === "virtual"){ left -= this.table.columnManager.renderer.vDomPadLeft; } left = left + "px"; this.groupList.forEach((group) => { group.scrollHeader(left); }); } } removeGroup(group){ var groupKey = group.level + "_" + group.key, index; if(this.groups[groupKey]){ delete this.groups[groupKey]; index = this.groupList.indexOf(group); if(index > -1){ this.groupList.splice(index, 1); } } } checkBasicModeGroupHeaderWidth(){ var element = this.table.rowManager.tableElement, onlyGroupHeaders = true; this.table.rowManager.getDisplayRows().forEach((row, index) =>{ this.table.rowManager.styleRow(row, index); element.appendChild(row.getElement()); row.initialize(true); if(row.type !== "group"){ onlyGroupHeaders = false; } }); if(onlyGroupHeaders){ element.style.minWidth = this.table.columnManager.getWidth() + "px"; }else { element.style.minWidth = ""; } } } GroupRows.moduleName = "groupRows"; var defaultUndoers = { cellEdit: function(action){ action.component.setValueProcessData(action.data.oldValue); action.component.cellRendered(); }, rowAdd: function(action){ action.component.deleteActual(); }, rowDelete: function(action){ var newRow = this.table.rowManager.addRowActual(action.data.data, action.data.pos, action.data.index); if(this.table.options.groupBy && this.table.modExists("groupRows")){ this.table.modules.groupRows.updateGroupRows(true); } this._rebindRow(action.component, newRow); }, rowMove: function(action){ this.table.rowManager.moveRowActual(action.component, this.table.rowManager.rows[action.data.posFrom], !action.data.after); this.table.rowManager.redraw(); }, }; var defaultRedoers = { cellEdit: function(action){ action.component.setValueProcessData(action.data.newValue); action.component.cellRendered(); }, rowAdd: function(action){ var newRow = this.table.rowManager.addRowActual(action.data.data, action.data.pos, action.data.index); if(this.table.options.groupBy && this.table.modExists("groupRows")){ this.table.modules.groupRows.updateGroupRows(true); } this._rebindRow(action.component, newRow); }, rowDelete:function(action){ action.component.deleteActual(); }, rowMove: function(action){ this.table.rowManager.moveRowActual(action.component, this.table.rowManager.rows[action.data.posTo], action.data.after); this.table.rowManager.redraw(); }, }; class History extends Module{ constructor(table){ super(table); this.history = []; this.index = -1; this.registerTableOption("history", false); //enable edit history } initialize(){ if(this.table.options.history){ this.subscribe("cell-value-updated", this.cellUpdated.bind(this)); this.subscribe("cell-delete", this.clearComponentHistory.bind(this)); this.subscribe("row-delete", this.rowDeleted.bind(this)); this.subscribe("rows-wipe", this.clear.bind(this)); this.subscribe("row-added", this.rowAdded.bind(this)); this.subscribe("row-move", this.rowMoved.bind(this)); } this.registerTableFunction("undo", this.undo.bind(this)); this.registerTableFunction("redo", this.redo.bind(this)); this.registerTableFunction("getHistoryUndoSize", this.getHistoryUndoSize.bind(this)); this.registerTableFunction("getHistoryRedoSize", this.getHistoryRedoSize.bind(this)); this.registerTableFunction("clearHistory", this.clear.bind(this)); } rowMoved(from, to, after){ this.action("rowMove", from, {posFrom:from.getPosition(), posTo:to.getPosition(), to:to, after:after}); } rowAdded(row, data, pos, index){ this.action("rowAdd", row, {data:data, pos:pos, index:index}); } rowDeleted(row){ var index, rows; if(this.table.options.groupBy){ rows = row.getComponent().getGroup()._getSelf().rows; index = rows.indexOf(row); if(index){ index = rows[index-1]; } }else { index = row.table.rowManager.getRowIndex(row); if(index){ index = row.table.rowManager.rows[index-1]; } } this.action("rowDelete", row, {data:row.getData(), pos:!index, index:index}); } cellUpdated(cell){ this.action("cellEdit", cell, {oldValue:cell.oldValue, newValue:cell.value}); } clear(){ this.history = []; this.index = -1; } action(type, component, data){ this.history = this.history.slice(0, this.index + 1); this.history.push({ type:type, component:component, data:data, }); this.index ++; } getHistoryUndoSize(){ return this.index + 1; } getHistoryRedoSize(){ return this.history.length - (this.index + 1); } clearComponentHistory(component){ var index = this.history.findIndex(function(item){ return item.component === component; }); if(index > -1){ this.history.splice(index, 1); if(index <= this.index){ this.index--; } this.clearComponentHistory(component); } } undo(){ if(this.index > -1){ let action = this.history[this.index]; History.undoers[action.type].call(this, action); this.index--; this.dispatchExternal("historyUndo", action.type, action.component.getComponent(), action.data); return true; }else { console.warn("History Undo Error - No more history to undo"); return false; } } redo(){ if(this.history.length-1 > this.index){ this.index++; let action = this.history[this.index]; History.redoers[action.type].call(this, action); this.dispatchExternal("historyRedo", action.type, action.component.getComponent(), action.data); return true; }else { console.warn("History Redo Error - No more history to redo"); return false; } } //rebind rows to new element after deletion _rebindRow(oldRow, newRow){ this.history.forEach(function(action){ if(action.component instanceof Row){ if(action.component === oldRow){ action.component = newRow; } }else if(action.component instanceof Cell){ if(action.component.row === oldRow){ var field = action.component.column.getField(); if(field){ action.component = newRow.getCell(field); } } } }); } } History.moduleName = "history"; //load defaults History.undoers = defaultUndoers; History.redoers = defaultRedoers; class HtmlTableImport extends Module{ constructor(table){ super(table); this.fieldIndex = []; this.hasIndex = false; } initialize(){ this.tableElementCheck(); } tableElementCheck(){ if(this.table.originalElement && this.table.originalElement.tagName === "TABLE"){ if(this.table.originalElement.childNodes.length){ this.parseTable(); }else { console.warn("Unable to parse data from empty table tag, Tabulator should be initialized on a div tag unless importing data from a table element."); } } } parseTable(){ var element = this.table.originalElement, options = this.table.options, headers = element.getElementsByTagName("th"), rows = element.getElementsByTagName("tbody")[0], data = []; this.hasIndex = false; this.dispatchExternal("htmlImporting"); rows = rows ? rows.getElementsByTagName("tr") : []; //check for Tabulator inline options this._extractOptions(element, options); if(headers.length){ this._extractHeaders(headers, rows); }else { this._generateBlankHeaders(headers, rows); } //iterate through table rows and build data set for(var index = 0; index < rows.length; index++){ var row = rows[index], cells = row.getElementsByTagName("td"), item = {}; //create index if the don't exist in table if(!this.hasIndex){ item[options.index] = index; } for(var i = 0; i < cells.length; i++){ var cell = cells[i]; if(typeof this.fieldIndex[i] !== "undefined"){ item[this.fieldIndex[i]] = cell.innerHTML; } } //add row data to item data.push(item); } options.data = data; this.dispatchExternal("htmlImported"); } //extract tabulator attribute options _extractOptions(element, options, defaultOptions){ var attributes = element.attributes; var optionsArr = defaultOptions ? Object.keys(defaultOptions) : Object.keys(options); var optionsList = {}; optionsArr.forEach((item) => { optionsList[item.toLowerCase()] = item; }); for(var index in attributes){ var attrib = attributes[index]; var name; if(attrib && typeof attrib == "object" && attrib.name && attrib.name.indexOf("tabulator-") === 0){ name = attrib.name.replace("tabulator-", ""); if(typeof optionsList[name] !== "undefined"){ options[optionsList[name]] = this._attribValue(attrib.value); } } } } //get value of attribute _attribValue(value){ if(value === "true"){ return true; } if(value === "false"){ return false; } return value; } //find column if it has already been defined _findCol(title){ var match = this.table.options.columns.find((column) => { return column.title === title; }); return match || false; } //extract column from headers _extractHeaders(headers, rows){ for(var index = 0; index < headers.length; index++){ var header = headers[index], exists = false, col = this._findCol(header.textContent), width; if(col){ exists = true; }else { col = {title:header.textContent.trim()}; } if(!col.field) { col.field = header.textContent.trim().toLowerCase().replace(" ", "_"); } width = header.getAttribute("width"); if(width && !col.width) { col.width = width; } //check for Tabulator inline options this._extractOptions(header, col, this.table.columnManager.optionsList.registeredDefaults); this.fieldIndex[index] = col.field; if(col.field == this.table.options.index){ this.hasIndex = true; } if(!exists){ this.table.options.columns.push(col); } } } //generate blank headers _generateBlankHeaders(headers, rows){ for(var index = 0; index < headers.length; index++){ var header = headers[index], col = {title:"", field:"col" + index}; this.fieldIndex[index] = col.field; var width = header.getAttribute("width"); if(width){ col.width = width; } this.table.options.columns.push(col); } } } HtmlTableImport.moduleName = "htmlTableImport"; function csvImporter(input){ var data = [], row = 0, col = 0, inQuote = false; //Iterate over each character for (let index = 0; index < input.length; index++) { let char = input[index], nextChar = input[index+1]; //Initialize empty row if(!data[row]){ data[row] = []; } //Initialize empty column if(!data[row][col]){ data[row][col] = ""; } //Handle quotation mark inside string if (char == '"' && inQuote && nextChar == '"') { data[row][col] += char; index++; continue; } //Begin / End Quote if (char == '"') { inQuote = !inQuote; continue; } //Next column (if not in quote) if (char == ',' && !inQuote) { col++; continue; } //New row if new line and not in quote (CRLF) if (char == '\r' && nextChar == '\n' && !inQuote) { col = 0; row++; index++; continue; } //New row if new line and not in quote (CR or LF) if ((char == '\r' || char == '\n') && !inQuote) { col = 0; row++; continue; } //Normal Character, append to column data[row][col] += char; } return data; } function json$1(input){ try { return JSON.parse(input); } catch(e) { console.warn("JSON Import Error - File contents is invalid JSON", e); return Promise.reject(); } } function arrayImporter(input){ return input; } var defaultImporters = { csv:csvImporter, json:json$1, array:arrayImporter, }; class Import extends Module{ constructor(table){ super(table); this.registerTableOption("importFormat"); this.registerTableOption("importReader", "text"); } initialize(){ this.registerTableFunction("import", this.importFromFile.bind(this)); if(this.table.options.importFormat){ this.subscribe("data-loading", this.loadDataCheck.bind(this), 10); this.subscribe("data-load", this.loadData.bind(this), 10); } } loadDataCheck(data){ return this.table.options.importFormat && (typeof data === "string" || (Array.isArray(data) && data.length && Array.isArray(data))); } loadData(data, params, config, silent, previousData){ return this.importData(this.lookupImporter(), data) .then(this.structureData.bind(this)) .catch((err) => { console.error("Import Error:", err || "Unable to import data"); return Promise.reject(err); }); } lookupImporter(importFormat){ var importer; if(!importFormat){ importFormat = this.table.options.importFormat; } if(typeof importFormat === "string"){ importer = Import.importers[importFormat]; }else { importer = importFormat; } if(!importer){ console.error("Import Error - Importer not found:", importFormat); } return importer; } importFromFile(importFormat, extension){ var importer = this.lookupImporter(importFormat); if(importer){ return this.pickFile(extension) .then(this.importData.bind(this, importer)) .then(this.structureData.bind(this)) .then(this.setData.bind(this)) .catch((err) => { console.error("Import Error:", err || "Unable to import file"); return Promise.reject(err); }); } } pickFile(extensions){ return new Promise((resolve, reject) => { var input = document.createElement("input"); input.type = "file"; input.accept = extensions; input.addEventListener("change", (e) => { var file = input.files[0], reader = new FileReader(); switch(this.table.options.importReader){ case "buffer": reader.readAsArrayBuffer(file); break; case "binary": reader.readAsBinaryString(file); break; case "url": reader.readAsDataURL(file); break; case "text": default: reader.readAsText(file); } reader.onload = (e) => { resolve(reader.result); }; reader.onerror = (e) => { console.warn("File Load Error - Unable to read file"); reject(); }; }); input.click(); }); } importData(importer, fileContents){ var data = importer.call(this.table, fileContents); if(data instanceof Promise){ return data; }else { return data ? Promise.resolve(data) : Promise.reject(); } } structureData(parsedData){ var data = []; if(Array.isArray(parsedData) && parsedData.length && Array.isArray(parsedData[0])){ if(this.table.options.autoColumns){ data = this.structureArrayToObject(parsedData); }else { data = this.structureArrayToColumns(parsedData); } return data; }else { return parsedData; } } structureArrayToObject(parsedData){ var columns = parsedData.shift(); var data = parsedData.map((values) => { var row = {}; columns.forEach((key, i) => { row[key] = values[i]; }); return row; }); return data; } structureArrayToColumns(parsedData){ var data = [], columns = this.table.getColumns(); //remove first row if it is the column names if(columns[0] && parsedData[0][0]){ if(columns[0].getDefinition().title === parsedData[0][0]){ parsedData.shift(); } } //convert row arrays to objects parsedData.forEach((rowData) => { var row = {}; rowData.forEach((value, index) => { var column = columns[index]; if(column){ row[column.getField()] = value; } }); data.push(row); }); return data; } setData(data){ return this.table.setData(data); } } Import.moduleName = "import"; //load defaults Import.importers = defaultImporters; class Interaction extends Module{ constructor(table){ super(table); this.eventMap = { //row events rowClick:"row-click", rowDblClick:"row-dblclick", rowContext:"row-contextmenu", rowMouseEnter:"row-mouseenter", rowMouseLeave:"row-mouseleave", rowMouseOver:"row-mouseover", rowMouseOut:"row-mouseout", rowMouseMove:"row-mousemove", rowMouseDown:"row-mousedown", rowMouseUp:"row-mouseup", rowTap:"row", rowDblTap:"row", rowTapHold:"row", //cell events cellClick:"cell-click", cellDblClick:"cell-dblclick", cellContext:"cell-contextmenu", cellMouseEnter:"cell-mouseenter", cellMouseLeave:"cell-mouseleave", cellMouseOver:"cell-mouseover", cellMouseOut:"cell-mouseout", cellMouseMove:"cell-mousemove", cellMouseDown:"cell-mousedown", cellMouseUp:"cell-mouseup", cellTap:"cell", cellDblTap:"cell", cellTapHold:"cell", //column header events headerClick:"column-click", headerDblClick:"column-dblclick", headerContext:"column-contextmenu", headerMouseEnter:"column-mouseenter", headerMouseLeave:"column-mouseleave", headerMouseOver:"column-mouseover", headerMouseOut:"column-mouseout", headerMouseMove:"column-mousemove", headerMouseDown:"column-mousedown", headerMouseUp:"column-mouseup", headerTap:"column", headerDblTap:"column", headerTapHold:"column", //group header groupClick:"group-click", groupDblClick:"group-dblclick", groupContext:"group-contextmenu", groupMouseEnter:"group-mouseenter", groupMouseLeave:"group-mouseleave", groupMouseOver:"group-mouseover", groupMouseOut:"group-mouseout", groupMouseMove:"group-mousemove", groupMouseDown:"group-mousedown", groupMouseUp:"group-mouseup", groupTap:"group", groupDblTap:"group", groupTapHold:"group", }; this.subscribers = {}; this.touchSubscribers = {}; this.columnSubscribers = {}; this.touchWatchers = { row:{ tap:null, tapDbl:null, tapHold:null, }, cell:{ tap:null, tapDbl:null, tapHold:null, }, column:{ tap:null, tapDbl:null, tapHold:null, }, group:{ tap:null, tapDbl:null, tapHold:null, } }; this.registerColumnOption("headerClick"); this.registerColumnOption("headerDblClick"); this.registerColumnOption("headerContext"); this.registerColumnOption("headerMouseEnter"); this.registerColumnOption("headerMouseLeave"); this.registerColumnOption("headerMouseOver"); this.registerColumnOption("headerMouseOut"); this.registerColumnOption("headerMouseMove"); this.registerColumnOption("headerMouseDown"); this.registerColumnOption("headerMouseUp"); this.registerColumnOption("headerTap"); this.registerColumnOption("headerDblTap"); this.registerColumnOption("headerTapHold"); this.registerColumnOption("cellClick"); this.registerColumnOption("cellDblClick"); this.registerColumnOption("cellContext"); this.registerColumnOption("cellMouseEnter"); this.registerColumnOption("cellMouseLeave"); this.registerColumnOption("cellMouseOver"); this.registerColumnOption("cellMouseOut"); this.registerColumnOption("cellMouseMove"); this.registerColumnOption("cellMouseDown"); this.registerColumnOption("cellMouseUp"); this.registerColumnOption("cellTap"); this.registerColumnOption("cellDblTap"); this.registerColumnOption("cellTapHold"); } initialize(){ this.initializeExternalEvents(); this.subscribe("column-init", this.initializeColumn.bind(this)); this.subscribe("cell-dblclick", this.cellContentsSelectionFixer.bind(this)); } cellContentsSelectionFixer(e, cell){ var range; if(this.table.modExists("edit")){ if (this.table.modules.edit.currentCell === cell){ return; //prevent instant selection of editor content } } e.preventDefault(); try{ if (document.selection) { // IE range = document.body.createTextRange(); range.moveToElementText(cell.getElement()); range.select(); } else if (window.getSelection) { range = document.createRange(); range.selectNode(cell.getElement()); window.getSelection().removeAllRanges(); window.getSelection().addRange(range); } }catch(e){} } initializeExternalEvents(){ for(let key in this.eventMap){ this.subscriptionChangeExternal(key, this.subscriptionChanged.bind(this, key)); } } subscriptionChanged(key, added){ if(added){ if(!this.subscribers[key]){ if(this.eventMap[key].includes("-")){ this.subscribers[key] = this.handle.bind(this, key); this.subscribe(this.eventMap[key], this.subscribers[key]); }else { this.subscribeTouchEvents(key); } } }else { if(this.eventMap[key].includes("-")){ if(this.subscribers[key] && !this.columnSubscribers[key] && !this.subscribedExternal(key)){ this.unsubscribe(this.eventMap[key], this.subscribers[key]); delete this.subscribers[key]; } }else { this.unsubscribeTouchEvents(key); } } } subscribeTouchEvents(key){ var type = this.eventMap[key]; if(!this.touchSubscribers[type + "-touchstart"]){ this.touchSubscribers[type + "-touchstart"] = this.handleTouch.bind(this, type, "start"); this.touchSubscribers[type + "-touchend"] = this.handleTouch.bind(this, type, "end"); this.subscribe(type + "-touchstart", this.touchSubscribers[type + "-touchstart"]); this.subscribe(type + "-touchend", this.touchSubscribers[type + "-touchend"]); } this.subscribers[key] = true; } unsubscribeTouchEvents(key){ var noTouch = true, type = this.eventMap[key]; if(this.subscribers[key] && !this.subscribedExternal(key)){ delete this.subscribers[key]; for(let i in this.eventMap){ if(this.eventMap[i] === type){ if(this.subscribers[i]){ noTouch = false; } } } if(noTouch){ this.unsubscribe(type + "-touchstart", this.touchSubscribers[type + "-touchstart"]); this.unsubscribe(type + "-touchend", this.touchSubscribers[type + "-touchend"]); delete this.touchSubscribers[type + "-touchstart"]; delete this.touchSubscribers[type + "-touchend"]; } } } initializeColumn(column){ var def = column.definition; for(let key in this.eventMap){ if(def[key]){ this.subscriptionChanged(key, true); if(!this.columnSubscribers[key]){ this.columnSubscribers[key] = []; } this.columnSubscribers[key].push(column); } } } handle(action, e, component){ this.dispatchEvent(action, e, component); } handleTouch(type, action, e, component){ var watchers = this.touchWatchers[type]; if(type === "column"){ type = "header"; } switch(action){ case "start": watchers.tap = true; clearTimeout(watchers.tapHold); watchers.tapHold = setTimeout(() => { clearTimeout(watchers.tapHold); watchers.tapHold = null; watchers.tap = null; clearTimeout(watchers.tapDbl); watchers.tapDbl = null; this.dispatchEvent(type + "TapHold", e, component); }, 1000); break; case "end": if(watchers.tap){ watchers.tap = null; this.dispatchEvent(type + "Tap", e, component); } if(watchers.tapDbl){ clearTimeout(watchers.tapDbl); watchers.tapDbl = null; this.dispatchEvent(type + "DblTap", e, component); }else { watchers.tapDbl = setTimeout(() => { clearTimeout(watchers.tapDbl); watchers.tapDbl = null; }, 300); } clearTimeout(watchers.tapHold); watchers.tapHold = null; break; } } dispatchEvent(action, e, component){ var componentObj = component.getComponent(), callback; if(this.columnSubscribers[action]){ if(component instanceof Cell){ callback = component.column.definition[action]; }else if(component instanceof Column){ callback = component.definition[action]; } if(callback){ callback(e, componentObj); } } this.dispatchExternal(action, e, componentObj); } } Interaction.moduleName = "interaction"; var defaultBindings = { navPrev:"shift + 9", navNext:9, navUp:38, navDown:40, scrollPageUp:33, scrollPageDown:34, scrollToStart:36, scrollToEnd:35, undo:["ctrl + 90", "meta + 90"], redo:["ctrl + 89", "meta + 89"], copyToClipboard:["ctrl + 67", "meta + 89"], }; var defaultActions = { keyBlock:function(e){ e.stopPropagation(); e.preventDefault(); }, scrollPageUp:function(e){ var rowManager = this.table.rowManager, newPos = rowManager.scrollTop - rowManager.element.clientHeight; e.preventDefault(); if(rowManager.displayRowsCount){ if(newPos >= 0){ rowManager.element.scrollTop = newPos; }else { rowManager.scrollToRow(rowManager.getDisplayRows()[0]); } } this.table.element.focus(); }, scrollPageDown:function(e){ var rowManager = this.table.rowManager, newPos = rowManager.scrollTop + rowManager.element.clientHeight, scrollMax = rowManager.element.scrollHeight; e.preventDefault(); if(rowManager.displayRowsCount){ if(newPos <= scrollMax){ rowManager.element.scrollTop = newPos; }else { rowManager.scrollToRow(rowManager.getDisplayRows()[rowManager.displayRowsCount - 1]); } } this.table.element.focus(); }, scrollToStart:function(e){ var rowManager = this.table.rowManager; e.preventDefault(); if(rowManager.displayRowsCount){ rowManager.scrollToRow(rowManager.getDisplayRows()[0]); } this.table.element.focus(); }, scrollToEnd:function(e){ var rowManager = this.table.rowManager; e.preventDefault(); if(rowManager.displayRowsCount){ rowManager.scrollToRow(rowManager.getDisplayRows()[rowManager.displayRowsCount - 1]); } this.table.element.focus(); }, navPrev:function(e){ this.dispatch("keybinding-nav-prev", e); }, navNext:function(e){ this.dispatch("keybinding-nav-next", e); }, navLeft:function(e){ this.dispatch("keybinding-nav-left", e); }, navRight:function(e){ this.dispatch("keybinding-nav-right", e); }, navUp:function(e){ this.dispatch("keybinding-nav-up", e); }, navDown:function(e){ this.dispatch("keybinding-nav-down", e); }, undo:function(e){ var cell = false; if(this.table.options.history && this.table.modExists("history") && this.table.modExists("edit")){ cell = this.table.modules.edit.currentCell; if(!cell){ e.preventDefault(); this.table.modules.history.undo(); } } }, redo:function(e){ var cell = false; if(this.table.options.history && this.table.modExists("history") && this.table.modExists("edit")){ cell = this.table.modules.edit.currentCell; if(!cell){ e.preventDefault(); this.table.modules.history.redo(); } } }, copyToClipboard:function(e){ if(!this.table.modules.edit.currentCell){ if(this.table.modExists("clipboard", true)){ this.table.modules.clipboard.copy(false, true); } } }, }; class Keybindings extends Module{ constructor(table){ super(table); this.watchKeys = null; this.pressedKeys = null; this.keyupBinding = false; this.keydownBinding = false; this.registerTableOption("keybindings", {}); //array for keybindings this.registerTableOption("tabEndNewRow", false); //create new row when tab to end of table } initialize(){ var bindings = this.table.options.keybindings, mergedBindings = {}; this.watchKeys = {}; this.pressedKeys = []; if(bindings !== false){ Object.assign(mergedBindings, Keybindings.bindings); Object.assign(mergedBindings, bindings); this.mapBindings(mergedBindings); this.bindEvents(); } this.subscribe("table-destroy", this.clearBindings.bind(this)); } mapBindings(bindings){ for(let key in bindings){ if(Keybindings.actions[key]){ if(bindings[key]){ if(typeof bindings[key] !== "object"){ bindings[key] = [bindings[key]]; } bindings[key].forEach((binding) => { var bindingList = Array.isArray(binding) ? binding : [binding]; bindingList.forEach((item) => { this.mapBinding(key, item); }); }); } }else { console.warn("Key Binding Error - no such action:", key); } } } mapBinding(action, symbolsList){ var binding = { action: Keybindings.actions[action], keys: [], ctrl: false, shift: false, meta: false, }; var symbols = symbolsList.toString().toLowerCase().split(" ").join("").split("+"); symbols.forEach((symbol) => { switch(symbol){ case "ctrl": binding.ctrl = true; break; case "shift": binding.shift = true; break; case "meta": binding.meta = true; break; default: symbol = isNaN(symbol) ? symbol.toUpperCase().charCodeAt(0) : parseInt(symbol); binding.keys.push(symbol); if(!this.watchKeys[symbol]){ this.watchKeys[symbol] = []; } this.watchKeys[symbol].push(binding); } }); } bindEvents(){ var self = this; this.keyupBinding = function(e){ var code = e.keyCode; var bindings = self.watchKeys[code]; if(bindings){ self.pressedKeys.push(code); bindings.forEach(function(binding){ self.checkBinding(e, binding); }); } }; this.keydownBinding = function(e){ var code = e.keyCode; var bindings = self.watchKeys[code]; if(bindings){ var index = self.pressedKeys.indexOf(code); if(index > -1){ self.pressedKeys.splice(index, 1); } } }; this.table.element.addEventListener("keydown", this.keyupBinding); this.table.element.addEventListener("keyup", this.keydownBinding); } clearBindings(){ if(this.keyupBinding){ this.table.element.removeEventListener("keydown", this.keyupBinding); } if(this.keydownBinding){ this.table.element.removeEventListener("keyup", this.keydownBinding); } } checkBinding(e, binding){ var match = true; if(e.ctrlKey == binding.ctrl && e.shiftKey == binding.shift && e.metaKey == binding.meta){ binding.keys.forEach((key) => { var index = this.pressedKeys.indexOf(key); if(index == -1){ match = false; } }); if(match){ binding.action.call(this, e); } return true; } return false; } } Keybindings.moduleName = "keybindings"; //load defaults Keybindings.bindings = defaultBindings; Keybindings.actions = defaultActions; class Menu extends Module{ constructor(table){ super(table); this.menuContainer = null; this.nestedMenuBlock = false; this.currentComponent = null; this.rootPopup = null; this.columnSubscribers = {}; this.registerTableOption("menuContainer", undefined); //deprecated this.registerTableOption("rowContextMenu", false); this.registerTableOption("rowClickMenu", false); this.registerTableOption("rowDblClickMenu", false); this.registerTableOption("groupContextMenu", false); this.registerTableOption("groupClickMenu", false); this.registerTableOption("groupDblClickMenu", false); this.registerColumnOption("headerContextMenu"); this.registerColumnOption("headerClickMenu"); this.registerColumnOption("headerDblClickMenu"); this.registerColumnOption("headerMenu"); this.registerColumnOption("headerMenuIcon"); this.registerColumnOption("contextMenu"); this.registerColumnOption("clickMenu"); this.registerColumnOption("dblClickMenu"); } initialize(){ this.deprecatedOptionsCheck(); this.initializeRowWatchers(); this.initializeGroupWatchers(); this.subscribe("column-init", this.initializeColumn.bind(this)); } deprecatedOptionsCheck(){ if(!this.deprecationCheck("menuContainer", "popupContainer")){ this.table.options.popupContainer = this.table.options.menuContainer; } } initializeRowWatchers(){ if(this.table.options.rowContextMenu){ this.subscribe("row-contextmenu", this.loadMenuEvent.bind(this, this.table.options.rowContextMenu)); this.table.on("rowTapHold", this.loadMenuEvent.bind(this, this.table.options.rowContextMenu)); } if(this.table.options.rowClickMenu){ this.subscribe("row-click", this.loadMenuEvent.bind(this, this.table.options.rowClickMenu)); } if(this.table.options.rowDblClickMenu){ this.subscribe("row-dblclick", this.loadMenuEvent.bind(this, this.table.options.rowDblClickMenu)); } } initializeGroupWatchers(){ if(this.table.options.groupContextMenu){ this.subscribe("group-contextmenu", this.loadMenuEvent.bind(this, this.table.options.groupContextMenu)); this.table.on("groupTapHold", this.loadMenuEvent.bind(this, this.table.options.groupContextMenu)); } if(this.table.options.groupClickMenu){ this.subscribe("group-click", this.loadMenuEvent.bind(this, this.table.options.groupClickMenu)); } if(this.table.options.groupDblClickMenu){ this.subscribe("group-dblclick", this.loadMenuEvent.bind(this, this.table.options.groupDblClickMenu)); } } initializeColumn(column){ var def = column.definition; //handle column events if(def.headerContextMenu && !this.columnSubscribers.headerContextMenu){ this.columnSubscribers.headerContextMenu = this.loadMenuTableColumnEvent.bind(this, "headerContextMenu"); this.subscribe("column-contextmenu", this.columnSubscribers.headerContextMenu); this.table.on("headerTapHold", this.loadMenuTableColumnEvent.bind(this, "headerContextMenu")); } if(def.headerClickMenu && !this.columnSubscribers.headerClickMenu){ this.columnSubscribers.headerClickMenu = this.loadMenuTableColumnEvent.bind(this, "headerClickMenu"); this.subscribe("column-click", this.columnSubscribers.headerClickMenu); } if(def.headerDblClickMenu && !this.columnSubscribers.headerDblClickMenu){ this.columnSubscribers.headerDblClickMenu = this.loadMenuTableColumnEvent.bind(this, "headerDblClickMenu"); this.subscribe("column-dblclick", this.columnSubscribers.headerDblClickMenu); } if(def.headerMenu){ this.initializeColumnHeaderMenu(column); } //handle cell events if(def.contextMenu && !this.columnSubscribers.contextMenu){ this.columnSubscribers.contextMenu = this.loadMenuTableCellEvent.bind(this, "contextMenu"); this.subscribe("cell-contextmenu", this.columnSubscribers.contextMenu); this.table.on("cellTapHold", this.loadMenuTableCellEvent.bind(this, "contextMenu")); } if(def.clickMenu && !this.columnSubscribers.clickMenu){ this.columnSubscribers.clickMenu = this.loadMenuTableCellEvent.bind(this, "clickMenu"); this.subscribe("cell-click", this.columnSubscribers.clickMenu); } if(def.dblClickMenu && !this.columnSubscribers.dblClickMenu){ this.columnSubscribers.dblClickMenu = this.loadMenuTableCellEvent.bind(this, "dblClickMenu"); this.subscribe("cell-dblclick", this.columnSubscribers.dblClickMenu); } } initializeColumnHeaderMenu(column){ var icon = column.definition.headerMenuIcon, headerMenuEl; headerMenuEl = document.createElement("span"); headerMenuEl.classList.add("tabulator-header-popup-button"); if(icon){ if(typeof icon === "function"){ icon = icon(column.getComponent()); } if(icon instanceof HTMLElement){ headerMenuEl.appendChild(icon); }else { headerMenuEl.innerHTML = icon; } }else { headerMenuEl.innerHTML = "⋮"; } headerMenuEl.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); this.loadMenuEvent(column.definition.headerMenu, e, column); }); column.titleElement.insertBefore(headerMenuEl, column.titleElement.firstChild); } loadMenuTableCellEvent(option, e, cell){ if(cell._cell){ cell = cell._cell; } if(cell.column.definition[option]){ this.loadMenuEvent(cell.column.definition[option], e, cell); } } loadMenuTableColumnEvent(option, e, column){ if(column._column){ column = column._column; } if(column.definition[option]){ this.loadMenuEvent(column.definition[option], e, column); } } loadMenuEvent(menu, e, component){ if(component._group){ component = component._group; }else if(component._row){ component = component._row; } menu = typeof menu == "function" ? menu.call(this.table, e, component.getComponent()) : menu; this.loadMenu(e, component, menu); } loadMenu(e, component, menu, parentEl, parentPopup){ var touch = !(e instanceof MouseEvent), menuEl = document.createElement("div"), popup; menuEl.classList.add("tabulator-menu"); if(!touch){ e.preventDefault(); } //abort if no menu set if(!menu || !menu.length){ return; } if(!parentEl){ if(this.nestedMenuBlock){ //abort if child menu already open if(this.rootPopup){ return; } }else { this.nestedMenuBlock = setTimeout(() => { this.nestedMenuBlock = false; }, 100); } if(this.rootPopup){ this.rootPopup.hide(); } this.rootPopup = popup = this.popup(menuEl); }else { popup = parentPopup.child(menuEl); } menu.forEach((item) => { var itemEl = document.createElement("div"), label = item.label, disabled = item.disabled; if(item.separator){ itemEl.classList.add("tabulator-menu-separator"); }else { itemEl.classList.add("tabulator-menu-item"); if(typeof label == "function"){ label = label.call(this.table, component.getComponent()); } if(label instanceof Node){ itemEl.appendChild(label); }else { itemEl.innerHTML = label; } if(typeof disabled == "function"){ disabled = disabled.call(this.table, component.getComponent()); } if(disabled){ itemEl.classList.add("tabulator-menu-item-disabled"); itemEl.addEventListener("click", (e) => { e.stopPropagation(); }); }else { if(item.menu && item.menu.length){ itemEl.addEventListener("click", (e) => { e.stopPropagation(); this.loadMenu(e, component, item.menu, itemEl, popup); }); }else { if(item.action){ itemEl.addEventListener("click", (e) => { item.action(e, component.getComponent()); }); } } } if(item.menu && item.menu.length){ itemEl.classList.add("tabulator-menu-item-submenu"); } } menuEl.appendChild(itemEl); }); menuEl.addEventListener("click", (e) => { if(this.rootPopup){ this.rootPopup.hide(); } }); popup.show(parentEl || e); if(popup === this.rootPopup){ this.rootPopup.hideOnBlur(() => { this.rootPopup = null; if(this.currentComponent){ this.dispatchExternal("menuClosed", this.currentComponent.getComponent()); this.currentComponent = null; } }); this.currentComponent = component; this.dispatchExternal("menuOpened", component.getComponent()); } } } Menu.moduleName = "menu"; class MoveColumns extends Module{ constructor(table){ super(table); this.placeholderElement = this.createPlaceholderElement(); this.hoverElement = false; //floating column header element this.checkTimeout = false; //click check timeout holder this.checkPeriod = 250; //period to wait on mousedown to consider this a move and not a click this.moving = false; //currently moving column this.toCol = false; //destination column this.toColAfter = false; //position of moving column relative to the destination column this.startX = 0; //starting position within header element this.autoScrollMargin = 40; //auto scroll on edge when within margin this.autoScrollStep = 5; //auto scroll distance in pixels this.autoScrollTimeout = false; //auto scroll timeout this.touchMove = false; this.moveHover = this.moveHover.bind(this); this.endMove = this.endMove.bind(this); this.registerTableOption("movableColumns", false); //enable movable columns } createPlaceholderElement(){ var el = document.createElement("div"); el.classList.add("tabulator-col"); el.classList.add("tabulator-col-placeholder"); return el; } initialize(){ if(this.table.options.movableColumns){ this.subscribe("column-init", this.initializeColumn.bind(this)); } } initializeColumn(column){ var self = this, config = {}, colEl; if(!column.modules.frozen && !column.isGroup){ colEl = column.getElement(); config.mousemove = function(e){ if(column.parent === self.moving.parent){ if((((self.touchMove ? e.touches[0].pageX : e.pageX) - Helpers.elOffset(colEl).left) + self.table.columnManager.contentsElement.scrollLeft) > (column.getWidth() / 2)){ if(self.toCol !== column || !self.toColAfter){ colEl.parentNode.insertBefore(self.placeholderElement, colEl.nextSibling); self.moveColumn(column, true); } }else { if(self.toCol !== column || self.toColAfter){ colEl.parentNode.insertBefore(self.placeholderElement, colEl); self.moveColumn(column, false); } } } }.bind(self); colEl.addEventListener("mousedown", function(e){ self.touchMove = false; if(e.which === 1){ self.checkTimeout = setTimeout(function(){ self.startMove(e, column); }, self.checkPeriod); } }); colEl.addEventListener("mouseup", function(e){ if(e.which === 1){ if(self.checkTimeout){ clearTimeout(self.checkTimeout); } } }); self.bindTouchEvents(column); } column.modules.moveColumn = config; } bindTouchEvents(column){ var colEl = column.getElement(), startXMove = false, //shifting center position of the cell nextCol, prevCol, nextColWidth, prevColWidth, nextColWidthLast, prevColWidthLast; colEl.addEventListener("touchstart", (e) => { this.checkTimeout = setTimeout(() => { this.touchMove = true; nextCol = column.nextColumn(); nextColWidth = nextCol ? nextCol.getWidth()/2 : 0; prevCol = column.prevColumn(); prevColWidth = prevCol ? prevCol.getWidth()/2 : 0; nextColWidthLast = 0; prevColWidthLast = 0; startXMove = false; this.startMove(e, column); }, this.checkPeriod); }, {passive: true}); colEl.addEventListener("touchmove", (e) => { var diff, moveToCol; if(this.moving){ this.moveHover(e); if(!startXMove){ startXMove = e.touches[0].pageX; } diff = e.touches[0].pageX - startXMove; if(diff > 0){ if(nextCol && diff - nextColWidthLast > nextColWidth){ moveToCol = nextCol; if(moveToCol !== column){ startXMove = e.touches[0].pageX; moveToCol.getElement().parentNode.insertBefore(this.placeholderElement, moveToCol.getElement().nextSibling); this.moveColumn(moveToCol, true); } } }else { if(prevCol && -diff - prevColWidthLast > prevColWidth){ moveToCol = prevCol; if(moveToCol !== column){ startXMove = e.touches[0].pageX; moveToCol.getElement().parentNode.insertBefore(this.placeholderElement, moveToCol.getElement()); this.moveColumn(moveToCol, false); } } } if(moveToCol){ nextCol = moveToCol.nextColumn(); nextColWidthLast = nextColWidth; nextColWidth = nextCol ? nextCol.getWidth() / 2 : 0; prevCol = moveToCol.prevColumn(); prevColWidthLast = prevColWidth; prevColWidth = prevCol ? prevCol.getWidth() / 2 : 0; } } }, {passive: true}); colEl.addEventListener("touchend", (e) => { if(this.checkTimeout){ clearTimeout(this.checkTimeout); } if(this.moving){ this.endMove(e); } }); } startMove(e, column){ var element = column.getElement(), headerElement = this.table.columnManager.getContentsElement(), headersElement = this.table.columnManager.getHeadersElement(); this.moving = column; this.startX = (this.touchMove ? e.touches[0].pageX : e.pageX) - Helpers.elOffset(element).left; this.table.element.classList.add("tabulator-block-select"); //create placeholder this.placeholderElement.style.width = column.getWidth() + "px"; this.placeholderElement.style.height = column.getHeight() + "px"; element.parentNode.insertBefore(this.placeholderElement, element); element.parentNode.removeChild(element); //create hover element this.hoverElement = element.cloneNode(true); this.hoverElement.classList.add("tabulator-moving"); headerElement.appendChild(this.hoverElement); this.hoverElement.style.left = "0"; this.hoverElement.style.bottom = (headerElement.clientHeight - headersElement.offsetHeight) + "px"; if(!this.touchMove){ this._bindMouseMove(); document.body.addEventListener("mousemove", this.moveHover); document.body.addEventListener("mouseup", this.endMove); } this.moveHover(e); } _bindMouseMove(){ this.table.columnManager.columnsByIndex.forEach(function(column){ if(column.modules.moveColumn.mousemove){ column.getElement().addEventListener("mousemove", column.modules.moveColumn.mousemove); } }); } _unbindMouseMove(){ this.table.columnManager.columnsByIndex.forEach(function(column){ if(column.modules.moveColumn.mousemove){ column.getElement().removeEventListener("mousemove", column.modules.moveColumn.mousemove); } }); } moveColumn(column, after){ var movingCells = this.moving.getCells(); this.toCol = column; this.toColAfter = after; if(after){ column.getCells().forEach(function(cell, i){ var cellEl = cell.getElement(true); if(cellEl.parentNode && movingCells[i]){ cellEl.parentNode.insertBefore(movingCells[i].getElement(), cellEl.nextSibling); } }); }else { column.getCells().forEach(function(cell, i){ var cellEl = cell.getElement(true); if(cellEl.parentNode && movingCells[i]){ cellEl.parentNode.insertBefore(movingCells[i].getElement(), cellEl); } }); } } endMove(e){ if(e.which === 1 || this.touchMove){ this._unbindMouseMove(); this.placeholderElement.parentNode.insertBefore(this.moving.getElement(), this.placeholderElement.nextSibling); this.placeholderElement.parentNode.removeChild(this.placeholderElement); this.hoverElement.parentNode.removeChild(this.hoverElement); this.table.element.classList.remove("tabulator-block-select"); if(this.toCol){ this.table.columnManager.moveColumnActual(this.moving, this.toCol, this.toColAfter); } this.moving = false; this.toCol = false; this.toColAfter = false; if(!this.touchMove){ document.body.removeEventListener("mousemove", this.moveHover); document.body.removeEventListener("mouseup", this.endMove); } } } moveHover(e){ var columnHolder = this.table.columnManager.getContentsElement(), scrollLeft = columnHolder.scrollLeft, xPos = ((this.touchMove ? e.touches[0].pageX : e.pageX) - Helpers.elOffset(columnHolder).left) + scrollLeft, scrollPos; this.hoverElement.style.left = (xPos - this.startX) + "px"; if(xPos - scrollLeft < this.autoScrollMargin){ if(!this.autoScrollTimeout){ this.autoScrollTimeout = setTimeout(() => { scrollPos = Math.max(0,scrollLeft-5); this.table.rowManager.getElement().scrollLeft = scrollPos; this.autoScrollTimeout = false; }, 1); } } if(scrollLeft + columnHolder.clientWidth - xPos < this.autoScrollMargin){ if(!this.autoScrollTimeout){ this.autoScrollTimeout = setTimeout(() => { scrollPos = Math.min(columnHolder.clientWidth, scrollLeft+5); this.table.rowManager.getElement().scrollLeft = scrollPos; this.autoScrollTimeout = false; }, 1); } } } } MoveColumns.moduleName = "moveColumn"; class MoveRows extends Module{ constructor(table){ super(table); this.placeholderElement = this.createPlaceholderElement(); this.hoverElement = false; //floating row header element this.checkTimeout = false; //click check timeout holder this.checkPeriod = 150; //period to wait on mousedown to consider this a move and not a click this.moving = false; //currently moving row this.toRow = false; //destination row this.toRowAfter = false; //position of moving row relative to the destination row this.hasHandle = false; //row has handle instead of fully movable row this.startY = 0; //starting Y position within header element this.startX = 0; //starting X position within header element this.moveHover = this.moveHover.bind(this); this.endMove = this.endMove.bind(this); this.tableRowDropEvent = false; this.touchMove = false; this.connection = false; this.connectionSelectorsTables = false; this.connectionSelectorsElements = false; this.connectionElements = []; this.connections = []; this.connectedTable = false; this.connectedRow = false; this.registerTableOption("movableRows", false); //enable movable rows this.registerTableOption("movableRowsConnectedTables", false); //tables for movable rows to be connected to this.registerTableOption("movableRowsConnectedElements", false); //other elements for movable rows to be connected to this.registerTableOption("movableRowsSender", false); this.registerTableOption("movableRowsReceiver", "insert"); this.registerColumnOption("rowHandle"); } createPlaceholderElement(){ var el = document.createElement("div"); el.classList.add("tabulator-row"); el.classList.add("tabulator-row-placeholder"); return el; } initialize(){ if(this.table.options.movableRows){ this.connectionSelectorsTables = this.table.options.movableRowsConnectedTables; this.connectionSelectorsElements = this.table.options.movableRowsConnectedElements; this.connection = this.connectionSelectorsTables || this.connectionSelectorsElements; this.subscribe("cell-init", this.initializeCell.bind(this)); this.subscribe("column-init", this.initializeColumn.bind(this)); this.subscribe("row-init", this.initializeRow.bind(this)); } } initializeGroupHeader(group){ var self = this, config = {}; //inter table drag drop config.mouseup = function(e){ self.tableRowDrop(e, group); }.bind(self); //same table drag drop config.mousemove = function(e){ var rowEl; if(((e.pageY - Helpers.elOffset(group.element).top) + self.table.rowManager.element.scrollTop) > (group.getHeight() / 2)){ if(self.toRow !== group || !self.toRowAfter){ rowEl = group.getElement(); rowEl.parentNode.insertBefore(self.placeholderElement, rowEl.nextSibling); self.moveRow(group, true); } }else { if(self.toRow !== group || self.toRowAfter){ rowEl = group.getElement(); if(rowEl.previousSibling){ rowEl.parentNode.insertBefore(self.placeholderElement, rowEl); self.moveRow(group, false); } } } }.bind(self); group.modules.moveRow = config; } initializeRow(row){ var self = this, config = {}, rowEl; //inter table drag drop config.mouseup = function(e){ self.tableRowDrop(e, row); }.bind(self); //same table drag drop config.mousemove = function(e){ var rowEl = row.getElement(); if(((e.pageY - Helpers.elOffset(rowEl).top) + self.table.rowManager.element.scrollTop) > (row.getHeight() / 2)){ if(self.toRow !== row || !self.toRowAfter){ rowEl.parentNode.insertBefore(self.placeholderElement, rowEl.nextSibling); self.moveRow(row, true); } }else { if(self.toRow !== row || self.toRowAfter){ rowEl.parentNode.insertBefore(self.placeholderElement, rowEl); self.moveRow(row, false); } } }.bind(self); if(!this.hasHandle){ rowEl = row.getElement(); rowEl.addEventListener("mousedown", function(e){ if(e.which === 1){ self.checkTimeout = setTimeout(function(){ self.startMove(e, row); }, self.checkPeriod); } }); rowEl.addEventListener("mouseup", function(e){ if(e.which === 1){ if(self.checkTimeout){ clearTimeout(self.checkTimeout); } } }); this.bindTouchEvents(row, row.getElement()); } row.modules.moveRow = config; } initializeColumn(column){ if(column.definition.rowHandle && this.table.options.movableRows !== false){ this.hasHandle = true; } } initializeCell(cell){ if(cell.column.definition.rowHandle && this.table.options.movableRows !== false){ var self = this, cellEl = cell.getElement(true); cellEl.addEventListener("mousedown", function(e){ if(e.which === 1){ self.checkTimeout = setTimeout(function(){ self.startMove(e, cell.row); }, self.checkPeriod); } }); cellEl.addEventListener("mouseup", function(e){ if(e.which === 1){ if(self.checkTimeout){ clearTimeout(self.checkTimeout); } } }); this.bindTouchEvents(cell.row, cellEl); } } bindTouchEvents(row, element){ var startYMove = false, //shifting center position of the cell nextRow, prevRow, nextRowHeight, prevRowHeight, nextRowHeightLast, prevRowHeightLast; element.addEventListener("touchstart", (e) => { this.checkTimeout = setTimeout(() => { this.touchMove = true; nextRow = row.nextRow(); nextRowHeight = nextRow ? nextRow.getHeight()/2 : 0; prevRow = row.prevRow(); prevRowHeight = prevRow ? prevRow.getHeight()/2 : 0; nextRowHeightLast = 0; prevRowHeightLast = 0; startYMove = false; this.startMove(e, row); }, this.checkPeriod); }, {passive: true}); this.moving, this.toRow, this.toRowAfter; element.addEventListener("touchmove", (e) => { var diff, moveToRow; if(this.moving){ e.preventDefault(); this.moveHover(e); if(!startYMove){ startYMove = e.touches[0].pageY; } diff = e.touches[0].pageY - startYMove; if(diff > 0){ if(nextRow && diff - nextRowHeightLast > nextRowHeight){ moveToRow = nextRow; if(moveToRow !== row){ startYMove = e.touches[0].pageY; moveToRow.getElement().parentNode.insertBefore(this.placeholderElement, moveToRow.getElement().nextSibling); this.moveRow(moveToRow, true); } } }else { if(prevRow && -diff - prevRowHeightLast > prevRowHeight){ moveToRow = prevRow; if(moveToRow !== row){ startYMove = e.touches[0].pageY; moveToRow.getElement().parentNode.insertBefore(this.placeholderElement, moveToRow.getElement()); this.moveRow(moveToRow, false); } } } if(moveToRow){ nextRow = moveToRow.nextRow(); nextRowHeightLast = nextRowHeight; nextRowHeight = nextRow ? nextRow.getHeight() / 2 : 0; prevRow = moveToRow.prevRow(); prevRowHeightLast = prevRowHeight; prevRowHeight = prevRow ? prevRow.getHeight() / 2 : 0; } } }); element.addEventListener("touchend", (e) => { if(this.checkTimeout){ clearTimeout(this.checkTimeout); } if(this.moving){ this.endMove(e); this.touchMove = false; } }); } _bindMouseMove(){ this.table.rowManager.getDisplayRows().forEach((row) => { if((row.type === "row" || row.type === "group") && row.modules.moveRow && row.modules.moveRow.mousemove){ row.getElement().addEventListener("mousemove", row.modules.moveRow.mousemove); } }); } _unbindMouseMove(){ this.table.rowManager.getDisplayRows().forEach((row) => { if((row.type === "row" || row.type === "group") && row.modules.moveRow && row.modules.moveRow.mousemove){ row.getElement().removeEventListener("mousemove", row.modules.moveRow.mousemove); } }); } startMove(e, row){ var element = row.getElement(); this.setStartPosition(e, row); this.moving = row; this.table.element.classList.add("tabulator-block-select"); //create placeholder this.placeholderElement.style.width = row.getWidth() + "px"; this.placeholderElement.style.height = row.getHeight() + "px"; if(!this.connection){ element.parentNode.insertBefore(this.placeholderElement, element); element.parentNode.removeChild(element); }else { this.table.element.classList.add("tabulator-movingrow-sending"); this.connectToTables(row); } //create hover element this.hoverElement = element.cloneNode(true); this.hoverElement.classList.add("tabulator-moving"); if(this.connection){ document.body.appendChild(this.hoverElement); this.hoverElement.style.left = "0"; this.hoverElement.style.top = "0"; this.hoverElement.style.width = this.table.element.clientWidth + "px"; this.hoverElement.style.whiteSpace = "nowrap"; this.hoverElement.style.overflow = "hidden"; this.hoverElement.style.pointerEvents = "none"; }else { this.table.rowManager.getTableElement().appendChild(this.hoverElement); this.hoverElement.style.left = "0"; this.hoverElement.style.top = "0"; this._bindMouseMove(); } document.body.addEventListener("mousemove", this.moveHover); document.body.addEventListener("mouseup", this.endMove); this.dispatchExternal("rowMoving", row.getComponent()); this.moveHover(e); } setStartPosition(e, row){ var pageX = this.touchMove ? e.touches[0].pageX : e.pageX, pageY = this.touchMove ? e.touches[0].pageY : e.pageY, element, position; element = row.getElement(); if(this.connection){ position = element.getBoundingClientRect(); this.startX = position.left - pageX + window.pageXOffset; this.startY = position.top - pageY + window.pageYOffset; }else { this.startY = (pageY - element.getBoundingClientRect().top); } } endMove(e){ if(!e || e.which === 1 || this.touchMove){ this._unbindMouseMove(); if(!this.connection){ this.placeholderElement.parentNode.insertBefore(this.moving.getElement(), this.placeholderElement.nextSibling); this.placeholderElement.parentNode.removeChild(this.placeholderElement); } this.hoverElement.parentNode.removeChild(this.hoverElement); this.table.element.classList.remove("tabulator-block-select"); if(this.toRow){ this.table.rowManager.moveRow(this.moving, this.toRow, this.toRowAfter); }else { this.dispatchExternal("rowMoveCancelled", this.moving.getComponent()); } this.moving = false; this.toRow = false; this.toRowAfter = false; document.body.removeEventListener("mousemove", this.moveHover); document.body.removeEventListener("mouseup", this.endMove); if(this.connection){ this.table.element.classList.remove("tabulator-movingrow-sending"); this.disconnectFromTables(); } } } moveRow(row, after){ this.toRow = row; this.toRowAfter = after; } moveHover(e){ if(this.connection){ this.moveHoverConnections.call(this, e); }else { this.moveHoverTable.call(this, e); } } moveHoverTable(e){ var rowHolder = this.table.rowManager.getElement(), scrollTop = rowHolder.scrollTop, yPos = ((this.touchMove ? e.touches[0].pageY : e.pageY) - rowHolder.getBoundingClientRect().top) + scrollTop; this.hoverElement.style.top = Math.min(yPos - this.startY, this.table.rowManager.element.scrollHeight - this.hoverElement.offsetHeight) + "px"; } moveHoverConnections(e){ this.hoverElement.style.left = (this.startX + (this.touchMove ? e.touches[0].pageX : e.pageX)) + "px"; this.hoverElement.style.top = (this.startY + (this.touchMove ? e.touches[0].pageY : e.pageY)) + "px"; } elementRowDrop(e, element, row){ this.dispatchExternal("movableRowsElementDrop", e, element, row ? row.getComponent() : false); } //establish connection with other tables connectToTables(row){ var connectionTables; if(this.connectionSelectorsTables){ connectionTables = this.commsConnections(this.connectionSelectorsTables); this.dispatchExternal("movableRowsSendingStart", connectionTables); this.commsSend(this.connectionSelectorsTables, "moveRow", "connect", { row:row, }); } if(this.connectionSelectorsElements){ this.connectionElements = []; if(!Array.isArray(this.connectionSelectorsElements)){ this.connectionSelectorsElements = [this.connectionSelectorsElements]; } this.connectionSelectorsElements.forEach((query) => { if(typeof query === "string"){ this.connectionElements = this.connectionElements.concat(Array.prototype.slice.call(document.querySelectorAll(query))); }else { this.connectionElements.push(query); } }); this.connectionElements.forEach((element) => { var dropEvent = (e) => { this.elementRowDrop(e, element, this.moving); }; element.addEventListener("mouseup", dropEvent); element.tabulatorElementDropEvent = dropEvent; element.classList.add("tabulator-movingrow-receiving"); }); } } //disconnect from other tables disconnectFromTables(){ var connectionTables; if(this.connectionSelectorsTables){ connectionTables = this.commsConnections(this.connectionSelectorsTables); this.dispatchExternal("movableRowsSendingStop", connectionTables); this.commsSend(this.connectionSelectorsTables, "moveRow", "disconnect"); } this.connectionElements.forEach((element) => { element.classList.remove("tabulator-movingrow-receiving"); element.removeEventListener("mouseup", element.tabulatorElementDropEvent); delete element.tabulatorElementDropEvent; }); } //accept incomming connection connect(table, row){ if(!this.connectedTable){ this.connectedTable = table; this.connectedRow = row; this.table.element.classList.add("tabulator-movingrow-receiving"); this.table.rowManager.getDisplayRows().forEach((row) => { if(row.type === "row" && row.modules.moveRow && row.modules.moveRow.mouseup){ row.getElement().addEventListener("mouseup", row.modules.moveRow.mouseup); } }); this.tableRowDropEvent = this.tableRowDrop.bind(this); this.table.element.addEventListener("mouseup", this.tableRowDropEvent); this.dispatchExternal("movableRowsReceivingStart", row, table); return true; }else { console.warn("Move Row Error - Table cannot accept connection, already connected to table:", this.connectedTable); return false; } } //close incoming connection disconnect(table){ if(table === this.connectedTable){ this.connectedTable = false; this.connectedRow = false; this.table.element.classList.remove("tabulator-movingrow-receiving"); this.table.rowManager.getDisplayRows().forEach((row) =>{ if(row.type === "row" && row.modules.moveRow && row.modules.moveRow.mouseup){ row.getElement().removeEventListener("mouseup", row.modules.moveRow.mouseup); } }); this.table.element.removeEventListener("mouseup", this.tableRowDropEvent); this.dispatchExternal("movableRowsReceivingStop", table); }else { console.warn("Move Row Error - trying to disconnect from non connected table"); } } dropComplete(table, row, success){ var sender = false; if(success){ switch(typeof this.table.options.movableRowsSender){ case "string": sender = this.senders[this.table.options.movableRowsSender]; break; case "function": sender = this.table.options.movableRowsSender; break; } if(sender){ sender.call(this, this.moving ? this.moving.getComponent() : undefined, row ? row.getComponent() : undefined, table); }else { if(this.table.options.movableRowsSender){ console.warn("Mover Row Error - no matching sender found:", this.table.options.movableRowsSender); } } this.dispatchExternal("movableRowsSent", this.moving.getComponent(), row ? row.getComponent() : undefined, table); }else { this.dispatchExternal("movableRowsSentFailed", this.moving.getComponent(), row ? row.getComponent() : undefined, table); } this.endMove(); } tableRowDrop(e, row){ var receiver = false, success = false; e.stopImmediatePropagation(); switch(typeof this.table.options.movableRowsReceiver){ case "string": receiver = this.receivers[this.table.options.movableRowsReceiver]; break; case "function": receiver = this.table.options.movableRowsReceiver; break; } if(receiver){ success = receiver.call(this, this.connectedRow.getComponent(), row ? row.getComponent() : undefined, this.connectedTable); }else { console.warn("Mover Row Error - no matching receiver found:", this.table.options.movableRowsReceiver); } if(success){ this.dispatchExternal("movableRowsReceived", this.connectedRow.getComponent(), row ? row.getComponent() : undefined, this.connectedTable); }else { this.dispatchExternal("movableRowsReceivedFailed", this.connectedRow.getComponent(), row ? row.getComponent() : undefined, this.connectedTable); } this.commsSend(this.connectedTable, "moveRow", "dropcomplete", { row:row, success:success, }); } commsReceived(table, action, data){ switch(action){ case "connect": return this.connect(table, data.row); case "disconnect": return this.disconnect(table); case "dropcomplete": return this.dropComplete(table, data.row, data.success); } } } MoveRows.prototype.receivers = { insert:function(fromRow, toRow, fromTable){ this.table.addRow(fromRow.getData(), undefined, toRow); return true; }, add:function(fromRow, toRow, fromTable){ this.table.addRow(fromRow.getData()); return true; }, update:function(fromRow, toRow, fromTable){ if(toRow){ toRow.update(fromRow.getData()); return true; } return false; }, replace:function(fromRow, toRow, fromTable){ if(toRow){ this.table.addRow(fromRow.getData(), undefined, toRow); toRow.delete(); return true; } return false; }, }; MoveRows.prototype.senders = { delete:function(fromRow, toRow, toTable){ fromRow.delete(); } }; MoveRows.moduleName = "moveRow"; var defaultMutators = {}; class Mutator extends Module{ constructor(table){ super(table); this.allowedTypes = ["", "data", "edit", "clipboard"]; //list of mutation types this.enabled = true; this.registerColumnOption("mutator"); this.registerColumnOption("mutatorParams"); this.registerColumnOption("mutatorData"); this.registerColumnOption("mutatorDataParams"); this.registerColumnOption("mutatorEdit"); this.registerColumnOption("mutatorEditParams"); this.registerColumnOption("mutatorClipboard"); this.registerColumnOption("mutatorClipboardParams"); this.registerColumnOption("mutateLink"); } initialize(){ this.subscribe("cell-value-changing", this.transformCell.bind(this)); this.subscribe("cell-value-changed", this.mutateLink.bind(this)); this.subscribe("column-layout", this.initializeColumn.bind(this)); this.subscribe("row-data-init-before", this.rowDataChanged.bind(this)); this.subscribe("row-data-changing", this.rowDataChanged.bind(this)); } rowDataChanged(row, tempData, updatedData){ return this.transformRow(tempData, "data", updatedData); } //initialize column mutator initializeColumn(column){ var match = false, config = {}; this.allowedTypes.forEach((type) => { var key = "mutator" + (type.charAt(0).toUpperCase() + type.slice(1)), mutator; if(column.definition[key]){ mutator = this.lookupMutator(column.definition[key]); if(mutator){ match = true; config[key] = { mutator:mutator, params: column.definition[key + "Params"] || {}, }; } } }); if(match){ column.modules.mutate = config; } } lookupMutator(value){ var mutator = false; //set column mutator switch(typeof value){ case "string": if(Mutator.mutators[value]){ mutator = Mutator.mutators[value]; }else { console.warn("Mutator Error - No such mutator found, ignoring: ", value); } break; case "function": mutator = value; break; } return mutator; } //apply mutator to row transformRow(data, type, updatedData){ var key = "mutator" + (type.charAt(0).toUpperCase() + type.slice(1)), value; if(this.enabled){ this.table.columnManager.traverse((column) => { var mutator, params, component; if(column.modules.mutate){ mutator = column.modules.mutate[key] || column.modules.mutate.mutator || false; if(mutator){ value = column.getFieldValue(typeof updatedData !== "undefined" ? updatedData : data); if((type == "data" && !updatedData)|| typeof value !== "undefined"){ component = column.getComponent(); params = typeof mutator.params === "function" ? mutator.params(value, data, type, component) : mutator.params; column.setFieldValue(data, mutator.mutator(value, data, type, params, component)); } } } }); } return data; } //apply mutator to new cell value transformCell(cell, value){ if(cell.column.modules.mutate){ var mutator = cell.column.modules.mutate.mutatorEdit || cell.column.modules.mutate.mutator || false, tempData = {}; if(mutator){ tempData = Object.assign(tempData, cell.row.getData()); cell.column.setFieldValue(tempData, value); return mutator.mutator(value, tempData, "edit", mutator.params, cell.getComponent()); } } return value; } mutateLink(cell){ var links = cell.column.definition.mutateLink; if(links){ if(!Array.isArray(links)){ links = [links]; } links.forEach((link) => { var linkCell = cell.row.getCell(link); if(linkCell){ linkCell.setValue(linkCell.getValue(), true, true); } }); } } enable(){ this.enabled = true; } disable(){ this.enabled = false; } } Mutator.moduleName = "mutator"; //load defaults Mutator.mutators = defaultMutators; function rows(pageSize, currentRow, currentPage, totalRows, totalPages){ var el = document.createElement("span"), showingEl = document.createElement("span"), valueEl = document.createElement("span"), ofEl = document.createElement("span"), totalEl = document.createElement("span"), rowsEl = document.createElement("span"); this.table.modules.localize.langBind("pagination|counter|showing", (value) => { showingEl.innerHTML = value; }); this.table.modules.localize.langBind("pagination|counter|of", (value) => { ofEl.innerHTML = value; }); this.table.modules.localize.langBind("pagination|counter|rows", (value) => { rowsEl.innerHTML = value; }); if(totalRows){ valueEl.innerHTML = " " + currentRow + "-" + Math.min((currentRow + pageSize - 1), totalRows) + " "; totalEl.innerHTML = " " + totalRows + " "; el.appendChild(showingEl); el.appendChild(valueEl); el.appendChild(ofEl); el.appendChild(totalEl); el.appendChild(rowsEl); }else { valueEl.innerHTML = " 0 "; el.appendChild(showingEl); el.appendChild(valueEl); el.appendChild(rowsEl); } return el; } function pages(pageSize, currentRow, currentPage, totalRows, totalPages){ var el = document.createElement("span"), showingEl = document.createElement("span"), valueEl = document.createElement("span"), ofEl = document.createElement("span"), totalEl = document.createElement("span"), rowsEl = document.createElement("span"); this.table.modules.localize.langBind("pagination|counter|showing", (value) => { showingEl.innerHTML = value; }); valueEl.innerHTML = " " + currentPage + " "; this.table.modules.localize.langBind("pagination|counter|of", (value) => { ofEl.innerHTML = value; }); totalEl.innerHTML = " " + totalPages + " "; this.table.modules.localize.langBind("pagination|counter|pages", (value) => { rowsEl.innerHTML = value; }); el.appendChild(showingEl); el.appendChild(valueEl); el.appendChild(ofEl); el.appendChild(totalEl); el.appendChild(rowsEl); return el; } var defaultPageCounters = { rows:rows, pages:pages, }; class Page extends Module{ constructor(table){ super(table); this.mode = "local"; this.progressiveLoad = false; this.element = null; this.pageCounterElement = null; this.pageCounter = null; this.size = 0; this.page = 1; this.count = 5; this.max = 1; this.remoteRowCountEstimate = null; this.initialLoad = true; this.dataChanging = false; //flag to check if data is being changed by this module this.pageSizes = []; this.registerTableOption("pagination", false); //set pagination type this.registerTableOption("paginationMode", "local"); //local or remote pagination this.registerTableOption("paginationSize", false); //set number of rows to a page this.registerTableOption("paginationInitialPage", 1); //initial page to show on load this.registerTableOption("paginationCounter", false); // set pagination counter this.registerTableOption("paginationCounterElement", false); // set pagination counter this.registerTableOption("paginationButtonCount", 5); // set count of page button this.registerTableOption("paginationSizeSelector", false); //add pagination size selector element this.registerTableOption("paginationElement", false); //element to hold pagination numbers // this.registerTableOption("paginationDataSent", {}); //pagination data sent to the server // this.registerTableOption("paginationDataReceived", {}); //pagination data received from the server this.registerTableOption("paginationAddRow", "page"); //add rows on table or page this.registerTableOption("progressiveLoad", false); //progressive loading this.registerTableOption("progressiveLoadDelay", 0); //delay between requests this.registerTableOption("progressiveLoadScrollMargin", 0); //margin before scroll begins this.registerTableFunction("setMaxPage", this.setMaxPage.bind(this)); this.registerTableFunction("setPage", this.setPage.bind(this)); this.registerTableFunction("setPageToRow", this.userSetPageToRow.bind(this)); this.registerTableFunction("setPageSize", this.userSetPageSize.bind(this)); this.registerTableFunction("getPageSize", this.getPageSize.bind(this)); this.registerTableFunction("previousPage", this.previousPage.bind(this)); this.registerTableFunction("nextPage", this.nextPage.bind(this)); this.registerTableFunction("getPage", this.getPage.bind(this)); this.registerTableFunction("getPageMax", this.getPageMax.bind(this)); //register component functions this.registerComponentFunction("row", "pageTo", this.setPageToRow.bind(this)); } initialize(){ if(this.table.options.pagination){ this.subscribe("row-deleted", this.rowsUpdated.bind(this)); this.subscribe("row-added", this.rowsUpdated.bind(this)); this.subscribe("data-processed", this.initialLoadComplete.bind(this)); this.subscribe("table-built", this.calculatePageSizes.bind(this)); this.subscribe("footer-redraw", this.footerRedraw.bind(this)); if(this.table.options.paginationAddRow == "page"){ this.subscribe("row-adding-position", this.rowAddingPosition.bind(this)); } if(this.table.options.paginationMode === "remote"){ this.subscribe("data-params", this.remotePageParams.bind(this)); this.subscribe("data-loaded", this._parseRemoteData.bind(this)); } if(this.table.options.progressiveLoad){ console.error("Progressive Load Error - Pagination and progressive load cannot be used at the same time"); } this.registerDisplayHandler(this.restOnRenderBefore.bind(this), 40); this.registerDisplayHandler(this.getRows.bind(this), 50); this.createElements(); this.initializePageCounter(); this.initializePaginator(); }else if(this.table.options.progressiveLoad){ this.subscribe("data-params", this.remotePageParams.bind(this)); this.subscribe("data-loaded", this._parseRemoteData.bind(this)); this.subscribe("table-built", this.calculatePageSizes.bind(this)); this.subscribe("data-processed", this.initialLoadComplete.bind(this)); this.initializeProgressive(this.table.options.progressiveLoad); if(this.table.options.progressiveLoad === "scroll"){ this.subscribe("scroll-vertical", this.scrollVertical.bind(this)); } } } rowAddingPosition(row, top){ var rowManager = this.table.rowManager, displayRows = rowManager.getDisplayRows(), index; if(top){ if(displayRows.length){ index = displayRows[0]; }else { if(rowManager.activeRows.length){ index = rowManager.activeRows[rowManager.activeRows.length-1]; top = false; } } }else { if(displayRows.length){ index = displayRows[displayRows.length - 1]; top = displayRows.length < this.size ? false : true; } } return {index, top}; } calculatePageSizes(){ var testElRow, testElCell; if(this.table.options.paginationSize){ this.size = this.table.options.paginationSize; }else { testElRow = document.createElement("div"); testElRow.classList.add("tabulator-row"); testElRow.style.visibility = "hidden"; testElCell = document.createElement("div"); testElCell.classList.add("tabulator-cell"); testElCell.innerHTML = "Page Row Test"; testElRow.appendChild(testElCell); this.table.rowManager.getTableElement().appendChild(testElRow); this.size = Math.floor(this.table.rowManager.getElement().clientHeight / testElRow.offsetHeight); this.table.rowManager.getTableElement().removeChild(testElRow); } this.dispatchExternal("pageSizeChanged", this.size); this.generatePageSizeSelectList(); } initialLoadComplete(){ this.initialLoad = false; } remotePageParams(data, config, silent, params){ if(!this.initialLoad){ if((this.progressiveLoad && !silent) || (!this.progressiveLoad && !this.dataChanging)){ this.reset(true); } } //configure request params params.page = this.page; //set page size if defined if(this.size){ params.size = this.size; } return params; } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// userSetPageToRow(row){ if(this.table.options.pagination){ row = this.rowManager.findRow(row); if(row){ return this.setPageToRow(row); } } return Promise.reject(); } userSetPageSize(size){ if(this.table.options.pagination){ this.setPageSize(size); return this.setPage(1); }else { return false; } } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// scrollVertical(top, dir){ var element, diff, margin; if(!dir && !this.table.dataLoader.loading){ element = this.table.rowManager.getElement(); diff = element.scrollHeight - element.clientHeight - top; margin = this.table.options.progressiveLoadScrollMargin || (element.clientHeight * 2); if(diff < margin){ this.nextPage() .catch(() => {}); //consume the exception thrown when on the last page } } } restOnRenderBefore(rows, renderInPosition){ if(!renderInPosition){ if(this.mode === "local"){ this.reset(); } } return rows; } rowsUpdated(){ this.refreshData(true, "all"); } createElements(){ var button; this.element = document.createElement("span"); this.element.classList.add("tabulator-paginator"); this.pagesElement = document.createElement("span"); this.pagesElement.classList.add("tabulator-pages"); button = document.createElement("button"); button.classList.add("tabulator-page"); button.setAttribute("type", "button"); button.setAttribute("role", "button"); button.setAttribute("aria-label", ""); button.setAttribute("title", ""); this.firstBut = button.cloneNode(true); this.firstBut.setAttribute("data-page", "first"); this.prevBut = button.cloneNode(true); this.prevBut.setAttribute("data-page", "prev"); this.nextBut = button.cloneNode(true); this.nextBut.setAttribute("data-page", "next"); this.lastBut = button.cloneNode(true); this.lastBut.setAttribute("data-page", "last"); if(this.table.options.paginationSizeSelector){ this.pageSizeSelect = document.createElement("select"); this.pageSizeSelect.classList.add("tabulator-page-size"); } } generatePageSizeSelectList(){ var pageSizes = []; if(this.pageSizeSelect){ if(Array.isArray(this.table.options.paginationSizeSelector)){ pageSizes = this.table.options.paginationSizeSelector; this.pageSizes = pageSizes; if(this.pageSizes.indexOf(this.size) == -1){ pageSizes.unshift(this.size); } }else { if(this.pageSizes.indexOf(this.size) == -1){ pageSizes = []; for (let i = 1; i < 5; i++){ pageSizes.push(this.size * i); } this.pageSizes = pageSizes; }else { pageSizes = this.pageSizes; } } while(this.pageSizeSelect.firstChild) this.pageSizeSelect.removeChild(this.pageSizeSelect.firstChild); pageSizes.forEach((item) => { var itemEl = document.createElement("option"); itemEl.value = item; if(item === true){ this.langBind("pagination|all", function(value){ itemEl.innerHTML = value; }); }else { itemEl.innerHTML = item; } this.pageSizeSelect.appendChild(itemEl); }); this.pageSizeSelect.value = this.size; } } initializePageCounter(){ var counter = this.table.options.paginationCounter, pageCounter = null; if(counter){ if(typeof counter === "function"){ pageCounter = counter; }else { pageCounter = Page.pageCounters[counter]; } if(pageCounter){ this.pageCounter = pageCounter; this.pageCounterElement = document.createElement("span"); this.pageCounterElement.classList.add("tabulator-page-counter"); }else { console.warn("Pagination Error - No such page counter found: ", counter); } } } //setup pagination initializePaginator(hidden){ var pageSelectLabel, paginationCounterHolder; if(!hidden){ //build pagination element //bind localizations this.langBind("pagination|first", (value) => { this.firstBut.innerHTML = value; }); this.langBind("pagination|first_title", (value) => { this.firstBut.setAttribute("aria-label", value); this.firstBut.setAttribute("title", value); }); this.langBind("pagination|prev", (value) => { this.prevBut.innerHTML = value; }); this.langBind("pagination|prev_title", (value) => { this.prevBut.setAttribute("aria-label", value); this.prevBut.setAttribute("title", value); }); this.langBind("pagination|next", (value) => { this.nextBut.innerHTML = value; }); this.langBind("pagination|next_title", (value) => { this.nextBut.setAttribute("aria-label", value); this.nextBut.setAttribute("title", value); }); this.langBind("pagination|last", (value) => { this.lastBut.innerHTML = value; }); this.langBind("pagination|last_title", (value) => { this.lastBut.setAttribute("aria-label", value); this.lastBut.setAttribute("title", value); }); //click bindings this.firstBut.addEventListener("click", () => { this.setPage(1); }); this.prevBut.addEventListener("click", () => { this.previousPage(); }); this.nextBut.addEventListener("click", () => { this.nextPage(); }); this.lastBut.addEventListener("click", () => { this.setPage(this.max); }); if(this.table.options.paginationElement){ this.element = this.table.options.paginationElement; } if(this.pageSizeSelect){ pageSelectLabel = document.createElement("label"); this.langBind("pagination|page_size", (value) => { this.pageSizeSelect.setAttribute("aria-label", value); this.pageSizeSelect.setAttribute("title", value); pageSelectLabel.innerHTML = value; }); this.element.appendChild(pageSelectLabel); this.element.appendChild(this.pageSizeSelect); this.pageSizeSelect.addEventListener("change", (e) => { this.setPageSize(this.pageSizeSelect.value == "true" ? true : this.pageSizeSelect.value); this.setPage(1); }); } //append to DOM this.element.appendChild(this.firstBut); this.element.appendChild(this.prevBut); this.element.appendChild(this.pagesElement); this.element.appendChild(this.nextBut); this.element.appendChild(this.lastBut); if(!this.table.options.paginationElement){ if(this.table.options.paginationCounter){ if(this.table.options.paginationCounterElement){ if(this.table.options.paginationCounterElement instanceof HTMLElement){ this.table.options.paginationCounterElement.appendChild(this.pageCounterElement); }else if(typeof this.table.options.paginationCounterElement === "string"){ paginationCounterHolder = document.querySelector(this.table.options.paginationCounterElement); if(paginationCounterHolder){ paginationCounterHolder.appendChild(this.pageCounterElement); }else { console.warn("Pagination Error - Unable to find element matching paginationCounterElement selector:", this.table.options.paginationCounterElement); } } }else { this.footerAppend(this.pageCounterElement); } } this.footerAppend(this.element); } this.page = this.table.options.paginationInitialPage; this.count = this.table.options.paginationButtonCount; } //set default values this.mode = this.table.options.paginationMode; } initializeProgressive(mode){ this.initializePaginator(true); this.mode = "progressive_" + mode; this.progressiveLoad = true; } trackChanges(){ this.dispatch("page-changed"); } //calculate maximum page from number of rows setMaxRows(rowCount){ if(!rowCount){ this.max = 1; }else { this.max = this.size === true ? 1 : Math.ceil(rowCount/this.size); } if(this.page > this.max){ this.page = this.max; } } //reset to first page without triggering action reset(force){ if(!this.initialLoad){ if(this.mode == "local" || force){ this.page = 1; this.trackChanges(); } } } //set the maximum page setMaxPage(max){ max = parseInt(max); this.max = max || 1; if(this.page > this.max){ this.page = this.max; this.trigger(); } } //set current page number setPage(page){ switch(page){ case "first": return this.setPage(1); case "prev": return this.previousPage(); case "next": return this.nextPage(); case "last": return this.setPage(this.max); } page = parseInt(page); if((page > 0 && page <= this.max) || this.mode !== "local"){ this.page = page; this.trackChanges(); return this.trigger(); }else { console.warn("Pagination Error - Requested page is out of range of 1 - " + this.max + ":", page); return Promise.reject(); } } setPageToRow(row){ var rows = this.displayRows(-1); var index = rows.indexOf(row); if(index > -1){ var page = this.size === true ? 1 : Math.ceil((index + 1) / this.size); return this.setPage(page); }else { console.warn("Pagination Error - Requested row is not visible"); return Promise.reject(); } } setPageSize(size){ if(size !== true){ size = parseInt(size); } if(size > 0){ this.size = size; this.dispatchExternal("pageSizeChanged", size); } if(this.pageSizeSelect){ // this.pageSizeSelect.value = size; this.generatePageSizeSelectList(); } this.trackChanges(); } _setPageCounter(totalRows, size, currentRow){ var content; if(this.pageCounter){ if(this.mode === "remote"){ size = this.size; currentRow = ((this.page - 1) * this.size) + 1; totalRows = this.remoteRowCountEstimate; } content = this.pageCounter.call(this, size, currentRow, this.page, totalRows, this.max); switch(typeof content){ case "object": if(content instanceof Node){ //clear previous cell contents while(this.pageCounterElement.firstChild) this.pageCounterElement.removeChild(this.pageCounterElement.firstChild); this.pageCounterElement.appendChild(content); }else { this.pageCounterElement.innerHTML = ""; if(content != null){ console.warn("Page Counter Error - Page Counter has returned a type of object, the only valid page counter object return is an instance of Node, the page counter returned:", content); } } break; case "undefined": this.pageCounterElement.innerHTML = ""; break; default: this.pageCounterElement.innerHTML = content; } } } //setup the pagination buttons _setPageButtons(){ let leftSize = Math.floor((this.count-1) / 2); let rightSize = Math.ceil((this.count-1) / 2); let min = this.max - this.page + leftSize + 1 < this.count ? this.max-this.count+1: Math.max(this.page-leftSize,1); let max = this.page <= rightSize? Math.min(this.count, this.max) :Math.min(this.page+rightSize, this.max); while(this.pagesElement.firstChild) this.pagesElement.removeChild(this.pagesElement.firstChild); if(this.page == 1){ this.firstBut.disabled = true; this.prevBut.disabled = true; }else { this.firstBut.disabled = false; this.prevBut.disabled = false; } if(this.page == this.max){ this.lastBut.disabled = true; this.nextBut.disabled = true; }else { this.lastBut.disabled = false; this.nextBut.disabled = false; } for(let i = min; i <= max; i++){ if(i>0 && i <= this.max){ this.pagesElement.appendChild(this._generatePageButton(i)); } } this.footerRedraw(); } _generatePageButton(page){ var button = document.createElement("button"); button.classList.add("tabulator-page"); if(page == this.page){ button.classList.add("active"); } button.setAttribute("type", "button"); button.setAttribute("role", "button"); this.langBind("pagination|page_title", (value) => { button.setAttribute("aria-label", value + " " + page); button.setAttribute("title", value + " " + page); }); button.setAttribute("data-page", page); button.textContent = page; button.addEventListener("click", (e) => { this.setPage(page); }); return button; } //previous page previousPage(){ if(this.page > 1){ this.page--; this.trackChanges(); return this.trigger(); }else { console.warn("Pagination Error - Previous page would be less than page 1:", 0); return Promise.reject(); } } //next page nextPage(){ if(this.page < this.max){ this.page++; this.trackChanges(); return this.trigger(); }else { if(!this.progressiveLoad){ console.warn("Pagination Error - Next page would be greater than maximum page of " + this.max + ":", this.max + 1); } return Promise.reject(); } } //return current page number getPage(){ return this.page; } //return max page number getPageMax(){ return this.max; } getPageSize(size){ return this.size; } getMode(){ return this.mode; } //return appropriate rows for current page getRows(data){ var actualRowPageSize = 0, output, start, end, actualStartRow; var actualRows = data.filter((row) => { return row.type === "row"; }); if(this.mode == "local"){ output = []; this.setMaxRows(data.length); if(this.size === true){ start = 0; end = data.length; }else { start = this.size * (this.page - 1); end = start + parseInt(this.size); } this._setPageButtons(); for(let i = start; i < end; i++){ let row = data[i]; if(row){ output.push(row); if(row.type === "row"){ if(!actualStartRow){ actualStartRow = row; } actualRowPageSize++; } } } this._setPageCounter(actualRows.length, actualRowPageSize, actualStartRow ? (actualRows.indexOf(actualStartRow) + 1) : 0); return output; }else { this._setPageButtons(); this._setPageCounter(actualRows.length); return data.slice(0); } } trigger(){ var left; switch(this.mode){ case "local": left = this.table.rowManager.scrollLeft; this.refreshData(); this.table.rowManager.scrollHorizontal(left); this.dispatchExternal("pageLoaded", this.getPage()); return Promise.resolve(); case "remote": this.dataChanging = true; return this.reloadData(null) .finally(() => { this.dataChanging = false; }); case "progressive_load": case "progressive_scroll": return this.reloadData(null, true); default: console.warn("Pagination Error - no such pagination mode:", this.mode); return Promise.reject(); } } _parseRemoteData(data){ var margin; if(typeof data.last_page === "undefined"){ console.warn("Remote Pagination Error - Server response missing '" + (this.options("dataReceiveParams").last_page || "last_page") + "' property"); } if(data.data){ this.max = parseInt(data.last_page) || 1; this.remoteRowCountEstimate = typeof data.last_row !== "undefined" ? data.last_row : (data.last_page * this.size - (this.page == data.last_page ? (this.size - data.data.length) : 0)); if(this.progressiveLoad){ switch(this.mode){ case "progressive_load": if(this.page == 1){ this.table.rowManager.setData(data.data, false, this.page == 1); }else { this.table.rowManager.addRows(data.data); } if(this.page < this.max){ setTimeout(() => { this.nextPage(); }, this.table.options.progressiveLoadDelay); } break; case "progressive_scroll": data = this.page === 1 ? data.data : this.table.rowManager.getData().concat(data.data); this.table.rowManager.setData(data, this.page !== 1, this.page == 1); margin = this.table.options.progressiveLoadScrollMargin || (this.table.rowManager.element.clientHeight * 2); if(this.table.rowManager.element.scrollHeight <= (this.table.rowManager.element.clientHeight + margin)){ if(this.page < this.max){ setTimeout(() => { this.nextPage(); }); } } break; } return false; }else { // left = this.table.rowManager.scrollLeft; this.dispatchExternal("pageLoaded", this.getPage()); // this.table.rowManager.scrollHorizontal(left); // this.table.columnManager.scrollHorizontal(left); } }else { console.warn("Remote Pagination Error - Server response missing '" + (this.options("dataReceiveParams").data || "data") + "' property"); } return data.data; } //handle the footer element being redrawn footerRedraw(){ var footer = this.table.footerManager.containerElement; if((Math.ceil(footer.clientWidth) - footer.scrollWidth) < 0){ this.pagesElement.style.display = 'none'; }else { this.pagesElement.style.display = ''; if((Math.ceil(footer.clientWidth) - footer.scrollWidth) < 0){ this.pagesElement.style.display = 'none'; } } } } Page.moduleName = "page"; //load defaults Page.pageCounters = defaultPageCounters; // read persistance information from storage var defaultReaders = { local:function(id, type){ var data = localStorage.getItem(id + "-" + type); return data ? JSON.parse(data) : false; }, cookie:function(id, type){ var cookie = document.cookie, key = id + "-" + type, cookiePos = cookie.indexOf(key + "="), end, data; //if cookie exists, decode and load column data into tabulator if(cookiePos > -1){ cookie = cookie.slice(cookiePos); end = cookie.indexOf(";"); if(end > -1){ cookie = cookie.slice(0, end); } data = cookie.replace(key + "=", ""); } return data ? JSON.parse(data) : false; } }; //write persistence information to storage var defaultWriters = { local:function(id, type, data){ localStorage.setItem(id + "-" + type, JSON.stringify(data)); }, cookie:function(id, type, data){ var expireDate = new Date(); expireDate.setDate(expireDate.getDate() + 10000); document.cookie = id + "-" + type + "=" + JSON.stringify(data) + "; expires=" + expireDate.toUTCString(); } }; class Persistence extends Module{ constructor(table){ super(table); this.mode = ""; this.id = ""; // this.persistProps = ["field", "width", "visible"]; this.defWatcherBlock = false; this.config = {}; this.readFunc = false; this.writeFunc = false; this.registerTableOption("persistence", false); this.registerTableOption("persistenceID", ""); //key for persistent storage this.registerTableOption("persistenceMode", true); //mode for storing persistence information this.registerTableOption("persistenceReaderFunc", false); //function for handling persistence data reading this.registerTableOption("persistenceWriterFunc", false); //function for handling persistence data writing } // Test for whether localStorage is available for use. localStorageTest() { var testKey = "_tabulator_test"; try { window.localStorage.setItem( testKey, testKey); window.localStorage.removeItem( testKey ); return true; } catch(e) { return false; } } //setup parameters initialize(){ if(this.table.options.persistence){ //determine persistent layout storage type var mode = this.table.options.persistenceMode, id = this.table.options.persistenceID, retrievedData; this.mode = mode !== true ? mode : (this.localStorageTest() ? "local" : "cookie"); if(this.table.options.persistenceReaderFunc){ if(typeof this.table.options.persistenceReaderFunc === "function"){ this.readFunc = this.table.options.persistenceReaderFunc; }else { if(Persistence.readers[this.table.options.persistenceReaderFunc]){ this.readFunc = Persistence.readers[this.table.options.persistenceReaderFunc]; }else { console.warn("Persistence Read Error - invalid reader set", this.table.options.persistenceReaderFunc); } } }else { if(Persistence.readers[this.mode]){ this.readFunc = Persistence.readers[this.mode]; }else { console.warn("Persistence Read Error - invalid reader set", this.mode); } } if(this.table.options.persistenceWriterFunc){ if(typeof this.table.options.persistenceWriterFunc === "function"){ this.writeFunc = this.table.options.persistenceWriterFunc; }else { if(Persistence.writers[this.table.options.persistenceWriterFunc]){ this.writeFunc = Persistence.writers[this.table.options.persistenceWriterFunc]; }else { console.warn("Persistence Write Error - invalid reader set", this.table.options.persistenceWriterFunc); } } }else { if(Persistence.writers[this.mode]){ this.writeFunc = Persistence.writers[this.mode]; }else { console.warn("Persistence Write Error - invalid writer set", this.mode); } } //set storage tag this.id = "tabulator-" + (id || (this.table.element.getAttribute("id") || "")); this.config = { sort:this.table.options.persistence === true || this.table.options.persistence.sort, filter:this.table.options.persistence === true || this.table.options.persistence.filter, group:this.table.options.persistence === true || this.table.options.persistence.group, page:this.table.options.persistence === true || this.table.options.persistence.page, columns:this.table.options.persistence === true ? ["title", "width", "visible"] : this.table.options.persistence.columns, }; //load pagination data if needed if(this.config.page){ retrievedData = this.retrieveData("page"); if(retrievedData){ if(typeof retrievedData.paginationSize !== "undefined" && (this.config.page === true || this.config.page.size)){ this.table.options.paginationSize = retrievedData.paginationSize; } if(typeof retrievedData.paginationInitialPage !== "undefined" && (this.config.page === true || this.config.page.page)){ this.table.options.paginationInitialPage = retrievedData.paginationInitialPage; } } } //load group data if needed if(this.config.group){ retrievedData = this.retrieveData("group"); if(retrievedData){ if(typeof retrievedData.groupBy !== "undefined" && (this.config.group === true || this.config.group.groupBy)){ this.table.options.groupBy = retrievedData.groupBy; } if(typeof retrievedData.groupStartOpen !== "undefined" && (this.config.group === true || this.config.group.groupStartOpen)){ this.table.options.groupStartOpen = retrievedData.groupStartOpen; } if(typeof retrievedData.groupHeader !== "undefined" && (this.config.group === true || this.config.group.groupHeader)){ this.table.options.groupHeader = retrievedData.groupHeader; } } } if(this.config.columns){ this.table.options.columns = this.load("columns", this.table.options.columns); this.subscribe("column-init", this.initializeColumn.bind(this)); this.subscribe("column-show", this.save.bind(this, "columns")); this.subscribe("column-hide", this.save.bind(this, "columns")); this.subscribe("column-moved", this.save.bind(this, "columns")); } this.subscribe("table-built", this.tableBuilt.bind(this), 0); this.subscribe("table-redraw", this.tableRedraw.bind(this)); this.subscribe("filter-changed", this.eventSave.bind(this, "filter")); this.subscribe("sort-changed", this.eventSave.bind(this, "sort")); this.subscribe("group-changed", this.eventSave.bind(this, "group")); this.subscribe("page-changed", this.eventSave.bind(this, "page")); this.subscribe("column-resized", this.eventSave.bind(this, "columns")); this.subscribe("column-width", this.eventSave.bind(this, "columns")); this.subscribe("layout-refreshed", this.eventSave.bind(this, "columns")); } this.registerTableFunction("getColumnLayout", this.getColumnLayout.bind(this)); this.registerTableFunction("setColumnLayout", this.setColumnLayout.bind(this)); } eventSave(type){ if(this.config[type]){ this.save(type); } } tableBuilt(){ var sorters, filters; if(this.config.sort){ sorters = this.load("sort"); if(!sorters === false){ this.table.options.initialSort = sorters; } } if(this.config.filter){ filters = this.load("filter"); if(!filters === false){ this.table.options.initialFilter = filters; } } } tableRedraw(force){ if(force && this.config.columns){ this.save("columns"); } } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// getColumnLayout(){ return this.parseColumns(this.table.columnManager.getColumns()); } setColumnLayout(layout){ this.table.columnManager.setColumns(this.mergeDefinition(this.table.options.columns, layout)); return true; } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// initializeColumn(column){ var def, keys; if(this.config.columns){ this.defWatcherBlock = true; def = column.getDefinition(); keys = this.config.columns === true ? Object.keys(def) : this.config.columns; keys.forEach((key)=>{ var props = Object.getOwnPropertyDescriptor(def, key); var value = def[key]; if(props){ Object.defineProperty(def, key, { set: (newValue) => { value = newValue; if(!this.defWatcherBlock){ this.save("columns"); } if(props.set){ props.set(newValue); } }, get:() => { if(props.get){ props.get(); } return value; } }); } }); this.defWatcherBlock = false; } } //load saved definitions load(type, current){ var data = this.retrieveData(type); if(current){ data = data ? this.mergeDefinition(current, data) : current; } return data; } //retrieve data from memory retrieveData(type){ return this.readFunc ? this.readFunc(this.id, type) : false; } //merge old and new column definitions mergeDefinition(oldCols, newCols){ var output = []; newCols = newCols || []; newCols.forEach((column, to) => { var from = this._findColumn(oldCols, column), keys; if(from){ if(this.config.columns === true || this.config.columns == undefined){ keys = Object.keys(from); keys.push("width"); }else { keys = this.config.columns; } keys.forEach((key)=>{ if(key !== "columns" && typeof column[key] !== "undefined"){ from[key] = column[key]; } }); if(from.columns){ from.columns = this.mergeDefinition(from.columns, column.columns); } output.push(from); } }); oldCols.forEach((column, i) => { var from = this._findColumn(newCols, column); if (!from) { if(output.length>i){ output.splice(i, 0, column); }else { output.push(column); } } }); return output; } //find matching columns _findColumn(columns, subject){ var type = subject.columns ? "group" : (subject.field ? "field" : "object"); return columns.find(function(col){ switch(type){ case "group": return col.title === subject.title && col.columns.length === subject.columns.length; case "field": return col.field === subject.field; case "object": return col === subject; } }); } //save data save(type){ var data = {}; switch(type){ case "columns": data = this.parseColumns(this.table.columnManager.getColumns()); break; case "filter": data = this.table.modules.filter.getFilters(); break; case "sort": data = this.validateSorters(this.table.modules.sort.getSort()); break; case "group": data = this.getGroupConfig(); break; case "page": data = this.getPageConfig(); break; } if(this.writeFunc){ this.writeFunc(this.id, type, data); } } //ensure sorters contain no function data validateSorters(data){ data.forEach(function(item){ item.column = item.field; delete item.field; }); return data; } getGroupConfig(){ var data = {}; if(this.config.group){ if(this.config.group === true || this.config.group.groupBy){ data.groupBy = this.table.options.groupBy; } if(this.config.group === true || this.config.group.groupStartOpen){ data.groupStartOpen = this.table.options.groupStartOpen; } if(this.config.group === true || this.config.group.groupHeader){ data.groupHeader = this.table.options.groupHeader; } } return data; } getPageConfig(){ var data = {}; if(this.config.page){ if(this.config.page === true || this.config.page.size){ data.paginationSize = this.table.modules.page.getPageSize(); } if(this.config.page === true || this.config.page.page){ data.paginationInitialPage = this.table.modules.page.getPage(); } } return data; } //parse columns for data to store parseColumns(columns){ var definitions = [], excludedKeys = ["headerContextMenu", "headerMenu", "contextMenu", "clickMenu"]; columns.forEach((column) => { var defStore = {}, colDef = column.getDefinition(), keys; if(column.isGroup){ defStore.title = colDef.title; defStore.columns = this.parseColumns(column.getColumns()); }else { defStore.field = column.getField(); if(this.config.columns === true || this.config.columns == undefined){ keys = Object.keys(colDef); keys.push("width"); keys.push("visible"); }else { keys = this.config.columns; } keys.forEach((key)=>{ switch(key){ case "width": defStore.width = column.getWidth(); break; case "visible": defStore.visible = column.visible; break; default: if(typeof colDef[key] !== "function" && excludedKeys.indexOf(key) === -1){ defStore[key] = colDef[key]; } } }); } definitions.push(defStore); }); return definitions; } } Persistence.moduleName = "persistence"; Persistence.moduleInitOrder = -10; //load defaults Persistence.readers = defaultReaders; Persistence.writers = defaultWriters; class Popup$1 extends Module{ constructor(table){ super(table); this.columnSubscribers = {}; this.registerTableOption("rowContextPopup", false); this.registerTableOption("rowClickPopup", false); this.registerTableOption("rowDblClickPopup", false); this.registerTableOption("groupContextPopup", false); this.registerTableOption("groupClickPopup", false); this.registerTableOption("groupDblClickPopup", false); this.registerColumnOption("headerContextPopup"); this.registerColumnOption("headerClickPopup"); this.registerColumnOption("headerDblClickPopup"); this.registerColumnOption("headerPopup"); this.registerColumnOption("headerPopupIcon"); this.registerColumnOption("contextPopup"); this.registerColumnOption("clickPopup"); this.registerColumnOption("dblClickPopup"); this.registerComponentFunction("cell", "popup", this._componentPopupCall.bind(this)); this.registerComponentFunction("column", "popup", this._componentPopupCall.bind(this)); this.registerComponentFunction("row", "popup", this._componentPopupCall.bind(this)); this.registerComponentFunction("group", "popup", this._componentPopupCall.bind(this)); } initialize(){ this.initializeRowWatchers(); this.initializeGroupWatchers(); this.subscribe("column-init", this.initializeColumn.bind(this)); } _componentPopupCall(component, contents, position){ this.loadPopupEvent(contents, null, component, position); } initializeRowWatchers(){ if(this.table.options.rowContextPopup){ this.subscribe("row-contextmenu", this.loadPopupEvent.bind(this, this.table.options.rowContextPopup)); this.table.on("rowTapHold", this.loadPopupEvent.bind(this, this.table.options.rowContextPopup)); } if(this.table.options.rowClickPopup){ this.subscribe("row-click", this.loadPopupEvent.bind(this, this.table.options.rowClickPopup)); } if(this.table.options.rowDblClickPopup){ this.subscribe("row-dblclick", this.loadPopupEvent.bind(this, this.table.options.rowDblClickPopup)); } } initializeGroupWatchers(){ if(this.table.options.groupContextPopup){ this.subscribe("group-contextmenu", this.loadPopupEvent.bind(this, this.table.options.groupContextPopup)); this.table.on("groupTapHold", this.loadPopupEvent.bind(this, this.table.options.groupContextPopup)); } if(this.table.options.groupClickPopup){ this.subscribe("group-click", this.loadPopupEvent.bind(this, this.table.options.groupClickPopup)); } if(this.table.options.groupDblClickPopup){ this.subscribe("group-dblclick", this.loadPopupEvent.bind(this, this.table.options.groupDblClickPopup)); } } initializeColumn(column){ var def = column.definition; //handle column events if(def.headerContextPopup && !this.columnSubscribers.headerContextPopup){ this.columnSubscribers.headerContextPopup = this.loadPopupTableColumnEvent.bind(this, "headerContextPopup"); this.subscribe("column-contextmenu", this.columnSubscribers.headerContextPopup); this.table.on("headerTapHold", this.loadPopupTableColumnEvent.bind(this, "headerContextPopup")); } if(def.headerClickPopup && !this.columnSubscribers.headerClickPopup){ this.columnSubscribers.headerClickPopup = this.loadPopupTableColumnEvent.bind(this, "headerClickPopup"); this.subscribe("column-click", this.columnSubscribers.headerClickPopup); }if(def.headerDblClickPopup && !this.columnSubscribers.headerDblClickPopup){ this.columnSubscribers.headerDblClickPopup = this.loadPopupTableColumnEvent.bind(this, "headerDblClickPopup"); this.subscribe("column-dblclick", this.columnSubscribers.headerDblClickPopup); } if(def.headerPopup){ this.initializeColumnHeaderPopup(column); } //handle cell events if(def.contextPopup && !this.columnSubscribers.contextPopup){ this.columnSubscribers.contextPopup = this.loadPopupTableCellEvent.bind(this, "contextPopup"); this.subscribe("cell-contextmenu", this.columnSubscribers.contextPopup); this.table.on("cellTapHold", this.loadPopupTableCellEvent.bind(this, "contextPopup")); } if(def.clickPopup && !this.columnSubscribers.clickPopup){ this.columnSubscribers.clickPopup = this.loadPopupTableCellEvent.bind(this, "clickPopup"); this.subscribe("cell-click", this.columnSubscribers.clickPopup); } if(def.dblClickPopup && !this.columnSubscribers.dblClickPopup){ this.columnSubscribers.dblClickPopup = this.loadPopupTableCellEvent.bind(this, "dblClickPopup"); this.subscribe("cell-click", this.columnSubscribers.dblClickPopup); } } initializeColumnHeaderPopup(column){ var icon = column.definition.headerPopupIcon, headerPopupEl; headerPopupEl = document.createElement("span"); headerPopupEl.classList.add("tabulator-header-popup-button"); if(icon){ if(typeof icon === "function"){ icon = icon(column.getComponent()); } if(icon instanceof HTMLElement){ headerPopupEl.appendChild(icon); }else { headerPopupEl.innerHTML = icon; } }else { headerPopupEl.innerHTML = "⋮"; } headerPopupEl.addEventListener("click", (e) => { e.stopPropagation(); e.preventDefault(); this.loadPopupEvent(column.definition.headerPopup, e, column); }); column.titleElement.insertBefore(headerPopupEl, column.titleElement.firstChild); } loadPopupTableCellEvent(option, e, cell){ if(cell._cell){ cell = cell._cell; } if(cell.column.definition[option]){ this.loadPopupEvent(cell.column.definition[option], e, cell); } } loadPopupTableColumnEvent(option, e, column){ if(column._column){ column = column._column; } if(column.definition[option]){ this.loadPopupEvent(column.definition[option], e, column); } } loadPopupEvent(contents, e, component, position){ var renderedCallback; function onRendered(callback){ renderedCallback = callback; } if(component._group){ component = component._group; }else if(component._row){ component = component._row; } contents = typeof contents == "function" ? contents.call(this.table, e, component.getComponent(), onRendered) : contents; this.loadPopup(e, component, contents, renderedCallback, position); } loadPopup(e, component, contents, renderedCallback, position){ var touch = !(e instanceof MouseEvent), contentsEl, popup; if(contents instanceof HTMLElement){ contentsEl = contents; }else { contentsEl = document.createElement("div"); contentsEl.innerHTML = contents; } contentsEl.classList.add("tabulator-popup"); contentsEl.addEventListener("click", (e) =>{ e.stopPropagation(); }); if(!touch){ e.preventDefault(); } popup = this.popup(contentsEl); if(typeof renderedCallback === "function"){ popup.renderCallback(renderedCallback); } if(e){ popup.show(e); }else { popup.show(component.getElement(), position || "center"); } popup.hideOnBlur(() => { this.dispatchExternal("popupClosed", component.getComponent()); }); this.dispatchExternal("popupOpened", component.getComponent()); } } Popup$1.moduleName = "popup"; class Print extends Module{ constructor(table){ super(table); this.element = false; this.manualBlock = false; this.beforeprintEventHandler = null; this.afterprintEventHandler = null; this.registerTableOption("printAsHtml", false); //enable print as html this.registerTableOption("printFormatter", false); //printing page formatter this.registerTableOption("printHeader", false); //page header contents this.registerTableOption("printFooter", false); //page footer contents this.registerTableOption("printStyled", true); //enable print as html styling this.registerTableOption("printRowRange", "visible"); //restrict print to visible rows only this.registerTableOption("printConfig", {}); //print config options this.registerColumnOption("print"); this.registerColumnOption("titlePrint"); } initialize(){ if(this.table.options.printAsHtml){ this.beforeprintEventHandler = this.replaceTable.bind(this); this.afterprintEventHandler = this.cleanup.bind(this); window.addEventListener("beforeprint", this.beforeprintEventHandler ); window.addEventListener("afterprint", this.afterprintEventHandler); this.subscribe("table-destroy", this.destroy.bind(this)); } this.registerTableFunction("print", this.printFullscreen.bind(this)); } destroy(){ if(this.table.options.printAsHtml){ window.removeEventListener( "beforeprint", this.beforeprintEventHandler ); window.removeEventListener( "afterprint", this.afterprintEventHandler ); } } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// replaceTable(){ if(!this.manualBlock){ this.element = document.createElement("div"); this.element.classList.add("tabulator-print-table"); this.element.appendChild(this.table.modules.export.generateTable(this.table.options.printConfig, this.table.options.printStyled, this.table.options.printRowRange, "print")); this.table.element.style.display = "none"; this.table.element.parentNode.insertBefore(this.element, this.table.element); } } cleanup(){ document.body.classList.remove("tabulator-print-fullscreen-hide"); if(this.element && this.element.parentNode){ this.element.parentNode.removeChild(this.element); this.table.element.style.display = ""; } } printFullscreen(visible, style, config){ var scrollX = window.scrollX, scrollY = window.scrollY, headerEl = document.createElement("div"), footerEl = document.createElement("div"), tableEl = this.table.modules.export.generateTable(typeof config != "undefined" ? config : this.table.options.printConfig, typeof style != "undefined" ? style : this.table.options.printStyled, visible || this.table.options.printRowRange, "print"), headerContent, footerContent; this.manualBlock = true; this.element = document.createElement("div"); this.element.classList.add("tabulator-print-fullscreen"); if(this.table.options.printHeader){ headerEl.classList.add("tabulator-print-header"); headerContent = typeof this.table.options.printHeader == "function" ? this.table.options.printHeader.call(this.table) : this.table.options.printHeader; if(typeof headerContent == "string"){ headerEl.innerHTML = headerContent; }else { headerEl.appendChild(headerContent); } this.element.appendChild(headerEl); } this.element.appendChild(tableEl); if(this.table.options.printFooter){ footerEl.classList.add("tabulator-print-footer"); footerContent = typeof this.table.options.printFooter == "function" ? this.table.options.printFooter.call(this.table) : this.table.options.printFooter; if(typeof footerContent == "string"){ footerEl.innerHTML = footerContent; }else { footerEl.appendChild(footerContent); } this.element.appendChild(footerEl); } document.body.classList.add("tabulator-print-fullscreen-hide"); document.body.appendChild(this.element); if(this.table.options.printFormatter){ this.table.options.printFormatter(this.element, tableEl); } window.print(); this.cleanup(); window.scrollTo(scrollX, scrollY); this.manualBlock = false; } } Print.moduleName = "print"; class ReactiveData extends Module{ constructor(table){ super(table); this.data = false; this.blocked = false; //block reactivity while performing update this.origFuncs = {}; // hold original data array functions to allow replacement after data is done with this.currentVersion = 0; this.registerTableOption("reactiveData", false); //enable data reactivity } initialize(){ if(this.table.options.reactiveData){ this.subscribe("cell-value-save-before", this.block.bind(this, "cellsave")); this.subscribe("cell-value-save-after", this.unblock.bind(this, "cellsave")); this.subscribe("row-data-save-before", this.block.bind(this, "rowsave")); this.subscribe("row-data-save-after", this.unblock.bind(this, "rowsave")); this.subscribe("row-data-init-after", this.watchRow.bind(this)); this.subscribe("data-processing", this.watchData.bind(this)); this.subscribe("table-destroy", this.unwatchData.bind(this)); } } watchData(data){ var self = this, version; this.currentVersion ++; version = this.currentVersion; this.unwatchData(); this.data = data; //override array push function this.origFuncs.push = data.push; Object.defineProperty(this.data, "push", { enumerable: false, configurable: true, value: function(){ var args = Array.from(arguments), result; if(!self.blocked && version === self.currentVersion){ self.block("data-push"); args.forEach((arg) => { self.table.rowManager.addRowActual(arg, false); }); result = self.origFuncs.push.apply(data, arguments); self.unblock("data-push"); } return result; } }); //override array unshift function this.origFuncs.unshift = data.unshift; Object.defineProperty(this.data, "unshift", { enumerable: false, configurable: true, value: function(){ var args = Array.from(arguments), result; if(!self.blocked && version === self.currentVersion){ self.block("data-unshift"); args.forEach((arg) => { self.table.rowManager.addRowActual(arg, true); }); result = self.origFuncs.unshift.apply(data, arguments); self.unblock("data-unshift"); } return result; } }); //override array shift function this.origFuncs.shift = data.shift; Object.defineProperty(this.data, "shift", { enumerable: false, configurable: true, value: function(){ var row, result; if(!self.blocked && version === self.currentVersion){ self.block("data-shift"); if(self.data.length){ row = self.table.rowManager.getRowFromDataObject(self.data[0]); if(row){ row.deleteActual(); } } result = self.origFuncs.shift.call(data); self.unblock("data-shift"); } return result; } }); //override array pop function this.origFuncs.pop = data.pop; Object.defineProperty(this.data, "pop", { enumerable: false, configurable: true, value: function(){ var row, result; if(!self.blocked && version === self.currentVersion){ self.block("data-pop"); if(self.data.length){ row = self.table.rowManager.getRowFromDataObject(self.data[self.data.length - 1]); if(row){ row.deleteActual(); } } result = self.origFuncs.pop.call(data); self.unblock("data-pop"); } return result; } }); //override array splice function this.origFuncs.splice = data.splice; Object.defineProperty(this.data, "splice", { enumerable: false, configurable: true, value: function(){ var args = Array.from(arguments), start = args[0] < 0 ? data.length + args[0] : args[0], end = args[1], newRows = args[2] ? args.slice(2) : false, startRow, result; if(!self.blocked && version === self.currentVersion){ self.block("data-splice"); //add new rows if(newRows){ startRow = data[start] ? self.table.rowManager.getRowFromDataObject(data[start]) : false; if(startRow){ newRows.forEach((rowData) => { self.table.rowManager.addRowActual(rowData, true, startRow, true); }); }else { newRows = newRows.slice().reverse(); newRows.forEach((rowData) => { self.table.rowManager.addRowActual(rowData, true, false, true); }); } } //delete removed rows if(end !== 0){ var oldRows = data.slice(start, typeof args[1] === "undefined" ? args[1] : start + end); oldRows.forEach((rowData, i) => { var row = self.table.rowManager.getRowFromDataObject(rowData); if(row){ row.deleteActual(i !== oldRows.length - 1); } }); } if(newRows || end !== 0){ self.table.rowManager.reRenderInPosition(); } result = self.origFuncs.splice.apply(data, arguments); self.unblock("data-splice"); } return result ; } }); } unwatchData(){ if(this.data !== false){ for(var key in this.origFuncs){ Object.defineProperty(this.data, key, { enumerable: true, configurable:true, writable:true, value: this.origFuncs.key, }); } } } watchRow(row){ var data = row.getData(); for(var key in data){ this.watchKey(row, data, key); } if(this.table.options.dataTree){ this.watchTreeChildren(row); } } watchTreeChildren (row){ var self = this, childField = row.getData()[this.table.options.dataTreeChildField], origFuncs = {}; if(childField){ origFuncs.push = childField.push; Object.defineProperty(childField, "push", { enumerable: false, configurable: true, value: () => { if(!self.blocked){ self.block("tree-push"); var result = origFuncs.push.apply(childField, arguments); this.rebuildTree(row); self.unblock("tree-push"); } return result; } }); origFuncs.unshift = childField.unshift; Object.defineProperty(childField, "unshift", { enumerable: false, configurable: true, value: () => { if(!self.blocked){ self.block("tree-unshift"); var result = origFuncs.unshift.apply(childField, arguments); this.rebuildTree(row); self.unblock("tree-unshift"); } return result; } }); origFuncs.shift = childField.shift; Object.defineProperty(childField, "shift", { enumerable: false, configurable: true, value: () => { if(!self.blocked){ self.block("tree-shift"); var result = origFuncs.shift.call(childField); this.rebuildTree(row); self.unblock("tree-shift"); } return result; } }); origFuncs.pop = childField.pop; Object.defineProperty(childField, "pop", { enumerable: false, configurable: true, value: () => { if(!self.blocked){ self.block("tree-pop"); var result = origFuncs.pop.call(childField); this.rebuildTree(row); self.unblock("tree-pop"); } return result; } }); origFuncs.splice = childField.splice; Object.defineProperty(childField, "splice", { enumerable: false, configurable: true, value: () => { if(!self.blocked){ self.block("tree-splice"); var result = origFuncs.splice.apply(childField, arguments); this.rebuildTree(row); self.unblock("tree-splice"); } return result; } }); } } rebuildTree(row){ this.table.modules.dataTree.initializeRow(row); this.table.modules.dataTree.layoutRow(row); this.table.rowManager.refreshActiveData("tree", false, true); } watchKey(row, data, key){ var self = this, props = Object.getOwnPropertyDescriptor(data, key), value = data[key], version = this.currentVersion; Object.defineProperty(data, key, { set: (newValue) => { value = newValue; if(!self.blocked && version === self.currentVersion){ self.block("key"); var update = {}; update[key] = newValue; row.updateData(update); self.unblock("key"); } if(props.set){ props.set(newValue); } }, get:() => { if(props.get){ props.get(); } return value; } }); } unwatchRow(row){ var data = row.getData(); for(var key in data){ Object.defineProperty(data, key, { value:data[key], }); } } block(key){ if(!this.blocked){ this.blocked = key; } } unblock(key){ if(this.blocked === key){ this.blocked = false; } } } ReactiveData.moduleName = "reactiveData"; class ResizeColumns extends Module{ constructor(table){ super(table); this.startColumn = false; this.startX = false; this.startWidth = false; this.latestX = false; this.handle = null; this.initialNextColumn = null; this.nextColumn = null; this.initialized = false; this.registerColumnOption("resizable", true); this.registerTableOption("resizableColumnFit", false); } initialize(){ this.subscribe("column-rendered", this.layoutColumnHeader.bind(this)); } initializeEventWatchers(){ if(!this.initialized){ this.subscribe("cell-rendered", this.layoutCellHandles.bind(this)); this.subscribe("cell-delete", this.deInitializeComponent.bind(this)); this.subscribe("cell-height", this.resizeHandle.bind(this)); this.subscribe("column-moved", this.columnLayoutUpdated.bind(this)); this.subscribe("column-hide", this.deInitializeColumn.bind(this)); this.subscribe("column-show", this.columnLayoutUpdated.bind(this)); this.subscribe("column-width", this.columnWidthUpdated.bind(this)); this.subscribe("column-delete", this.deInitializeComponent.bind(this)); this.subscribe("column-height", this.resizeHandle.bind(this)); this.initialized = true; } } layoutCellHandles(cell){ if(cell.row.type === "row"){ this.deInitializeComponent(cell); this.initializeColumn("cell", cell, cell.column, cell.element); } } layoutColumnHeader(column){ if(column.definition.resizable){ this.initializeEventWatchers(); this.deInitializeComponent(column); this.initializeColumn("header", column, column, column.element); } } columnLayoutUpdated(column){ var prev = column.prevColumn(); this.reinitializeColumn(column); if(prev){ this.reinitializeColumn(prev); } } columnWidthUpdated(column){ if(column.modules.frozen){ if(this.table.modules.frozenColumns.leftColumns.includes(column)){ this.table.modules.frozenColumns.leftColumns.forEach((col) => { this.reinitializeColumn(col); }); }else if(this.table.modules.frozenColumns.rightColumns.includes(column)){ this.table.modules.frozenColumns.rightColumns.forEach((col) => { this.reinitializeColumn(col); }); } } } frozenColumnOffset(column){ var offset = false; if(column.modules.frozen){ offset = column.modules.frozen.marginValue; if(column.modules.frozen.position === "left"){ offset += column.getWidth() - 3; }else { if(offset){ offset -= 3; } } } return offset !== false ? offset + "px" : false; } reinitializeColumn(column){ var frozenOffset = this.frozenColumnOffset(column); column.cells.forEach((cell) => { if(cell.modules.resize && cell.modules.resize.handleEl){ if(frozenOffset){ cell.modules.resize.handleEl.style[column.modules.frozen.position] = frozenOffset; } cell.element.after(cell.modules.resize.handleEl); } }); if(column.modules.resize && column.modules.resize.handleEl){ if(frozenOffset){ column.modules.resize.handleEl.style[column.modules.frozen.position] = frozenOffset; } column.element.after(column.modules.resize.handleEl); } } initializeColumn(type, component, column, element){ var self = this, variableHeight = false, mode = column.definition.resizable, config = {}, nearestColumn = column.getLastColumn(); //set column resize mode if(type === "header"){ variableHeight = column.definition.formatter == "textarea" || column.definition.variableHeight; config = {variableHeight:variableHeight}; } if((mode === true || mode == type) && this._checkResizability(nearestColumn)){ var handle = document.createElement('span'); handle.className = "tabulator-col-resize-handle"; handle.addEventListener("click", function(e){ e.stopPropagation(); }); var handleDown = function(e){ self.startColumn = column; self.initialNextColumn = self.nextColumn = nearestColumn.nextColumn(); self._mouseDown(e, nearestColumn, handle); }; handle.addEventListener("mousedown", handleDown); handle.addEventListener("touchstart", handleDown, {passive: true}); //resize column on double click handle.addEventListener("dblclick", (e) => { var oldWidth = nearestColumn.getWidth(); e.stopPropagation(); nearestColumn.reinitializeWidth(true); if(oldWidth !== nearestColumn.getWidth()){ self.dispatch("column-resized", nearestColumn); self.table.externalEvents.dispatch("columnResized", nearestColumn.getComponent()); } }); if(column.modules.frozen){ handle.style.position = "sticky"; handle.style[column.modules.frozen.position] = this.frozenColumnOffset(column); } config.handleEl = handle; if(element.parentNode && column.visible){ element.after(handle); } } component.modules.resize = config; } deInitializeColumn(column){ this.deInitializeComponent(column); column.cells.forEach((cell) => { this.deInitializeComponent(cell); }); } deInitializeComponent(component){ var handleEl; if(component.modules.resize){ handleEl = component.modules.resize.handleEl; if(handleEl && handleEl.parentElement){ handleEl.parentElement.removeChild(handleEl); } } } resizeHandle(component, height){ if(component.modules.resize && component.modules.resize.handleEl){ component.modules.resize.handleEl.style.height = height; } } _checkResizability(column){ return column.definition.resizable; } _mouseDown(e, column, handle){ var self = this; self.table.element.classList.add("tabulator-block-select"); function mouseMove(e){ var x = typeof e.screenX === "undefined" ? e.touches[0].screenX : e.screenX, startDiff = x - self.startX, moveDiff = x - self.latestX, blockedBefore, blockedAfter; self.latestX = x; if(self.table.rtl){ startDiff = -startDiff; moveDiff = -moveDiff; } blockedBefore = column.width == column.minWidth || column.width == column.maxWidth; column.setWidth(self.startWidth + startDiff); blockedAfter = column.width == column.minWidth || column.width == column.maxWidth; if(moveDiff < 0){ self.nextColumn = self.initialNextColumn; } if(self.table.options.resizableColumnFit && self.nextColumn && !(blockedBefore && blockedAfter)){ let colWidth = self.nextColumn.getWidth(); if(moveDiff > 0){ if(colWidth <= self.nextColumn.minWidth){ self.nextColumn = self.nextColumn.nextColumn(); } } if(self.nextColumn){ self.nextColumn.setWidth(self.nextColumn.getWidth() - moveDiff); } } self.table.columnManager.rerenderColumns(true); if(!self.table.browserSlow && column.modules.resize && column.modules.resize.variableHeight){ column.checkCellHeights(); } } function mouseUp(e){ //block editor from taking action while resizing is taking place if(self.startColumn.modules.edit){ self.startColumn.modules.edit.blocked = false; } if(self.table.browserSlow && column.modules.resize && column.modules.resize.variableHeight){ column.checkCellHeights(); } document.body.removeEventListener("mouseup", mouseUp); document.body.removeEventListener("mousemove", mouseMove); handle.removeEventListener("touchmove", mouseMove); handle.removeEventListener("touchend", mouseUp); self.table.element.classList.remove("tabulator-block-select"); if(self.startWidth !== column.getWidth()){ self.table.columnManager.verticalAlignHeaders(); self.dispatch("column-resized", column); self.table.externalEvents.dispatch("columnResized", column.getComponent()); } } e.stopPropagation(); //prevent resize from interfering with movable columns //block editor from taking action while resizing is taking place if(self.startColumn.modules.edit){ self.startColumn.modules.edit.blocked = true; } self.startX = typeof e.screenX === "undefined" ? e.touches[0].screenX : e.screenX; self.latestX = self.startX; self.startWidth = column.getWidth(); document.body.addEventListener("mousemove", mouseMove); document.body.addEventListener("mouseup", mouseUp); handle.addEventListener("touchmove", mouseMove, {passive: true}); handle.addEventListener("touchend", mouseUp); } } ResizeColumns.moduleName = "resizeColumns"; class ResizeRows extends Module{ constructor(table){ super(table); this.startColumn = false; this.startY = false; this.startHeight = false; this.handle = null; this.prevHandle = null; this.registerTableOption("resizableRows", false); //resizable rows } initialize(){ if(this.table.options.resizableRows){ this.subscribe("row-layout-after", this.initializeRow.bind(this)); } } initializeRow(row){ var self = this, rowEl = row.getElement(); var handle = document.createElement('div'); handle.className = "tabulator-row-resize-handle"; var prevHandle = document.createElement('div'); prevHandle.className = "tabulator-row-resize-handle prev"; handle.addEventListener("click", function(e){ e.stopPropagation(); }); var handleDown = function(e){ self.startRow = row; self._mouseDown(e, row, handle); }; handle.addEventListener("mousedown", handleDown); handle.addEventListener("touchstart", handleDown, {passive: true}); prevHandle.addEventListener("click", function(e){ e.stopPropagation(); }); var prevHandleDown = function(e){ var prevRow = self.table.rowManager.prevDisplayRow(row); if(prevRow){ self.startRow = prevRow; self._mouseDown(e, prevRow, prevHandle); } }; prevHandle.addEventListener("mousedown",prevHandleDown); prevHandle.addEventListener("touchstart",prevHandleDown, {passive: true}); rowEl.appendChild(handle); rowEl.appendChild(prevHandle); } _mouseDown(e, row, handle){ var self = this; self.table.element.classList.add("tabulator-block-select"); function mouseMove(e){ row.setHeight(self.startHeight + ((typeof e.screenY === "undefined" ? e.touches[0].screenY : e.screenY) - self.startY)); } function mouseUp(e){ // //block editor from taking action while resizing is taking place // if(self.startColumn.modules.edit){ // self.startColumn.modules.edit.blocked = false; // } document.body.removeEventListener("mouseup", mouseMove); document.body.removeEventListener("mousemove", mouseMove); handle.removeEventListener("touchmove", mouseMove); handle.removeEventListener("touchend", mouseUp); self.table.element.classList.remove("tabulator-block-select"); self.dispatchExternal("rowResized", row.getComponent()); } e.stopPropagation(); //prevent resize from interfering with movable columns //block editor from taking action while resizing is taking place // if(self.startColumn.modules.edit){ // self.startColumn.modules.edit.blocked = true; // } self.startY = typeof e.screenY === "undefined" ? e.touches[0].screenY : e.screenY; self.startHeight = row.getHeight(); document.body.addEventListener("mousemove", mouseMove); document.body.addEventListener("mouseup", mouseUp); handle.addEventListener("touchmove", mouseMove, {passive: true}); handle.addEventListener("touchend", mouseUp); } } ResizeRows.moduleName = "resizeRows"; class ResizeTable extends Module{ constructor(table){ super(table); this.binding = false; this.visibilityObserver = false; this.resizeObserver = false; this.containerObserver = false; this.tableHeight = 0; this.tableWidth = 0; this.containerHeight = 0; this.containerWidth = 0; this.autoResize = false; this.visible = false; this.initialized = false; this.initialRedraw = false; this.registerTableOption("autoResize", true); //auto resize table } initialize(){ if(this.table.options.autoResize){ var table = this.table, tableStyle; this.tableHeight = table.element.clientHeight; this.tableWidth = table.element.clientWidth; if(table.element.parentNode){ this.containerHeight = table.element.parentNode.clientHeight; this.containerWidth = table.element.parentNode.clientWidth; } if(typeof IntersectionObserver !== "undefined" && typeof ResizeObserver !== "undefined" && table.rowManager.getRenderMode() === "virtual"){ this.initializeVisibilityObserver(); this.autoResize = true; this.resizeObserver = new ResizeObserver((entry) => { if(!table.browserMobile || (table.browserMobile &&!table.modules.edit.currentCell)){ var nodeHeight = Math.floor(entry[0].contentRect.height); var nodeWidth = Math.floor(entry[0].contentRect.width); if(this.tableHeight != nodeHeight || this.tableWidth != nodeWidth){ this.tableHeight = nodeHeight; this.tableWidth = nodeWidth; if(table.element.parentNode){ this.containerHeight = table.element.parentNode.clientHeight; this.containerWidth = table.element.parentNode.clientWidth; } this.redrawTable(); } } }); this.resizeObserver.observe(table.element); tableStyle = window.getComputedStyle(table.element); if(this.table.element.parentNode && !this.table.rowManager.fixedHeight && (tableStyle.getPropertyValue("max-height") || tableStyle.getPropertyValue("min-height"))){ this.containerObserver = new ResizeObserver((entry) => { if(!table.browserMobile || (table.browserMobile &&!table.modules.edit.currentCell)){ var nodeHeight = Math.floor(entry[0].contentRect.height); var nodeWidth = Math.floor(entry[0].contentRect.width); if(this.containerHeight != nodeHeight || this.containerWidth != nodeWidth){ this.containerHeight = nodeHeight; this.containerWidth = nodeWidth; this.tableHeight = table.element.clientHeight; this.tableWidth = table.element.clientWidth; } this.redrawTable(); } }); this.containerObserver.observe(this.table.element.parentNode); } this.subscribe("table-resize", this.tableResized.bind(this)); }else { this.binding = function(){ if(!table.browserMobile || (table.browserMobile && !table.modules.edit.currentCell)){ table.columnManager.rerenderColumns(true); table.redraw(); } }; window.addEventListener("resize", this.binding); } this.subscribe("table-destroy", this.clearBindings.bind(this)); } } initializeVisibilityObserver(){ this.visibilityObserver = new IntersectionObserver((entries) => { this.visible = entries[0].isIntersecting; if(!this.initialized){ this.initialized = true; this.initialRedraw = !this.visible; }else { if(this.visible){ this.redrawTable(this.initialRedraw); this.initialRedraw = false; } } }); this.visibilityObserver.observe(this.table.element); } redrawTable(force){ if(this.initialized && this.visible){ this.table.columnManager.rerenderColumns(true); this.table.redraw(force); } } tableResized(){ this.table.rowManager.redraw(); } clearBindings(){ if(this.binding){ window.removeEventListener("resize", this.binding); } if(this.resizeObserver){ this.resizeObserver.unobserve(this.table.element); } if(this.visibilityObserver){ this.visibilityObserver.unobserve(this.table.element); } if(this.containerObserver){ this.containerObserver.unobserve(this.table.element.parentNode); } } } ResizeTable.moduleName = "resizeTable"; class ResponsiveLayout extends Module{ constructor(table){ super(table); this.columns = []; this.hiddenColumns = []; this.mode = ""; this.index = 0; this.collapseFormatter = []; this.collapseStartOpen = true; this.collapseHandleColumn = false; this.registerTableOption("responsiveLayout", false); //responsive layout flags this.registerTableOption("responsiveLayoutCollapseStartOpen", true); //start showing collapsed data this.registerTableOption("responsiveLayoutCollapseUseFormatters", true); //responsive layout collapse formatter this.registerTableOption("responsiveLayoutCollapseFormatter", false); //responsive layout collapse formatter this.registerColumnOption("responsive"); } //generate responsive columns list initialize(){ if(this.table.options.responsiveLayout){ this.subscribe("column-layout", this.initializeColumn.bind(this)); this.subscribe("column-show", this.updateColumnVisibility.bind(this)); this.subscribe("column-hide", this.updateColumnVisibility.bind(this)); this.subscribe("columns-loaded", this.initializeResponsivity.bind(this)); this.subscribe("column-moved", this.initializeResponsivity.bind(this)); this.subscribe("column-add", this.initializeResponsivity.bind(this)); this.subscribe("column-delete", this.initializeResponsivity.bind(this)); this.subscribe("table-redrawing", this.tableRedraw.bind(this)); if(this.table.options.responsiveLayout === "collapse"){ this.subscribe("row-data-changed", this.generateCollapsedRowContent.bind(this)); this.subscribe("row-init", this.initializeRow.bind(this)); this.subscribe("row-layout", this.layoutRow.bind(this)); } } } tableRedraw(force){ if(["fitColumns", "fitDataStretch"].indexOf(this.layoutMode()) === -1){ if(!force){ this.update(); } } } initializeResponsivity(){ var columns = []; this.mode = this.table.options.responsiveLayout; this.collapseFormatter = this.table.options.responsiveLayoutCollapseFormatter || this.formatCollapsedData; this.collapseStartOpen = this.table.options.responsiveLayoutCollapseStartOpen; this.hiddenColumns = []; //determine level of responsivity for each column this.table.columnManager.columnsByIndex.forEach((column, i) => { if(column.modules.responsive){ if(column.modules.responsive.order && column.modules.responsive.visible){ column.modules.responsive.index = i; columns.push(column); if(!column.visible && this.mode === "collapse"){ this.hiddenColumns.push(column); } } } }); //sort list by responsivity columns = columns.reverse(); columns = columns.sort((a, b) => { var diff = b.modules.responsive.order - a.modules.responsive.order; return diff || (b.modules.responsive.index - a.modules.responsive.index); }); this.columns = columns; if(this.mode === "collapse"){ this.generateCollapsedContent(); } //assign collapse column for (let col of this.table.columnManager.columnsByIndex){ if(col.definition.formatter == "responsiveCollapse"){ this.collapseHandleColumn = col; break; } } if(this.collapseHandleColumn){ if(this.hiddenColumns.length){ this.collapseHandleColumn.show(); }else { this.collapseHandleColumn.hide(); } } } //define layout information initializeColumn(column){ var def = column.getDefinition(); column.modules.responsive = {order: typeof def.responsive === "undefined" ? 1 : def.responsive, visible:def.visible === false ? false : true}; } initializeRow(row){ var el; if(row.type !== "calc"){ el = document.createElement("div"); el.classList.add("tabulator-responsive-collapse"); row.modules.responsiveLayout = { element:el, open:this.collapseStartOpen, }; if(!this.collapseStartOpen){ el.style.display = 'none'; } } } layoutRow(row){ var rowEl = row.getElement(); if(row.modules.responsiveLayout){ rowEl.appendChild(row.modules.responsiveLayout.element); this.generateCollapsedRowContent(row); } } //update column visibility updateColumnVisibility(column, responsiveToggle){ if(!responsiveToggle && column.modules.responsive){ column.modules.responsive.visible = column.visible; this.initializeResponsivity(); } } hideColumn(column){ var colCount = this.hiddenColumns.length; column.hide(false, true); if(this.mode === "collapse"){ this.hiddenColumns.unshift(column); this.generateCollapsedContent(); if(this.collapseHandleColumn && !colCount){ this.collapseHandleColumn.show(); } } } showColumn(column){ var index; column.show(false, true); //set column width to prevent calculation loops on uninitialized columns column.setWidth(column.getWidth()); if(this.mode === "collapse"){ index = this.hiddenColumns.indexOf(column); if(index > -1){ this.hiddenColumns.splice(index, 1); } this.generateCollapsedContent(); if(this.collapseHandleColumn && !this.hiddenColumns.length){ this.collapseHandleColumn.hide(); } } } //redraw columns to fit space update(){ var working = true; while(working){ let width = this.table.modules.layout.getMode() == "fitColumns" ? this.table.columnManager.getFlexBaseWidth() : this.table.columnManager.getWidth(); let diff = (this.table.options.headerVisible ? this.table.columnManager.element.clientWidth : this.table.element.clientWidth) - width; if(diff < 0){ //table is too wide let column = this.columns[this.index]; if(column){ this.hideColumn(column); this.index ++; }else { working = false; } }else { //table has spare space let column = this.columns[this.index -1]; if(column){ if(diff > 0){ if(diff >= column.getWidth()){ this.showColumn(column); this.index --; }else { working = false; } }else { working = false; } }else { working = false; } } if(!this.table.rowManager.activeRowsCount){ this.table.rowManager.renderEmptyScroll(); } } } generateCollapsedContent(){ var rows = this.table.rowManager.getDisplayRows(); rows.forEach((row) => { this.generateCollapsedRowContent(row); }); } generateCollapsedRowContent(row){ var el, contents; if(row.modules.responsiveLayout){ el = row.modules.responsiveLayout.element; while(el.firstChild) el.removeChild(el.firstChild); contents = this.collapseFormatter(this.generateCollapsedRowData(row)); if(contents){ el.appendChild(contents); } } } generateCollapsedRowData(row){ var data = row.getData(), output = [], mockCellComponent; this.hiddenColumns.forEach((column) => { var value = column.getFieldValue(data); if(column.definition.title && column.field){ if(column.modules.format && this.table.options.responsiveLayoutCollapseUseFormatters){ mockCellComponent = { value:false, data:{}, getValue:function(){ return value; }, getData:function(){ return data; }, getElement:function(){ return document.createElement("div"); }, getRow:function(){ return row.getComponent(); }, getColumn:function(){ return column.getComponent(); }, getTable:() => { return this.table; }, }; function onRendered(callback){ callback(); } output.push({ field: column.field, title: column.definition.title, value: column.modules.format.formatter.call(this.table.modules.format, mockCellComponent, column.modules.format.params, onRendered) }); }else { output.push({ field: column.field, title: column.definition.title, value: value }); } } }); return output; } formatCollapsedData(data){ var list = document.createElement("table"); data.forEach(function(item){ var row = document.createElement("tr"); var titleData = document.createElement("td"); var valueData = document.createElement("td"); var node_content; var titleHighlight = document.createElement("strong"); titleData.appendChild(titleHighlight); this.langBind("columns|" + item.field, function(text){ titleHighlight.innerHTML = text || item.title; }); if(item.value instanceof Node){ node_content = document.createElement("div"); node_content.appendChild(item.value); valueData.appendChild(node_content); }else { valueData.innerHTML = item.value; } row.appendChild(titleData); row.appendChild(valueData); list.appendChild(row); }, this); return Object.keys(data).length ? list : ""; } } ResponsiveLayout.moduleName = "responsiveLayout"; class SelectRow extends Module{ constructor(table){ super(table); this.selecting = false; //flag selecting in progress this.lastClickedRow = false; //last clicked row this.selectPrev = []; //hold previously selected element for drag drop selection this.selectedRows = []; //hold selected rows this.headerCheckboxElement = null; // hold header select element this.registerTableOption("selectable", "highlight"); //highlight rows on hover this.registerTableOption("selectableRangeMode", "drag"); //highlight rows on hover this.registerTableOption("selectableRollingSelection", true); //roll selection once maximum number of selectable rows is reached this.registerTableOption("selectablePersistence", true); // maintain selection when table view is updated this.registerTableOption("selectableCheck", function(data, row){return true;}); //check whether row is selectable this.registerTableFunction("selectRow", this.selectRows.bind(this)); this.registerTableFunction("deselectRow", this.deselectRows.bind(this)); this.registerTableFunction("toggleSelectRow", this.toggleRow.bind(this)); this.registerTableFunction("getSelectedRows", this.getSelectedRows.bind(this)); this.registerTableFunction("getSelectedData", this.getSelectedData.bind(this)); //register component functions this.registerComponentFunction("row", "select", this.selectRows.bind(this)); this.registerComponentFunction("row", "deselect", this.deselectRows.bind(this)); this.registerComponentFunction("row", "toggleSelect", this.toggleRow.bind(this)); this.registerComponentFunction("row", "isSelected", this.isRowSelected.bind(this)); } initialize(){ if(this.table.options.selectable !== false){ this.subscribe("row-init", this.initializeRow.bind(this)); this.subscribe("row-deleting", this.rowDeleted.bind(this)); this.subscribe("rows-wipe", this.clearSelectionData.bind(this)); this.subscribe("rows-retrieve", this.rowRetrieve.bind(this)); if(this.table.options.selectable && !this.table.options.selectablePersistence){ this.subscribe("data-refreshing", this.deselectRows.bind(this)); } } } rowRetrieve(type, prevValue){ return type === "selected" ? this.selectedRows : prevValue; } rowDeleted(row){ this._deselectRow(row, true); } clearSelectionData(silent){ var prevSelected = this.selectedRows.length; this.selecting = false; this.lastClickedRow = false; this.selectPrev = []; this.selectedRows = []; if(prevSelected && silent !== true){ this._rowSelectionChanged(); } } initializeRow(row){ var self = this, element = row.getElement(); // trigger end of row selection var endSelect = function(){ setTimeout(function(){ self.selecting = false; }, 50); document.body.removeEventListener("mouseup", endSelect); }; row.modules.select = {selected:false}; //set row selection class if(self.checkRowSelectability(row)){ element.classList.add("tabulator-selectable"); element.classList.remove("tabulator-unselectable"); if(self.table.options.selectable && self.table.options.selectable != "highlight"){ if(self.table.options.selectableRangeMode === "click"){ element.addEventListener("click", this.handleComplexRowClick.bind(this, row)); }else { element.addEventListener("click", function(e){ if(!self.table.modExists("edit") || !self.table.modules.edit.getCurrentCell()){ self.table._clearSelection(); } if(!self.selecting){ self.toggleRow(row); } }); element.addEventListener("mousedown", function(e){ if(e.shiftKey){ self.table._clearSelection(); self.selecting = true; self.selectPrev = []; document.body.addEventListener("mouseup", endSelect); document.body.addEventListener("keyup", endSelect); self.toggleRow(row); return false; } }); element.addEventListener("mouseenter", function(e){ if(self.selecting){ self.table._clearSelection(); self.toggleRow(row); if(self.selectPrev[1] == row){ self.toggleRow(self.selectPrev[0]); } } }); element.addEventListener("mouseout", function(e){ if(self.selecting){ self.table._clearSelection(); self.selectPrev.unshift(row); } }); } } }else { element.classList.add("tabulator-unselectable"); element.classList.remove("tabulator-selectable"); } } handleComplexRowClick(row, e){ if(e.shiftKey){ this.table._clearSelection(); this.lastClickedRow = this.lastClickedRow || row; var lastClickedRowIdx = this.table.rowManager.getDisplayRowIndex(this.lastClickedRow); var rowIdx = this.table.rowManager.getDisplayRowIndex(row); var fromRowIdx = lastClickedRowIdx <= rowIdx ? lastClickedRowIdx : rowIdx; var toRowIdx = lastClickedRowIdx >= rowIdx ? lastClickedRowIdx : rowIdx; var rows = this.table.rowManager.getDisplayRows().slice(0); var toggledRows = rows.splice(fromRowIdx, toRowIdx - fromRowIdx + 1); if(e.ctrlKey || e.metaKey){ toggledRows.forEach((toggledRow)=>{ if(toggledRow !== this.lastClickedRow){ if(this.table.options.selectable !== true && !this.isRowSelected(row)){ if(this.selectedRows.length < this.table.options.selectable){ this.toggleRow(toggledRow); } }else { this.toggleRow(toggledRow); } } }); this.lastClickedRow = row; }else { this.deselectRows(undefined, true); if(this.table.options.selectable !== true){ if(toggledRows.length > this.table.options.selectable){ toggledRows = toggledRows.slice(0, this.table.options.selectable); } } this.selectRows(toggledRows); } this.table._clearSelection(); } else if(e.ctrlKey || e.metaKey){ this.toggleRow(row); this.lastClickedRow = row; }else { this.deselectRows(undefined, true); this.selectRows(row); this.lastClickedRow = row; } } checkRowSelectability(row){ if(row.type === "row"){ return this.table.options.selectableCheck.call(this.table, row.getComponent()); } return false; } //toggle row selection toggleRow(row){ if(this.checkRowSelectability(row)){ if(row.modules.select && row.modules.select.selected){ this._deselectRow(row); }else { this._selectRow(row); } } } //select a number of rows selectRows(rows){ var rowMatch; switch(typeof rows){ case "undefined": this.table.rowManager.rows.forEach((row) => { this._selectRow(row, true, true); }); this._rowSelectionChanged(); break; case "string": rowMatch = this.table.rowManager.findRow(rows); if(rowMatch){ this._selectRow(rowMatch, true, true); this._rowSelectionChanged(); }else { rowMatch = this.table.rowManager.getRows(rows); rowMatch.forEach((row) => { this._selectRow(row, true, true); }); if(rowMatch.length){ this._rowSelectionChanged(); } } break; default: if(Array.isArray(rows)){ rows.forEach((row) => { this._selectRow(row, true, true); }); this._rowSelectionChanged(); }else { this._selectRow(rows, false, true); } break; } } //select an individual row _selectRow(rowInfo, silent, force){ //handle max row count if(!isNaN(this.table.options.selectable) && this.table.options.selectable !== true && !force){ if(this.selectedRows.length >= this.table.options.selectable){ if(this.table.options.selectableRollingSelection){ this._deselectRow(this.selectedRows[0]); }else { return false; } } } var row = this.table.rowManager.findRow(rowInfo); if(row){ if(this.selectedRows.indexOf(row) == -1){ row.getElement().classList.add("tabulator-selected"); if(!row.modules.select){ row.modules.select = {}; } row.modules.select.selected = true; if(row.modules.select.checkboxEl){ row.modules.select.checkboxEl.checked = true; } this.selectedRows.push(row); if(this.table.options.dataTreeSelectPropagate){ this.childRowSelection(row, true); } this.dispatchExternal("rowSelected", row.getComponent()); this._rowSelectionChanged(silent); } }else { if(!silent){ console.warn("Selection Error - No such row found, ignoring selection:" + rowInfo); } } } isRowSelected(row){ return this.selectedRows.indexOf(row) !== -1; } //deselect a number of rows deselectRows(rows, silent){ var self = this, rowCount; if(typeof rows == "undefined"){ rowCount = self.selectedRows.length; for(let i = 0; i < rowCount; i++){ self._deselectRow(self.selectedRows[0], true); } if(rowCount){ self._rowSelectionChanged(silent); } }else { if(Array.isArray(rows)){ rows.forEach(function(row){ self._deselectRow(row, true); }); self._rowSelectionChanged(silent); }else { self._deselectRow(rows, silent); } } } //deselect an individual row _deselectRow(rowInfo, silent){ var self = this, row = self.table.rowManager.findRow(rowInfo), index; if(row){ index = self.selectedRows.findIndex(function(selectedRow){ return selectedRow == row; }); if(index > -1){ row.getElement().classList.remove("tabulator-selected"); if(!row.modules.select){ row.modules.select = {}; } row.modules.select.selected = false; if(row.modules.select.checkboxEl){ row.modules.select.checkboxEl.checked = false; } self.selectedRows.splice(index, 1); if(this.table.options.dataTreeSelectPropagate){ this.childRowSelection(row, false); } this.dispatchExternal("rowDeselected", row.getComponent()); self._rowSelectionChanged(silent); } }else { if(!silent){ console.warn("Deselection Error - No such row found, ignoring selection:" + rowInfo); } } } getSelectedData(){ var data = []; this.selectedRows.forEach(function(row){ data.push(row.getData()); }); return data; } getSelectedRows(){ var rows = []; this.selectedRows.forEach(function(row){ rows.push(row.getComponent()); }); return rows; } _rowSelectionChanged(silent){ if(this.headerCheckboxElement){ if(this.selectedRows.length === 0){ this.headerCheckboxElement.checked = false; this.headerCheckboxElement.indeterminate = false; } else if(this.table.rowManager.rows.length === this.selectedRows.length){ this.headerCheckboxElement.checked = true; this.headerCheckboxElement.indeterminate = false; } else { this.headerCheckboxElement.indeterminate = true; this.headerCheckboxElement.checked = false; } } if(!silent){ this.dispatchExternal("rowSelectionChanged", this.getSelectedData(), this.getSelectedRows()); } } registerRowSelectCheckbox (row, element) { if(!row._row.modules.select){ row._row.modules.select = {}; } row._row.modules.select.checkboxEl = element; } registerHeaderSelectCheckbox (element) { this.headerCheckboxElement = element; } childRowSelection(row, select){ var children = this.table.modules.dataTree.getChildren(row, true); if(select){ for(let child of children){ this._selectRow(child, true); } }else { for(let child of children){ this._deselectRow(child, true); } } } } SelectRow.moduleName = "selectRow"; //sort numbers function number$1(a, b, aRow, bRow, column, dir, params){ var alignEmptyValues = params.alignEmptyValues; var decimal = params.decimalSeparator; var thousand = params.thousandSeparator; var emptyAlign = 0; a = String(a); b = String(b); if(thousand){ a = a.split(thousand).join(""); b = b.split(thousand).join(""); } if(decimal){ a = a.split(decimal).join("."); b = b.split(decimal).join("."); } a = parseFloat(a); b = parseFloat(b); //handle non numeric values if(isNaN(a)){ emptyAlign = isNaN(b) ? 0 : -1; }else if(isNaN(b)){ emptyAlign = 1; }else { //compare valid values return a - b; } //fix empty values in position if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){ emptyAlign *= -1; } return emptyAlign; } //sort strings function string(a, b, aRow, bRow, column, dir, params){ var alignEmptyValues = params.alignEmptyValues; var emptyAlign = 0; var locale; //handle empty values if(!a){ emptyAlign = !b ? 0 : -1; }else if(!b){ emptyAlign = 1; }else { //compare valid values switch(typeof params.locale){ case "boolean": if(params.locale){ locale = this.langLocale(); } break; case "string": locale = params.locale; break; } return String(a).toLowerCase().localeCompare(String(b).toLowerCase(), locale); } //fix empty values in position if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){ emptyAlign *= -1; } return emptyAlign; } //sort datetime function datetime$2(a, b, aRow, bRow, column, dir, params){ var DT = window.DateTime || luxon.DateTime; var format = params.format || "dd/MM/yyyy HH:mm:ss", alignEmptyValues = params.alignEmptyValues, emptyAlign = 0; if(typeof DT != "undefined"){ if(!DT.isDateTime(a)){ if(format === "iso"){ a = DT.fromISO(String(a)); }else { a = DT.fromFormat(String(a), format); } } if(!DT.isDateTime(b)){ if(format === "iso"){ b = DT.fromISO(String(b)); }else { b = DT.fromFormat(String(b), format); } } if(!a.isValid){ emptyAlign = !b.isValid ? 0 : -1; }else if(!b.isValid){ emptyAlign = 1; }else { //compare valid values return a - b; } //fix empty values in position if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){ emptyAlign *= -1; } return emptyAlign; }else { console.error("Sort Error - 'datetime' sorter is dependant on luxon.js"); } } //sort date function date$1(a, b, aRow, bRow, column, dir, params){ if(!params.format){ params.format = "dd/MM/yyyy"; } return datetime$2.call(this, a, b, aRow, bRow, column, dir, params); } //sort times function time$1(a, b, aRow, bRow, column, dir, params){ if(!params.format){ params.format = "HH:mm"; } return datetime$2.call(this, a, b, aRow, bRow, column, dir, params); } //sort booleans function boolean(a, b, aRow, bRow, column, dir, params){ var el1 = a === true || a === "true" || a === "True" || a === 1 ? 1 : 0; var el2 = b === true || b === "true" || b === "True" || b === 1 ? 1 : 0; return el1 - el2; } //sort if element contains any data function array(a, b, aRow, bRow, column, dir, params){ var type = params.type || "length", alignEmptyValues = params.alignEmptyValues, emptyAlign = 0; function calc(value){ var result; switch(type){ case "length": result = value.length; break; case "sum": result = value.reduce(function(c, d){ return c + d; }); break; case "max": result = Math.max.apply(null, value) ; break; case "min": result = Math.min.apply(null, value) ; break; case "avg": result = value.reduce(function(c, d){ return c + d; }) / value.length; break; } return result; } //handle non array values if(!Array.isArray(a)){ emptyAlign = !Array.isArray(b) ? 0 : -1; }else if(!Array.isArray(b)){ emptyAlign = 1; }else { return calc(b) - calc(a); } //fix empty values in position if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){ emptyAlign *= -1; } return emptyAlign; } //sort if element contains any data function exists(a, b, aRow, bRow, column, dir, params){ var el1 = typeof a == "undefined" ? 0 : 1; var el2 = typeof b == "undefined" ? 0 : 1; return el1 - el2; } //sort alpha numeric strings function alphanum(as, bs, aRow, bRow, column, dir, params){ var a, b, a1, b1, i= 0, L, rx = /(\d+)|(\D+)/g, rd = /\d/; var alignEmptyValues = params.alignEmptyValues; var emptyAlign = 0; //handle empty values if(!as && as!== 0){ emptyAlign = !bs && bs!== 0 ? 0 : -1; }else if(!bs && bs!== 0){ emptyAlign = 1; }else { if(isFinite(as) && isFinite(bs)) return as - bs; a = String(as).toLowerCase(); b = String(bs).toLowerCase(); if(a === b) return 0; if(!(rd.test(a) && rd.test(b))) return a > b ? 1 : -1; a = a.match(rx); b = b.match(rx); L = a.length > b.length ? b.length : a.length; while(i < L){ a1= a[i]; b1= b[i++]; if(a1 !== b1){ if(isFinite(a1) && isFinite(b1)){ if(a1.charAt(0) === "0") a1 = "." + a1; if(b1.charAt(0) === "0") b1 = "." + b1; return a1 - b1; } else return a1 > b1 ? 1 : -1; } } return a.length > b.length; } //fix empty values in position if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){ emptyAlign *= -1; } return emptyAlign; } var defaultSorters = { number:number$1, string:string, date:date$1, time:time$1, datetime:datetime$2, boolean:boolean, array:array, exists:exists, alphanum:alphanum }; class Sort extends Module{ constructor(table){ super(table); this.sortList = []; //holder current sort this.changed = false; //has the sort changed since last render this.registerTableOption("sortMode", "local"); //local or remote sorting this.registerTableOption("initialSort", false); //initial sorting criteria this.registerTableOption("columnHeaderSortMulti", true); //multiple or single column sorting this.registerTableOption("sortOrderReverse", false); //reverse internal sort ordering this.registerTableOption("headerSortElement", "
"); //header sort element this.registerTableOption("headerSortClickElement", "header"); //element which triggers sort when clicked this.registerColumnOption("sorter"); this.registerColumnOption("sorterParams"); this.registerColumnOption("headerSort", true); this.registerColumnOption("headerSortStartingDir"); this.registerColumnOption("headerSortTristate"); } initialize(){ this.subscribe("column-layout", this.initializeColumn.bind(this)); this.subscribe("table-built", this.tableBuilt.bind(this)); this.registerDataHandler(this.sort.bind(this), 20); this.registerTableFunction("setSort", this.userSetSort.bind(this)); this.registerTableFunction("getSorters", this.getSort.bind(this)); this.registerTableFunction("clearSort", this.clearSort.bind(this)); if(this.table.options.sortMode === "remote"){ this.subscribe("data-params", this.remoteSortParams.bind(this)); } } tableBuilt(){ if(this.table.options.initialSort){ this.setSort(this.table.options.initialSort); } } remoteSortParams(data, config, silent, params){ var sorters = this.getSort(); sorters.forEach((item) => { delete item.column; }); params.sort = sorters; return params; } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// userSetSort(sortList, dir){ this.setSort(sortList, dir); // this.table.rowManager.sorterRefresh(); this.refreshSort(); } clearSort(){ this.clear(); // this.table.rowManager.sorterRefresh(); this.refreshSort(); } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// //initialize column header for sorting initializeColumn(column){ var sorter = false, colEl, arrowEl; switch(typeof column.definition.sorter){ case "string": if(Sort.sorters[column.definition.sorter]){ sorter = Sort.sorters[column.definition.sorter]; }else { console.warn("Sort Error - No such sorter found: ", column.definition.sorter); } break; case "function": sorter = column.definition.sorter; break; } column.modules.sort = { sorter:sorter, dir:"none", params:column.definition.sorterParams || {}, startingDir:column.definition.headerSortStartingDir || "asc", tristate: column.definition.headerSortTristate, }; if(column.definition.headerSort !== false){ colEl = column.getElement(); colEl.classList.add("tabulator-sortable"); arrowEl = document.createElement("div"); arrowEl.classList.add("tabulator-col-sorter"); switch(this.table.options.headerSortClickElement){ case "icon": arrowEl.classList.add("tabulator-col-sorter-element"); break; case "header": colEl.classList.add("tabulator-col-sorter-element"); break; default: colEl.classList.add("tabulator-col-sorter-element"); break; } switch(this.table.options.headerSortElement){ case "function": //do nothing break; case "object": arrowEl.appendChild(this.table.options.headerSortElement); break; default: arrowEl.innerHTML = this.table.options.headerSortElement; } //create sorter arrow column.titleHolderElement.appendChild(arrowEl); column.modules.sort.element = arrowEl; this.setColumnHeaderSortIcon(column, "none"); //sort on click (this.table.options.headerSortClickElement === "icon" ? arrowEl : colEl).addEventListener("click", (e) => { var dir = "", sorters=[], match = false; if(column.modules.sort){ if(column.modules.sort.tristate){ if(column.modules.sort.dir == "none"){ dir = column.modules.sort.startingDir; }else { if(column.modules.sort.dir == column.modules.sort.startingDir){ dir = column.modules.sort.dir == "asc" ? "desc" : "asc"; }else { dir = "none"; } } }else { switch(column.modules.sort.dir){ case "asc": dir = "desc"; break; case "desc": dir = "asc"; break; default: dir = column.modules.sort.startingDir; } } if (this.table.options.columnHeaderSortMulti && (e.shiftKey || e.ctrlKey)) { sorters = this.getSort(); match = sorters.findIndex((sorter) => { return sorter.field === column.getField(); }); if(match > -1){ sorters[match].dir = dir; match = sorters.splice(match, 1)[0]; if(dir != "none"){ sorters.push(match); } }else { if(dir != "none"){ sorters.push({column:column, dir:dir}); } } //add to existing sort this.setSort(sorters); }else { if(dir == "none"){ this.clear(); }else { //sort by column only this.setSort(column, dir); } } // this.table.rowManager.sorterRefresh(!this.sortList.length); this.refreshSort(); } }); } } refreshSort(){ if(this.table.options.sortMode === "remote"){ this.reloadData(null, false, false); }else { this.refreshData(true); } //TODO - Persist left position of row manager // left = this.scrollLeft; // this.scrollHorizontal(left); } //check if the sorters have changed since last use hasChanged(){ var changed = this.changed; this.changed = false; return changed; } //return current sorters getSort(){ var self = this, sorters = []; self.sortList.forEach(function(item){ if(item.column){ sorters.push({column:item.column.getComponent(), field:item.column.getField(), dir:item.dir}); } }); return sorters; } //change sort list and trigger sort setSort(sortList, dir){ var self = this, newSortList = []; if(!Array.isArray(sortList)){ sortList = [{column: sortList, dir:dir}]; } sortList.forEach(function(item){ var column; column = self.table.columnManager.findColumn(item.column); if(column){ item.column = column; newSortList.push(item); self.changed = true; }else { console.warn("Sort Warning - Sort field does not exist and is being ignored: ", item.column); } }); self.sortList = newSortList; this.dispatch("sort-changed"); } //clear sorters clear(){ this.setSort([]); } //find appropriate sorter for column findSorter(column){ var row = this.table.rowManager.activeRows[0], sorter = "string", field, value; if(row){ row = row.getData(); field = column.getField(); if(field){ value = column.getFieldValue(row); switch(typeof value){ case "undefined": sorter = "string"; break; case "boolean": sorter = "boolean"; break; default: if(!isNaN(value) && value !== ""){ sorter = "number"; }else { if(value.match(/((^[0-9]+[a-z]+)|(^[a-z]+[0-9]+))+$/i)){ sorter = "alphanum"; } } break; } } } return Sort.sorters[sorter]; } //work through sort list sorting data sort(data){ var self = this, sortList = this.table.options.sortOrderReverse ? self.sortList.slice().reverse() : self.sortList, sortListActual = [], rowComponents = []; if(this.subscribedExternal("dataSorting")){ this.dispatchExternal("dataSorting", self.getSort()); } self.clearColumnHeaders(); if(this.table.options.sortMode !== "remote"){ //build list of valid sorters and trigger column specific callbacks before sort begins sortList.forEach(function(item, i){ var sortObj; if(item.column){ sortObj = item.column.modules.sort; if(sortObj){ //if no sorter has been defined, take a guess if(!sortObj.sorter){ sortObj.sorter = self.findSorter(item.column); } item.params = typeof sortObj.params === "function" ? sortObj.params(item.column.getComponent(), item.dir) : sortObj.params; sortListActual.push(item); } self.setColumnHeader(item.column, item.dir); } }); //sort data if (sortListActual.length) { self._sortItems(data, sortListActual); } }else { sortList.forEach(function(item, i){ self.setColumnHeader(item.column, item.dir); }); } if(this.subscribedExternal("dataSorted")){ data.forEach((row) => { rowComponents.push(row.getComponent()); }); this.dispatchExternal("dataSorted", self.getSort(), rowComponents); } return data; } //clear sort arrows on columns clearColumnHeaders(){ this.table.columnManager.getRealColumns().forEach((column) => { if(column.modules.sort){ column.modules.sort.dir = "none"; column.getElement().setAttribute("aria-sort", "none"); this.setColumnHeaderSortIcon(column, "none"); } }); } //set the column header sort direction setColumnHeader(column, dir){ column.modules.sort.dir = dir; column.getElement().setAttribute("aria-sort", dir === "asc" ? "ascending" : "descending"); this.setColumnHeaderSortIcon(column, dir); } setColumnHeaderSortIcon(column, dir){ var sortEl = column.modules.sort.element, arrowEl; if(column.definition.headerSort && typeof this.table.options.headerSortElement === "function"){ while(sortEl.firstChild) sortEl.removeChild(sortEl.firstChild); arrowEl = this.table.options.headerSortElement.call(this.table, column.getComponent(), dir); if(typeof arrowEl === "object"){ sortEl.appendChild(arrowEl); }else { sortEl.innerHTML = arrowEl; } } } //sort each item in sort list _sortItems(data, sortList){ var sorterCount = sortList.length - 1; data.sort((a, b) => { var result; for(var i = sorterCount; i>= 0; i--){ let sortItem = sortList[i]; result = this._sortRow(a, b, sortItem.column, sortItem.dir, sortItem.params); if(result !== 0){ break; } } return result; }); } //process individual rows for a sort function on active data _sortRow(a, b, column, dir, params){ var el1Comp, el2Comp; //switch elements depending on search direction var el1 = dir == "asc" ? a : b; var el2 = dir == "asc" ? b : a; a = column.getFieldValue(el1.getData()); b = column.getFieldValue(el2.getData()); a = typeof a !== "undefined" ? a : ""; b = typeof b !== "undefined" ? b : ""; el1Comp = el1.getComponent(); el2Comp = el2.getComponent(); return column.modules.sort.sorter.call(this, a, b, el1Comp, el2Comp, column.getComponent(), dir, params); } } Sort.moduleName = "sort"; //load defaults Sort.sorters = defaultSorters; class Tooltip extends Module{ constructor(table){ super(table); this.tooltipSubscriber = null, this.headerSubscriber = null, this.timeout = null; this.popupInstance = null; this.registerTableOption("tooltipGenerationMode", undefined); //deprecated this.registerTableOption("tooltipDelay", 300); this.registerColumnOption("tooltip"); this.registerColumnOption("headerTooltip"); } initialize(){ this.deprecatedOptionsCheck(); this.subscribe("column-init", this.initializeColumn.bind(this)); } deprecatedOptionsCheck(){ this.deprecationCheckMsg("tooltipGenerationMode", "This option is no longer needed as tooltips are always generated on hover now"); } initializeColumn(column){ if(column.definition.headerTooltip && !this.headerSubscriber){ this.headerSubscriber = true; this.subscribe("column-mousemove", this.mousemoveCheck.bind(this, "headerTooltip")); this.subscribe("column-mouseout", this.mouseoutCheck.bind(this, "headerTooltip")); } if(column.definition.tooltip && !this.tooltipSubscriber){ this.tooltipSubscriber = true; this.subscribe("cell-mousemove", this.mousemoveCheck.bind(this, "tooltip")); this.subscribe("cell-mouseout", this.mouseoutCheck.bind(this, "tooltip")); } } mousemoveCheck(action, e, component){ var tooltip = action === "tooltip" ? component.column.definition.tooltip : component.definition.headerTooltip; if(tooltip){ this.clearPopup(); this.timeout = setTimeout(this.loadTooltip.bind(this, e, component, tooltip), this.table.options.tooltipDelay); } } mouseoutCheck(action, e, component){ if(!this.popupInstance){ this.clearPopup(); } } clearPopup(action, e, component){ clearTimeout(this.timeout); this.timeout = null; if(this.popupInstance){ this.popupInstance.hide(); } } loadTooltip(e, component, tooltip){ var contentsEl, renderedCallback, coords; function onRendered(callback){ renderedCallback = callback; } if(typeof tooltip === "function"){ tooltip = tooltip(e, component.getComponent(), onRendered); } if(tooltip instanceof HTMLElement){ contentsEl = tooltip; }else { contentsEl = document.createElement("div"); if(tooltip === true){ if(component instanceof Cell){ tooltip = component.value; }else { if(component.definition.field){ this.langBind("columns|" + component.definition.field, (value) => { contentsEl.innerHTML = tooltip = value || component.definition.title; }); }else { tooltip = component.definition.title; } } } contentsEl.innerHTML = tooltip; } if(tooltip || tooltip === 0 || tooltip === false){ contentsEl.classList.add("tabulator-tooltip"); contentsEl.addEventListener("mousemove", e => e.preventDefault()); this.popupInstance = this.popup(contentsEl); if(typeof renderedCallback === "function"){ this.popupInstance.renderCallback(renderedCallback); } coords = this.popupInstance.containerEventCoords(e); this.popupInstance.show(coords.x + 15, coords.y + 15).hideOnBlur(() => { this.dispatchExternal("TooltipClosed", component.getComponent()); this.popupInstance = null; }); this.dispatchExternal("TooltipOpened", component.getComponent()); } } } Tooltip.moduleName = "tooltip"; var defaultValidators = { //is integer integer: function(cell, value, parameters){ if(value === "" || value === null || typeof value === "undefined"){ return true; } value = Number(value); return !isNaN(value) && isFinite(value) && Math.floor(value) === value; }, //is float float: function(cell, value, parameters){ if(value === "" || value === null || typeof value === "undefined"){ return true; } value = Number(value); return !isNaN(value) && isFinite(value) && value % 1 !== 0; }, //must be a number numeric: function(cell, value, parameters){ if(value === "" || value === null || typeof value === "undefined"){ return true; } return !isNaN(value); }, //must be a string string: function(cell, value, parameters){ if(value === "" || value === null || typeof value === "undefined"){ return true; } return isNaN(value); }, //maximum value max: function(cell, value, parameters){ if(value === "" || value === null || typeof value === "undefined"){ return true; } return parseFloat(value) <= parameters; }, //minimum value min: function(cell, value, parameters){ if(value === "" || value === null || typeof value === "undefined"){ return true; } return parseFloat(value) >= parameters; }, //starts with value starts: function(cell, value, parameters){ if(value === "" || value === null || typeof value === "undefined"){ return true; } return String(value).toLowerCase().startsWith(String(parameters).toLowerCase()); }, //ends with value ends: function(cell, value, parameters){ if(value === "" || value === null || typeof value === "undefined"){ return true; } return String(value).toLowerCase().endsWith(String(parameters).toLowerCase()); }, //minimum string length minLength: function(cell, value, parameters){ if(value === "" || value === null || typeof value === "undefined"){ return true; } return String(value).length >= parameters; }, //maximum string length maxLength: function(cell, value, parameters){ if(value === "" || value === null || typeof value === "undefined"){ return true; } return String(value).length <= parameters; }, //in provided value list in: function(cell, value, parameters){ if(value === "" || value === null || typeof value === "undefined"){ return true; } if(typeof parameters == "string"){ parameters = parameters.split("|"); } return parameters.indexOf(value) > -1; }, //must match provided regex regex: function(cell, value, parameters){ if(value === "" || value === null || typeof value === "undefined"){ return true; } var reg = new RegExp(parameters); return reg.test(value); }, //value must be unique in this column unique: function(cell, value, parameters){ if(value === "" || value === null || typeof value === "undefined"){ return true; } var unique = true; var cellData = cell.getData(); var column = cell.getColumn()._getSelf(); this.table.rowManager.rows.forEach(function(row){ var data = row.getData(); if(data !== cellData){ if(value == column.getFieldValue(data)){ unique = false; } } }); return unique; }, //must have a value required:function(cell, value, parameters){ return value !== "" && value !== null && typeof value !== "undefined"; }, }; class Validate extends Module{ constructor(table){ super(table); this.invalidCells = []; this.registerTableOption("validationMode", "blocking"); this.registerColumnOption("validator"); this.registerTableFunction("getInvalidCells", this.getInvalidCells.bind(this)); this.registerTableFunction("clearCellValidation", this.userClearCellValidation.bind(this)); this.registerTableFunction("validate", this.userValidate.bind(this)); this.registerComponentFunction("cell", "isValid", this.cellIsValid.bind(this)); this.registerComponentFunction("cell", "clearValidation", this.clearValidation.bind(this)); this.registerComponentFunction("cell", "validate", this.cellValidate.bind(this)); this.registerComponentFunction("column", "validate", this.columnValidate.bind(this)); this.registerComponentFunction("row", "validate", this.rowValidate.bind(this)); } initialize(){ this.subscribe("cell-delete", this.clearValidation.bind(this)); this.subscribe("column-layout", this.initializeColumnCheck.bind(this)); this.subscribe("edit-success", this.editValidate.bind(this)); this.subscribe("edit-editor-clear", this.editorClear.bind(this)); this.subscribe("edit-edited-clear", this.editedClear.bind(this)); } /////////////////////////////////// ///////// Event Handling ////////// /////////////////////////////////// editValidate(cell, value, previousValue){ var valid = this.table.options.validationMode !== "manual" ? this.validate(cell.column.modules.validate, cell, value) : true; // allow time for editor to make render changes then style cell if(valid !== true){ setTimeout(() => { cell.getElement().classList.add("tabulator-validation-fail"); this.dispatchExternal("validationFailed", cell.getComponent(), value, valid); }); } return valid; } editorClear(cell, cancelled){ if(cancelled){ if(cell.column.modules.validate){ this.cellValidate(cell); } } cell.getElement().classList.remove("tabulator-validation-fail"); } editedClear(cell){ if(cell.modules.validate){ cell.modules.validate.invalid = false; } } /////////////////////////////////// ////////// Cell Functions ///////// /////////////////////////////////// cellIsValid(cell){ return cell.modules.validate ? (cell.modules.validate.invalid || true) : true; } cellValidate(cell){ return this.validate(cell.column.modules.validate, cell, cell.getValue()); } /////////////////////////////////// ///////// Column Functions //////// /////////////////////////////////// columnValidate(column){ var invalid = []; column.cells.forEach((cell) => { if(this.cellValidate(cell) !== true){ invalid.push(cell.getComponent()); } }); return invalid.length ? invalid : true; } /////////////////////////////////// ////////// Row Functions ////////// /////////////////////////////////// rowValidate(row){ var invalid = []; row.cells.forEach((cell) => { if(this.cellValidate(cell) !== true){ invalid.push(cell.getComponent()); } }); return invalid.length ? invalid : true; } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// userClearCellValidation(cells){ if(!cells){ cells = this.getInvalidCells(); } if(!Array.isArray(cells)){ cells = [cells]; } cells.forEach((cell) => { this.clearValidation(cell._getSelf()); }); } userValidate(cells){ var output = []; //clear row data this.table.rowManager.rows.forEach((row) => { row = row.getComponent(); var valid = row.validate(); if(valid !== true){ output = output.concat(valid); } }); return output.length ? output : true; } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// initializeColumnCheck(column){ if(typeof column.definition.validator !== "undefined"){ this.initializeColumn(column); } } //validate initializeColumn(column){ var self = this, config = [], validator; if(column.definition.validator){ if(Array.isArray(column.definition.validator)){ column.definition.validator.forEach((item) => { validator = self._extractValidator(item); if(validator){ config.push(validator); } }); }else { validator = this._extractValidator(column.definition.validator); if(validator){ config.push(validator); } } column.modules.validate = config.length ? config : false; } } _extractValidator(value){ var type, params, pos; switch(typeof value){ case "string": pos = value.indexOf(':'); if(pos > -1){ type = value.substring(0,pos); params = value.substring(pos+1); }else { type = value; } return this._buildValidator(type, params); case "function": return this._buildValidator(value); case "object": return this._buildValidator(value.type, value.parameters); } } _buildValidator(type, params){ var func = typeof type == "function" ? type : Validate.validators[type]; if(!func){ console.warn("Validator Setup Error - No matching validator found:", type); return false; }else { return { type:typeof type == "function" ? "function" : type, func:func, params:params, }; } } validate(validators, cell, value){ var self = this, failedValidators = [], invalidIndex = this.invalidCells.indexOf(cell); if(validators){ validators.forEach((item) => { if(!item.func.call(self, cell.getComponent(), value, item.params)){ failedValidators.push({ type:item.type, parameters:item.params }); } }); } if(!cell.modules.validate){ cell.modules.validate = {}; } if(!failedValidators.length){ cell.modules.validate.invalid = false; cell.getElement().classList.remove("tabulator-validation-fail"); if(invalidIndex > -1){ this.invalidCells.splice(invalidIndex, 1); } }else { cell.modules.validate.invalid = failedValidators; if(this.table.options.validationMode !== "manual"){ cell.getElement().classList.add("tabulator-validation-fail"); } if(invalidIndex == -1){ this.invalidCells.push(cell); } } return failedValidators.length ? failedValidators : true; } getInvalidCells(){ var output = []; this.invalidCells.forEach((cell) => { output.push(cell.getComponent()); }); return output; } clearValidation(cell){ var invalidIndex; if(cell.modules.validate && cell.modules.validate.invalid){ cell.getElement().classList.remove("tabulator-validation-fail"); cell.modules.validate.invalid = false; invalidIndex = this.invalidCells.indexOf(cell); if(invalidIndex > -1){ this.invalidCells.splice(invalidIndex, 1); } } } } Validate.moduleName = "validate"; //load defaults Validate.validators = defaultValidators; var modules = /*#__PURE__*/Object.freeze({ __proto__: null, AccessorModule: Accessor, AjaxModule: Ajax, ClipboardModule: Clipboard, ColumnCalcsModule: ColumnCalcs, DataTreeModule: DataTree, DownloadModule: Download, EditModule: Edit$1, ExportModule: Export, FilterModule: Filter, FormatModule: Format, FrozenColumnsModule: FrozenColumns, FrozenRowsModule: FrozenRows, GroupRowsModule: GroupRows, HistoryModule: History, HtmlTableImportModule: HtmlTableImport, ImportModule: Import, InteractionModule: Interaction, KeybindingsModule: Keybindings, MenuModule: Menu, MoveColumnsModule: MoveColumns, MoveRowsModule: MoveRows, MutatorModule: Mutator, PageModule: Page, PersistenceModule: Persistence, PopupModule: Popup$1, PrintModule: Print, ReactiveDataModule: ReactiveData, ResizeColumnsModule: ResizeColumns, ResizeRowsModule: ResizeRows, ResizeTableModule: ResizeTable, ResponsiveLayoutModule: ResponsiveLayout, SelectRowModule: SelectRow, SortModule: Sort, TooltipModule: Tooltip, ValidateModule: Validate }); var defaultOptions = { debugEventsExternal:false, //flag to console log events debugEventsInternal:false, //flag to console log events debugInvalidOptions:true, //allow toggling of invalid option warnings debugInvalidComponentFuncs:true, //allow toggling of invalid component warnings debugInitialization:true, //allow toggling of pre initialization function call warnings debugDeprecation:true, //allow toggling of deprecation warnings height:false, //height of tabulator minHeight:false, //minimum height of tabulator maxHeight:false, //maximum height of tabulator columnHeaderVertAlign:"top", //vertical alignment of column headers popupContainer:false, columns:[],//store for colum header info columnDefaults:{}, //store column default props data:false, //default starting data autoColumns:false, //build columns from data row structure autoColumnsDefinitions:false, nestedFieldSeparator:".", //separator for nested data footerElement:false, //hold footer element index:"id", //filed for row index textDirection:"auto", addRowPos:"bottom", //position to insert blank rows, top|bottom headerVisible:true, //hide header renderVertical:"virtual", renderHorizontal:"basic", renderVerticalBuffer:0, // set virtual DOM buffer size scrollToRowPosition:"top", scrollToRowIfVisible:true, scrollToColumnPosition:"left", scrollToColumnIfVisible:true, rowFormatter:false, rowFormatterPrint:null, rowFormatterClipboard:null, rowFormatterHtmlOutput:null, rowHeight:null, placeholder:false, dataLoader:true, dataLoaderLoading:false, dataLoaderError:false, dataLoaderErrorTimeout:3000, dataSendParams:{}, dataReceiveParams:{}, }; class OptionsList { constructor(table, msgType, defaults = {}){ this.table = table; this.msgType = msgType; this.registeredDefaults = Object.assign({}, defaults); } register(option, value){ this.registeredDefaults[option] = value; } generate(defaultOptions, userOptions = {}){ var output = Object.assign({}, this.registeredDefaults), warn = this.table.options.debugInvalidOptions || userOptions.debugInvalidOptions === true; Object.assign(output, defaultOptions); for (let key in userOptions){ if(!output.hasOwnProperty(key)){ if(warn){ console.warn("Invalid " + this.msgType + " option:", key); } output[key] = userOptions.key; } } for (let key in output){ if(key in userOptions){ output[key] = userOptions[key]; }else { if(Array.isArray(output[key])){ output[key] = Object.assign([], output[key]); }else if(typeof output[key] === "object" && output[key] !== null){ output[key] = Object.assign({}, output[key]); }else if (typeof output[key] === "undefined"){ delete output[key]; } } } return output; } } class Renderer extends CoreFeature{ constructor(table){ super(table); this.elementVertical = table.rowManager.element; this.elementHorizontal = table.columnManager.element; this.tableElement = table.rowManager.tableElement; this.verticalFillMode = "fit"; // used by row manager to determine how to size the render area ("fit" - fits container to the contents, "fill" - fills the container without resizing it) } /////////////////////////////////// /////// Internal Bindings ///////// /////////////////////////////////// initialize(){ //initialize core functionality } clearRows(){ //clear down existing rows layout } clearColumns(){ //clear down existing columns layout } reinitializeColumnWidths(columns){ //resize columns to fit data } renderRows(){ //render rows from a clean slate } renderColumns(){ //render columns from a clean slate } rerenderRows(callback){ // rerender rows and keep position if(callback){ callback(); } } rerenderColumns(update, blockRedraw){ //rerender columns } renderRowCells(row){ //render the cells in a row } rerenderRowCells(row, force){ //rerender the cells in a row } scrollColumns(left, dir){ //handle horizontal scrolling } scrollRows(top, dir){ //handle vertical scrolling } resize(){ //container has resized, carry out any needed recalculations (DO NOT RERENDER IN THIS FUNCTION) } scrollToRow(row){ //scroll to a specific row } scrollToRowNearestTop(row){ //determine weather the row is nearest the top or bottom of the table, return true for top or false for bottom } visibleRows(includingBuffer){ //return the visible rows return []; } /////////////////////////////////// //////// Helper Functions ///////// /////////////////////////////////// rows(){ return this.table.rowManager.getDisplayRows(); } styleRow(row, index){ var rowEl = row.getElement(); if(index % 2){ rowEl.classList.add("tabulator-row-even"); rowEl.classList.remove("tabulator-row-odd"); }else { rowEl.classList.add("tabulator-row-odd"); rowEl.classList.remove("tabulator-row-even"); } } /////////////////////////////////// /////// External Triggers ///////// /////// (DO NOT OVERRIDE) ///////// /////////////////////////////////// clear(){ //clear down existing layout this.clearRows(); this.clearColumns(); } render(){ //render from a clean slate this.renderRows(); this.renderColumns(); } rerender(callback){ // rerender and keep position this.rerenderRows(); this.rerenderColumns(); } scrollToRowPosition(row, position, ifVisible){ var rowIndex = this.rows().indexOf(row), rowEl = row.getElement(), offset = 0; return new Promise((resolve, reject) => { if(rowIndex > -1){ if(typeof ifVisible === "undefined"){ ifVisible = this.table.options.scrollToRowIfVisible; } //check row visibility if(!ifVisible){ if(Helpers.elVisible(rowEl)){ offset = Helpers.elOffset(rowEl).top - Helpers.elOffset(this.elementVertical).top; if(offset > 0 && offset < this.elementVertical.clientHeight - rowEl.offsetHeight){ resolve(); return false; } } } if(typeof position === "undefined"){ position = this.table.options.scrollToRowPosition; } if(position === "nearest"){ position = this.scrollToRowNearestTop(row) ? "top" : "bottom"; } //scroll to row this.scrollToRow(row); //align to correct position switch(position){ case "middle": case "center": if(this.elementVertical.scrollHeight - this.elementVertical.scrollTop == this.elementVertical.clientHeight){ this.elementVertical.scrollTop = this.elementVertical.scrollTop + (rowEl.offsetTop - this.elementVertical.scrollTop) - ((this.elementVertical.scrollHeight - rowEl.offsetTop) / 2); }else { this.elementVertical.scrollTop = this.elementVertical.scrollTop - (this.elementVertical.clientHeight / 2); } break; case "bottom": if(this.elementVertical.scrollHeight - this.elementVertical.scrollTop == this.elementVertical.clientHeight){ this.elementVertical.scrollTop = this.elementVertical.scrollTop - (this.elementVertical.scrollHeight - rowEl.offsetTop) + rowEl.offsetHeight; }else { this.elementVertical.scrollTop = this.elementVertical.scrollTop - this.elementVertical.clientHeight + rowEl.offsetHeight; } break; case "top": this.elementVertical.scrollTop = rowEl.offsetTop; break; } resolve(); }else { console.warn("Scroll Error - Row not visible"); reject("Scroll Error - Row not visible"); } }); } } class BasicHorizontal extends Renderer{ constructor(table){ super(table); } renderRowCells(row){ row.cells.forEach((cell) => { row.element.appendChild(cell.getElement()); cell.cellRendered(); }); } reinitializeColumnWidths(columns){ columns.forEach(function(column){ column.reinitializeWidth(); }); } } class VirtualDomHorizontal extends Renderer{ constructor(table){ super(table); this.leftCol = 0; this.rightCol = 0; this.scrollLeft = 0; this.vDomScrollPosLeft = 0; this.vDomScrollPosRight = 0; this.vDomPadLeft = 0; this.vDomPadRight = 0; this.fitDataColAvg = 0; this.windowBuffer = 200; //pixel margin to make column visible before it is shown on screen this.visibleRows = null; this.initialized = false; this.isFitData = false; this.columns = []; } initialize(){ this.compatibilityCheck(); this.layoutCheck(); this.vertScrollListen(); } compatibilityCheck(){ if(this.options("layout") == "fitDataTable"){ console.warn("Horizontal Virtual DOM is not compatible with fitDataTable layout mode"); } if(this.options("responsiveLayout")){ console.warn("Horizontal Virtual DOM is not compatible with responsive columns"); } if(this.options("rtl")){ console.warn("Horizontal Virtual DOM is not currently compatible with RTL text direction"); } } layoutCheck(){ this.isFitData = this.options("layout").startsWith('fitData'); } vertScrollListen(){ this.subscribe("scroll-vertical", this.clearVisRowCache.bind(this)); this.subscribe("data-refreshed", this.clearVisRowCache.bind(this)); } clearVisRowCache(){ this.visibleRows = null; } ////////////////////////////////////// ///////// Public Functions /////////// ////////////////////////////////////// renderColumns(row, force){ this.dataChange(); } scrollColumns(left, dir){ if(this.scrollLeft != left){ this.scrollLeft = left; this.scroll(left - (this.vDomScrollPosLeft + this.windowBuffer)); } } calcWindowBuffer(){ var buffer = this.elementVertical.clientWidth; this.table.columnManager.columnsByIndex.forEach((column) => { if(column.visible){ var width = column.getWidth(); if(width > buffer){ buffer = width; } } }); this.windowBuffer = buffer * 2; } rerenderColumns(update, blockRedraw){ var old = { cols:this.columns, leftCol:this.leftCol, rightCol:this.rightCol, }, colPos = 0; if(update && !this.initialized){ return; } this.clear(); this.calcWindowBuffer(); this.scrollLeft = this.elementVertical.scrollLeft; this.vDomScrollPosLeft = this.scrollLeft - this.windowBuffer; this.vDomScrollPosRight = this.scrollLeft + this.elementVertical.clientWidth + this.windowBuffer; this.table.columnManager.columnsByIndex.forEach((column) => { var config = {}, width; if(column.visible){ if(!column.modules.frozen){ width = column.getWidth(); config.leftPos = colPos; config.rightPos = colPos + width; config.width = width; if (this.isFitData) { config.fitDataCheck = column.modules.vdomHoz ? column.modules.vdomHoz.fitDataCheck : true; } if((colPos + width > this.vDomScrollPosLeft) && (colPos < this.vDomScrollPosRight)){ //column is visible if(this.leftCol == -1){ this.leftCol = this.columns.length; this.vDomPadLeft = colPos; } this.rightCol = this.columns.length; }else { // column is hidden if(this.leftCol !== -1){ this.vDomPadRight += width; } } this.columns.push(column); column.modules.vdomHoz = config; colPos += width; } } }); this.tableElement.style.paddingLeft = this.vDomPadLeft + "px"; this.tableElement.style.paddingRight = this.vDomPadRight + "px"; this.initialized = true; if(!blockRedraw){ if(!update || this.reinitChanged(old)){ this.reinitializeRows(); } } this.elementVertical.scrollLeft = this.scrollLeft; } renderRowCells(row){ if(this.initialized){ this.initializeRow(row); }else { row.cells.forEach((cell) => { row.element.appendChild(cell.getElement()); cell.cellRendered(); }); } } rerenderRowCells(row, force){ this.reinitializeRow(row, force); } reinitializeColumnWidths(columns){ for(let i = this.leftCol; i <= this.rightCol; i++){ this.columns[i].reinitializeWidth(); } } ////////////////////////////////////// //////// Internal Rendering ////////// ////////////////////////////////////// deinitialize(){ this.initialized = false; } clear(){ this.columns = []; this.leftCol = -1; this.rightCol = 0; this.vDomScrollPosLeft = 0; this.vDomScrollPosRight = 0; this.vDomPadLeft = 0; this.vDomPadRight = 0; } dataChange(){ var change = false, row, rowEl; if(this.isFitData){ this.table.columnManager.columnsByIndex.forEach((column) => { if(!column.definition.width && column.visible){ change = true; } }); if(change && this.table.rowManager.getDisplayRows().length){ this.vDomScrollPosRight = this.scrollLeft + this.elementVertical.clientWidth + this.windowBuffer; row = this.chain("rows-sample", [1], [], () => { return this.table.rowManager.getDisplayRows(); })[0]; if(row){ rowEl = row.getElement(); row.generateCells(); this.tableElement.appendChild(rowEl); for(let colEnd = 0; colEnd < row.cells.length; colEnd++){ let cell = row.cells[colEnd]; rowEl.appendChild(cell.getElement()); cell.column.reinitializeWidth(); } rowEl.parentNode.removeChild(rowEl); this.rerenderColumns(false, true); } } }else { if(this.options("layout") === "fitColumns"){ this.layoutRefresh(); this.rerenderColumns(false, true); } } } reinitChanged(old){ var match = true; if(old.cols.length !== this.columns.length || old.leftCol !== this.leftCol || old.rightCol !== this.rightCol){ return true; } old.cols.forEach((col, i) => { if(col !== this.columns[i]){ match = false; } }); return !match; } reinitializeRows(){ var visibleRows = this.getVisibleRows(), otherRows = this.table.rowManager.getRows().filter(row => !visibleRows.includes(row)); visibleRows.forEach((row) => { this.reinitializeRow(row, true); }); otherRows.forEach((row) =>{ row.deinitialize(); }); } getVisibleRows(){ if (!this.visibleRows){ this.visibleRows = this.table.rowManager.getVisibleRows(); } return this.visibleRows; } scroll(diff){ this.vDomScrollPosLeft += diff; this.vDomScrollPosRight += diff; if(Math.abs(diff) > (this.windowBuffer / 2)){ this.rerenderColumns(); }else { if(diff > 0){ //scroll right this.addColRight(); this.removeColLeft(); }else { //scroll left this.addColLeft(); this.removeColRight(); } } } colPositionAdjust (start, end, diff){ for(let i = start; i < end; i++){ let column = this.columns[i]; column.modules.vdomHoz.leftPos += diff; column.modules.vdomHoz.rightPos += diff; } } addColRight(){ var changes = false, working = true; while(working){ let column = this.columns[this.rightCol + 1]; if(column){ if(column.modules.vdomHoz.leftPos <= this.vDomScrollPosRight){ changes = true; this.getVisibleRows().forEach((row) => { if(row.type !== "group"){ var cell = row.getCell(column); row.getElement().insertBefore(cell.getElement(), row.getCell(this.columns[this.rightCol]).getElement().nextSibling); cell.cellRendered(); } }); this.fitDataColActualWidthCheck(column); this.rightCol++; // Don't move this below the >= check below this.getVisibleRows().forEach((row) => { if(row.type !== "group"){ row.modules.vdomHoz.rightCol = this.rightCol; } }); if(this.rightCol >= (this.columns.length - 1)){ this.vDomPadRight = 0; }else { this.vDomPadRight -= column.getWidth(); } }else { working = false; } }else { working = false; } } if(changes){ this.tableElement.style.paddingRight = this.vDomPadRight + "px"; } } addColLeft(){ var changes = false, working = true; while(working){ let column = this.columns[this.leftCol - 1]; if(column){ if(column.modules.vdomHoz.rightPos >= this.vDomScrollPosLeft){ changes = true; this.getVisibleRows().forEach((row) => { if(row.type !== "group"){ var cell = row.getCell(column); row.getElement().insertBefore(cell.getElement(), row.getCell(this.columns[this.leftCol]).getElement()); cell.cellRendered(); } }); this.leftCol--; // don't move this below the <= check below this.getVisibleRows().forEach((row) => { if(row.type !== "group"){ row.modules.vdomHoz.leftCol = this.leftCol; } }); if(this.leftCol <= 0){ // replicating logic in addColRight this.vDomPadLeft = 0; }else { this.vDomPadLeft -= column.getWidth(); } let diff = this.fitDataColActualWidthCheck(column); if(diff){ this.scrollLeft = this.elementVertical.scrollLeft = this.elementVertical.scrollLeft + diff; this.vDomPadRight -= diff; } }else { working = false; } }else { working = false; } } if(changes){ this.tableElement.style.paddingLeft = this.vDomPadLeft + "px"; } } removeColRight(){ var changes = false, working = true; while(working){ let column = this.columns[this.rightCol]; if(column){ if(column.modules.vdomHoz.leftPos > this.vDomScrollPosRight){ changes = true; this.getVisibleRows().forEach((row) => { if(row.type !== "group"){ var cell = row.getCell(column); try { row.getElement().removeChild(cell.getElement()); } catch (ex) { console.warn("Could not removeColRight", ex.message); } } }); this.vDomPadRight += column.getWidth(); this.rightCol --; this.getVisibleRows().forEach((row) => { if(row.type !== "group"){ row.modules.vdomHoz.rightCol = this.rightCol; } }); }else { working = false; } }else { working = false; } } if(changes){ this.tableElement.style.paddingRight = this.vDomPadRight + "px"; } } removeColLeft(){ var changes = false, working = true; while(working){ let column = this.columns[this.leftCol]; if(column){ if(column.modules.vdomHoz.rightPos < this.vDomScrollPosLeft){ changes = true; this.getVisibleRows().forEach((row) => { if(row.type !== "group"){ var cell = row.getCell(column); try { row.getElement().removeChild(cell.getElement()); } catch (ex) { console.warn("Could not removeColLeft", ex.message); } } }); this.vDomPadLeft += column.getWidth(); this.leftCol ++; this.getVisibleRows().forEach((row) => { if(row.type !== "group"){ row.modules.vdomHoz.leftCol = this.leftCol; } }); }else { working = false; } }else { working = false; } } if(changes){ this.tableElement.style.paddingLeft = this.vDomPadLeft + "px"; } } fitDataColActualWidthCheck(column){ var newWidth, widthDiff; if(column.modules.vdomHoz.fitDataCheck){ column.reinitializeWidth(); newWidth = column.getWidth(); widthDiff = newWidth - column.modules.vdomHoz.width; if(widthDiff){ column.modules.vdomHoz.rightPos += widthDiff; column.modules.vdomHoz.width = newWidth; this.colPositionAdjust(this.columns.indexOf(column) + 1, this.columns.length, widthDiff); } column.modules.vdomHoz.fitDataCheck = false; } return widthDiff; } initializeRow(row){ if(row.type !== "group"){ row.modules.vdomHoz = { leftCol:this.leftCol, rightCol:this.rightCol, }; if(this.table.modules.frozenColumns){ this.table.modules.frozenColumns.leftColumns.forEach((column) => { this.appendCell(row, column); }); } for(let i = this.leftCol; i <= this.rightCol; i++){ this.appendCell(row, this.columns[i]); } if(this.table.modules.frozenColumns){ this.table.modules.frozenColumns.rightColumns.forEach((column) => { this.appendCell(row, column); }); } } } appendCell(row, column){ if(column && column.visible){ let cell = row.getCell(column); row.getElement().appendChild(cell.getElement()); cell.cellRendered(); } } reinitializeRow(row, force){ if(row.type !== "group"){ if(force || !row.modules.vdomHoz || row.modules.vdomHoz.leftCol !== this.leftCol || row.modules.vdomHoz.rightCol !== this.rightCol){ var rowEl = row.getElement(); while(rowEl.firstChild) rowEl.removeChild(rowEl.firstChild); this.initializeRow(row); } } } } class ColumnManager extends CoreFeature { constructor (table){ super(table); this.blockHozScrollEvent = false; this.headersElement = null; this.contentsElement = null; this.element = null ; //containing element this.columns = []; // column definition object this.columnsByIndex = []; //columns by index this.columnsByField = {}; //columns by field this.scrollLeft = 0; this.optionsList = new OptionsList(this.table, "column definition", defaultColumnOptions); this.redrawBlock = false; //prevent redraws to allow multiple data manipulations before continuing this.redrawBlockUpdate = null; //store latest redraw update only status this.renderer = null; } ////////////// Setup Functions ///////////////// initialize(){ this.initializeRenderer(); this.headersElement = this.createHeadersElement(); this.contentsElement = this.createHeaderContentsElement(); this.element = this.createHeaderElement(); this.contentsElement.insertBefore(this.headersElement, this.contentsElement.firstChild); this.element.insertBefore(this.contentsElement, this.element.firstChild); this.subscribe("scroll-horizontal", this.scrollHorizontal.bind(this)); this.subscribe("scrollbar-vertical", this.padVerticalScrollbar.bind(this)); } padVerticalScrollbar(width){ if(this.table.rtl){ this.headersElement.style.marginLeft = width + "px"; }else { this.headersElement.style.marginRight = width + "px"; } } initializeRenderer(){ var renderClass; var renderers = { "virtual": VirtualDomHorizontal, "basic": BasicHorizontal, }; if(typeof this.table.options.renderHorizontal === "string"){ renderClass = renderers[this.table.options.renderHorizontal]; }else { renderClass = this.table.options.renderHorizontal; } if(renderClass){ this.renderer = new renderClass(this.table, this.element, this.tableElement); this.renderer.initialize(); }else { console.error("Unable to find matching renderer:", this.table.options.renderHorizontal); } } createHeadersElement (){ var el = document.createElement("div"); el.classList.add("tabulator-headers"); el.setAttribute("role", "row"); return el; } createHeaderContentsElement (){ var el = document.createElement("div"); el.classList.add("tabulator-header-contents"); el.setAttribute("role", "rowgroup"); return el; } createHeaderElement (){ var el = document.createElement("div"); el.classList.add("tabulator-header"); el.setAttribute("role", "rowgroup"); if(!this.table.options.headerVisible){ el.classList.add("tabulator-header-hidden"); } return el; } //return containing element getElement(){ return this.element; } //return containing contents element getContentsElement(){ return this.contentsElement; } //return header containing element getHeadersElement(){ return this.headersElement; } //scroll horizontally to match table body scrollHorizontal(left){ this.contentsElement.scrollLeft = left; this.scrollLeft = left; this.renderer.scrollColumns(left); } ///////////// Column Setup Functions ///////////// generateColumnsFromRowData(data){ var cols = [], definitions = this.table.options.autoColumnsDefinitions, row, sorter; if(data && data.length){ row = data[0]; for(var key in row){ let col = { field:key, title:key, }; let value = row[key]; switch(typeof value){ case "undefined": sorter = "string"; break; case "boolean": sorter = "boolean"; break; case "object": if(Array.isArray(value)){ sorter = "array"; }else { sorter = "string"; } break; default: if(!isNaN(value) && value !== ""){ sorter = "number"; }else { if(value.match(/((^[0-9]+[a-z]+)|(^[a-z]+[0-9]+))+$/i)){ sorter = "alphanum"; }else { sorter = "string"; } } break; } col.sorter = sorter; cols.push(col); } if(definitions){ switch(typeof definitions){ case "function": this.table.options.columns = definitions.call(this.table, cols); break; case "object": if(Array.isArray(definitions)){ cols.forEach((col) => { var match = definitions.find((def) => { return def.field === col.field; }); if(match){ Object.assign(col, match); } }); }else { cols.forEach((col) => { if(definitions[col.field]){ Object.assign(col, definitions[col.field]); } }); } this.table.options.columns = cols; break; } }else { this.table.options.columns = cols; } this.setColumns(this.table.options.columns); } } setColumns(cols, row){ while(this.headersElement.firstChild) this.headersElement.removeChild(this.headersElement.firstChild); this.columns = []; this.columnsByIndex = []; this.columnsByField = {}; this.dispatch("columns-loading"); cols.forEach((def, i) => { this._addColumn(def); }); this._reIndexColumns(); this.dispatch("columns-loaded"); this.rerenderColumns(false, true); this.redraw(true); } _addColumn(definition, before, nextToColumn){ var column = new Column(definition, this), colEl = column.getElement(), index = nextToColumn ? this.findColumnIndex(nextToColumn) : nextToColumn; if(nextToColumn && index > -1){ var topColumn = nextToColumn.getTopColumn(); var parentIndex = this.columns.indexOf(topColumn); var nextEl = topColumn.getElement(); if(before){ this.columns.splice(parentIndex, 0, column); nextEl.parentNode.insertBefore(colEl, nextEl); }else { this.columns.splice(parentIndex + 1, 0, column); nextEl.parentNode.insertBefore(colEl, nextEl.nextSibling); } }else { if(before){ this.columns.unshift(column); this.headersElement.insertBefore(column.getElement(), this.headersElement.firstChild); }else { this.columns.push(column); this.headersElement.appendChild(column.getElement()); } } column.columnRendered(); return column; } registerColumnField(col){ if(col.definition.field){ this.columnsByField[col.definition.field] = col; } } registerColumnPosition(col){ this.columnsByIndex.push(col); } _reIndexColumns(){ this.columnsByIndex = []; this.columns.forEach(function(column){ column.reRegisterPosition(); }); } //ensure column headers take up the correct amount of space in column groups verticalAlignHeaders(){ var minHeight = 0; if(!this.redrawBlock){ this.headersElement.style.height=""; this.columns.forEach((column) => { column.clearVerticalAlign(); }); this.columns.forEach((column) => { var height = column.getHeight(); if(height > minHeight){ minHeight = height; } }); this.headersElement.style.height = minHeight + "px"; this.columns.forEach((column) => { column.verticalAlign(this.table.options.columnHeaderVertAlign, minHeight); }); this.table.rowManager.adjustTableSize(); } } //////////////// Column Details ///////////////// findColumn(subject){ var columns; if(typeof subject == "object"){ if(subject instanceof Column){ //subject is column element return subject; }else if(subject instanceof ColumnComponent){ //subject is public column component return subject._getSelf() || false; }else if(typeof HTMLElement !== "undefined" && subject instanceof HTMLElement){ columns = []; this.columns.forEach((column) => { columns.push(column); columns = columns.concat(column.getColumns(true)); }); //subject is a HTML element of the column header let match = columns.find((column) => { return column.element === subject; }); return match || false; } }else { //subject should be treated as the field name of the column return this.columnsByField[subject] || false; } //catch all for any other type of input return false; } getColumnByField(field){ return this.columnsByField[field]; } getColumnsByFieldRoot(root){ var matches = []; Object.keys(this.columnsByField).forEach((field) => { var fieldRoot = field.split(".")[0]; if(fieldRoot === root){ matches.push(this.columnsByField[field]); } }); return matches; } getColumnByIndex(index){ return this.columnsByIndex[index]; } getFirstVisibleColumn(){ var index = this.columnsByIndex.findIndex((col) => { return col.visible; }); return index > -1 ? this.columnsByIndex[index] : false; } getColumns(){ return this.columns; } findColumnIndex(column){ return this.columnsByIndex.findIndex((col) => { return column === col; }); } //return all columns that are not groups getRealColumns(){ return this.columnsByIndex; } //traverse across columns and call action traverse(callback){ this.columnsByIndex.forEach((column,i) =>{ callback(column, i); }); } //get definitions of actual columns getDefinitions(active){ var output = []; this.columnsByIndex.forEach((column) => { if(!active || (active && column.visible)){ output.push(column.getDefinition()); } }); return output; } //get full nested definition tree getDefinitionTree(){ var output = []; this.columns.forEach((column) => { output.push(column.getDefinition(true)); }); return output; } getComponents(structured){ var output = [], columns = structured ? this.columns : this.columnsByIndex; columns.forEach((column) => { output.push(column.getComponent()); }); return output; } getWidth(){ var width = 0; this.columnsByIndex.forEach((column) => { if(column.visible){ width += column.getWidth(); } }); return width; } moveColumn(from, to, after){ to.element.parentNode.insertBefore(from.element, to.element); if(after){ to.element.parentNode.insertBefore(to.element, from.element); } this.moveColumnActual(from, to, after); this.verticalAlignHeaders(); this.table.rowManager.reinitialize(); } moveColumnActual(from, to, after){ if(from.parent.isGroup){ this._moveColumnInArray(from.parent.columns, from, to, after); }else { this._moveColumnInArray(this.columns, from, to, after); } this._moveColumnInArray(this.columnsByIndex, from, to, after, true); this.rerenderColumns(true); this.dispatch("column-moved", from, to, after); if(this.subscribedExternal("columnMoved")){ this.dispatchExternal("columnMoved", from.getComponent(), this.table.columnManager.getComponents()); } } _moveColumnInArray(columns, from, to, after, updateRows){ var fromIndex = columns.indexOf(from), toIndex, rows = []; if (fromIndex > -1) { columns.splice(fromIndex, 1); toIndex = columns.indexOf(to); if (toIndex > -1) { if(after){ toIndex = toIndex+1; } }else { toIndex = fromIndex; } columns.splice(toIndex, 0, from); if(updateRows){ rows = this.chain("column-moving-rows", [from, to, after], null, []) || []; rows = rows.concat(this.table.rowManager.rows); rows.forEach(function(row){ if(row.cells.length){ var cell = row.cells.splice(fromIndex, 1)[0]; row.cells.splice(toIndex, 0, cell); } }); } } } scrollToColumn(column, position, ifVisible){ var left = 0, offset = column.getLeftOffset(), adjust = 0, colEl = column.getElement(); return new Promise((resolve, reject) => { if(typeof position === "undefined"){ position = this.table.options.scrollToColumnPosition; } if(typeof ifVisible === "undefined"){ ifVisible = this.table.options.scrollToColumnIfVisible; } if(column.visible){ //align to correct position switch(position){ case "middle": case "center": adjust = -this.element.clientWidth / 2; break; case "right": adjust = colEl.clientWidth - this.headersElement.clientWidth; break; } //check column visibility if(!ifVisible){ if(offset > 0 && offset + colEl.offsetWidth < this.element.clientWidth){ return false; } } //calculate scroll position left = offset + adjust; left = Math.max(Math.min(left, this.table.rowManager.element.scrollWidth - this.table.rowManager.element.clientWidth),0); this.table.rowManager.scrollHorizontal(left); this.scrollHorizontal(left); resolve(); }else { console.warn("Scroll Error - Column not visible"); reject("Scroll Error - Column not visible"); } }); } //////////////// Cell Management ///////////////// generateCells(row){ var cells = []; this.columnsByIndex.forEach((column) => { cells.push(column.generateCell(row)); }); return cells; } //////////////// Column Management ///////////////// getFlexBaseWidth(){ var totalWidth = this.table.element.clientWidth, //table element width fixedWidth = 0; //adjust for vertical scrollbar if present if(this.table.rowManager.element.scrollHeight > this.table.rowManager.element.clientHeight){ totalWidth -= this.table.rowManager.element.offsetWidth - this.table.rowManager.element.clientWidth; } this.columnsByIndex.forEach(function(column){ var width, minWidth, colWidth; if(column.visible){ width = column.definition.width || 0; minWidth = parseInt(column.minWidth); if(typeof(width) == "string"){ if(width.indexOf("%") > -1){ colWidth = (totalWidth / 100) * parseInt(width) ; }else { colWidth = parseInt(width); } }else { colWidth = width; } fixedWidth += colWidth > minWidth ? colWidth : minWidth; } }); return fixedWidth; } addColumn(definition, before, nextToColumn){ return new Promise((resolve, reject) => { var column = this._addColumn(definition, before, nextToColumn); this._reIndexColumns(); this.dispatch("column-add", definition, before, nextToColumn); if(this.layoutMode() != "fitColumns"){ column.reinitializeWidth(); } this.redraw(true); this.table.rowManager.reinitialize(); this.rerenderColumns(); resolve(column); }); } //remove column from system deregisterColumn(column){ var field = column.getField(), index; //remove from field list if(field){ delete this.columnsByField[field]; } //remove from index list index = this.columnsByIndex.indexOf(column); if(index > -1){ this.columnsByIndex.splice(index, 1); } //remove from column list index = this.columns.indexOf(column); if(index > -1){ this.columns.splice(index, 1); } this.verticalAlignHeaders(); this.redraw(); } rerenderColumns(update, silent){ if(!this.redrawBlock){ this.renderer.rerenderColumns(update, silent); }else { if(update === false || (update === true && this.redrawBlockUpdate === null)){ this.redrawBlockUpdate = update; } } } blockRedraw(){ this.redrawBlock = true; this.redrawBlockUpdate = null; } restoreRedraw(){ this.redrawBlock = false; this.verticalAlignHeaders(); this.renderer.rerenderColumns(this.redrawBlockUpdate); } //redraw columns redraw(force){ if(Helpers.elVisible(this.element)){ this.verticalAlignHeaders(); } if(force){ this.table.rowManager.resetScroll(); this.table.rowManager.reinitialize(); } if(!this.confirm("table-redrawing", force)){ this.layoutRefresh(force); } this.dispatch("table-redraw", force); this.table.footerManager.redraw(); } } class BasicVertical extends Renderer{ constructor(table){ super(table); this.verticalFillMode = "fill"; this.scrollTop = 0; this.scrollLeft = 0; this.scrollTop = 0; this.scrollLeft = 0; } clearRows(){ var element = this.tableElement; // element.children.detach(); while(element.firstChild) element.removeChild(element.firstChild); element.scrollTop = 0; element.scrollLeft = 0; element.style.minWidth = ""; element.style.minHeight = ""; element.style.display = ""; element.style.visibility = ""; } renderRows(){ var element = this.tableElement, onlyGroupHeaders = true; this.rows().forEach((row, index) => { this.styleRow(row, index); element.appendChild(row.getElement()); row.initialize(true); if(row.type !== "group"){ onlyGroupHeaders = false; } }); if(onlyGroupHeaders){ element.style.minWidth = this.table.columnManager.getWidth() + "px"; }else { element.style.minWidth = ""; } } rerenderRows(callback){ this.clearRows(); if(callback){ callback(); } this.renderRows(); } scrollToRowNearestTop(row){ var rowTop = Helpers.elOffset(row.getElement()).top; return !(Math.abs(this.elementVertical.scrollTop - rowTop) > Math.abs(this.elementVertical.scrollTop + this.elementVertical.clientHeight - rowTop)); } scrollToRow(row){ var rowEl = row.getElement(); this.elementVertical.scrollTop = Helpers.elOffset(rowEl).top - Helpers.elOffset(this.elementVertical).top + this.elementVertical.scrollTop; } visibleRows(includingBuffer){ return this.rows(); } } class VirtualDomVertical extends Renderer{ constructor(table){ super(table); this.verticalFillMode = "fill"; this.scrollTop = 0; this.scrollLeft = 0; this.vDomRowHeight = 20; //approximation of row heights for padding this.vDomTop = 0; //hold position for first rendered row in the virtual DOM this.vDomBottom = 0; //hold position for last rendered row in the virtual DOM this.vDomScrollPosTop = 0; //last scroll position of the vDom top; this.vDomScrollPosBottom = 0; //last scroll position of the vDom bottom; this.vDomTopPad = 0; //hold value of padding for top of virtual DOM this.vDomBottomPad = 0; //hold value of padding for bottom of virtual DOM this.vDomMaxRenderChain = 90; //the maximum number of dom elements that can be rendered in 1 go this.vDomWindowBuffer = 0; //window row buffer before removing elements, to smooth scrolling this.vDomWindowMinTotalRows = 20; //minimum number of rows to be generated in virtual dom (prevent buffering issues on tables with tall rows) this.vDomWindowMinMarginRows = 5; //minimum number of rows to be generated in virtual dom margin this.vDomTopNewRows = []; //rows to normalize after appending to optimize render speed this.vDomBottomNewRows = []; //rows to normalize after appending to optimize render speed } ////////////////////////////////////// ///////// Public Functions /////////// ////////////////////////////////////// clearRows(){ var element = this.tableElement; // element.children.detach(); while(element.firstChild) element.removeChild(element.firstChild); element.style.paddingTop = ""; element.style.paddingBottom = ""; // element.style.minWidth = ""; element.style.minHeight = ""; element.style.display = ""; element.style.visibility = ""; this.elementVertical.scrollTop = 0; this.elementVertical.scrollLeft = 0; this.scrollTop = 0; this.scrollLeft = 0; this.vDomTop = 0; this.vDomBottom = 0; this.vDomTopPad = 0; this.vDomBottomPad = 0; this.vDomScrollPosTop = 0; this.vDomScrollPosBottom = 0; } renderRows(){ this._virtualRenderFill(); } rerenderRows(callback){ var scrollTop = this.elementVertical.scrollTop; var topRow = false; var topOffset = false; var left = this.table.rowManager.scrollLeft; var rows = this.rows(); for(var i = this.vDomTop; i <= this.vDomBottom; i++){ if(rows[i]){ var diff = scrollTop - rows[i].getElement().offsetTop; if(topOffset === false || Math.abs(diff) < topOffset){ topOffset = diff; topRow = i; }else { break; } } } rows.forEach((row) => { row.deinitializeHeight(); }); if(callback){ callback(); } if(this.rows().length){ this._virtualRenderFill((topRow === false ? this.rows.length - 1 : topRow), true, topOffset || 0); }else { this.clear(); this.table.rowManager.tableEmpty(); } this.scrollColumns(left); } scrollColumns(left){ this.table.rowManager.scrollHorizontal(left); } scrollRows(top, dir){ var topDiff = top - this.vDomScrollPosTop; var bottomDiff = top - this.vDomScrollPosBottom; var margin = this.vDomWindowBuffer * 2; var rows = this.rows(); this.scrollTop = top; if(-topDiff > margin || bottomDiff > margin){ //if big scroll redraw table; var left = this.table.rowManager.scrollLeft; this._virtualRenderFill(Math.floor((this.elementVertical.scrollTop / this.elementVertical.scrollHeight) * rows.length)); this.scrollColumns(left); }else { if(dir){ //scrolling up if(topDiff < 0){ this._addTopRow(rows, -topDiff); } if(bottomDiff < 0){ //hide bottom row if needed if(this.vDomScrollHeight - this.scrollTop > this.vDomWindowBuffer){ this._removeBottomRow(rows, -bottomDiff); }else { this.vDomScrollPosBottom = this.scrollTop; } } }else { if(bottomDiff >= 0){ this._addBottomRow(rows, bottomDiff); } //scrolling down if(topDiff >= 0){ //hide top row if needed if(this.scrollTop > this.vDomWindowBuffer){ this._removeTopRow(rows, topDiff); }else { this.vDomScrollPosTop = this.scrollTop; } } } } } resize(){ this.vDomWindowBuffer = this.table.options.renderVerticalBuffer || this.elementVertical.clientHeight; } scrollToRowNearestTop(row){ var rowIndex = this.rows().indexOf(row); return !(Math.abs(this.vDomTop - rowIndex) > Math.abs(this.vDomBottom - rowIndex)); } scrollToRow(row){ var index = this.rows().indexOf(row); if(index > -1){ this._virtualRenderFill(index, true); } } visibleRows(includingBuffer){ var topEdge = this.elementVertical.scrollTop, bottomEdge = this.elementVertical.clientHeight + topEdge, topFound = false, topRow = 0, bottomRow = 0, rows = this.rows(); if(includingBuffer){ topRow = this.vDomTop; bottomRow = this.vDomBottom; }else { for(var i = this.vDomTop; i <= this.vDomBottom; i++){ if(rows[i]){ if(!topFound){ if((topEdge - rows[i].getElement().offsetTop) >= 0){ topRow = i; }else { topFound = true; if(bottomEdge - rows[i].getElement().offsetTop >= 0){ bottomRow = i; }else { break; } } }else { if(bottomEdge - rows[i].getElement().offsetTop >= 0){ bottomRow = i; }else { break; } } } } } return rows.slice(topRow, bottomRow + 1); } ////////////////////////////////////// //////// Internal Rendering ////////// ////////////////////////////////////// //full virtual render _virtualRenderFill(position, forceMove, offset){ var element = this.tableElement, holder = this.elementVertical, topPad = 0, rowsHeight = 0, heightOccupied = 0, topPadHeight = 0, i = 0, rows = this.rows(), rowsCount = rows.length, containerHeight = this.elementVertical.clientHeight; position = position || 0; offset = offset || 0; if(!position){ this.clear(); }else { while(element.firstChild) element.removeChild(element.firstChild); //check if position is too close to bottom of table heightOccupied = (rowsCount - position + 1) * this.vDomRowHeight; if(heightOccupied < containerHeight){ position -= Math.ceil((containerHeight - heightOccupied) / this.vDomRowHeight); if(position < 0){ position = 0; } } //calculate initial pad topPad = Math.min(Math.max(Math.floor(this.vDomWindowBuffer / this.vDomRowHeight), this.vDomWindowMinMarginRows), position); position -= topPad; } if(rowsCount && Helpers.elVisible(this.elementVertical)){ this.vDomTop = position; this.vDomBottom = position -1; while ((rowsHeight <= containerHeight + this.vDomWindowBuffer || i < this.vDomWindowMinTotalRows) && this.vDomBottom < rowsCount -1){ var index = this.vDomBottom + 1, row = rows[index], rowHeight = 0; this.styleRow(row, index); element.appendChild(row.getElement()); row.initialize(); if(!row.heightInitialized){ row.normalizeHeight(true); } rowHeight = row.getHeight(); if(i < topPad){ topPadHeight += rowHeight; }else { rowsHeight += rowHeight; } if(rowHeight > this.vDomWindowBuffer){ this.vDomWindowBuffer = rowHeight * 2; } this.vDomBottom ++; i++; } if(!position){ this.vDomTopPad = 0; //adjust row height to match average of rendered elements this.vDomRowHeight = Math.floor((rowsHeight + topPadHeight) / i); this.vDomBottomPad = this.vDomRowHeight * (rowsCount - this.vDomBottom -1); this.vDomScrollHeight = topPadHeight + rowsHeight + this.vDomBottomPad - containerHeight; }else { this.vDomTopPad = !forceMove ? this.scrollTop - topPadHeight : (this.vDomRowHeight * this.vDomTop) + offset; this.vDomBottomPad = this.vDomBottom == rowsCount-1 ? 0 : Math.max(this.vDomScrollHeight - this.vDomTopPad - rowsHeight - topPadHeight, 0); } element.style.paddingTop = this.vDomTopPad + "px"; element.style.paddingBottom = this.vDomBottomPad + "px"; if(forceMove){ this.scrollTop = this.vDomTopPad + (topPadHeight) + offset - (this.elementVertical.scrollWidth > this.elementVertical.clientWidth ? this.elementVertical.offsetHeight - containerHeight : 0); } this.scrollTop = Math.min(this.scrollTop, this.elementVertical.scrollHeight - containerHeight); //adjust for horizontal scrollbar if present (and not at top of table) if(this.elementVertical.scrollWidth > this.elementVertical.clientWidth && forceMove){ this.scrollTop += this.elementVertical.offsetHeight - containerHeight; } this.vDomScrollPosTop = this.scrollTop; this.vDomScrollPosBottom = this.scrollTop; holder.scrollTop = this.scrollTop; this.dispatch("render-virtual-fill"); } } _addTopRow(rows, fillableSpace){ var table = this.tableElement, addedRows = [], paddingAdjust = 0, index = this.vDomTop -1, i = 0, working = true; while(working){ if(this.vDomTop){ let row = rows[index], rowHeight, initialized; if(row && i < this.vDomMaxRenderChain){ rowHeight = row.getHeight() || this.vDomRowHeight; initialized = row.initialized; if(fillableSpace >= rowHeight){ this.styleRow(row, index); table.insertBefore(row.getElement(), table.firstChild); if(!row.initialized || !row.heightInitialized){ addedRows.push(row); } row.initialize(); if(!initialized){ rowHeight = row.getElement().offsetHeight; if(rowHeight > this.vDomWindowBuffer){ this.vDomWindowBuffer = rowHeight * 2; } } fillableSpace -= rowHeight; paddingAdjust += rowHeight; this.vDomTop--; index--; i++; }else { working = false; } }else { working = false; } }else { working = false; } } for (let row of addedRows){ row.clearCellHeight(); } this._quickNormalizeRowHeight(addedRows); if(paddingAdjust){ this.vDomTopPad -= paddingAdjust; if(this.vDomTopPad < 0){ this.vDomTopPad = index * this.vDomRowHeight; } if(index < 1){ this.vDomTopPad = 0; } table.style.paddingTop = this.vDomTopPad + "px"; this.vDomScrollPosTop -= paddingAdjust; } } _removeTopRow(rows, fillableSpace){ var removableRows = [], paddingAdjust = 0, i = 0, working = true; while(working){ let row = rows[this.vDomTop], rowHeight; if(row && i < this.vDomMaxRenderChain){ rowHeight = row.getHeight() || this.vDomRowHeight; if(fillableSpace >= rowHeight){ this.vDomTop++; fillableSpace -= rowHeight; paddingAdjust += rowHeight; removableRows.push(row); i++; }else { working = false; } }else { working = false; } } for (let row of removableRows){ let rowEl = row.getElement(); if(rowEl.parentNode){ rowEl.parentNode.removeChild(rowEl); } } if(paddingAdjust){ this.vDomTopPad += paddingAdjust; this.tableElement.style.paddingTop = this.vDomTopPad + "px"; this.vDomScrollPosTop += this.vDomTop ? paddingAdjust : paddingAdjust + this.vDomWindowBuffer; } } _addBottomRow(rows, fillableSpace){ var table = this.tableElement, addedRows = [], paddingAdjust = 0, index = this.vDomBottom + 1, i = 0, working = true; while(working){ let row = rows[index], rowHeight, initialized; if(row && i < this.vDomMaxRenderChain){ rowHeight = row.getHeight() || this.vDomRowHeight; initialized = row.initialized; if(fillableSpace >= rowHeight){ this.styleRow(row, index); table.appendChild(row.getElement()); if(!row.initialized || !row.heightInitialized){ addedRows.push(row); } row.initialize(); if(!initialized){ rowHeight = row.getElement().offsetHeight; if(rowHeight > this.vDomWindowBuffer){ this.vDomWindowBuffer = rowHeight * 2; } } fillableSpace -= rowHeight; paddingAdjust += rowHeight; this.vDomBottom++; index++; i++; }else { working = false; } }else { working = false; } } for (let row of addedRows){ row.clearCellHeight(); } this._quickNormalizeRowHeight(addedRows); if(paddingAdjust){ this.vDomBottomPad -= paddingAdjust; if(this.vDomBottomPad < 0 || index == rows.length -1){ this.vDomBottomPad = 0; } table.style.paddingBottom = this.vDomBottomPad + "px"; this.vDomScrollPosBottom += paddingAdjust; } } _removeBottomRow(rows, fillableSpace){ var removableRows = [], paddingAdjust = 0, i = 0, working = true; while(working){ let row = rows[this.vDomBottom], rowHeight; if(row && i < this.vDomMaxRenderChain){ rowHeight = row.getHeight() || this.vDomRowHeight; if(fillableSpace >= rowHeight){ this.vDomBottom --; fillableSpace -= rowHeight; paddingAdjust += rowHeight; removableRows.push(row); i++; }else { working = false; } }else { working = false; } } for (let row of removableRows){ let rowEl = row.getElement(); if(rowEl.parentNode){ rowEl.parentNode.removeChild(rowEl); } } if(paddingAdjust){ this.vDomBottomPad += paddingAdjust; if(this.vDomBottomPad < 0){ this.vDomBottomPad = 0; } this.tableElement.style.paddingBottom = this.vDomBottomPad + "px"; this.vDomScrollPosBottom -= paddingAdjust; } } _quickNormalizeRowHeight(rows){ for(let row of rows){ row.calcHeight(); } for(let row of rows){ row.setCellHeight(); } } } class RowManager extends CoreFeature{ constructor(table){ super(table); this.element = this.createHolderElement(); //containing element this.tableElement = this.createTableElement(); //table element this.heightFixer = this.createTableElement(); //table element this.placeholder = null; //placeholder element this.placeholderContents = null; //placeholder element this.firstRender = false; //handle first render this.renderMode = "virtual"; //current rendering mode this.fixedHeight = false; //current rendering mode this.rows = []; //hold row data objects this.activeRowsPipeline = []; //hold calculation of active rows this.activeRows = []; //rows currently available to on display in the table this.activeRowsCount = 0; //count of active rows this.displayRows = []; //rows currently on display in the table this.displayRowsCount = 0; //count of display rows this.scrollTop = 0; this.scrollLeft = 0; this.redrawBlock = false; //prevent redraws to allow multiple data manipulations before continuing this.redrawBlockRestoreConfig = false; //store latest redraw function calls for when redraw is needed this.redrawBlockRenderInPosition = false; //store latest redraw function calls for when redraw is needed this.dataPipeline = []; //hold data pipeline tasks this.displayPipeline = []; //hold data display pipeline tasks this.scrollbarWidth = 0; this.renderer = null; } //////////////// Setup Functions ///////////////// createHolderElement (){ var el = document.createElement("div"); el.classList.add("tabulator-tableholder"); el.setAttribute("tabindex", 0); // el.setAttribute("role", "rowgroup"); return el; } createTableElement (){ var el = document.createElement("div"); el.classList.add("tabulator-table"); el.setAttribute("role", "rowgroup"); return el; } initializePlaceholder(){ var placeholder = this.table.options.placeholder; //configure placeholder element if(placeholder){ let el = document.createElement("div"); el.classList.add("tabulator-placeholder"); if(typeof placeholder == "string"){ let contents = document.createElement("div"); contents.classList.add("tabulator-placeholder-contents"); contents.innerHTML = placeholder; el.appendChild(contents); this.placeholderContents = contents; }else if(typeof HTMLElement !== "undefined" && placeholder instanceof HTMLElement){ el.appendChild(placeholder); this.placeholderContents = placeholder; }else { console.warn("Invalid placeholder provided, must be string or HTML Element", placeholder); this.el = null; } this.placeholder = el; } } //return containing element getElement(){ return this.element; } //return table element getTableElement(){ return this.tableElement; } initialize(){ this.initializePlaceholder(); this.initializeRenderer(); //initialize manager this.element.appendChild(this.tableElement); this.firstRender = true; //scroll header along with table body this.element.addEventListener("scroll", () => { var left = this.element.scrollLeft, leftDir = this.scrollLeft > left, top = this.element.scrollTop, topDir = this.scrollTop > top; //handle horizontal scrolling if(this.scrollLeft != left){ this.scrollLeft = left; this.dispatch("scroll-horizontal", left, leftDir); this.dispatchExternal("scrollHorizontal", left, leftDir); this._positionPlaceholder(); } //handle vertical scrolling if(this.scrollTop != top){ this.scrollTop = top; this.renderer.scrollRows(top, topDir); this.dispatch("scroll-vertical", top, topDir); this.dispatchExternal("scrollVertical", top, topDir); } }); } ////////////////// Row Manipulation ////////////////// findRow(subject){ if(typeof subject == "object"){ if(subject instanceof Row){ //subject is row element return subject; }else if(subject instanceof RowComponent){ //subject is public row component return subject._getSelf() || false; }else if(typeof HTMLElement !== "undefined" && subject instanceof HTMLElement){ //subject is a HTML element of the row let match = this.rows.find((row) => { return row.getElement() === subject; }); return match || false; }else if(subject === null){ return false; } }else if(typeof subject == "undefined"){ return false; }else { //subject should be treated as the index of the row let match = this.rows.find((row) => { return row.data[this.table.options.index] == subject; }); return match || false; } //catch all for any other type of input return false; } getRowFromDataObject(data){ var match = this.rows.find((row) => { return row.data === data; }); return match || false; } getRowFromPosition(position){ return this.getDisplayRows().find((row) => { return row.getPosition() === position && row.isDisplayed(); }); } scrollToRow(row, position, ifVisible){ return this.renderer.scrollToRowPosition(row, position, ifVisible); } ////////////////// Data Handling ////////////////// setData(data, renderInPosition, columnsChanged){ return new Promise((resolve, reject)=>{ if(renderInPosition && this.getDisplayRows().length){ if(this.table.options.pagination){ this._setDataActual(data, true); }else { this.reRenderInPosition(() => { this._setDataActual(data); }); } }else { if(this.table.options.autoColumns && columnsChanged && this.table.initialized){ this.table.columnManager.generateColumnsFromRowData(data); } this.resetScroll(); this._setDataActual(data); } resolve(); }); } _setDataActual(data, renderInPosition){ this.dispatchExternal("dataProcessing", data); this._wipeElements(); if(Array.isArray(data)){ this.dispatch("data-processing", data); data.forEach((def, i) => { if(def && typeof def === "object"){ var row = new Row(def, this); this.rows.push(row); }else { console.warn("Data Loading Warning - Invalid row data detected and ignored, expecting object but received:", def); } }); this.refreshActiveData(false, false, renderInPosition); this.dispatch("data-processed", data); this.dispatchExternal("dataProcessed", data); }else { console.error("Data Loading Error - Unable to process data due to invalid data type \nExpecting: array \nReceived: ", typeof data, "\nData: ", data); } } _wipeElements(){ this.dispatch("rows-wipe"); this.destroy(); this.adjustTableSize(); this.dispatch("rows-wiped"); } destroy(){ this.rows.forEach((row) => { row.wipe(); }); this.rows = []; this.activeRows = []; this.activeRowsPipeline = []; this.activeRowsCount = 0; this.displayRows = []; this.displayRowsCount = 0; } deleteRow(row, blockRedraw){ var allIndex = this.rows.indexOf(row), activeIndex = this.activeRows.indexOf(row); if(activeIndex > -1){ this.activeRows.splice(activeIndex, 1); } if(allIndex > -1){ this.rows.splice(allIndex, 1); } this.setActiveRows(this.activeRows); this.displayRowIterator((rows) => { var displayIndex = rows.indexOf(row); if(displayIndex > -1){ rows.splice(displayIndex, 1); } }); if(!blockRedraw){ this.reRenderInPosition(); } this.regenerateRowPositions(); this.dispatchExternal("rowDeleted", row.getComponent()); if(!this.displayRowsCount){ this.tableEmpty(); } if(this.subscribedExternal("dataChanged")){ this.dispatchExternal("dataChanged", this.getData()); } } addRow(data, pos, index, blockRedraw){ var row = this.addRowActual(data, pos, index, blockRedraw); return row; } //add multiple rows addRows(data, pos, index, refreshDisplayOnly){ var rows = []; return new Promise((resolve, reject) => { pos = this.findAddRowPos(pos); if(!Array.isArray(data)){ data = [data]; } if((typeof index == "undefined" && pos) || (typeof index !== "undefined" && !pos)){ data.reverse(); } data.forEach((item, i) => { var row = this.addRow(item, pos, index, true); rows.push(row); this.dispatch("row-added", row, item, pos, index); }); this.refreshActiveData(refreshDisplayOnly ? "displayPipeline" : false, false, true); this.regenerateRowPositions(); if(rows.length){ this._clearPlaceholder(); } resolve(rows); }); } findAddRowPos(pos){ if(typeof pos === "undefined"){ pos = this.table.options.addRowPos; } if(pos === "pos"){ pos = true; } if(pos === "bottom"){ pos = false; } return pos; } addRowActual(data, pos, index, blockRedraw){ var row = data instanceof Row ? data : new Row(data || {}, this), top = this.findAddRowPos(pos), allIndex = -1, activeIndex, chainResult; if(!index){ chainResult = this.chain("row-adding-position", [row, top], null, {index, top}); index = chainResult.index; top = chainResult.top; } if(typeof index !== "undefined"){ index = this.findRow(index); } index = this.chain("row-adding-index", [row, index, top], null, index); if(index){ allIndex = this.rows.indexOf(index); } if(index && allIndex > -1){ activeIndex = this.activeRows.indexOf(index); this.displayRowIterator(function(rows){ var displayIndex = rows.indexOf(index); if(displayIndex > -1){ rows.splice((top ? displayIndex : displayIndex + 1), 0, row); } }); if(activeIndex > -1){ this.activeRows.splice((top ? activeIndex : activeIndex + 1), 0, row); } this.rows.splice((top ? allIndex : allIndex + 1), 0, row); }else { if(top){ this.displayRowIterator(function(rows){ rows.unshift(row); }); this.activeRows.unshift(row); this.rows.unshift(row); }else { this.displayRowIterator(function(rows){ rows.push(row); }); this.activeRows.push(row); this.rows.push(row); } } this.setActiveRows(this.activeRows); this.dispatchExternal("rowAdded", row.getComponent()); if(this.subscribedExternal("dataChanged")){ this.dispatchExternal("dataChanged", this.table.rowManager.getData()); } if(!blockRedraw){ this.reRenderInPosition(); } return row; } moveRow(from, to, after){ this.dispatch("row-move", from, to, after); this.moveRowActual(from, to, after); this.regenerateRowPositions(); this.dispatch("row-moved", from, to, after); this.dispatchExternal("rowMoved", from.getComponent()); } moveRowActual(from, to, after){ this.moveRowInArray(this.rows, from, to, after); this.moveRowInArray(this.activeRows, from, to, after); this.displayRowIterator((rows) => { this.moveRowInArray(rows, from, to, after); }); this.dispatch("row-moving", from, to, after); } moveRowInArray(rows, from, to, after){ var fromIndex, toIndex, start, end; if(from !== to){ fromIndex = rows.indexOf(from); if (fromIndex > -1) { rows.splice(fromIndex, 1); toIndex = rows.indexOf(to); if (toIndex > -1) { if(after){ rows.splice(toIndex+1, 0, from); }else { rows.splice(toIndex, 0, from); } }else { rows.splice(fromIndex, 0, from); } } //restyle rows if(rows === this.getDisplayRows()){ start = fromIndex < toIndex ? fromIndex : toIndex; end = toIndex > fromIndex ? toIndex : fromIndex +1; for(let i = start; i <= end; i++){ if(rows[i]){ this.styleRow(rows[i], i); } } } } } clearData(){ this.setData([]); } getRowIndex(row){ return this.findRowIndex(row, this.rows); } getDisplayRowIndex(row){ var index = this.getDisplayRows().indexOf(row); return index > -1 ? index : false; } nextDisplayRow(row, rowOnly){ var index = this.getDisplayRowIndex(row), nextRow = false; if(index !== false && index < this.displayRowsCount -1){ nextRow = this.getDisplayRows()[index+1]; } if(nextRow && (!(nextRow instanceof Row) || nextRow.type != "row")){ return this.nextDisplayRow(nextRow, rowOnly); } return nextRow; } prevDisplayRow(row, rowOnly){ var index = this.getDisplayRowIndex(row), prevRow = false; if(index){ prevRow = this.getDisplayRows()[index-1]; } if(rowOnly && prevRow && (!(prevRow instanceof Row) || prevRow.type != "row")){ return this.prevDisplayRow(prevRow, rowOnly); } return prevRow; } findRowIndex(row, list){ var rowIndex; row = this.findRow(row); if(row){ rowIndex = list.indexOf(row); if(rowIndex > -1){ return rowIndex; } } return false; } getData(active, transform){ var output = [], rows = this.getRows(active); rows.forEach(function(row){ if(row.type == "row"){ output.push(row.getData(transform || "data")); } }); return output; } getComponents(active){ var output = [], rows = this.getRows(active); rows.forEach(function(row){ output.push(row.getComponent()); }); return output; } getDataCount(active){ var rows = this.getRows(active); return rows.length; } scrollHorizontal(left){ this.scrollLeft = left; this.element.scrollLeft = left; this.dispatch("scroll-horizontal", left); } registerDataPipelineHandler(handler, priority){ if(typeof priority !== "undefined"){ this.dataPipeline.push({handler, priority}); this.dataPipeline.sort((a, b) => { return a.priority - b.priority; }); }else { console.error("Data pipeline handlers must have a priority in order to be registered"); } } registerDisplayPipelineHandler(handler, priority){ if(typeof priority !== "undefined"){ this.displayPipeline.push({handler, priority}); this.displayPipeline.sort((a, b) => { return a.priority - b.priority; }); }else { console.error("Display pipeline handlers must have a priority in order to be registered"); } } //set active data set refreshActiveData(handler, skipStage, renderInPosition){ var table = this.table, stage = "", index = 0, cascadeOrder = ["all", "dataPipeline", "display", "displayPipeline", "end"]; if(!this.table.destroyed){ if(typeof handler === "function"){ index = this.dataPipeline.findIndex((item) => { return item.handler === handler; }); if(index > -1){ stage = "dataPipeline"; if(skipStage){ if(index == this.dataPipeline.length - 1){ stage = "display"; }else { index++; } } }else { index = this.displayPipeline.findIndex((item) => { return item.handler === handler; }); if(index > -1){ stage = "displayPipeline"; if(skipStage){ if(index == this.displayPipeline.length - 1){ stage = "end"; }else { index++; } } }else { console.error("Unable to refresh data, invalid handler provided", handler); return; } } }else { stage = handler || "all"; index = 0; } if(this.redrawBlock){ if(!this.redrawBlockRestoreConfig || (this.redrawBlockRestoreConfig && ((this.redrawBlockRestoreConfig.stage === stage && index < this.redrawBlockRestoreConfig.index) || (cascadeOrder.indexOf(stage) < cascadeOrder.indexOf(this.redrawBlockRestoreConfig.stage))))){ this.redrawBlockRestoreConfig = { handler: handler, skipStage: skipStage, renderInPosition: renderInPosition, stage:stage, index:index, }; } return; }else { if(Helpers.elVisible(this.element)){ if(renderInPosition){ this.reRenderInPosition(this.refreshPipelines.bind(this, handler, stage, index, renderInPosition)); }else { this.refreshPipelines(handler, stage, index, renderInPosition); if(!handler){ this.table.columnManager.renderer.renderColumns(); } this.renderTable(); if(table.options.layoutColumnsOnNewData){ this.table.columnManager.redraw(true); } } }else { this.refreshPipelines(handler, stage, index, renderInPosition); } this.dispatch("data-refreshed"); } } } refreshPipelines(handler, stage, index, renderInPosition){ this.dispatch("data-refreshing"); if(!handler){ this.activeRowsPipeline[0] = this.rows.slice(0); } //cascade through data refresh stages switch(stage){ case "all": //handle case where all data needs refreshing case "dataPipeline": for(let i = index; i < this.dataPipeline.length; i++){ let result = this.dataPipeline[i].handler(this.activeRowsPipeline[i].slice(0)); this.activeRowsPipeline[i + 1] = result || this.activeRowsPipeline[i].slice(0); } this.setActiveRows(this.activeRowsPipeline[this.dataPipeline.length]); case "display": index = 0; this.resetDisplayRows(); case "displayPipeline": for(let i = index; i < this.displayPipeline.length; i++){ let result = this.displayPipeline[i].handler((i ? this.getDisplayRows(i - 1) : this.activeRows).slice(0), renderInPosition); this.setDisplayRows(result || this.getDisplayRows(i - 1).slice(0), i); } case "end": //case to handle scenario when trying to skip past end stage this.regenerateRowPositions(); } if(this.getDisplayRows().length){ this._clearPlaceholder(); } } //regenerate row positions regenerateRowPositions(){ var rows = this.getDisplayRows(); var index = 1; rows.forEach((row) => { if (row.type === "row"){ row.setPosition(index); index++; } }); } setActiveRows(activeRows){ this.activeRows = this.activeRows = Object.assign([], activeRows); this.activeRowsCount = this.activeRows.length; } //reset display rows array resetDisplayRows(){ this.displayRows = []; this.displayRows.push(this.activeRows.slice(0)); this.displayRowsCount = this.displayRows[0].length; } //set display row pipeline data setDisplayRows(displayRows, index){ this.displayRows[index] = displayRows; if(index == this.displayRows.length -1){ this.displayRowsCount = this.displayRows[this.displayRows.length -1].length; } } getDisplayRows(index){ if(typeof index == "undefined"){ return this.displayRows.length ? this.displayRows[this.displayRows.length -1] : []; }else { return this.displayRows[index] || []; } } getVisibleRows(chain, viewable){ var rows = Object.assign([], this.renderer.visibleRows(!viewable)); if(chain){ rows = this.chain("rows-visible", [viewable], rows, rows); } return rows; } //repeat action across display rows displayRowIterator(callback){ this.activeRowsPipeline.forEach(callback); this.displayRows.forEach(callback); this.displayRowsCount = this.displayRows[this.displayRows.length -1].length; } //return only actual rows (not group headers etc) getRows(type){ var rows = []; switch(type){ case "active": rows = this.activeRows; break; case "display": rows = this.table.rowManager.getDisplayRows(); break; case "visible": rows = this.getVisibleRows(false, true); break; default: rows = this.chain("rows-retrieve", type, null, this.rows) || this.rows; } return rows; } ///////////////// Table Rendering ///////////////// //trigger rerender of table in current position reRenderInPosition(callback){ if(this.redrawBlock){ if(callback){ callback(); }else { this.redrawBlockRenderInPosition = true; } }else { this.dispatchExternal("renderStarted"); this.renderer.rerenderRows(callback); if(!this.fixedHeight){ this.adjustTableSize(); } this.scrollBarCheck(); this.dispatchExternal("renderComplete"); } } scrollBarCheck(){ var scrollbarWidth = 0; //adjust for vertical scrollbar moving table when present if(this.element.scrollHeight > this.element.clientHeight){ scrollbarWidth = this.element.offsetWidth - this.element.clientWidth; } if(scrollbarWidth !== this.scrollbarWidth){ this.scrollbarWidth = scrollbarWidth; this.dispatch("scrollbar-vertical", scrollbarWidth); } } initializeRenderer(){ var renderClass; var renderers = { "virtual": VirtualDomVertical, "basic": BasicVertical, }; if(typeof this.table.options.renderVertical === "string"){ renderClass = renderers[this.table.options.renderVertical]; }else { renderClass = this.table.options.renderVertical; } if(renderClass){ this.renderMode = this.table.options.renderVertical; this.renderer = new renderClass(this.table, this.element, this.tableElement); this.renderer.initialize(); if((this.table.element.clientHeight || this.table.options.height) && !(this.table.options.minHeight && this.table.options.maxHeight)){ this.fixedHeight = true; }else { this.fixedHeight = false; } }else { console.error("Unable to find matching renderer:", this.table.options.renderVertical); } } getRenderMode(){ return this.renderMode; } renderTable(){ this.dispatchExternal("renderStarted"); this.element.scrollTop = 0; this._clearTable(); if(this.displayRowsCount){ this.renderer.renderRows(); if(this.firstRender){ this.firstRender = false; if(!this.fixedHeight){ this.adjustTableSize(); } this.layoutRefresh(true); } }else { this.renderEmptyScroll(); } if(!this.fixedHeight){ this.adjustTableSize(); } this.dispatch("table-layout"); if(!this.displayRowsCount){ this._showPlaceholder(); } this.scrollBarCheck(); this.dispatchExternal("renderComplete"); } //show scrollbars on empty table div renderEmptyScroll(){ if(this.placeholder){ this.tableElement.style.display = "none"; }else { this.tableElement.style.minWidth = this.table.columnManager.getWidth() + "px"; // this.tableElement.style.minHeight = "1px"; // this.tableElement.style.visibility = "hidden"; } } _clearTable(){ this._clearPlaceholder(); this.scrollTop = 0; this.scrollLeft = 0; this.renderer.clearRows(); } tableEmpty(){ this.renderEmptyScroll(); this._showPlaceholder(); } _showPlaceholder(){ if(this.placeholder){ this.placeholder.setAttribute("tabulator-render-mode", this.renderMode); this.getElement().appendChild(this.placeholder); this._positionPlaceholder(); } } _clearPlaceholder(){ if(this.placeholder && this.placeholder.parentNode){ this.placeholder.parentNode.removeChild(this.placeholder); } // clear empty table placeholder min this.tableElement.style.minWidth = ""; this.tableElement.style.display = ""; } _positionPlaceholder(){ if(this.placeholder && this.placeholder.parentNode){ this.placeholder.style.width = this.table.columnManager.getWidth() + "px"; this.placeholderContents.style.width = this.table.rowManager.element.clientWidth + "px"; this.placeholderContents.style.marginLeft = this.scrollLeft + "px"; } } styleRow(row, index){ var rowEl = row.getElement(); if(index % 2){ rowEl.classList.add("tabulator-row-even"); rowEl.classList.remove("tabulator-row-odd"); }else { rowEl.classList.add("tabulator-row-odd"); rowEl.classList.remove("tabulator-row-even"); } } //normalize height of active rows normalizeHeight(){ this.activeRows.forEach(function(row){ row.normalizeHeight(); }); } //adjust the height of the table holder to fit in the Tabulator element adjustTableSize(){ var initialHeight = this.element.clientHeight, minHeight; if(this.renderer.verticalFillMode === "fill"){ let otherHeight = Math.floor(this.table.columnManager.getElement().getBoundingClientRect().height + (this.table.footerManager && this.table.footerManager.active && !this.table.footerManager.external ? this.table.footerManager.getElement().getBoundingClientRect().height : 0)); if(this.fixedHeight){ minHeight = isNaN(this.table.options.minHeight) ? this.table.options.minHeight : this.table.options.minHeight + "px"; this.element.style.minHeight = minHeight || "calc(100% - " + otherHeight + "px)"; this.element.style.height = "calc(100% - " + otherHeight + "px)"; this.element.style.maxHeight = "calc(100% - " + otherHeight + "px)"; }else { this.element.style.height = ""; this.element.style.height = (this.table.element.clientHeight - otherHeight) + "px"; this.element.scrollTop = this.scrollTop; } this.renderer.resize(); //check if the table has changed size when dealing with variable height tables if(!this.fixedHeight && initialHeight != this.element.clientHeight){ if(this.subscribed("table-resize")){ this.dispatch("table-resize"); }else { this.redraw(); } } this.scrollBarCheck(); } this._positionPlaceholder(); } //reinitialize all rows reinitialize(){ this.rows.forEach(function(row){ row.reinitialize(true); }); } //prevent table from being redrawn blockRedraw (){ this.redrawBlock = true; this.redrawBlockRestoreConfig = false; } //restore table redrawing restoreRedraw (){ this.redrawBlock = false; if(this.redrawBlockRestoreConfig){ this.refreshActiveData(this.redrawBlockRestoreConfig.handler, this.redrawBlockRestoreConfig.skipStage, this.redrawBlockRestoreConfig.renderInPosition); this.redrawBlockRestoreConfig = false; }else { if(this.redrawBlockRenderInPosition){ this.reRenderInPosition(); } } this.redrawBlockRenderInPosition = false; } //redraw table redraw (force){ var left = this.scrollLeft; this.adjustTableSize(); this.table.tableWidth = this.table.element.clientWidth; if(!force){ this.reRenderInPosition(); this.scrollHorizontal(left); }else { this.renderTable(); } } resetScroll(){ this.element.scrollLeft = 0; this.element.scrollTop = 0; if(this.table.browser === "ie"){ var event = document.createEvent("Event"); event.initEvent("scroll", false, true); this.element.dispatchEvent(event); }else { this.element.dispatchEvent(new Event('scroll')); } } } class FooterManager extends CoreFeature{ constructor(table){ super(table); this.active = false; this.element = this.createElement(); //containing element this.containerElement = this.createContainerElement(); //containing element this.external = false; } initialize(){ this.initializeElement(); } createElement(){ var el = document.createElement("div"); el.classList.add("tabulator-footer"); return el; } createContainerElement(){ var el = document.createElement("div"); el.classList.add("tabulator-footer-contents"); this.element.appendChild(el); return el; } initializeElement(){ if(this.table.options.footerElement){ switch(typeof this.table.options.footerElement){ case "string": if(this.table.options.footerElement[0] === "<"){ this.containerElement.innerHTML = this.table.options.footerElement; }else { this.external = true; this.containerElement = document.querySelector(this.table.options.footerElement); } break; default: this.element = this.table.options.footerElement; break; } } } getElement(){ return this.element; } append(element){ this.activate(); this.containerElement.appendChild(element); this.table.rowManager.adjustTableSize(); } prepend(element){ this.activate(); this.element.insertBefore(element, this.element.firstChild); this.table.rowManager.adjustTableSize(); } remove(element){ element.parentNode.removeChild(element); this.deactivate(); } deactivate(force){ if(!this.element.firstChild || force){ if(!this.external){ this.element.parentNode.removeChild(this.element); } this.active = false; } } activate(){ if(!this.active){ this.active = true; if(!this.external){ this.table.element.appendChild(this.getElement()); this.table.element.style.display = ''; } } } redraw(){ this.dispatch("footer-redraw"); } } class InteractionManager extends CoreFeature { constructor (table){ super(table); this.el = null; this.abortClasses = ["tabulator-headers", "tabulator-table"]; this.previousTargets = {}; this.listeners = [ "click", "dblclick", "contextmenu", "mouseenter", "mouseleave", "mouseover", "mouseout", "mousemove", "mouseup", "mousedown", "touchstart", "touchend", ]; this.componentMap = { "tabulator-cell":"cell", "tabulator-row":"row", "tabulator-group":"group", "tabulator-col":"column", }; this.pseudoTrackers = { "row":{ subscriber:null, target:null, }, "cell":{ subscriber:null, target:null, }, "group":{ subscriber:null, target:null, }, "column":{ subscriber:null, target:null, }, }; this.pseudoTracking = false; } initialize(){ this.el = this.table.element; this.buildListenerMap(); this.bindSubscriptionWatchers(); } buildListenerMap(){ var listenerMap = {}; this.listeners.forEach((listener) => { listenerMap[listener] = { handler:null, components:[], }; }); this.listeners = listenerMap; } bindPseudoEvents(){ Object.keys(this.pseudoTrackers).forEach((key) => { this.pseudoTrackers[key].subscriber = this.pseudoMouseEnter.bind(this, key); this.subscribe(key + "-mouseover", this.pseudoTrackers[key].subscriber); }); this.pseudoTracking = true; } pseudoMouseEnter(key, e, target){ if(this.pseudoTrackers[key].target !== target){ if(this.pseudoTrackers[key].target){ this.dispatch(key + "-mouseleave", e, this.pseudoTrackers[key].target); } this.pseudoMouseLeave(key, e); this.pseudoTrackers[key].target = target; this.dispatch(key + "-mouseenter", e, target); } } pseudoMouseLeave(key, e){ var leaveList = Object.keys(this.pseudoTrackers), linkedKeys = { "row":["cell"], "cell":["row"], }; leaveList = leaveList.filter((item) => { var links = linkedKeys[key]; return item !== key && (!links || (links && !links.includes(item))); }); leaveList.forEach((key) => { var target = this.pseudoTrackers[key].target; if(this.pseudoTrackers[key].target){ this.dispatch(key + "-mouseleave", e, target); this.pseudoTrackers[key].target = null; } }); } bindSubscriptionWatchers(){ var listeners = Object.keys(this.listeners), components = Object.values(this.componentMap); for(let comp of components){ for(let listener of listeners){ let key = comp + "-" + listener; this.subscriptionChange(key, this.subscriptionChanged.bind(this, comp, listener)); } } this.subscribe("table-destroy", this.clearWatchers.bind(this)); } subscriptionChanged(component, key, added){ var listener = this.listeners[key].components, index = listener.indexOf(component), changed = false; if(added){ if(index === -1){ listener.push(component); changed = true; } }else { if(!this.subscribed(component + "-" + key)){ if(index > -1){ listener.splice(index, 1); changed = true; } } } if((key === "mouseenter" || key === "mouseleave") && !this.pseudoTracking){ this.bindPseudoEvents(); } if(changed){ this.updateEventListeners(); } } updateEventListeners(){ for(let key in this.listeners){ let listener = this.listeners[key]; if(listener.components.length){ if(!listener.handler){ listener.handler = this.track.bind(this, key); this.el.addEventListener(key, listener.handler); // this.el.addEventListener(key, listener.handler, {passive: true}) } }else { if(listener.handler){ this.el.removeEventListener(key, listener.handler); listener.handler = null; } } } } track(type, e){ var path = (e.composedPath && e.composedPath()) || e.path; var targets = this.findTargets(path); targets = this.bindComponents(type, targets); this.triggerEvents(type, e, targets); if(this.pseudoTracking && (type == "mouseover" || type == "mouseleave") && !Object.keys(targets).length){ this.pseudoMouseLeave("none", e); } } findTargets(path){ var targets = {}; let componentMap = Object.keys(this.componentMap); for (let el of path) { let classList = el.classList ? [...el.classList] : []; let abort = classList.filter((item) => { return this.abortClasses.includes(item); }); if(abort.length){ break; } let elTargets = classList.filter((item) => { return componentMap.includes(item); }); for (let target of elTargets) { if(!targets[this.componentMap[target]]){ targets[this.componentMap[target]] = el; } } } if(targets.group && targets.group === targets.row){ delete targets.row; } return targets; } bindComponents(type, targets){ //ensure row component is looked up before cell var keys = Object.keys(targets).reverse(), listener = this.listeners[type], matches = {}, targetMatches = {}; for(let key of keys){ let component, target = targets[key], previousTarget = this.previousTargets[key]; if(previousTarget && previousTarget.target === target){ component = previousTarget.component; }else { switch(key){ case "row": case "group": if(listener.components.includes("row") || listener.components.includes("cell") || listener.components.includes("group")){ let rows = this.table.rowManager.getVisibleRows(true); component = rows.find((row) => { return row.getElement() === target; }); if(targets["row"] && targets["row"].parentNode && targets["row"].parentNode.closest(".tabulator-row")){ targets[key] = false; } } break; case "column": if(listener.components.includes("column")){ component = this.table.columnManager.findColumn(target); } break; case "cell": if(listener.components.includes("cell")){ if(matches["row"] instanceof Row){ component = matches["row"].findCell(target); }else { if(targets["row"]){ console.warn("Event Target Lookup Error - The row this cell is attached to cannot be found, has the table been reinitialized without being destroyed first?"); } } } break; } } if(component){ matches[key] = component; targetMatches[key] = { target:target, component:component, }; } } this.previousTargets = targetMatches; return matches; } triggerEvents(type, e, targets){ var listener = this.listeners[type]; for(let key in targets){ if(targets[key] && listener.components.includes(key)){ this.dispatch(key + "-" + type, e, targets[key]); } } } clearWatchers(){ for(let key in this.listeners){ let listener = this.listeners[key]; if(listener.handler){ this.el.removeEventListener(key, listener.handler); listener.handler = null; } } } } class ComponentFunctionBinder{ constructor(table){ this.table = table; this.bindings = {}; } bind(type, funcName, handler){ if(!this.bindings[type]){ this.bindings[type] = {}; } if(this.bindings[type][funcName]){ console.warn("Unable to bind component handler, a matching function name is already bound", type, funcName, handler); }else { this.bindings[type][funcName] = handler; } } handle(type, component, name){ if(this.bindings[type] && this.bindings[type][name] && typeof this.bindings[type][name].bind === 'function'){ return this.bindings[type][name].bind(null, component); }else { if(name !== "then" && typeof name === "string" && !name.startsWith("_")){ if(this.table.options.debugInvalidComponentFuncs){ console.error("The " + type + " component does not have a " + name + " function, have you checked that you have the correct Tabulator module installed?"); } } } } } class DataLoader extends CoreFeature{ constructor(table){ super(table); this.requestOrder = 0; //prevent requests coming out of sequence if overridden by another load request this.loading = false; } initialize(){} load(data, params, config, replace, silent, columnsChanged){ var requestNo = ++this.requestOrder; this.dispatchExternal("dataLoading", data); //parse json data to array if (data && (data.indexOf("{") == 0 || data.indexOf("[") == 0)){ data = JSON.parse(data); } if(this.confirm("data-loading", [data, params, config, silent])){ this.loading = true; if(!silent){ this.alertLoader(); } //get params for request params = this.chain("data-params", [data, config, silent], params || {}, params || {}); params = this.mapParams(params, this.table.options.dataSendParams); var result = this.chain("data-load", [data, params, config, silent], false, Promise.resolve([])); return result.then((response) => { if(!Array.isArray(response) && typeof response == "object"){ response = this.mapParams(response, this.objectInvert(this.table.options.dataReceiveParams)); } var rowData = this.chain("data-loaded", response, null, response); if(requestNo == this.requestOrder){ this.clearAlert(); if(rowData !== false){ this.dispatchExternal("dataLoaded", rowData); this.table.rowManager.setData(rowData, replace, typeof columnsChanged === "undefined" ? !replace : columnsChanged); } }else { console.warn("Data Load Response Blocked - An active data load request was blocked by an attempt to change table data while the request was being made"); } }).catch((error) => { console.error("Data Load Error: ", error); this.dispatchExternal("dataLoadError", error); if(!silent){ this.alertError(); } setTimeout(() => { this.clearAlert(); }, this.table.options.dataLoaderErrorTimeout); }) .finally(() => { this.loading = false; }); }else { this.dispatchExternal("dataLoaded", data); if(!data){ data = []; } this.table.rowManager.setData(data, replace, typeof columnsChanged === "undefined" ? !replace : columnsChanged); return Promise.resolve(); } } mapParams(params, map){ var output = {}; for(let key in params){ output[map.hasOwnProperty(key) ? map[key] : key] = params[key]; } return output; } objectInvert(obj){ var output = {}; for(let key in obj){ output[obj[key]] = key; } return output; } blockActiveLoad(){ this.requestOrder++; } alertLoader(){ var shouldLoad = typeof this.table.options.dataLoader === "function" ? this.table.options.dataLoader() : this.table.options.dataLoader; if(shouldLoad){ this.table.alertManager.alert(this.table.options.dataLoaderLoading || this.langText("data|loading")); } } alertError(){ this.table.alertManager.alert(this.table.options.dataLoaderError || this.langText("data|error"), "error"); } clearAlert(){ this.table.alertManager.clear(); } } class ExternalEventBus { constructor(table, optionsList, debug){ this.table = table; this.events = {}; this.optionsList = optionsList || {}; this.subscriptionNotifiers = {}; this.dispatch = debug ? this._debugDispatch.bind(this) : this._dispatch.bind(this); this.debug = debug; } subscriptionChange(key, callback){ if(!this.subscriptionNotifiers[key]){ this.subscriptionNotifiers[key] = []; } this.subscriptionNotifiers[key].push(callback); if(this.subscribed(key)){ this._notifySubscriptionChange(key, true); } } subscribe(key, callback){ if(!this.events[key]){ this.events[key] = []; } this.events[key].push(callback); this._notifySubscriptionChange(key, true); } unsubscribe(key, callback){ var index; if(this.events[key]){ if(callback){ index = this.events[key].findIndex((item) => { return item === callback; }); if(index > -1){ this.events[key].splice(index, 1); }else { console.warn("Cannot remove event, no matching event found:", key, callback); return; } }else { delete this.events[key]; } }else { console.warn("Cannot remove event, no events set on:", key); return; } this._notifySubscriptionChange(key, false); } subscribed(key){ return this.events[key] && this.events[key].length; } _notifySubscriptionChange(key, subscribed){ var notifiers = this.subscriptionNotifiers[key]; if(notifiers){ notifiers.forEach((callback)=>{ callback(subscribed); }); } } _dispatch(){ var args = Array.from(arguments), key = args.shift(), result; if(this.events[key]){ this.events[key].forEach((callback, i) => { let callResult = callback.apply(this.table, args); if(!i){ result = callResult; } }); } return result; } _debugDispatch(){ var args = Array.from(arguments), key = args[0]; args[0] = "ExternalEvent:" + args[0]; if(this.debug === true || this.debug.includes(key)){ console.log(...args); } return this._dispatch(...arguments); } } class InternalEventBus { constructor(debug){ this.events = {}; this.subscriptionNotifiers = {}; this.dispatch = debug ? this._debugDispatch.bind(this) : this._dispatch.bind(this); this.chain = debug ? this._debugChain.bind(this) : this._chain.bind(this); this.confirm = debug ? this._debugConfirm.bind(this) : this._confirm.bind(this); this.debug = debug; } subscriptionChange(key, callback){ if(!this.subscriptionNotifiers[key]){ this.subscriptionNotifiers[key] = []; } this.subscriptionNotifiers[key].push(callback); if(this.subscribed(key)){ this._notifySubscriptionChange(key, true); } } subscribe(key, callback, priority = 10000){ if(!this.events[key]){ this.events[key] = []; } this.events[key].push({callback, priority}); this.events[key].sort((a, b) => { return a.priority - b.priority; }); this._notifySubscriptionChange(key, true); } unsubscribe(key, callback){ var index; if(this.events[key]){ if(callback){ index = this.events[key].findIndex((item) => { return item.callback === callback; }); if(index > -1){ this.events[key].splice(index, 1); }else { console.warn("Cannot remove event, no matching event found:", key, callback); return; } } }else { console.warn("Cannot remove event, no events set on:", key); return; } this._notifySubscriptionChange(key, false); } subscribed(key){ return this.events[key] && this.events[key].length; } _chain(key, args, initialValue, fallback){ var value = initialValue; if(!Array.isArray(args)){ args = [args]; } if(this.subscribed(key)){ this.events[key].forEach((subscriber, i) => { value = subscriber.callback.apply(this, args.concat([value])); }); return value; }else { return typeof fallback === "function" ? fallback() : fallback; } } _confirm(key, args){ var confirmed = false; if(!Array.isArray(args)){ args = [args]; } if(this.subscribed(key)){ this.events[key].forEach((subscriber, i) => { if(subscriber.callback.apply(this, args)){ confirmed = true; } }); } return confirmed; } _notifySubscriptionChange(key, subscribed){ var notifiers = this.subscriptionNotifiers[key]; if(notifiers){ notifiers.forEach((callback)=>{ callback(subscribed); }); } } _dispatch(){ var args = Array.from(arguments), key = args.shift(); if(this.events[key]){ this.events[key].forEach((subscriber) => { subscriber.callback.apply(this, args); }); } } _debugDispatch(){ var args = Array.from(arguments), key = args[0]; args[0] = "InternalEvent:" + key; if(this.debug === true || this.debug.includes(key)){ console.log(...args); } return this._dispatch(...arguments); } _debugChain(){ var args = Array.from(arguments), key = args[0]; args[0] = "InternalEvent:" + key; if(this.debug === true || this.debug.includes(key)){ console.log(...args); } return this._chain(...arguments); } _debugConfirm(){ var args = Array.from(arguments), key = args[0]; args[0] = "InternalEvent:" + key; if(this.debug === true || this.debug.includes(key)){ console.log(...args); } return this._confirm(...arguments); } } class DeprecationAdvisor extends CoreFeature{ constructor(table){ super(table); } _warnUser(){ if(this.options("debugDeprecation")){ console.warn(...arguments); } } check(oldOption, newOption){ var msg = ""; if(typeof this.options(oldOption) !== "undefined"){ msg = "Deprecated Setup Option - Use of the %c" + oldOption + "%c option is now deprecated"; if(newOption){ msg = msg + ", Please use the %c" + newOption + "%c option instead"; this._warnUser(msg, 'font-weight: bold;', 'font-weight: normal;', 'font-weight: bold;', 'font-weight: normal;'); }else { this._warnUser(msg, 'font-weight: bold;', 'font-weight: normal;'); } return false; }else { return true; } } checkMsg(oldOption, msg){ if(typeof this.options(oldOption) !== "undefined"){ this._warnUser("%cDeprecated Setup Option - Use of the %c" + oldOption + " %c option is now deprecated, " + msg, 'font-weight: normal;', 'font-weight: bold;', 'font-weight: normal;'); return false; }else { return true; } } msg(msg){ this._warnUser(msg); } } class TableRegistry { static register(table){ TableRegistry.tables.push(table); } static deregister(table){ var index = TableRegistry.tables.indexOf(table); if(index > -1){ TableRegistry.tables.splice(index, 1); } } static lookupTable(query, silent){ var results = [], matches, match; if(typeof query === "string"){ matches = document.querySelectorAll(query); if(matches.length){ for(var i = 0; i < matches.length; i++){ match = TableRegistry.matchElement(matches[i]); if(match){ results.push(match); } } } }else if((typeof HTMLElement !== "undefined" && query instanceof HTMLElement) || query instanceof Tabulator){ match = TableRegistry.matchElement(query); if(match){ results.push(match); } }else if(Array.isArray(query)){ query.forEach(function(item){ results = results.concat(TableRegistry.lookupTable(item)); }); }else { if(!silent){ console.warn("Table Connection Error - Invalid Selector", query); } } return results; } static matchElement(element){ return TableRegistry.tables.find(function(table){ return element instanceof Tabulator ? table === element : table.element === element; }); } } TableRegistry.tables = []; //resize columns to fit data they contain function fitData(columns, forced){ if(forced){ this.table.columnManager.renderer.reinitializeColumnWidths(columns); } if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){ this.table.modules.responsiveLayout.update(); } } //resize columns to fit data they contain and stretch row to fill table, also used for fitDataTable function fitDataGeneral(columns, forced){ columns.forEach(function(column){ column.reinitializeWidth(); }); if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){ this.table.modules.responsiveLayout.update(); } } //resize columns to fit data the contain and stretch last column to fill table function fitDataStretch(columns, forced){ var colsWidth = 0, tableWidth = this.table.rowManager.element.clientWidth, gap = 0, lastCol = false; columns.forEach((column, i) => { if(!column.widthFixed){ column.reinitializeWidth(); } if(this.table.options.responsiveLayout ? column.modules.responsive.visible : column.visible){ lastCol = column; } if(column.visible){ colsWidth += column.getWidth(); } }); if(lastCol){ gap = tableWidth - colsWidth + lastCol.getWidth(); if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){ lastCol.setWidth(0); this.table.modules.responsiveLayout.update(); } if(gap > 0){ lastCol.setWidth(gap); }else { lastCol.reinitializeWidth(); } }else { if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){ this.table.modules.responsiveLayout.update(); } } } //resize columns to fit function fitColumns(columns, forced){ var totalWidth = this.table.rowManager.element.getBoundingClientRect().width; //table element width var fixedWidth = 0; //total width of columns with a defined width var flexWidth = 0; //total width available to flexible columns var flexGrowUnits = 0; //total number of widthGrow blocks across all columns var flexColWidth = 0; //desired width of flexible columns var flexColumns = []; //array of flexible width columns var fixedShrinkColumns = []; //array of fixed width columns that can shrink var flexShrinkUnits = 0; //total number of widthShrink blocks across all columns var overflowWidth = 0; //horizontal overflow width var gapFill = 0; //number of pixels to be added to final column to close and half pixel gaps function calcWidth(width){ var colWidth; if(typeof(width) == "string"){ if(width.indexOf("%") > -1){ colWidth = (totalWidth / 100) * parseInt(width); }else { colWidth = parseInt(width); } }else { colWidth = width; } return colWidth; } //ensure columns resize to take up the correct amount of space function scaleColumns(columns, freeSpace, colWidth, shrinkCols){ var oversizeCols = [], oversizeSpace = 0, remainingSpace = 0, nextColWidth = 0, remainingFlexGrowUnits = flexGrowUnits, gap = 0, changeUnits = 0, undersizeCols = []; function calcGrow(col){ return (colWidth * (col.column.definition.widthGrow || 1)); } function calcShrink(col){ return (calcWidth(col.width) - (colWidth * (col.column.definition.widthShrink || 0))); } columns.forEach(function(col, i){ var width = shrinkCols ? calcShrink(col) : calcGrow(col); if(col.column.minWidth >= width){ oversizeCols.push(col); }else { if(col.column.maxWidth && col.column.maxWidth < width){ col.width = col.column.maxWidth; freeSpace -= col.column.maxWidth; remainingFlexGrowUnits -= shrinkCols ? (col.column.definition.widthShrink || 1) : (col.column.definition.widthGrow || 1); if(remainingFlexGrowUnits){ colWidth = Math.floor(freeSpace/remainingFlexGrowUnits); } }else { undersizeCols.push(col); changeUnits += shrinkCols ? (col.column.definition.widthShrink || 1) : (col.column.definition.widthGrow || 1); } } }); if(oversizeCols.length){ oversizeCols.forEach(function(col){ oversizeSpace += shrinkCols ? col.width - col.column.minWidth : col.column.minWidth; col.width = col.column.minWidth; }); remainingSpace = freeSpace - oversizeSpace; nextColWidth = changeUnits ? Math.floor(remainingSpace/changeUnits) : remainingSpace; gap = scaleColumns(undersizeCols, remainingSpace, nextColWidth, shrinkCols); }else { gap = changeUnits ? freeSpace - (Math.floor(freeSpace/changeUnits) * changeUnits) : freeSpace; undersizeCols.forEach(function(column){ column.width = shrinkCols ? calcShrink(column) : calcGrow(column); }); } return gap; } if(this.table.options.responsiveLayout && this.table.modExists("responsiveLayout", true)){ this.table.modules.responsiveLayout.update(); } //adjust for vertical scrollbar if present if(this.table.rowManager.element.scrollHeight > this.table.rowManager.element.clientHeight){ totalWidth -= this.table.rowManager.element.offsetWidth - this.table.rowManager.element.clientWidth; } columns.forEach(function(column){ var width, minWidth, colWidth; if(column.visible){ width = column.definition.width; minWidth = parseInt(column.minWidth); if(width){ colWidth = calcWidth(width); fixedWidth += colWidth > minWidth ? colWidth : minWidth; if(column.definition.widthShrink){ fixedShrinkColumns.push({ column:column, width:colWidth > minWidth ? colWidth : minWidth }); flexShrinkUnits += column.definition.widthShrink; } }else { flexColumns.push({ column:column, width:0, }); flexGrowUnits += column.definition.widthGrow || 1; } } }); //calculate available space flexWidth = totalWidth - fixedWidth; //calculate correct column size flexColWidth = Math.floor(flexWidth / flexGrowUnits); //generate column widths gapFill = scaleColumns(flexColumns, flexWidth, flexColWidth, false); //increase width of last column to account for rounding errors if(flexColumns.length && gapFill > 0){ flexColumns[flexColumns.length-1].width += gapFill; } //calculate space for columns to be shrunk into flexColumns.forEach(function(col){ flexWidth -= col.width; }); overflowWidth = Math.abs(gapFill) + flexWidth; //shrink oversize columns if there is no available space if(overflowWidth > 0 && flexShrinkUnits){ gapFill = scaleColumns(fixedShrinkColumns, overflowWidth, Math.floor(overflowWidth / flexShrinkUnits), true); } //decrease width of last column to account for rounding errors if(gapFill && fixedShrinkColumns.length){ fixedShrinkColumns[fixedShrinkColumns.length-1].width -= gapFill; } flexColumns.forEach(function(col){ col.column.setWidth(col.width); }); fixedShrinkColumns.forEach(function(col){ col.column.setWidth(col.width); }); } var defaultModes = { fitData:fitData, fitDataFill:fitDataGeneral, fitDataTable:fitDataGeneral, fitDataStretch:fitDataStretch, fitColumns:fitColumns , }; class Layout extends Module{ constructor(table){ super(table, "layout"); this.mode = null; this.registerTableOption("layout", "fitData"); //layout type this.registerTableOption("layoutColumnsOnNewData", false); //update column widths on setData this.registerColumnOption("widthGrow"); this.registerColumnOption("widthShrink"); } //initialize layout system initialize(){ var layout = this.table.options.layout; if(Layout.modes[layout]){ this.mode = layout; }else { console.warn("Layout Error - invalid mode set, defaulting to 'fitData' : " + layout); this.mode = 'fitData'; } this.table.element.setAttribute("tabulator-layout", this.mode); } getMode(){ return this.mode; } //trigger table layout layout(dataChanged){ this.dispatch("layout-refreshing"); Layout.modes[this.mode].call(this, this.table.columnManager.columnsByIndex, dataChanged); this.dispatch("layout-refreshed"); } } Layout.moduleName = "layout"; //load defaults Layout.modes = defaultModes; var defaultLangs = { "default":{ //hold default locale text "groups":{ "item":"item", "items":"items", }, "columns":{ }, "data":{ "loading":"Loading", "error":"Error", }, "pagination":{ "page_size":"Page Size", "page_title":"Show Page", "first":"First", "first_title":"First Page", "last":"Last", "last_title":"Last Page", "prev":"Prev", "prev_title":"Prev Page", "next":"Next", "next_title":"Next Page", "all":"All", "counter":{ "showing": "Showing", "of": "of", "rows": "rows", "pages": "pages", } }, "headerFilters":{ "default":"filter column...", "columns":{} } }, }; class Localize extends Module{ constructor(table){ super(table); this.locale = "default"; //current locale this.lang = false; //current language this.bindings = {}; //update events to call when locale is changed this.langList = {}; this.registerTableOption("locale", false); //current system language this.registerTableOption("langs", {}); } initialize(){ this.langList = Helpers.deepClone(Localize.langs); if(this.table.options.columnDefaults.headerFilterPlaceholder !== false){ this.setHeaderFilterPlaceholder(this.table.options.columnDefaults.headerFilterPlaceholder); } for(let locale in this.table.options.langs){ this.installLang(locale, this.table.options.langs[locale]); } this.setLocale(this.table.options.locale); this.registerTableFunction("setLocale", this.setLocale.bind(this)); this.registerTableFunction("getLocale", this.getLocale.bind(this)); this.registerTableFunction("getLang", this.getLang.bind(this)); } //set header placeholder setHeaderFilterPlaceholder(placeholder){ this.langList.default.headerFilters.default = placeholder; } //setup a lang description object installLang(locale, lang){ if(this.langList[locale]){ this._setLangProp(this.langList[locale], lang); }else { this.langList[locale] = lang; } } _setLangProp(lang, values){ for(let key in values){ if(lang[key] && typeof lang[key] == "object"){ this._setLangProp(lang[key], values[key]); }else { lang[key] = values[key]; } } } //set current locale setLocale(desiredLocale){ desiredLocale = desiredLocale || "default"; //fill in any matching language values function traverseLang(trans, path){ for(var prop in trans){ if(typeof trans[prop] == "object"){ if(!path[prop]){ path[prop] = {}; } traverseLang(trans[prop], path[prop]); }else { path[prop] = trans[prop]; } } } //determining correct locale to load if(desiredLocale === true && navigator.language){ //get local from system desiredLocale = navigator.language.toLowerCase(); } if(desiredLocale){ //if locale is not set, check for matching top level locale else use default if(!this.langList[desiredLocale]){ let prefix = desiredLocale.split("-")[0]; if(this.langList[prefix]){ console.warn("Localization Error - Exact matching locale not found, using closest match: ", desiredLocale, prefix); desiredLocale = prefix; }else { console.warn("Localization Error - Matching locale not found, using default: ", desiredLocale); desiredLocale = "default"; } } } this.locale = desiredLocale; //load default lang template this.lang = Helpers.deepClone(this.langList.default || {}); if(desiredLocale != "default"){ traverseLang(this.langList[desiredLocale], this.lang); } this.dispatchExternal("localized", this.locale, this.lang); this._executeBindings(); } //get current locale getLocale(locale){ return this.locale; } //get lang object for given local or current if none provided getLang(locale){ return locale ? this.langList[locale] : this.lang; } //get text for current locale getText(path, value){ var fillPath = value ? path + "|" + value : path, pathArray = fillPath.split("|"), text = this._getLangElement(pathArray, this.locale); // if(text === false){ // console.warn("Localization Error - Matching localized text not found for given path: ", path); // } return text || ""; } //traverse langs object and find localized copy _getLangElement(path, locale){ var root = this.lang; path.forEach(function(level){ var rootPath; if(root){ rootPath = root[level]; if(typeof rootPath != "undefined"){ root = rootPath; }else { root = false; } } }); return root; } //set update binding bind(path, callback){ if(!this.bindings[path]){ this.bindings[path] = []; } this.bindings[path].push(callback); callback(this.getText(path), this.lang); } //iterate through bindings and trigger updates _executeBindings(){ for(let path in this.bindings){ this.bindings[path].forEach((binding) => { binding(this.getText(path), this.lang); }); } } } Localize.moduleName = "localize"; //load defaults Localize.langs = defaultLangs; class Comms extends Module{ constructor(table){ super(table); } initialize(){ this.registerTableFunction("tableComms", this.receive.bind(this)); } getConnections(selectors){ var connections = [], connection; connection = TableRegistry.lookupTable(selectors); connection.forEach((con) =>{ if(this.table !== con){ connections.push(con); } }); return connections; } send(selectors, module, action, data){ var connections = this.getConnections(selectors); connections.forEach((connection) => { connection.tableComms(this.table.element, module, action, data); }); if(!connections.length && selectors){ console.warn("Table Connection Error - No tables matching selector found", selectors); } } receive(table, module, action, data){ if(this.table.modExists(module)){ return this.table.modules[module].commsReceived(table, action, data); }else { console.warn("Inter-table Comms Error - no such module:", module); } } } Comms.moduleName = "comms"; var coreModules = /*#__PURE__*/Object.freeze({ __proto__: null, LayoutModule: Layout, LocalizeModule: Localize, CommsModule: Comms }); class ModuleBinder { constructor(tabulator, modules){ this.bindStaticFunctionality(tabulator); this.bindModules(tabulator, coreModules, true); if(modules){ this.bindModules(tabulator, modules); } } bindStaticFunctionality(tabulator){ tabulator.moduleBindings = {}; tabulator.extendModule = function(name, property, values){ if(tabulator.moduleBindings[name]){ var source = tabulator.moduleBindings[name][property]; if(source){ if(typeof values == "object"){ for(let key in values){ source[key] = values[key]; } }else { console.warn("Module Error - Invalid value type, it must be an object"); } }else { console.warn("Module Error - property does not exist:", property); } }else { console.warn("Module Error - module does not exist:", name); } }; tabulator.registerModule = function(modules){ if(!Array.isArray(modules)){ modules = [modules]; } modules.forEach((mod) => { tabulator.registerModuleBinding(mod); }); }; tabulator.registerModuleBinding = function(mod){ tabulator.moduleBindings[mod.moduleName] = mod; }; tabulator.findTable = function(query){ var results = TableRegistry.lookupTable(query, true); return Array.isArray(results) && !results.length ? false : results; }; //ensure that module are bound to instantiated function tabulator.prototype.bindModules = function(){ var orderedStartMods = [], orderedEndMods = [], unOrderedMods = []; this.modules = {}; for(var name in tabulator.moduleBindings){ let mod = tabulator.moduleBindings[name]; let module = new mod(this); this.modules[name] = module; if(mod.prototype.moduleCore){ this.modulesCore.push(module); }else { if(mod.moduleInitOrder){ if(mod.moduleInitOrder < 0){ orderedStartMods.push(module); }else { orderedEndMods.push(module); } }else { unOrderedMods.push(module); } } } orderedStartMods.sort((a, b) => a.moduleInitOrder > b.moduleInitOrder ? 1 : -1); orderedEndMods.sort((a, b) => a.moduleInitOrder > b.moduleInitOrder ? 1 : -1); this.modulesRegular = orderedStartMods.concat(unOrderedMods.concat(orderedEndMods)); }; } bindModules(tabulator, modules, core){ var mods = Object.values(modules); if(core){ mods.forEach((mod) => { mod.prototype.moduleCore = true; }); } tabulator.registerModule(mods); } } class Alert extends CoreFeature{ constructor(table){ super(table); this.element = this._createAlertElement(); this.msgElement = this._createMsgElement(); this.type = null; this.element.appendChild(this.msgElement); } _createAlertElement(){ var el = document.createElement("div"); el.classList.add("tabulator-alert"); return el; } _createMsgElement(){ var el = document.createElement("div"); el.classList.add("tabulator-alert-msg"); el.setAttribute("role", "alert"); return el; } _typeClass(){ return "tabulator-alert-state-" + this.type; } alert(content, type = "msg"){ if(content){ this.clear(); this.type = type; while(this.msgElement.firstChild) this.msgElement.removeChild(this.msgElement.firstChild); this.msgElement.classList.add(this._typeClass()); if(typeof content === "function"){ content = content(); } if(content instanceof HTMLElement){ this.msgElement.appendChild(content); }else { this.msgElement.innerHTML = content; } this.table.element.appendChild(this.element); } } clear(){ if(this.element.parentNode){ this.element.parentNode.removeChild(this.element); } this.msgElement.classList.remove(this._typeClass()); } } class Tabulator { constructor(element, options){ this.options = {}; this.columnManager = null; // hold Column Manager this.rowManager = null; //hold Row Manager this.footerManager = null; //holder Footer Manager this.alertManager = null; //hold Alert Manager this.vdomHoz = null; //holder horizontal virtual dom this.externalEvents = null; //handle external event messaging this.eventBus = null; //handle internal event messaging this.interactionMonitor = false; //track user interaction this.browser = ""; //hold current browser type this.browserSlow = false; //handle reduced functionality for slower browsers this.browserMobile = false; //check if running on mobile, prevent resize cancelling edit on keyboard appearance this.rtl = false; //check if the table is in RTL mode this.originalElement = null; //hold original table element if it has been replaced this.componentFunctionBinder = new ComponentFunctionBinder(this); //bind component functions this.dataLoader = false; //bind component functions this.modules = {}; //hold all modules bound to this table this.modulesCore = []; //hold core modules bound to this table (for initialization purposes) this.modulesRegular = []; //hold regular modules bound to this table (for initialization purposes) this.deprecationAdvisor = new DeprecationAdvisor(this); this.optionsList = new OptionsList(this, "table constructor"); this.initialized = false; this.destroyed = false; if(this.initializeElement(element)){ this.initializeCoreSystems(options); //delay table creation to allow event bindings immediately after the constructor setTimeout(() => { this._create(); }); } TableRegistry.register(this); //register table for inter-device communication } initializeElement(element){ if(typeof HTMLElement !== "undefined" && element instanceof HTMLElement){ this.element = element; return true; }else if(typeof element === "string"){ this.element = document.querySelector(element); if(this.element){ return true; }else { console.error("Tabulator Creation Error - no element found matching selector: ", element); return false; } }else { console.error("Tabulator Creation Error - Invalid element provided:", element); return false; } } initializeCoreSystems(options){ this.columnManager = new ColumnManager(this); this.rowManager = new RowManager(this); this.footerManager = new FooterManager(this); this.dataLoader = new DataLoader(this); this.alertManager = new Alert(this); this.bindModules(); this.options = this.optionsList.generate(Tabulator.defaultOptions, options); this._clearObjectPointers(); this._mapDeprecatedFunctionality(); this.externalEvents = new ExternalEventBus(this, this.options, this.options.debugEventsExternal); this.eventBus = new InternalEventBus(this.options.debugEventsInternal); this.interactionMonitor = new InteractionManager(this); this.dataLoader.initialize(); // this.columnManager.initialize(); // this.rowManager.initialize(); this.footerManager.initialize(); } //convert deprecated functionality to new functions _mapDeprecatedFunctionality(){ //all previously deprecated functionality removed in the 5.0 release } _clearSelection(){ this.element.classList.add("tabulator-block-select"); if (window.getSelection) { if (window.getSelection().empty) { // Chrome window.getSelection().empty(); } else if (window.getSelection().removeAllRanges) { // Firefox window.getSelection().removeAllRanges(); } } else if (document.selection) { // IE? document.selection.empty(); } this.element.classList.remove("tabulator-block-select"); } //create table _create(){ this.externalEvents.dispatch("tableBuilding"); this.eventBus.dispatch("table-building"); this._rtlCheck(); this._buildElement(); this._initializeTable(); this._loadInitialData(); this.initialized = true; this.externalEvents.dispatch("tableBuilt"); } _rtlCheck(){ var style = window.getComputedStyle(this.element); switch(this.options.textDirection){ case"auto": if(style.direction !== "rtl"){ break; } case "rtl": this.element.classList.add("tabulator-rtl"); this.rtl = true; break; case "ltr": this.element.classList.add("tabulator-ltr"); default: this.rtl = false; } } //clear pointers to objects in default config object _clearObjectPointers(){ this.options.columns = this.options.columns.slice(0); if(Array.isArray(this.options.data) && !this.options.reactiveData){ this.options.data = this.options.data.slice(0); } } //build tabulator element _buildElement(){ var element = this.element, options = this.options, newElement; if(element.tagName === "TABLE"){ this.originalElement = this.element; newElement = document.createElement("div"); //transfer attributes to new element var attributes = element.attributes; // loop through attributes and apply them on div for(var i in attributes){ if(typeof attributes[i] == "object"){ newElement.setAttribute(attributes[i].name, attributes[i].value); } } // replace table with div element element.parentNode.replaceChild(newElement, element); this.element = element = newElement; } element.classList.add("tabulator"); element.setAttribute("role", "grid"); //empty element while(element.firstChild) element.removeChild(element.firstChild); //set table height if(options.height){ options.height = isNaN(options.height) ? options.height : options.height + "px"; element.style.height = options.height; } //set table min height if(options.minHeight !== false){ options.minHeight = isNaN(options.minHeight) ? options.minHeight : options.minHeight + "px"; element.style.minHeight = options.minHeight; } //set table maxHeight if(options.maxHeight !== false){ options.maxHeight = isNaN(options.maxHeight) ? options.maxHeight : options.maxHeight + "px"; element.style.maxHeight = options.maxHeight; } } //initialize core systems and modules _initializeTable(){ var element = this.element, options = this.options; this.interactionMonitor.initialize(); this.columnManager.initialize(); this.rowManager.initialize(); this._detectBrowser(); //initialize core modules this.modulesCore.forEach((mod) => { mod.initialize(); }); //build table elements element.appendChild(this.columnManager.getElement()); element.appendChild(this.rowManager.getElement()); if(options.footerElement){ this.footerManager.activate(); } if(options.autoColumns && options.data){ this.columnManager.generateColumnsFromRowData(this.options.data); } //initialize regular modules this.modulesRegular.forEach((mod) => { mod.initialize(); }); this.columnManager.setColumns(options.columns); this.eventBus.dispatch("table-built"); } _loadInitialData(){ this.dataLoader.load(this.options.data); } //deconstructor destroy(){ var element = this.element; this.destroyed = true; TableRegistry.deregister(this); //deregister table from inter-device communication this.eventBus.dispatch("table-destroy"); //clear row data this.rowManager.destroy(); //clear DOM while(element.firstChild) element.removeChild(element.firstChild); element.classList.remove("tabulator"); this.externalEvents.dispatch("tableDestroyed"); } _detectBrowser(){ var ua = navigator.userAgent||navigator.vendor||window.opera; if(ua.indexOf("Trident") > -1){ this.browser = "ie"; this.browserSlow = true; }else if(ua.indexOf("Edge") > -1){ this.browser = "edge"; this.browserSlow = true; }else if(ua.indexOf("Firefox") > -1){ this.browser = "firefox"; this.browserSlow = false; }else if(ua.indexOf("Mac OS") > -1){ this.browser = "safari"; this.browserSlow = false; }else { this.browser = "other"; this.browserSlow = false; } this.browserMobile = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(ua)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(ua.slice(0,4)); } initGuard(func, msg){ var stack, line; if(this.options.debugInitialization && !this.initialized){ if(!func){ stack = new Error().stack.split("\n"); line = stack[0] == "Error" ? stack[2] : stack[1]; if(line[0] == " "){ func = line.trim().split(" ")[1].split(".")[1]; }else { func = line.trim().split("@")[0]; } } console.warn("Table Not Initialized - Calling the " + func + " function before the table is initialized may result in inconsistent behavior, Please wait for the `tableBuilt` event before calling this function." + (msg ? " " + msg : "")); } return this.initialized; } ////////////////// Data Handling ////////////////// //block table redrawing blockRedraw(){ this.initGuard(); this.eventBus.dispatch("redraw-blocking"); this.rowManager.blockRedraw(); this.columnManager.blockRedraw(); this.eventBus.dispatch("redraw-blocked"); } //restore table redrawing restoreRedraw(){ this.initGuard(); this.eventBus.dispatch("redraw-restoring"); this.rowManager.restoreRedraw(); this.columnManager.restoreRedraw(); this.eventBus.dispatch("redraw-restored"); } //load data setData(data, params, config){ this.initGuard(false, "To set initial data please use the 'data' property in the table constructor."); return this.dataLoader.load(data, params, config, false); } //clear data clearData(){ this.initGuard(); this.dataLoader.blockActiveLoad(); this.rowManager.clearData(); } //get table data array getData(active){ return this.rowManager.getData(active); } //get table data array count getDataCount(active){ return this.rowManager.getDataCount(active); } //replace data, keeping table in position with same sort replaceData(data, params, config){ this.initGuard(); return this.dataLoader.load(data, params, config, true, true); } //update table data updateData(data){ var responses = 0; this.initGuard(); return new Promise((resolve, reject) => { this.dataLoader.blockActiveLoad(); if(typeof data === "string"){ data = JSON.parse(data); } if(data && data.length > 0){ data.forEach((item) => { var row = this.rowManager.findRow(item[this.options.index]); if(row){ responses++; row.updateData(item) .then(()=>{ responses--; if(!responses){ resolve(); } }) .catch((e) => { reject("Update Error - Unable to update row", item, e); }); }else { reject("Update Error - Unable to find row", item); } }); }else { console.warn("Update Error - No data provided"); reject("Update Error - No data provided"); } }); } addData(data, pos, index){ this.initGuard(); return new Promise((resolve, reject) => { this.dataLoader.blockActiveLoad(); if(typeof data === "string"){ data = JSON.parse(data); } if(data){ this.rowManager.addRows(data, pos, index) .then((rows) => { var output = []; rows.forEach(function(row){ output.push(row.getComponent()); }); resolve(output); }); }else { console.warn("Update Error - No data provided"); reject("Update Error - No data provided"); } }); } //update table data updateOrAddData(data){ var rows = [], responses = 0; this.initGuard(); return new Promise((resolve, reject) => { this.dataLoader.blockActiveLoad(); if(typeof data === "string"){ data = JSON.parse(data); } if(data && data.length > 0){ data.forEach((item) => { var row = this.rowManager.findRow(item[this.options.index]); responses++; if(row){ row.updateData(item) .then(()=>{ responses--; rows.push(row.getComponent()); if(!responses){ resolve(rows); } }); }else { this.rowManager.addRows(item) .then((newRows)=>{ responses--; rows.push(newRows[0].getComponent()); if(!responses){ resolve(rows); } }); } }); }else { console.warn("Update Error - No data provided"); reject("Update Error - No data provided"); } }); } //get row object getRow(index){ var row = this.rowManager.findRow(index); if(row){ return row.getComponent(); }else { console.warn("Find Error - No matching row found:", index); return false; } } //get row object getRowFromPosition(position){ var row = this.rowManager.getRowFromPosition(position); if(row){ return row.getComponent(); }else { console.warn("Find Error - No matching row found:", position); return false; } } //delete row from table deleteRow(index){ var foundRows = []; this.initGuard(); if(!Array.isArray(index)){ index = [index]; } //find matching rows for(let item of index){ let row = this.rowManager.findRow(item, true); if(row){ foundRows.push(row); }else { console.error("Delete Error - No matching row found:", item); return Promise.reject("Delete Error - No matching row found"); } } //sort rows into correct order to ensure smooth delete from table foundRows.sort((a, b) => { return this.rowManager.rows.indexOf(a) > this.rowManager.rows.indexOf(b) ? 1 : -1; }); //delete rows foundRows.forEach((row) =>{ row.delete(); }); this.rowManager.reRenderInPosition(); return Promise.resolve(); } //add row to table addRow(data, pos, index){ this.initGuard(); if(typeof data === "string"){ data = JSON.parse(data); } return this.rowManager.addRows(data, pos, index, true) .then((rows)=>{ return rows[0].getComponent(); }); } //update a row if it exists otherwise create it updateOrAddRow(index, data){ var row = this.rowManager.findRow(index); this.initGuard(); if(typeof data === "string"){ data = JSON.parse(data); } if(row){ return row.updateData(data) .then(()=>{ return row.getComponent(); }); }else { return this.rowManager.addRows(data) .then((rows)=>{ return rows[0].getComponent(); }); } } //update row data updateRow(index, data){ var row = this.rowManager.findRow(index); this.initGuard(); if(typeof data === "string"){ data = JSON.parse(data); } if(row){ return row.updateData(data) .then(()=>{ return Promise.resolve(row.getComponent()); }); }else { console.warn("Update Error - No matching row found:", index); return Promise.reject("Update Error - No matching row found"); } } //scroll to row in DOM scrollToRow(index, position, ifVisible){ var row = this.rowManager.findRow(index); if(row){ return this.rowManager.scrollToRow(row, position, ifVisible); }else { console.warn("Scroll Error - No matching row found:", index); return Promise.reject("Scroll Error - No matching row found"); } } moveRow(from, to, after){ var fromRow = this.rowManager.findRow(from); this.initGuard(); if(fromRow){ fromRow.moveToRow(to, after); }else { console.warn("Move Error - No matching row found:", from); } } getRows(active){ return this.rowManager.getComponents(active); } //get position of row in table getRowPosition(index){ var row = this.rowManager.findRow(index); if(row){ return row.getPosition(); }else { console.warn("Position Error - No matching row found:", index); return false; } } /////////////// Column Functions /////////////// setColumns(definition){ this.initGuard(false, "To set initial columns please use the 'columns' property in the table constructor"); this.columnManager.setColumns(definition); } getColumns(structured){ return this.columnManager.getComponents(structured); } getColumn(field){ var column = this.columnManager.findColumn(field); if(column){ return column.getComponent(); }else { console.warn("Find Error - No matching column found:", field); return false; } } getColumnDefinitions(){ return this.columnManager.getDefinitionTree(); } showColumn(field){ var column = this.columnManager.findColumn(field); this.initGuard(); if(column){ column.show(); }else { console.warn("Column Show Error - No matching column found:", field); return false; } } hideColumn(field){ var column = this.columnManager.findColumn(field); this.initGuard(); if(column){ column.hide(); }else { console.warn("Column Hide Error - No matching column found:", field); return false; } } toggleColumn(field){ var column = this.columnManager.findColumn(field); this.initGuard(); if(column){ if(column.visible){ column.hide(); }else { column.show(); } }else { console.warn("Column Visibility Toggle Error - No matching column found:", field); return false; } } addColumn(definition, before, field){ var column = this.columnManager.findColumn(field); this.initGuard(); return this.columnManager.addColumn(definition, before, column) .then((column) => { return column.getComponent(); }); } deleteColumn(field){ var column = this.columnManager.findColumn(field); this.initGuard(); if(column){ return column.delete(); }else { console.warn("Column Delete Error - No matching column found:", field); return Promise.reject(); } } updateColumnDefinition(field, definition){ var column = this.columnManager.findColumn(field); this.initGuard(); if(column){ return column.updateDefinition(definition); }else { console.warn("Column Update Error - No matching column found:", field); return Promise.reject(); } } moveColumn(from, to, after){ var fromColumn = this.columnManager.findColumn(from), toColumn = this.columnManager.findColumn(to); this.initGuard(); if(fromColumn){ if(toColumn){ this.columnManager.moveColumn(fromColumn, toColumn, after); }else { console.warn("Move Error - No matching column found:", toColumn); } }else { console.warn("Move Error - No matching column found:", from); } } //scroll to column in DOM scrollToColumn(field, position, ifVisible){ return new Promise((resolve, reject) => { var column = this.columnManager.findColumn(field); if(column){ return this.columnManager.scrollToColumn(column, position, ifVisible); }else { console.warn("Scroll Error - No matching column found:", field); return Promise.reject("Scroll Error - No matching column found"); } }); } //////////// General Public Functions //////////// //redraw list without updating data redraw(force){ this.initGuard(); this.columnManager.redraw(force); this.rowManager.redraw(force); } setHeight(height){ this.options.height = isNaN(height) ? height : height + "px"; this.element.style.height = this.options.height; this.rowManager.initializeRenderer(); this.rowManager.redraw(); } //////////////////// Event Bus /////////////////// on(key, callback){ this.externalEvents.subscribe(key, callback); } off(key, callback){ this.externalEvents.unsubscribe(key, callback); } dispatchEvent(){ var args = Array.from(arguments); args.shift(); this.externalEvents.dispatch(...arguments); } //////////////////// Alerts /////////////////// alert(contents, type){ this.initGuard(); this.alertManager.alert(contents, type); } clearAlert(){ this.initGuard(); this.alertManager.clear(); } ////////////// Extension Management ////////////// modExists(plugin, required){ if(this.modules[plugin]){ return true; }else { if(required){ console.error("Tabulator Module Not Installed: " + plugin); } return false; } } module(key){ var mod = this.modules[key]; if(!mod){ console.error("Tabulator module not installed: " + key); } return mod; } } //default setup options Tabulator.defaultOptions = defaultOptions; //bind modules and static functionality new ModuleBinder(Tabulator); //tabulator with all modules installed class TabulatorFull extends Tabulator {} //bind modules and static functionality new ModuleBinder(TabulatorFull, modules); class PseudoRow { constructor (type){ this.type = type; this.element = this._createElement(); } _createElement(){ var el = document.createElement("div"); el.classList.add("tabulator-row"); return el; } getElement(){ return this.element; } getComponent(){ return false; } getData(){ return {}; } getHeight(){ return this.element.outerHeight; } initialize(){} reinitialize(){} normalizeHeight(){} generateCells(){} reinitializeHeight(){} calcHeight(){} setCellHeight(){} clearCellHeight(){} } export { Accessor as AccessorModule, Ajax as AjaxModule, CalcComponent, CellComponent, Clipboard as ClipboardModule, ColumnCalcs as ColumnCalcsModule, ColumnComponent, DataTree as DataTreeModule, Download as DownloadModule, Edit$1 as EditModule, Export as ExportModule, Filter as FilterModule, Format as FormatModule, FrozenColumns as FrozenColumnsModule, FrozenRows as FrozenRowsModule, GroupComponent, GroupRows as GroupRowsModule, History as HistoryModule, HtmlTableImport as HtmlTableImportModule, Import as ImportModule, Interaction as InteractionModule, Keybindings as KeybindingsModule, Menu as MenuModule, Module, MoveColumns as MoveColumnsModule, MoveRows as MoveRowsModule, Mutator as MutatorModule, Page as PageModule, Persistence as PersistenceModule, Popup$1 as PopupModule, Print as PrintModule, PseudoRow, ReactiveData as ReactiveDataModule, Renderer, ResizeColumns as ResizeColumnsModule, ResizeRows as ResizeRowsModule, ResizeTable as ResizeTableModule, ResponsiveLayout as ResponsiveLayoutModule, RowComponent, SelectRow as SelectRowModule, Sort as SortModule, Tabulator, TabulatorFull, Tooltip as TooltipModule, Validate as ValidateModule }; //# sourceMappingURL=tabulator_esm.js.map