tabulator-tables
Version:
Interactive table generation JavaScript library
2,177 lines (1,669 loc) • 758 kB
JavaScript
/* Tabulator v6.3.1 (c) Oliver Folkerd 2025 */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Tabulator = factory());
})(this, (function () { 'use strict';
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
rowHeader:false,
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:{},
dependencies:{},
};
class CoreFeature{
constructor(table){
this.table = table;
}
//////////////////////////////////////////
/////////////// DataLoad /////////////////
//////////////////////////////////////////
reloadData(data, silent, columnsChanged){
return this.table.dataLoader.load(data, undefined, undefined, undefined, silent, columnsChanged);
}
//////////////////////////////////////////
///////////// Localization ///////////////
//////////////////////////////////////////
langText(){
return this.table.modules.localize.getText(...arguments);
}
langBind(){
return this.table.modules.localize.bind(...arguments);
}
langLocale(){
return this.table.modules.localize.getLocale(...arguments);
}
//////////////////////////////////////////
////////// Inter Table Comms /////////////
//////////////////////////////////////////
commsConnections(){
return this.table.modules.comms.getConnections(...arguments);
}
commsSend(){
return this.table.modules.comms.send(...arguments);
}
//////////////////////////////////////////
//////////////// Layout /////////////////
//////////////////////////////////////////
layoutMode(){
return this.table.modules.layout.getMode();
}
layoutRefresh(force){
return this.table.modules.layout.layout(force);
}
//////////////////////////////////////////
/////////////// Event Bus ////////////////
//////////////////////////////////////////
subscribe(){
return this.table.eventBus.subscribe(...arguments);
}
unsubscribe(){
return this.table.eventBus.unsubscribe(...arguments);
}
subscribed(key){
return this.table.eventBus.subscribed(key);
}
subscriptionChange(){
return this.table.eventBus.subscriptionChange(...arguments);
}
dispatch(){
return this.table.eventBus.dispatch(...arguments);
}
chain(){
return this.table.eventBus.chain(...arguments);
}
confirm(){
return this.table.eventBus.confirm(...arguments);
}
dispatchExternal(){
return this.table.externalEvents.dispatch(...arguments);
}
subscribedExternal(key){
return this.table.externalEvents.subscribed(key);
}
subscriptionChangeExternal(){
return this.table.externalEvents.subscriptionChange(...arguments);
}
//////////////////////////////////////////
//////////////// Options /////////////////
//////////////////////////////////////////
options(key){
return this.table.options[key];
}
setOption(key, value){
if(typeof value !== "undefined"){
this.table.options[key] = value;
}
return this.table.options[key];
}
//////////////////////////////////////////
/////////// Deprecation Checks ///////////
//////////////////////////////////////////
deprecationCheck(oldOption, newOption, convert){
return this.table.deprecationAdvisor.check(oldOption, newOption, convert);
}
deprecationCheckMsg(oldOption, msg){
return this.table.deprecationAdvisor.checkMsg(oldOption, msg);
}
deprecationMsg(msg){
return this.table.deprecationAdvisor.msg(msg);
}
//////////////////////////////////////////
//////////////// Modules /////////////////
//////////////////////////////////////////
module(key){
return this.table.module(key);
}
}
//public column object
class ColumnComponent {
constructor (column){
this._column = column;
this.type = "ColumnComponent";
return new Proxy(this, {
get: function(target, name, receiver) {
if (typeof target[name] !== "undefined") {
return target[name];
}else {
return target._column.table.componentFunctionBinder.handle("column", target._column, name);
}
}
});
}
getElement(){
return this._column.getElement();
}
getDefinition(){
return this._column.getDefinition();
}
getField(){
return this._column.getField();
}
getTitleDownload() {
return this._column.getTitleDownload();
}
getCells(){
var cells = [];
this._column.cells.forEach(function(cell){
cells.push(cell.getComponent());
});
return cells;
}
isVisible(){
return this._column.visible;
}
show(){
if(this._column.isGroup){
this._column.columns.forEach(function(column){
column.show();
});
}else {
this._column.show();
}
}
hide(){
if(this._column.isGroup){
this._column.columns.forEach(function(column){
column.hide();
});
}else {
this._column.hide();
}
}
toggle(){
if(this._column.visible){
this.hide();
}else {
this.show();
}
}
delete(){
return this._column.delete();
}
getSubColumns(){
var output = [];
if(this._column.columns.length){
this._column.columns.forEach(function(column){
output.push(column.getComponent());
});
}
return output;
}
getParentColumn(){
return this._column.getParentComponent();
}
_getSelf(){
return this._column;
}
scrollTo(position, ifVisible){
return this._column.table.columnManager.scrollToColumn(this._column, position, ifVisible);
}
getTable(){
return this._column.table;
}
move(to, after){
var toColumn = this._column.table.columnManager.findColumn(to);
if(toColumn){
this._column.table.columnManager.moveColumn(this._column, toColumn, after);
}else {
console.warn("Move Error - No matching column found:", toColumn);
}
}
getNextColumn(){
var nextCol = this._column.nextColumn();
return nextCol ? nextCol.getComponent() : false;
}
getPrevColumn(){
var prevCol = this._column.prevColumn();
return prevCol ? prevCol.getComponent() : false;
}
updateDefinition(updates){
return this._column.updateDefinition(updates);
}
getWidth(){
return this._column.getWidth();
}
setWidth(width){
var result;
if(width === true){
result = this._column.reinitializeWidth(true);
}else {
result = this._column.setWidth(width);
}
this._column.table.columnManager.rerenderColumns(true);
return result;
}
}
var defaultColumnOptions = {
"title": undefined,
"field": undefined,
"columns": undefined,
"visible": undefined,
"hozAlign": undefined,
"vertAlign": undefined,
"width": undefined,
"minWidth": 40,
"maxWidth": undefined,
"maxInitialWidth": undefined,
"cssClass": undefined,
"variableHeight": undefined,
"headerVertical": undefined,
"headerHozAlign": undefined,
"headerWordWrap": false,
"editableTitle": undefined,
};
//public cell object
class CellComponent {
constructor (cell){
this._cell = cell;
return new Proxy(this, {
get: function(target, name, receiver) {
if (typeof target[name] !== "undefined") {
return target[name];
}else {
return target._cell.table.componentFunctionBinder.handle("cell", target._cell, name);
}
}
});
}
getValue(){
return this._cell.getValue();
}
getOldValue(){
return this._cell.getOldValue();
}
getInitialValue(){
return this._cell.initialValue;
}
getElement(){
return this._cell.getElement();
}
getRow(){
return this._cell.row.getComponent();
}
getData(transform){
return this._cell.row.getData(transform);
}
getType(){
return "cell";
}
getField(){
return this._cell.column.getField();
}
getColumn(){
return this._cell.column.getComponent();
}
setValue(value, mutate){
if(typeof mutate == "undefined"){
mutate = true;
}
this._cell.setValue(value, mutate);
}
restoreOldValue(){
this._cell.setValueActual(this._cell.getOldValue());
}
restoreInitialValue(){
this._cell.setValueActual(this._cell.initialValue);
}
checkHeight(){
this._cell.checkHeight();
}
getTable(){
return this._cell.table;
}
_getSelf(){
return this._cell;
}
}
class Cell extends CoreFeature{
constructor(column, row){
super(column.table);
this.table = column.table;
this.column = column;
this.row = row;
this.element = null;
this.value = null;
this.initialValue;
this.oldValue = null;
this.modules = {};
this.height = null;
this.width = null;
this.minWidth = null;
this.component = null;
this.loaded = false; //track if the cell has been added to the DOM yet
this.build();
}
//////////////// Setup Functions /////////////////
//generate element
build(){
this.generateElement();
this.setWidth();
this._configureCell();
this.setValueActual(this.column.getFieldValue(this.row.data));
this.initialValue = this.value;
}
generateElement(){
this.element = document.createElement('div');
this.element.className = "tabulator-cell";
this.element.setAttribute("role", "gridcell");
if(this.column.isRowHeader){
this.element.classList.add("tabulator-row-header");
}
}
_configureCell(){
var element = this.element,
field = this.column.getField(),
vertAligns = {
top:"flex-start",
bottom:"flex-end",
middle:"center",
},
hozAligns = {
left:"flex-start",
right:"flex-end",
center:"center",
};
//set text alignment
element.style.textAlign = this.column.hozAlign;
if(this.column.vertAlign){
element.style.display = "inline-flex";
element.style.alignItems = vertAligns[this.column.vertAlign] || "";
if(this.column.hozAlign){
element.style.justifyContent = hozAligns[this.column.hozAlign] || "";
}
}
if(field){
element.setAttribute("tabulator-field", field);
}
//add class to cell if needed
if(this.column.definition.cssClass){
var classNames = this.column.definition.cssClass.split(" ");
classNames.forEach((className) => {
element.classList.add(className);
});
}
this.dispatch("cell-init", this);
//hide cell if not visible
if(!this.column.visible){
this.hide();
}
}
//generate cell contents
_generateContents(){
var val;
val = this.chain("cell-format", this, null, () => {
return this.element.innerHTML = this.value;
});
switch(typeof val){
case "object":
if(val instanceof Node){
//clear previous cell contents
while(this.element.firstChild) this.element.removeChild(this.element.firstChild);
this.element.appendChild(val);
}else {
this.element.innerHTML = "";
if(val != null){
console.warn("Format Error - Formatter has returned a type of object, the only valid formatter object return is an instance of Node, the formatter returned:", val);
}
}
break;
case "undefined":
this.element.innerHTML = "";
break;
default:
this.element.innerHTML = val;
}
}
cellRendered(){
this.dispatch("cell-rendered", this);
}
//////////////////// Getters ////////////////////
getElement(containerOnly){
if(!this.loaded){
this.loaded = true;
if(!containerOnly){
this.layoutElement();
}
}
return this.element;
}
getValue(){
return this.value;
}
getOldValue(){
return this.oldValue;
}
//////////////////// Actions ////////////////////
setValue(value, mutate, force){
var changed = this.setValueProcessData(value, mutate, force);
if(changed){
this.dispatch("cell-value-updated", this);
this.cellRendered();
if(this.column.definition.cellEdited){
this.column.definition.cellEdited.call(this.table, this.getComponent());
}
this.dispatchExternal("cellEdited", this.getComponent());
if(this.subscribedExternal("dataChanged")){
this.dispatchExternal("dataChanged", this.table.rowManager.getData());
}
}
}
setValueProcessData(value, mutate, force){
var changed = false;
if(this.value !== value || force){
changed = true;
if(mutate){
value = this.chain("cell-value-changing", [this, value], null, value);
}
}
this.setValueActual(value);
if(changed){
this.dispatch("cell-value-changed", this);
}
return changed;
}
setValueActual(value){
this.oldValue = this.value;
this.value = value;
this.dispatch("cell-value-save-before", this);
this.column.setFieldValue(this.row.data, value);
this.dispatch("cell-value-save-after", this);
if(this.loaded){
this.layoutElement();
}
}
layoutElement(){
this._generateContents();
this.dispatch("cell-layout", this);
}
setWidth(){
this.width = this.column.width;
this.element.style.width = this.column.widthStyled;
}
clearWidth(){
this.width = "";
this.element.style.width = "";
}
getWidth(){
return this.width || this.element.offsetWidth;
}
setMinWidth(){
this.minWidth = this.column.minWidth;
this.element.style.minWidth = this.column.minWidthStyled;
}
setMaxWidth(){
this.maxWidth = this.column.maxWidth;
this.element.style.maxWidth = this.column.maxWidthStyled;
}
checkHeight(){
// var height = this.element.css("height");
this.row.reinitializeHeight();
}
clearHeight(){
this.element.style.height = "";
this.height = null;
this.dispatch("cell-height", this, "");
}
setHeight(){
this.height = this.row.height;
this.element.style.height = this.row.heightStyled;
this.dispatch("cell-height", this, this.row.heightStyled);
}
getHeight(){
return this.height || this.element.offsetHeight;
}
show(){
this.element.style.display = this.column.vertAlign ? "inline-flex" : "";
}
hide(){
this.element.style.display = "none";
}
delete(){
this.dispatch("cell-delete", this);
if(!this.table.rowManager.redrawBlock && this.element.parentNode){
this.element.parentNode.removeChild(this.element);
}
this.element = false;
this.column.deleteCell(this);
this.row.deleteCell(this);
this.calcs = {};
}
getIndex(){
return this.row.getCellIndex(this);
}
//////////////// Object Generation /////////////////
getComponent(){
if(!this.component){
this.component = new CellComponent(this);
}
return this.component;
}
}
class Column extends CoreFeature{
static defaultOptionList = defaultColumnOptions;
constructor(def, parent, rowHeader){
super(parent.table);
this.definition = def; //column definition
this.parent = parent; //hold parent object
this.type = "column"; //type of element
this.columns = []; //child columns
this.cells = []; //cells bound to this column
this.isGroup = false;
this.isRowHeader = rowHeader;
this.element = this.createElement(); //column header element
this.contentElement = false;
this.titleHolderElement = false;
this.titleElement = false;
this.groupElement = this.createGroupElement(); //column group holder element
this.hozAlign = ""; //horizontal text alignment
this.vertAlign = ""; //vert text alignment
//multi dimensional filed handling
this.field ="";
this.fieldStructure = "";
this.getFieldValue = "";
this.setFieldValue = "";
this.titleDownload = null;
this.titleFormatterRendered = false;
this.mapDefinitions();
this.setField(this.definition.field);
this.modules = {}; //hold module variables;
this.width = null; //column width
this.widthStyled = ""; //column width pre-styled to improve render efficiency
this.maxWidth = null; //column maximum width
this.maxWidthStyled = ""; //column maximum pre-styled to improve render efficiency
this.maxInitialWidth = null;
this.minWidth = null; //column minimum width
this.minWidthStyled = ""; //column minimum pre-styled to improve render efficiency
this.widthFixed = false; //user has specified a width for this column
this.visible = true; //default visible state
this.component = null;
//initialize column
if(this.definition.columns){
this.isGroup = true;
this.definition.columns.forEach((def, i) => {
var newCol = new Column(def, this);
this.attachColumn(newCol);
});
this.checkColumnVisibility();
}else {
parent.registerColumnField(this);
}
this._initialize();
}
createElement (){
var el = document.createElement("div");
el.classList.add("tabulator-col");
el.setAttribute("role", "columnheader");
el.setAttribute("aria-sort", "none");
if(this.isRowHeader){
el.classList.add("tabulator-row-header");
}
switch(this.table.options.columnHeaderVertAlign){
case "middle":
el.style.justifyContent = "center";
break;
case "bottom":
el.style.justifyContent = "flex-end";
break;
}
return el;
}
createGroupElement (){
var el = document.createElement("div");
el.classList.add("tabulator-col-group-cols");
return el;
}
mapDefinitions(){
var defaults = this.table.options.columnDefaults;
//map columnDefaults onto column definitions
if(defaults){
for(let key in defaults){
if(typeof this.definition[key] === "undefined"){
this.definition[key] = defaults[key];
}
}
}
this.definition = this.table.columnManager.optionsList.generate(Column.defaultOptionList, this.definition);
}
checkDefinition(){
Object.keys(this.definition).forEach((key) => {
if(Column.defaultOptionList.indexOf(key) === -1){
console.warn("Invalid column definition option in '" + (this.field || this.definition.title) + "' column:", key);
}
});
}
setField(field){
this.field = field;
this.fieldStructure = field ? (this.table.options.nestedFieldSeparator ? field.split(this.table.options.nestedFieldSeparator) : [field]) : [];
this.getFieldValue = this.fieldStructure.length > 1 ? this._getNestedData : this._getFlatData;
this.setFieldValue = this.fieldStructure.length > 1 ? this._setNestedData : this._setFlatData;
}
//register column position with column manager
registerColumnPosition(column){
this.parent.registerColumnPosition(column);
}
//register column position with column manager
registerColumnField(column){
this.parent.registerColumnField(column);
}
//trigger position registration
reRegisterPosition(){
if(this.isGroup){
this.columns.forEach(function(column){
column.reRegisterPosition();
});
}else {
this.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("mousedown", (e) => {
e.stopPropagation();
});
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.table.initialized) {
this.element.style.width = childWidth + "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);
if(this.subscribedExternal("columnWidth")){
this.dispatchExternal("columnWidth", this.getComponent());
}
}
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);
this.dispatch("column-deleted", this);
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(force){
this.setWidth(setTo);
}else {
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;
}
getPosition(){
return this.table.columnManager.getVisibleColumnsByIndex().indexOf(this) + 1;
}
getParentComponent(){
return this.parent instanceof Column ? this.parent.getComponent() : false;
}
}
class Helpers{
static elVisible(el){
return !(el.offsetWidth <= 0 && el.offsetHeight <= 0);
}
static elOffset(el){
var box = el.getBoundingClientRect();
return {
top: box.top + window.pageYOffset - document.documentElement.clientTop,
left: box.left + window.pageXOffset - document.documentElement.clientLeft
};
}
static retrieveNestedData(separator, field, data){
var structure = separator ? field.split(separator) : [field],
length = structure.length,
output;
for(let i = 0; i < length; i++){
data = data[structure[i]];
output = data;
if(!data){
break;
}
}
return output;
}
static deepClone(obj, clone, list = []){
var objectProto = {}.__proto__,
arrayProto = [].__proto__;
if (!clone){
clone = Object.assign(Array.isArray(obj) ? [] : {}, obj);
}
for(var i in obj) {
let subject = obj[i],
match, copy;
if(subject != null && typeof subject === "object" && (subject.__proto__ === objectProto || subject.__proto__ === arrayProto)){
match = list.findIndex((item) => {
return item.subject === subject;
});
if(match > -1){
clone[i] = list[match].copy;
}else {
copy = Object.assign(Array.isArray(subject) ? [] : {}, subject);
list.unshift({subject, copy});
clone[i] = this.deepClone(subject, copy, list);
}
}
}
return clone;
}
}
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, inFragment) {
const rowFrag = document.createDocumentFragment();
row.cells.forEach((cell) => {
rowFrag.appendChild(cell.getElement());
});
row.element.appendChild(rowFrag);
if(!inFragment){
row.cells.forEach((cell) => {
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.initi