tabulator-tables
Version:
Interactive table generation JavaScript library
894 lines (683 loc) • 23.8 kB
JavaScript
'use strict';
import defaultOptions from './defaults/options.js';
import ColumnManager from './ColumnManager.js';
import RowManager from './RowManager.js';
import FooterManager from './FooterManager.js';
import InteractionMonitor from './tools/InteractionMonitor.js';
import ComponentFunctionBinder from './tools/ComponentFunctionBinder.js';
import DataLoader from './tools/DataLoader.js';
import ExternalEventBus from './tools/ExternalEventBus.js';
import InternalEventBus from './tools/InternalEventBus.js';
import DeprecationAdvisor from './tools/DeprecationAdvisor.js';
import TableRegistry from './tools/TableRegistry.js';
import ModuleBinder from './tools/ModuleBinder.js';
import OptionsList from './tools/OptionsList.js';
import Alert from './tools/Alert.js';
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 InteractionMonitor(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.rows.forEach(function(row){
row.wipe();
});
this.rowManager.rows = [];
this.rowManager.activeRows = [];
this.rowManager.displayRows = [];
//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{
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();
}
});
}
});
}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)
.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);
export default Tabulator;