29630 lines
740 KiB
JavaScript
29630 lines
740 KiB
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.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 {
|
|
const rowFrag = document.createDocumentFragment();
|
|
row.cells.forEach((cell) => {
|
|
rowFrag.appendChild(cell.getElement());
|
|
});
|
|
row.element.appendChild(rowFrag);
|
|
|
|
row.cells.forEach((cell) => {
|
|
cell.cellRendered();
|
|
});
|
|
}
|
|
}
|
|
|
|
rerenderRowCells(row, force){
|
|
this.reinitializeRow(row, force);
|
|
}
|
|
|
|
reinitializeColumnWidths(columns){
|
|
for(let i = this.leftCol; i <= this.rightCol; i++){
|
|
let col = this.columns[i];
|
|
|
|
if(col){
|
|
col.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.rowHeader = 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.initializeScrollWheelWatcher();
|
|
|
|
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);
|
|
}
|
|
|
|
initializeScrollWheelWatcher(){
|
|
this.contentsElement.addEventListener("wheel", (e) => {
|
|
var left;
|
|
|
|
if(e.deltaX){
|
|
left = this.contentsElement.scrollLeft + e.deltaX;
|
|
|
|
this.table.rowManager.scrollHorizontal(left);
|
|
this.table.columnManager.scrollHorizontal(left);
|
|
}
|
|
});
|
|
}
|
|
|
|
///////////// Column Setup Functions /////////////
|
|
generateColumnsFromRowData(data){
|
|
var cols = [],
|
|
collProgress = {},
|
|
rowSample = this.table.options.autoColumns === "full" ? data : [data[0]],
|
|
definitions = this.table.options.autoColumnsDefinitions;
|
|
|
|
if(data && data.length){
|
|
|
|
rowSample.forEach((row) => {
|
|
|
|
Object.keys(row).forEach((key, index) => {
|
|
let value = row[key],
|
|
col;
|
|
|
|
if(!collProgress[key]){
|
|
col = {
|
|
field:key,
|
|
title:key,
|
|
sorter:this.calculateSorterFromValue(value),
|
|
};
|
|
|
|
cols.splice(index, 0, col);
|
|
collProgress[key] = typeof value === "undefined" ? col : true;
|
|
}else if(collProgress[key] !== true){
|
|
if(typeof value !== "undefined"){
|
|
collProgress[key].sorter = this.calculateSorterFromValue(value);
|
|
collProgress[key] = true;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
calculateSorterFromValue(value){
|
|
var sorter;
|
|
|
|
switch(typeof value){
|
|
case "undefined":
|
|
sorter = "string";
|
|
break;
|
|
|
|
case "boolean":
|
|
sorter = "boolean";
|
|
break;
|
|
|
|
case "number":
|
|
sorter = "number";
|
|
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;
|
|
}
|
|
|
|
return sorter;
|
|
}
|
|
|
|
setColumns(cols, row){
|
|
while(this.headersElement.firstChild) this.headersElement.removeChild(this.headersElement.firstChild);
|
|
|
|
this.columns = [];
|
|
this.columnsByIndex = [];
|
|
this.columnsByField = {};
|
|
|
|
this.dispatch("columns-loading");
|
|
this.dispatchExternal("columnsLoading");
|
|
|
|
if(this.table.options.rowHeader){
|
|
this.rowHeader = new Column(this.table.options.rowHeader === true ? {} : this.table.options.rowHeader, this, true);
|
|
this.columns.push(this.rowHeader);
|
|
this.headersElement.appendChild(this.rowHeader.getElement());
|
|
this.rowHeader.columnRendered();
|
|
}
|
|
|
|
cols.forEach((def, i) => {
|
|
this._addColumn(def);
|
|
});
|
|
|
|
this._reIndexColumns();
|
|
|
|
this.dispatch("columns-loaded");
|
|
|
|
if(this.subscribedExternal("columnsLoaded")){
|
|
this.dispatchExternal("columnsLoaded", this.getComponents());
|
|
}
|
|
|
|
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;
|
|
|
|
//prevent adding of rows in front of row header
|
|
if(before && this.rowHeader && (!nextToColumn || nextToColumn === this.rowHeader)){
|
|
before = false;
|
|
nextToColumn = this.rowHeader;
|
|
index = 0;
|
|
}
|
|
|
|
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 = this.table.options.nestedFieldSeparator ? field.split(this.table.options.nestedFieldSeparator)[0] : field;
|
|
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;
|
|
}
|
|
|
|
getVisibleColumnsByIndex() {
|
|
return this.columnsByIndex.filter((col) => col.visible);
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
//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(position, ifVisible){
|
|
return this._row.table.rowManager.scrollToRow(this._row, position, ifVisible);
|
|
}
|
|
|
|
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, inFragment){
|
|
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, inFragment);
|
|
|
|
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, inFragment);
|
|
}
|
|
}
|
|
|
|
rendered(){
|
|
this.cells.forEach((cell) => {
|
|
cell.cellRendered();
|
|
});
|
|
}
|
|
|
|
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 = 0;
|
|
|
|
if(this.table.options.rowHeight){
|
|
this.height = this.table.options.rowHeight;
|
|
}else {
|
|
minHeight = this.calcMinHeight();
|
|
maxHeight = this.calcMaxHeight();
|
|
|
|
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;
|
|
}
|
|
|
|
calcMinHeight(){
|
|
return this.table.options.resizableRows ? this.element.clientHeight : 0;
|
|
}
|
|
|
|
calcMaxHeight(){
|
|
var maxHeight = 0;
|
|
|
|
this.cells.forEach(function(cell){
|
|
var height = cell.getHeight();
|
|
|
|
if(height > maxHeight){
|
|
maxHeight = height;
|
|
}
|
|
});
|
|
|
|
return maxHeight;
|
|
}
|
|
|
|
//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;
|
|
|
|
if(this.subscribedExternal("rowHeight")){
|
|
this.dispatchExternal("rowHeight", this.getComponent());
|
|
}
|
|
}
|
|
}
|
|
|
|
//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;
|
|
}
|
|
}
|
|
|
|
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,
|
|
tableFrag = document.createDocumentFragment(),
|
|
rows = this.rows();
|
|
|
|
rows.forEach((row, index) => {
|
|
this.styleRow(row, index);
|
|
row.initialize(false, true);
|
|
|
|
if (row.type !== "group") {
|
|
onlyGroupHeaders = false;
|
|
}
|
|
|
|
tableFrag.appendChild(row.getElement());
|
|
});
|
|
|
|
element.appendChild(tableFrag);
|
|
|
|
rows.forEach((row) => {
|
|
row.rendered();
|
|
|
|
if(!row.heightInitialized) {
|
|
row.calcHeight(true);
|
|
}
|
|
});
|
|
|
|
rows.forEach((row) => {
|
|
if(!row.heightInitialized) {
|
|
row.setCellHeight();
|
|
}
|
|
});
|
|
|
|
if(onlyGroupHeaders){
|
|
element.style.minWidth = this.table.columnManager.getWidth() + "px";
|
|
}else {
|
|
element.style.minWidth = "";
|
|
}
|
|
}
|
|
|
|
|
|
rerenderRows(callback){
|
|
this.clearRows();
|
|
|
|
if(callback){
|
|
callback();
|
|
}
|
|
|
|
this.renderRows();
|
|
|
|
if(!this.rows().length){
|
|
this.table.rowManager.tableEmpty();
|
|
}
|
|
}
|
|
|
|
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.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,
|
|
rowHeight = 0,
|
|
heightOccupied = 0,
|
|
topPadHeight = 0,
|
|
i = 0,
|
|
rows = this.rows(),
|
|
rowsCount = rows.length,
|
|
index = 0,
|
|
row,
|
|
rowFragment,
|
|
renderedRows = [],
|
|
totalRowsRendered = 0,
|
|
rowsToRender = 0,
|
|
fixedHeight = this.table.rowManager.fixedHeight,
|
|
containerHeight = this.elementVertical.clientHeight,
|
|
avgRowHeight = this.table.options.rowHeight,
|
|
resized = true;
|
|
|
|
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;
|
|
|
|
if(fixedHeight || this.table.options.maxHeight) {
|
|
if(avgRowHeight) {
|
|
rowsToRender = (containerHeight / avgRowHeight) + (this.vDomWindowBuffer / avgRowHeight);
|
|
}
|
|
rowsToRender = Math.max(this.vDomWindowMinTotalRows, Math.ceil(rowsToRender));
|
|
}
|
|
else {
|
|
rowsToRender = rowsCount;
|
|
}
|
|
|
|
while(((rowsToRender == rowsCount || rowsHeight <= containerHeight + this.vDomWindowBuffer) || totalRowsRendered < this.vDomWindowMinTotalRows) && this.vDomBottom < rowsCount -1) {
|
|
renderedRows = [];
|
|
rowFragment = document.createDocumentFragment();
|
|
|
|
i = 0;
|
|
|
|
while ((i < rowsToRender) && this.vDomBottom < rowsCount -1) {
|
|
index = this.vDomBottom + 1,
|
|
row = rows[index];
|
|
|
|
this.styleRow(row, index);
|
|
|
|
row.initialize(false, true);
|
|
if(!row.heightInitialized && !this.table.options.rowHeight){
|
|
row.clearCellHeight();
|
|
}
|
|
|
|
rowFragment.appendChild(row.getElement());
|
|
renderedRows.push(row);
|
|
this.vDomBottom ++;
|
|
i++;
|
|
}
|
|
|
|
if(!renderedRows.length){
|
|
break;
|
|
}
|
|
|
|
element.appendChild(rowFragment);
|
|
|
|
// NOTE: The next 3 loops are separate on purpose
|
|
// This is to batch up the dom writes and reads which drastically improves performance
|
|
|
|
renderedRows.forEach((row) => {
|
|
row.rendered();
|
|
|
|
if(!row.heightInitialized) {
|
|
row.calcHeight(true);
|
|
}
|
|
});
|
|
|
|
renderedRows.forEach((row) => {
|
|
if(!row.heightInitialized) {
|
|
row.setCellHeight();
|
|
}
|
|
});
|
|
|
|
renderedRows.forEach((row) => {
|
|
rowHeight = row.getHeight();
|
|
|
|
if(totalRowsRendered < topPad){
|
|
topPadHeight += rowHeight;
|
|
}else {
|
|
rowsHeight += rowHeight;
|
|
}
|
|
|
|
if(rowHeight > this.vDomWindowBuffer){
|
|
this.vDomWindowBuffer = rowHeight * 2;
|
|
}
|
|
totalRowsRendered++;
|
|
});
|
|
|
|
resized = this.table.rowManager.adjustTableSize();
|
|
containerHeight = this.elementVertical.clientHeight;
|
|
if(resized && (fixedHeight || this.table.options.maxHeight))
|
|
{
|
|
avgRowHeight = rowsHeight / totalRowsRendered;
|
|
rowsToRender = Math.max(this.vDomWindowMinTotalRows, Math.ceil((containerHeight / avgRowHeight) + (this.vDomWindowBuffer / avgRowHeight)));
|
|
}
|
|
}
|
|
|
|
if(!position){
|
|
this.vDomTopPad = 0;
|
|
//adjust row height to match average of rendered elements
|
|
this.vDomRowHeight = Math.floor((rowsHeight + topPadHeight) / totalRowsRendered);
|
|
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;
|
|
|
|
if(typeof placeholder === "function"){
|
|
placeholder = placeholder.call(this.table);
|
|
}
|
|
|
|
placeholder = this.chain("placeholder", [placeholder], placeholder, placeholder) || 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.type === "row" && 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(this.displayRowsCount){
|
|
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.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();
|
|
}
|
|
|
|
checkPlaceholder(){
|
|
if(this.displayRowsCount){
|
|
this._clearPlaceholder();
|
|
}else {
|
|
this.tableEmpty();
|
|
}
|
|
}
|
|
|
|
_showPlaceholder(){
|
|
if(this.placeholder){
|
|
if(this.placeholder && this.placeholder.parentNode){
|
|
this.placeholder.parentNode.removeChild(this.placeholder);
|
|
}
|
|
|
|
this.initializePlaceholder();
|
|
|
|
this.placeholder.setAttribute("tabulator-render-mode", this.renderMode);
|
|
|
|
this.getElement().appendChild(this.placeholder);
|
|
this._positionPlaceholder();
|
|
|
|
this.adjustTableSize();
|
|
}
|
|
}
|
|
|
|
_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(force){
|
|
this.activeRows.forEach(function(row){
|
|
row.normalizeHeight(force);
|
|
});
|
|
}
|
|
|
|
//adjust the height of the table holder to fit in the Tabulator element
|
|
adjustTableSize(){
|
|
let initialHeight = this.element.clientHeight, minHeight;
|
|
let resized = false;
|
|
|
|
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";
|
|
|
|
const height = "calc(100% - " + otherHeight + "px)";
|
|
this.element.style.minHeight = minHeight || "calc(100% - " + otherHeight + "px)";
|
|
this.element.style.height = height;
|
|
this.element.style.maxHeight = height;
|
|
} 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){
|
|
resized = true;
|
|
if(this.subscribed("table-resize")){
|
|
this.dispatch("table-resize");
|
|
}else {
|
|
this.redraw();
|
|
}
|
|
}
|
|
|
|
this.scrollBarCheck();
|
|
}
|
|
|
|
this._positionPlaceholder();
|
|
return resized;
|
|
}
|
|
|
|
//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){
|
|
this.adjustTableSize();
|
|
this.table.tableWidth = this.table.element.clientWidth;
|
|
|
|
if(!force){
|
|
this.reRenderInPosition();
|
|
this.scrollHorizontal(this.scrollLeft);
|
|
}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 = {},
|
|
output = {},
|
|
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;
|
|
|
|
//reverse order keys are set in so events trigger in correct sequence
|
|
Object.keys(targets).forEach((key) => {
|
|
let value = matches[key];
|
|
output[key] = value;
|
|
});
|
|
|
|
return output;
|
|
}
|
|
|
|
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;
|
|
|
|
if(this.table.destroyed){
|
|
return Promise.resolve();
|
|
}
|
|
|
|
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(!this.table.destroyed){
|
|
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");
|
|
}
|
|
}else {
|
|
console.warn("Data Load Response Blocked - Table has been destroyed");
|
|
}
|
|
}).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, convert){
|
|
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;');
|
|
|
|
if(convert){
|
|
this.table.options[newOption] = this.table.options[oldOption];
|
|
}
|
|
}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 DependencyRegistry extends CoreFeature{
|
|
|
|
constructor(table){
|
|
super(table);
|
|
|
|
this.deps = {};
|
|
|
|
this.props = {
|
|
|
|
};
|
|
}
|
|
|
|
initialize(){
|
|
this.deps = Object.assign({}, this.options("dependencies"));
|
|
}
|
|
|
|
lookup(key, prop, silent){
|
|
if(Array.isArray(key)){
|
|
for (const item of key) {
|
|
var match = this.lookup(item, prop, true);
|
|
|
|
if(match){
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(match){
|
|
return match;
|
|
}else {
|
|
this.error(key);
|
|
}
|
|
}else {
|
|
if(prop){
|
|
return this.lookupProp(key, prop, silent);
|
|
}else {
|
|
return this.lookupKey(key, silent);
|
|
}
|
|
}
|
|
}
|
|
|
|
lookupProp(key, prop, silent){
|
|
var dependency;
|
|
|
|
if(this.props[key] && this.props[key][prop]){
|
|
return this.props[key][prop];
|
|
}else {
|
|
dependency = this.lookupKey(key, silent);
|
|
|
|
if(dependency){
|
|
if(!this.props[key]){
|
|
this.props[key] = {};
|
|
}
|
|
|
|
this.props[key][prop] = dependency[prop] || dependency;
|
|
return this.props[key][prop];
|
|
}
|
|
}
|
|
}
|
|
|
|
lookupKey(key, silent){
|
|
var dependency;
|
|
|
|
if(this.deps[key]){
|
|
dependency = this.deps[key];
|
|
}else if(window[key]){
|
|
this.deps[key] = window[key];
|
|
dependency = this.deps[key];
|
|
}else {
|
|
if(!silent){
|
|
this.error(key);
|
|
}
|
|
}
|
|
|
|
return dependency;
|
|
}
|
|
|
|
error(key){
|
|
console.error("Unable to find dependency", key, "Please check documentation and ensure you have imported the required library into your project");
|
|
}
|
|
}
|
|
|
|
let Popup$1 = class Popup extends CoreFeature{
|
|
constructor(table, element, parent){
|
|
super(table);
|
|
|
|
this.element = element;
|
|
this.container = this._lookupContainer();
|
|
|
|
this.parent = parent;
|
|
|
|
this.reversedX = false;
|
|
this.childPopup = null;
|
|
this.blurable = false;
|
|
this.blurCallback = null;
|
|
this.blurEventsBound = false;
|
|
this.renderedCallback = null;
|
|
|
|
this.visible = false;
|
|
this.hideable = true;
|
|
|
|
this.element.classList.add("tabulator-popup-container");
|
|
|
|
this.blurEvent = this.hide.bind(this, false);
|
|
this.escEvent = this._escapeCheck.bind(this);
|
|
|
|
this.destroyBinding = this.tableDestroyed.bind(this);
|
|
this.destroyed = false;
|
|
}
|
|
|
|
tableDestroyed(){
|
|
this.destroyed = true;
|
|
this.hide(true);
|
|
}
|
|
|
|
_lookupContainer(){
|
|
var container = this.table.options.popupContainer;
|
|
|
|
if(typeof container === "string"){
|
|
container = document.querySelector(container);
|
|
|
|
if(!container){
|
|
console.warn("Menu Error - no container element found matching selector:", this.table.options.popupContainer , "(defaulting to document body)");
|
|
}
|
|
}else if (container === true){
|
|
container = this.table.element;
|
|
}
|
|
|
|
if(container && !this._checkContainerIsParent(container)){
|
|
container = false;
|
|
console.warn("Menu Error - container element does not contain this table:", this.table.options.popupContainer , "(defaulting to document body)");
|
|
}
|
|
|
|
if(!container){
|
|
container = document.body;
|
|
}
|
|
|
|
return container;
|
|
}
|
|
|
|
_checkContainerIsParent(container, element = this.table.element){
|
|
if(container === element){
|
|
return true;
|
|
}else {
|
|
return element.parentNode ? this._checkContainerIsParent(container, element.parentNode) : false;
|
|
}
|
|
}
|
|
|
|
renderCallback(callback){
|
|
this.renderedCallback = callback;
|
|
}
|
|
|
|
containerEventCoords(e){
|
|
var touch = !(e instanceof MouseEvent);
|
|
|
|
var x = touch ? e.touches[0].pageX : e.pageX;
|
|
var y = touch ? e.touches[0].pageY : e.pageY;
|
|
|
|
if(this.container !== document.body){
|
|
let parentOffset = Helpers.elOffset(this.container);
|
|
|
|
x -= parentOffset.left;
|
|
y -= parentOffset.top;
|
|
}
|
|
|
|
return {x, y};
|
|
}
|
|
|
|
elementPositionCoords(element, position = "right"){
|
|
var offset = Helpers.elOffset(element),
|
|
containerOffset, x, y;
|
|
|
|
if(this.container !== document.body){
|
|
containerOffset = Helpers.elOffset(this.container);
|
|
|
|
offset.left -= containerOffset.left;
|
|
offset.top -= containerOffset.top;
|
|
}
|
|
|
|
switch(position){
|
|
case "right":
|
|
x = offset.left + element.offsetWidth;
|
|
y = offset.top - 1;
|
|
break;
|
|
|
|
case "bottom":
|
|
x = offset.left;
|
|
y = offset.top + element.offsetHeight;
|
|
break;
|
|
|
|
case "left":
|
|
x = offset.left;
|
|
y = offset.top - 1;
|
|
break;
|
|
|
|
case "top":
|
|
x = offset.left;
|
|
y = offset.top;
|
|
break;
|
|
|
|
case "center":
|
|
x = offset.left + (element.offsetWidth / 2);
|
|
y = offset.top + (element.offsetHeight / 2);
|
|
break;
|
|
|
|
}
|
|
|
|
return {x, y, offset};
|
|
}
|
|
|
|
show(origin, position){
|
|
var x, y, parentEl, parentOffset, coords;
|
|
|
|
if(this.destroyed || this.table.destroyed){
|
|
return this;
|
|
}
|
|
|
|
if(origin instanceof HTMLElement){
|
|
parentEl = origin;
|
|
coords = this.elementPositionCoords(origin, position);
|
|
|
|
parentOffset = coords.offset;
|
|
x = coords.x;
|
|
y = coords.y;
|
|
|
|
}else if(typeof origin === "number"){
|
|
parentOffset = {top:0, left:0};
|
|
x = origin;
|
|
y = position;
|
|
}else {
|
|
coords = this.containerEventCoords(origin);
|
|
|
|
x = coords.x;
|
|
y = coords.y;
|
|
|
|
this.reversedX = false;
|
|
}
|
|
|
|
this.element.style.top = y + "px";
|
|
this.element.style.left = x + "px";
|
|
|
|
this.container.appendChild(this.element);
|
|
|
|
if(typeof this.renderedCallback === "function"){
|
|
this.renderedCallback();
|
|
}
|
|
|
|
this._fitToScreen(x, y, parentEl, parentOffset, position);
|
|
|
|
this.visible = true;
|
|
|
|
this.subscribe("table-destroy", this.destroyBinding);
|
|
|
|
this.element.addEventListener("mousedown", (e) => {
|
|
e.stopPropagation();
|
|
});
|
|
|
|
return this;
|
|
}
|
|
|
|
_fitToScreen(x, y, parentEl, parentOffset, position){
|
|
var scrollTop = this.container === document.body ? document.documentElement.scrollTop : this.container.scrollTop;
|
|
|
|
//move menu to start on right edge if it is too close to the edge of the screen
|
|
if((x + this.element.offsetWidth) >= this.container.offsetWidth || this.reversedX){
|
|
this.element.style.left = "";
|
|
|
|
if(parentEl){
|
|
this.element.style.right = (this.container.offsetWidth - parentOffset.left) + "px";
|
|
}else {
|
|
this.element.style.right = (this.container.offsetWidth - x) + "px";
|
|
}
|
|
|
|
this.reversedX = true;
|
|
}
|
|
|
|
//move menu to start on bottom edge if it is too close to the edge of the screen
|
|
let offsetHeight = Math.max(this.container.offsetHeight, scrollTop ? this.container.scrollHeight : 0);
|
|
if((y + this.element.offsetHeight) > offsetHeight) {
|
|
if(parentEl){
|
|
switch(position){
|
|
case "bottom":
|
|
this.element.style.top = (parseInt(this.element.style.top) - this.element.offsetHeight - parentEl.offsetHeight - 1) + "px";
|
|
break;
|
|
|
|
default:
|
|
this.element.style.top = (parseInt(this.element.style.top) - this.element.offsetHeight + parentEl.offsetHeight + 1) + "px";
|
|
}
|
|
|
|
}else {
|
|
this.element.style.height = offsetHeight + "px";
|
|
}
|
|
}
|
|
}
|
|
|
|
isVisible(){
|
|
return this.visible;
|
|
}
|
|
|
|
hideOnBlur(callback){
|
|
this.blurable = true;
|
|
|
|
if(this.visible){
|
|
setTimeout(() => {
|
|
if(this.visible){
|
|
this.table.rowManager.element.addEventListener("scroll", this.blurEvent);
|
|
this.subscribe("cell-editing", this.blurEvent);
|
|
document.body.addEventListener("click", this.blurEvent);
|
|
document.body.addEventListener("contextmenu", this.blurEvent);
|
|
document.body.addEventListener("mousedown", this.blurEvent);
|
|
window.addEventListener("resize", this.blurEvent);
|
|
document.body.addEventListener("keydown", this.escEvent);
|
|
|
|
this.blurEventsBound = true;
|
|
}
|
|
}, 100);
|
|
|
|
this.blurCallback = callback;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
_escapeCheck(e){
|
|
if(e.keyCode == 27){
|
|
this.hide();
|
|
}
|
|
}
|
|
|
|
blockHide(){
|
|
this.hideable = false;
|
|
}
|
|
|
|
restoreHide(){
|
|
this.hideable = true;
|
|
}
|
|
|
|
hide(silent = false){
|
|
if(this.visible && this.hideable){
|
|
if(this.blurable && this.blurEventsBound){
|
|
document.body.removeEventListener("keydown", this.escEvent);
|
|
document.body.removeEventListener("click", this.blurEvent);
|
|
document.body.removeEventListener("contextmenu", this.blurEvent);
|
|
document.body.removeEventListener("mousedown", this.blurEvent);
|
|
window.removeEventListener("resize", this.blurEvent);
|
|
this.table.rowManager.element.removeEventListener("scroll", this.blurEvent);
|
|
this.unsubscribe("cell-editing", this.blurEvent);
|
|
|
|
this.blurEventsBound = false;
|
|
}
|
|
|
|
if(this.childPopup){
|
|
this.childPopup.hide();
|
|
}
|
|
|
|
if(this.parent){
|
|
this.parent.childPopup = null;
|
|
}
|
|
|
|
if(this.element.parentNode){
|
|
this.element.parentNode.removeChild(this.element);
|
|
}
|
|
|
|
this.visible = false;
|
|
|
|
if(this.blurCallback && !silent){
|
|
this.blurCallback();
|
|
}
|
|
|
|
this.unsubscribe("table-destroy", this.destroyBinding);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
child(element){
|
|
if(this.childPopup){
|
|
this.childPopup.hide();
|
|
}
|
|
|
|
this.childPopup = new Popup(this.table, element, this);
|
|
|
|
return this.childPopup;
|
|
}
|
|
};
|
|
|
|
class Module extends CoreFeature{
|
|
|
|
constructor(table, name){
|
|
super(table);
|
|
|
|
this._handler = null;
|
|
}
|
|
|
|
initialize(){
|
|
// setup module when table is initialized, to be overridden in module
|
|
}
|
|
|
|
|
|
///////////////////////////////////
|
|
////// Options Registration ///////
|
|
///////////////////////////////////
|
|
|
|
registerTableOption(key, value){
|
|
this.table.optionsList.register(key, value);
|
|
}
|
|
|
|
registerColumnOption(key, value){
|
|
this.table.columnManager.optionsList.register(key, value);
|
|
}
|
|
|
|
///////////////////////////////////
|
|
/// Public Function Registration ///
|
|
///////////////////////////////////
|
|
|
|
registerTableFunction(name, func){
|
|
if(typeof this.table[name] === "undefined"){
|
|
this.table[name] = (...args) => {
|
|
this.table.initGuard(name);
|
|
|
|
return func(...args);
|
|
};
|
|
}else {
|
|
console.warn("Unable to bind table function, name already in use", name);
|
|
}
|
|
}
|
|
|
|
registerComponentFunction(component, func, handler){
|
|
return this.table.componentFunctionBinder.bind(component, func, handler);
|
|
}
|
|
|
|
///////////////////////////////////
|
|
////////// Data Pipeline //////////
|
|
///////////////////////////////////
|
|
|
|
registerDataHandler(handler, priority){
|
|
this.table.rowManager.registerDataPipelineHandler(handler, priority);
|
|
this._handler = handler;
|
|
}
|
|
|
|
registerDisplayHandler(handler, priority){
|
|
this.table.rowManager.registerDisplayPipelineHandler(handler, priority);
|
|
this._handler = handler;
|
|
}
|
|
|
|
displayRows(adjust){
|
|
var index = this.table.rowManager.displayRows.length - 1,
|
|
lookupIndex;
|
|
|
|
if(this._handler){
|
|
lookupIndex = this.table.rowManager.displayPipeline.findIndex((item) => {
|
|
return item.handler === this._handler;
|
|
});
|
|
|
|
if(lookupIndex > -1){
|
|
index = lookupIndex;
|
|
}
|
|
}
|
|
|
|
if(adjust){
|
|
index = index + adjust;
|
|
}
|
|
|
|
if(this._handler){
|
|
if(index > -1){
|
|
return this.table.rowManager.getDisplayRows(index);
|
|
}else {
|
|
return this.activeRows();
|
|
}
|
|
}
|
|
}
|
|
|
|
activeRows(){
|
|
return this.table.rowManager.activeRows;
|
|
}
|
|
|
|
refreshData(renderInPosition, handler){
|
|
if(!handler){
|
|
handler = this._handler;
|
|
}
|
|
|
|
if(handler){
|
|
this.table.rowManager.refreshActiveData(handler, false, renderInPosition);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////
|
|
//////// Footer Management ////////
|
|
///////////////////////////////////
|
|
|
|
footerAppend(element){
|
|
return this.table.footerManager.append(element);
|
|
}
|
|
|
|
footerPrepend(element){
|
|
return this.table.footerManager.prepend(element);
|
|
}
|
|
|
|
footerRemove(element){
|
|
return this.table.footerManager.remove(element);
|
|
}
|
|
|
|
///////////////////////////////////
|
|
//////// Popups Management ////////
|
|
///////////////////////////////////
|
|
|
|
popup(menuEl, menuContainer){
|
|
return new Popup$1(this.table, menuEl, menuContainer);
|
|
}
|
|
|
|
///////////////////////////////////
|
|
//////// Alert Management ////////
|
|
///////////////////////////////////
|
|
|
|
alert(content, type){
|
|
return this.table.alertManager.alert(content, type);
|
|
}
|
|
|
|
clearAlert(){
|
|
return this.table.alertManager.clear();
|
|
}
|
|
|
|
}
|
|
|
|
//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{
|
|
|
|
static moduleName = "layout";
|
|
|
|
//load defaults
|
|
static modes = defaultModes;
|
|
|
|
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);
|
|
this.subscribe("column-init", this.initializeColumn.bind(this));
|
|
}
|
|
|
|
initializeColumn(column){
|
|
if(column.definition.widthGrow){
|
|
column.definition.widthGrow = Number(column.definition.widthGrow);
|
|
}
|
|
if(column.definition.widthShrink){
|
|
column.definition.widthShrink = Number(column.definition.widthShrink);
|
|
}
|
|
}
|
|
|
|
getMode(){
|
|
return this.mode;
|
|
}
|
|
|
|
//trigger table layout
|
|
layout(dataChanged){
|
|
|
|
var variableHeight = this.table.columnManager.columnsByIndex.find((column) => column.definition.variableHeight || column.definition.formatter === "textarea");
|
|
|
|
this.dispatch("layout-refreshing");
|
|
Layout.modes[this.mode].call(this, this.table.columnManager.columnsByIndex, dataChanged);
|
|
|
|
if(variableHeight){
|
|
this.table.rowManager.normalizeHeight(true);
|
|
}
|
|
|
|
this.dispatch("layout-refreshed");
|
|
}
|
|
}
|
|
|
|
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{
|
|
|
|
static moduleName = "localize";
|
|
|
|
//load defaults
|
|
static langs = defaultLangs;
|
|
|
|
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);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
class Comms extends Module{
|
|
|
|
static moduleName = "comms";
|
|
|
|
constructor(table){
|
|
super(table);
|
|
}
|
|
|
|
initialize(){
|
|
this.registerTableFunction("tableComms", this.receive.bind(this));
|
|
}
|
|
|
|
getConnections(selectors){
|
|
var connections = [],
|
|
connection;
|
|
|
|
connection = this.table.constructor.registry.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);
|
|
}
|
|
}
|
|
}
|
|
|
|
var coreModules = /*#__PURE__*/Object.freeze({
|
|
__proto__: null,
|
|
CommsModule: Comms,
|
|
LayoutModule: Layout,
|
|
LocalizeModule: Localize
|
|
});
|
|
|
|
class TableRegistry {
|
|
static registry = {
|
|
tables:[],
|
|
|
|
register(table){
|
|
TableRegistry.registry.tables.push(table);
|
|
},
|
|
|
|
deregister(table){
|
|
var index = TableRegistry.registry.tables.indexOf(table);
|
|
|
|
if(index > -1){
|
|
TableRegistry.registry.tables.splice(index, 1);
|
|
}
|
|
},
|
|
|
|
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.registry.matchElement(matches[i]);
|
|
|
|
if(match){
|
|
results.push(match);
|
|
}
|
|
}
|
|
}
|
|
|
|
}else if((typeof HTMLElement !== "undefined" && query instanceof HTMLElement) || query instanceof TableRegistry){
|
|
match = TableRegistry.registry.matchElement(query);
|
|
|
|
if(match){
|
|
results.push(match);
|
|
}
|
|
}else if(Array.isArray(query)){
|
|
query.forEach(function(item){
|
|
results = results.concat(TableRegistry.registry.lookupTable(item));
|
|
});
|
|
}else {
|
|
if(!silent){
|
|
console.warn("Table Connection Error - Invalid Selector", query);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
},
|
|
|
|
matchElement(element){
|
|
return TableRegistry.registry.tables.find(function(table){
|
|
return element instanceof TableRegistry ? table === element : table.element === element;
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
static findTable(query){
|
|
var results = TableRegistry.registry.lookupTable(query, true);
|
|
return Array.isArray(results) && !results.length ? false : results;
|
|
}
|
|
}
|
|
|
|
class ModuleBinder extends TableRegistry {
|
|
|
|
static moduleBindings = {};
|
|
static moduleExtensions = {};
|
|
static modulesRegistered = false;
|
|
|
|
static defaultModules = false;
|
|
|
|
constructor(){
|
|
super();
|
|
}
|
|
|
|
static initializeModuleBinder(defaultModules){
|
|
if(!ModuleBinder.modulesRegistered){
|
|
ModuleBinder.modulesRegistered = true;
|
|
ModuleBinder._registerModules(coreModules, true);
|
|
|
|
if(defaultModules){
|
|
ModuleBinder._registerModules(defaultModules);
|
|
}
|
|
}
|
|
}
|
|
|
|
static _extendModule(name, property, values){
|
|
if(ModuleBinder.moduleBindings[name]){
|
|
var source = ModuleBinder.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);
|
|
}
|
|
}
|
|
|
|
static _registerModules(modules, core){
|
|
var mods = Object.values(modules);
|
|
|
|
if(core){
|
|
mods.forEach((mod) => {
|
|
mod.prototype.moduleCore = true;
|
|
});
|
|
}
|
|
|
|
ModuleBinder._registerModule(mods);
|
|
}
|
|
|
|
static _registerModule(modules){
|
|
if(!Array.isArray(modules)){
|
|
modules = [modules];
|
|
}
|
|
|
|
modules.forEach((mod) => {
|
|
ModuleBinder._registerModuleBinding(mod);
|
|
ModuleBinder._registerModuleExtensions(mod);
|
|
});
|
|
}
|
|
|
|
static _registerModuleBinding(mod){
|
|
if(mod.moduleName){
|
|
ModuleBinder.moduleBindings[mod.moduleName] = mod;
|
|
}else {
|
|
console.error("Unable to bind module, no moduleName defined", mod.moduleName);
|
|
}
|
|
}
|
|
|
|
static _registerModuleExtensions(mod){
|
|
var extensions = mod.moduleExtensions;
|
|
|
|
if(mod.moduleExtensions){
|
|
for (let modKey in extensions) {
|
|
let ext = extensions[modKey];
|
|
|
|
if(ModuleBinder.moduleBindings[modKey]){
|
|
for (let propKey in ext) {
|
|
ModuleBinder._extendModule(modKey, propKey, ext[propKey]);
|
|
}
|
|
}else {
|
|
if(!ModuleBinder.moduleExtensions[modKey]){
|
|
ModuleBinder.moduleExtensions[modKey] = {};
|
|
}
|
|
|
|
for (let propKey in ext) {
|
|
if(!ModuleBinder.moduleExtensions[modKey][propKey]){
|
|
ModuleBinder.moduleExtensions[modKey][propKey] = {};
|
|
}
|
|
|
|
Object.assign(ModuleBinder.moduleExtensions[modKey][propKey], ext[propKey]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ModuleBinder._extendModuleFromQueue(mod);
|
|
}
|
|
|
|
static _extendModuleFromQueue(mod){
|
|
var extensions = ModuleBinder.moduleExtensions[mod.moduleName];
|
|
|
|
if(extensions){
|
|
for (let propKey in extensions) {
|
|
ModuleBinder._extendModule(mod.moduleName, propKey, extensions[propKey]);
|
|
}
|
|
}
|
|
}
|
|
|
|
//ensure that module are bound to instantiated function
|
|
_bindModules(){
|
|
var orderedStartMods = [],
|
|
orderedEndMods = [],
|
|
unOrderedMods = [];
|
|
|
|
this.modules = {};
|
|
|
|
for(var name in ModuleBinder.moduleBindings){
|
|
let mod = ModuleBinder.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));
|
|
}
|
|
}
|
|
|
|
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.dispatch("alert-show", type);
|
|
|
|
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(){
|
|
this.dispatch("alert-hide", this.type);
|
|
|
|
if(this.element.parentNode){
|
|
this.element.parentNode.removeChild(this.element);
|
|
}
|
|
|
|
this.msgElement.classList.remove(this._typeClass());
|
|
}
|
|
}
|
|
|
|
class Tabulator extends ModuleBinder{
|
|
|
|
//default setup options
|
|
static defaultOptions = defaultOptions;
|
|
|
|
static extendModule(){
|
|
Tabulator.initializeModuleBinder();
|
|
Tabulator._extendModule(...arguments);
|
|
}
|
|
|
|
static registerModule(){
|
|
Tabulator.initializeModuleBinder();
|
|
Tabulator._registerModule(...arguments);
|
|
}
|
|
|
|
constructor(element, options, modules){
|
|
super();
|
|
|
|
Tabulator.initializeModuleBinder(modules);
|
|
|
|
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.dependencyRegistry = new DependencyRegistry(this);
|
|
|
|
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();
|
|
});
|
|
}
|
|
|
|
this.constructor.registry.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.footerManager.initialize();
|
|
|
|
this.dependencyRegistry.initialize();
|
|
}
|
|
|
|
//convert deprecated functionality to new functions
|
|
_mapDeprecatedFunctionality(){
|
|
//all previously deprecated functionality removed in the 6.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.initialized = true;
|
|
|
|
this._loadInitialData()
|
|
.finally(() => {
|
|
this.eventBus.dispatch("table-initialized");
|
|
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(){
|
|
return this.dataLoader.load(this.options.data)
|
|
.finally(() => {
|
|
this.columnManager.verticalAlignHeaders();
|
|
});
|
|
}
|
|
|
|
//deconstructor
|
|
destroy(){
|
|
var element = this.element;
|
|
|
|
this.destroyed = true;
|
|
|
|
this.constructor.registry.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(true);
|
|
}
|
|
|
|
//////////////////// 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;
|
|
}
|
|
}
|
|
|
|
var defaultAccessors = {
|
|
rownum:function(value, data, type, params, column, row){
|
|
return row.getPosition();
|
|
}
|
|
};
|
|
|
|
class Accessor extends Module{
|
|
|
|
static moduleName = "accessor";
|
|
|
|
//load defaults
|
|
static accessors = defaultAccessors;
|
|
|
|
constructor(table){
|
|
super(table);
|
|
|
|
this.allowedTypes = ["", "data", "download", "clipboard", "print", "htmlOutput"]; //list of accessor types
|
|
|
|
this.registerColumnOption("accessor");
|
|
this.registerColumnOption("accessorParams");
|
|
this.registerColumnOption("accessorData");
|
|
this.registerColumnOption("accessorDataParams");
|
|
this.registerColumnOption("accessorDownload");
|
|
this.registerColumnOption("accessorDownloadParams");
|
|
this.registerColumnOption("accessorClipboard");
|
|
this.registerColumnOption("accessorClipboardParams");
|
|
this.registerColumnOption("accessorPrint");
|
|
this.registerColumnOption("accessorPrintParams");
|
|
this.registerColumnOption("accessorHtmlOutput");
|
|
this.registerColumnOption("accessorHtmlOutputParams");
|
|
}
|
|
|
|
initialize(){
|
|
this.subscribe("column-layout", this.initializeColumn.bind(this));
|
|
this.subscribe("row-data-retrieve", this.transformRow.bind(this));
|
|
}
|
|
|
|
//initialize column accessor
|
|
initializeColumn(column){
|
|
var match = false,
|
|
config = {};
|
|
|
|
this.allowedTypes.forEach((type) => {
|
|
var key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1)),
|
|
accessor;
|
|
|
|
if(column.definition[key]){
|
|
accessor = this.lookupAccessor(column.definition[key]);
|
|
|
|
if(accessor){
|
|
match = true;
|
|
|
|
config[key] = {
|
|
accessor:accessor,
|
|
params: column.definition[key + "Params"] || {},
|
|
};
|
|
}
|
|
}
|
|
});
|
|
|
|
if(match){
|
|
column.modules.accessor = config;
|
|
}
|
|
}
|
|
|
|
lookupAccessor(value){
|
|
var accessor = false;
|
|
|
|
//set column accessor
|
|
switch(typeof value){
|
|
case "string":
|
|
if(Accessor.accessors[value]){
|
|
accessor = Accessor.accessors[value];
|
|
}else {
|
|
console.warn("Accessor Error - No such accessor found, ignoring: ", value);
|
|
}
|
|
break;
|
|
|
|
case "function":
|
|
accessor = value;
|
|
break;
|
|
}
|
|
|
|
return accessor;
|
|
}
|
|
|
|
//apply accessor to row
|
|
transformRow(row, type){
|
|
var key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1)),
|
|
rowComponent = row.getComponent();
|
|
|
|
//clone data object with deep copy to isolate internal data from returned result
|
|
var data = Helpers.deepClone(row.data || {});
|
|
|
|
this.table.columnManager.traverse(function(column){
|
|
var value, accessor, params, colComponent;
|
|
|
|
if(column.modules.accessor){
|
|
|
|
accessor = column.modules.accessor[key] || column.modules.accessor.accessor || false;
|
|
|
|
if(accessor){
|
|
value = column.getFieldValue(data);
|
|
|
|
if(value != "undefined"){
|
|
colComponent = column.getComponent();
|
|
params = typeof accessor.params === "function" ? accessor.params(value, data, type, colComponent, rowComponent) : accessor.params;
|
|
column.setFieldValue(data, accessor.accessor(value, data, type, params, colComponent, rowComponent));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return data;
|
|
}
|
|
}
|
|
|
|
var defaultConfig = {
|
|
method: "GET",
|
|
};
|
|
|
|
function generateParamsList$1(data, prefix){
|
|
var output = [];
|
|
|
|
prefix = prefix || "";
|
|
|
|
if(Array.isArray(data)){
|
|
data.forEach((item, i) => {
|
|
output = output.concat(generateParamsList$1(item, prefix ? prefix + "[" + i + "]" : i));
|
|
});
|
|
}else if (typeof data === "object"){
|
|
for (var key in data){
|
|
output = output.concat(generateParamsList$1(data[key], prefix ? prefix + "[" + key + "]" : key));
|
|
}
|
|
}else {
|
|
output.push({key:prefix, value:data});
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
function serializeParams(params){
|
|
var output = generateParamsList$1(params),
|
|
encoded = [];
|
|
|
|
output.forEach(function(item){
|
|
encoded.push(encodeURIComponent(item.key) + "=" + encodeURIComponent(item.value));
|
|
});
|
|
|
|
return encoded.join("&");
|
|
}
|
|
|
|
function urlBuilder(url, config, params){
|
|
if(url){
|
|
if(params && Object.keys(params).length){
|
|
if(!config.method || config.method.toLowerCase() == "get"){
|
|
config.method = "get";
|
|
|
|
url += (url.includes("?") ? "&" : "?") + serializeParams(params);
|
|
}
|
|
}
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
function defaultLoaderPromise(url, config, params){
|
|
var contentType;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
//set url
|
|
url = this.urlGenerator.call(this.table, url, config, params);
|
|
|
|
//set body content if not GET request
|
|
if(config.method.toUpperCase() != "GET"){
|
|
contentType = typeof this.table.options.ajaxContentType === "object" ? this.table.options.ajaxContentType : this.contentTypeFormatters[this.table.options.ajaxContentType];
|
|
if(contentType){
|
|
|
|
for(var key in contentType.headers){
|
|
if(!config.headers){
|
|
config.headers = {};
|
|
}
|
|
|
|
if(typeof config.headers[key] === "undefined"){
|
|
config.headers[key] = contentType.headers[key];
|
|
}
|
|
}
|
|
|
|
config.body = contentType.body.call(this, url, config, params);
|
|
|
|
}else {
|
|
console.warn("Ajax Error - Invalid ajaxContentType value:", this.table.options.ajaxContentType);
|
|
}
|
|
}
|
|
|
|
if(url){
|
|
//configure headers
|
|
if(typeof config.headers === "undefined"){
|
|
config.headers = {};
|
|
}
|
|
|
|
if(typeof config.headers.Accept === "undefined"){
|
|
config.headers.Accept = "application/json";
|
|
}
|
|
|
|
if(typeof config.headers["X-Requested-With"] === "undefined"){
|
|
config.headers["X-Requested-With"] = "XMLHttpRequest";
|
|
}
|
|
|
|
if(typeof config.mode === "undefined"){
|
|
config.mode = "cors";
|
|
}
|
|
|
|
if(config.mode == "cors"){
|
|
if(typeof config.headers["Origin"] === "undefined"){
|
|
config.headers["Origin"] = window.location.origin;
|
|
}
|
|
|
|
if(typeof config.credentials === "undefined"){
|
|
config.credentials = 'same-origin';
|
|
}
|
|
}else {
|
|
if(typeof config.credentials === "undefined"){
|
|
config.credentials = 'include';
|
|
}
|
|
}
|
|
|
|
//send request
|
|
fetch(url, config)
|
|
.then((response)=>{
|
|
if(response.ok) {
|
|
response.json()
|
|
.then((data)=>{
|
|
resolve(data);
|
|
}).catch((error)=>{
|
|
reject(error);
|
|
console.warn("Ajax Load Error - Invalid JSON returned", error);
|
|
});
|
|
}else {
|
|
console.error("Ajax Load Error - Connection Error: " + response.status, response.statusText);
|
|
reject(response);
|
|
}
|
|
})
|
|
.catch((error)=>{
|
|
console.error("Ajax Load Error - Connection Error: ", error);
|
|
reject(error);
|
|
});
|
|
}else {
|
|
console.warn("Ajax Load Error - No URL Set");
|
|
resolve([]);
|
|
}
|
|
});
|
|
}
|
|
|
|
function generateParamsList(data, prefix){
|
|
var output = [];
|
|
|
|
prefix = prefix || "";
|
|
|
|
if(Array.isArray(data)){
|
|
data.forEach((item, i) => {
|
|
output = output.concat(generateParamsList(item, prefix ? prefix + "[" + i + "]" : i));
|
|
});
|
|
}else if (typeof data === "object"){
|
|
for (var key in data){
|
|
output = output.concat(generateParamsList(data[key], prefix ? prefix + "[" + key + "]" : key));
|
|
}
|
|
}else {
|
|
output.push({key:prefix, value:data});
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
var defaultContentTypeFormatters = {
|
|
"json":{
|
|
headers:{
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body:function(url, config, params){
|
|
return JSON.stringify(params);
|
|
},
|
|
},
|
|
"form":{
|
|
headers:{
|
|
},
|
|
body:function(url, config, params){
|
|
|
|
var output = generateParamsList(params),
|
|
form = new FormData();
|
|
|
|
output.forEach(function(item){
|
|
form.append(item.key, item.value);
|
|
});
|
|
|
|
return form;
|
|
},
|
|
},
|
|
};
|
|
|
|
class Ajax extends Module{
|
|
|
|
static moduleName = "ajax";
|
|
|
|
//load defaults
|
|
static defaultConfig = defaultConfig;
|
|
static defaultURLGenerator = urlBuilder;
|
|
static defaultLoaderPromise = defaultLoaderPromise;
|
|
static contentTypeFormatters = defaultContentTypeFormatters;
|
|
|
|
constructor(table){
|
|
super(table);
|
|
|
|
this.config = {}; //hold config object for ajax request
|
|
this.url = ""; //request URL
|
|
this.urlGenerator = false;
|
|
this.params = false; //request parameters
|
|
|
|
this.loaderPromise = false;
|
|
|
|
this.registerTableOption("ajaxURL", false); //url for ajax loading
|
|
this.registerTableOption("ajaxURLGenerator", false);
|
|
this.registerTableOption("ajaxParams", {}); //params for ajax loading
|
|
this.registerTableOption("ajaxConfig", "get"); //ajax request type
|
|
this.registerTableOption("ajaxContentType", "form"); //ajax request type
|
|
this.registerTableOption("ajaxRequestFunc", false); //promise function
|
|
|
|
this.registerTableOption("ajaxRequesting", function(){});
|
|
this.registerTableOption("ajaxResponse", false);
|
|
|
|
this.contentTypeFormatters = Ajax.contentTypeFormatters;
|
|
}
|
|
|
|
//initialize setup options
|
|
initialize(){
|
|
this.loaderPromise = this.table.options.ajaxRequestFunc || Ajax.defaultLoaderPromise;
|
|
this.urlGenerator = this.table.options.ajaxURLGenerator || Ajax.defaultURLGenerator;
|
|
|
|
if(this.table.options.ajaxURL){
|
|
this.setUrl(this.table.options.ajaxURL);
|
|
}
|
|
|
|
|
|
this.setDefaultConfig(this.table.options.ajaxConfig);
|
|
|
|
this.registerTableFunction("getAjaxUrl", this.getUrl.bind(this));
|
|
|
|
this.subscribe("data-loading", this.requestDataCheck.bind(this));
|
|
this.subscribe("data-params", this.requestParams.bind(this));
|
|
this.subscribe("data-load", this.requestData.bind(this));
|
|
}
|
|
|
|
requestParams(data, config, silent, params){
|
|
var ajaxParams = this.table.options.ajaxParams;
|
|
|
|
if(ajaxParams){
|
|
if(typeof ajaxParams === "function"){
|
|
ajaxParams = ajaxParams.call(this.table);
|
|
}
|
|
|
|
params = Object.assign(Object.assign({}, ajaxParams), params);
|
|
}
|
|
|
|
return params;
|
|
}
|
|
|
|
requestDataCheck(data, params, config, silent){
|
|
return !!((!data && this.url) || typeof data === "string");
|
|
}
|
|
|
|
requestData(url, params, config, silent, previousData){
|
|
var ajaxConfig;
|
|
|
|
if(!previousData && this.requestDataCheck(url)){
|
|
if(url){
|
|
this.setUrl(url);
|
|
}
|
|
|
|
ajaxConfig = this.generateConfig(config);
|
|
|
|
return this.sendRequest(this.url, params, ajaxConfig);
|
|
}else {
|
|
return previousData;
|
|
}
|
|
}
|
|
|
|
setDefaultConfig(config = {}){
|
|
this.config = Object.assign({}, Ajax.defaultConfig);
|
|
|
|
if(typeof config == "string"){
|
|
this.config.method = config;
|
|
}else {
|
|
Object.assign(this.config, config);
|
|
}
|
|
}
|
|
|
|
//load config object
|
|
generateConfig(config = {}){
|
|
var ajaxConfig = Object.assign({}, this.config);
|
|
|
|
if(typeof config == "string"){
|
|
ajaxConfig.method = config;
|
|
}else {
|
|
Object.assign(ajaxConfig, config);
|
|
}
|
|
|
|
return ajaxConfig;
|
|
}
|
|
|
|
//set request url
|
|
setUrl(url){
|
|
this.url = url;
|
|
}
|
|
|
|
//get request url
|
|
getUrl(){
|
|
return this.url;
|
|
}
|
|
|
|
//send ajax request
|
|
sendRequest(url, params, config){
|
|
if(this.table.options.ajaxRequesting.call(this.table, url, params) !== false){
|
|
return this.loaderPromise(url, config, params)
|
|
.then((data)=>{
|
|
if(this.table.options.ajaxResponse){
|
|
data = this.table.options.ajaxResponse.call(this.table, url, params, data);
|
|
}
|
|
|
|
return data;
|
|
});
|
|
}else {
|
|
return Promise.reject();
|
|
}
|
|
}
|
|
}
|
|
|
|
var defaultPasteActions = {
|
|
replace:function(data){
|
|
return this.table.setData(data);
|
|
},
|
|
update:function(data){
|
|
return this.table.updateOrAddData(data);
|
|
},
|
|
insert:function(data){
|
|
return this.table.addData(data);
|
|
},
|
|
};
|
|
|
|
var defaultPasteParsers = {
|
|
table:function(clipboard){
|
|
var data = [],
|
|
headerFindSuccess = true,
|
|
columns = this.table.columnManager.columns,
|
|
columnMap = [],
|
|
rows = [];
|
|
|
|
//get data from clipboard into array of columns and rows.
|
|
clipboard = clipboard.split("\n");
|
|
|
|
clipboard.forEach(function(row){
|
|
data.push(row.split("\t"));
|
|
});
|
|
|
|
if(data.length && !(data.length === 1 && data[0].length < 2)){
|
|
|
|
//check if headers are present by title
|
|
data[0].forEach(function(value){
|
|
var column = columns.find(function(column){
|
|
return value && column.definition.title && value.trim() && column.definition.title.trim() === value.trim();
|
|
});
|
|
|
|
if(column){
|
|
columnMap.push(column);
|
|
}else {
|
|
headerFindSuccess = false;
|
|
}
|
|
});
|
|
|
|
//check if column headers are present by field
|
|
if(!headerFindSuccess){
|
|
headerFindSuccess = true;
|
|
columnMap = [];
|
|
|
|
data[0].forEach(function(value){
|
|
var column = columns.find(function(column){
|
|
return value && column.field && value.trim() && column.field.trim() === value.trim();
|
|
});
|
|
|
|
if(column){
|
|
columnMap.push(column);
|
|
}else {
|
|
headerFindSuccess = false;
|
|
}
|
|
});
|
|
|
|
if(!headerFindSuccess){
|
|
columnMap = this.table.columnManager.columnsByIndex;
|
|
}
|
|
}
|
|
|
|
//remove header row if found
|
|
if(headerFindSuccess){
|
|
data.shift();
|
|
}
|
|
|
|
data.forEach(function(item){
|
|
var row = {};
|
|
|
|
item.forEach(function(value, i){
|
|
if(columnMap[i]){
|
|
row[columnMap[i].field] = value;
|
|
}
|
|
});
|
|
|
|
rows.push(row);
|
|
});
|
|
|
|
return rows;
|
|
}else {
|
|
return false;
|
|
}
|
|
},
|
|
};
|
|
|
|
var bindings$2 = {
|
|
copyToClipboard:["ctrl + 67", "meta + 67"],
|
|
};
|
|
|
|
var actions$2 = {
|
|
copyToClipboard:function(e){
|
|
if(!this.table.modules.edit.currentCell){
|
|
if(this.table.modExists("clipboard", true)){
|
|
this.table.modules.clipboard.copy(false, true);
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
var extensions$4 = {
|
|
keybindings:{
|
|
bindings:bindings$2,
|
|
actions:actions$2
|
|
},
|
|
};
|
|
|
|
class Clipboard extends Module{
|
|
|
|
static moduleName = "clipboard";
|
|
static moduleExtensions = extensions$4;
|
|
|
|
//load defaults
|
|
static pasteActions = defaultPasteActions;
|
|
static pasteParsers = defaultPasteParsers;
|
|
|
|
constructor(table){
|
|
super(table);
|
|
|
|
this.mode = true;
|
|
this.pasteParser = function(){};
|
|
this.pasteAction = function(){};
|
|
this.customSelection = false;
|
|
this.rowRange = false;
|
|
this.blocked = true; //block copy actions not originating from this command
|
|
|
|
this.registerTableOption("clipboard", false); //enable clipboard
|
|
this.registerTableOption("clipboardCopyStyled", true); //formatted table data
|
|
this.registerTableOption("clipboardCopyConfig", false); //clipboard config
|
|
this.registerTableOption("clipboardCopyFormatter", false); //DEPRECATED - REMOVE in 5.0
|
|
this.registerTableOption("clipboardCopyRowRange", "active"); //restrict clipboard to visible rows only
|
|
this.registerTableOption("clipboardPasteParser", "table"); //convert pasted clipboard data to rows
|
|
this.registerTableOption("clipboardPasteAction", "insert"); //how to insert pasted data into the table
|
|
|
|
this.registerColumnOption("clipboard");
|
|
this.registerColumnOption("titleClipboard");
|
|
}
|
|
|
|
initialize(){
|
|
this.mode = this.table.options.clipboard;
|
|
|
|
this.rowRange = this.table.options.clipboardCopyRowRange;
|
|
|
|
if(this.mode === true || this.mode === "copy"){
|
|
this.table.element.addEventListener("copy", (e) => {
|
|
var plain, html, list;
|
|
|
|
if(!this.blocked){
|
|
e.preventDefault();
|
|
|
|
if(this.customSelection){
|
|
plain = this.customSelection;
|
|
|
|
if(this.table.options.clipboardCopyFormatter){
|
|
plain = this.table.options.clipboardCopyFormatter("plain", plain);
|
|
}
|
|
}else {
|
|
|
|
list = this.table.modules.export.generateExportList(this.table.options.clipboardCopyConfig, this.table.options.clipboardCopyStyled, this.rowRange, "clipboard");
|
|
|
|
html = this.table.modules.export.generateHTMLTable(list);
|
|
plain = html ? this.generatePlainContent(list) : "";
|
|
|
|
if(this.table.options.clipboardCopyFormatter){
|
|
plain = this.table.options.clipboardCopyFormatter("plain", plain);
|
|
html = this.table.options.clipboardCopyFormatter("html", html);
|
|
}
|
|
}
|
|
|
|
if (window.clipboardData && window.clipboardData.setData) {
|
|
window.clipboardData.setData('Text', plain);
|
|
} else if (e.clipboardData && e.clipboardData.setData) {
|
|
e.clipboardData.setData('text/plain', plain);
|
|
if(html){
|
|
e.clipboardData.setData('text/html', html);
|
|
}
|
|
} else if (e.originalEvent && e.originalEvent.clipboardData.setData) {
|
|
e.originalEvent.clipboardData.setData('text/plain', plain);
|
|
if(html){
|
|
e.originalEvent.clipboardData.setData('text/html', html);
|
|
}
|
|
}
|
|
|
|
this.dispatchExternal("clipboardCopied", plain, html);
|
|
|
|
this.reset();
|
|
}
|
|
});
|
|
}
|
|
|
|
if(this.mode === true || this.mode === "paste"){
|
|
this.table.element.addEventListener("paste", (e) => {
|
|
this.paste(e);
|
|
});
|
|
}
|
|
|
|
this.setPasteParser(this.table.options.clipboardPasteParser);
|
|
this.setPasteAction(this.table.options.clipboardPasteAction);
|
|
|
|
this.registerTableFunction("copyToClipboard", this.copy.bind(this));
|
|
}
|
|
|
|
reset(){
|
|
this.blocked = true;
|
|
this.customSelection = false;
|
|
}
|
|
|
|
generatePlainContent (list) {
|
|
var output = [];
|
|
|
|
list.forEach((row) => {
|
|
var rowData = [];
|
|
|
|
row.columns.forEach((col) => {
|
|
var value = "";
|
|
|
|
if(col){
|
|
|
|
if(row.type === "group"){
|
|
col.value = col.component.getKey();
|
|
}
|
|
|
|
if(col.value === null){
|
|
value = "";
|
|
}else {
|
|
switch(typeof col.value){
|
|
case "object":
|
|
value = JSON.stringify(col.value);
|
|
break;
|
|
|
|
case "undefined":
|
|
value = "";
|
|
break;
|
|
|
|
default:
|
|
value = col.value;
|
|
}
|
|
}
|
|
}
|
|
|
|
rowData.push(value);
|
|
});
|
|
|
|
output.push(rowData.join("\t"));
|
|
});
|
|
|
|
return output.join("\n");
|
|
}
|
|
|
|
copy (range, internal) {
|
|
var sel, textRange;
|
|
this.blocked = false;
|
|
this.customSelection = false;
|
|
|
|
|
|
if (this.mode === true || this.mode === "copy") {
|
|
|
|
this.rowRange = range || this.table.options.clipboardCopyRowRange;
|
|
|
|
if (typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") {
|
|
range = document.createRange();
|
|
range.selectNodeContents(this.table.element);
|
|
sel = window.getSelection();
|
|
|
|
if (sel.toString() && internal) {
|
|
this.customSelection = sel.toString();
|
|
}
|
|
|
|
sel.removeAllRanges();
|
|
sel.addRange(range);
|
|
} else if (typeof document.selection != "undefined" && typeof document.body.createTextRange != "undefined") {
|
|
textRange = document.body.createTextRange();
|
|
textRange.moveToElementText(this.table.element);
|
|
textRange.select();
|
|
}
|
|
|
|
document.execCommand('copy');
|
|
|
|
if (sel) {
|
|
sel.removeAllRanges();
|
|
}
|
|
}
|
|
}
|
|
|
|
//PASTE EVENT HANDLING
|
|
setPasteAction(action){
|
|
|
|
switch(typeof action){
|
|
case "string":
|
|
this.pasteAction = Clipboard.pasteActions[action];
|
|
|
|
if(!this.pasteAction){
|
|
console.warn("Clipboard Error - No such paste action found:", action);
|
|
}
|
|
break;
|
|
|
|
case "function":
|
|
this.pasteAction = action;
|
|
break;
|
|
}
|
|
}
|
|
|
|
setPasteParser(parser){
|
|
switch(typeof parser){
|
|
case "string":
|
|
this.pasteParser = Clipboard.pasteParsers[parser];
|
|
|
|
if(!this.pasteParser){
|
|
console.warn("Clipboard Error - No such paste parser found:", parser);
|
|
}
|
|
break;
|
|
|
|
case "function":
|
|
this.pasteParser = parser;
|
|
break;
|
|
}
|
|
}
|
|
|
|
paste(e){
|
|
var data, rowData, rows;
|
|
|
|
if(this.checkPasteOrigin(e)){
|
|
|
|
data = this.getPasteData(e);
|
|
|
|
rowData = this.pasteParser.call(this, data);
|
|
|
|
if(rowData){
|
|
e.preventDefault();
|
|
|
|
if(this.table.modExists("mutator")){
|
|
rowData = this.mutateData(rowData);
|
|
}
|
|
|
|
rows = this.pasteAction.call(this, rowData);
|
|
|
|
this.dispatchExternal("clipboardPasted", data, rowData, rows);
|
|
}else {
|
|
this.dispatchExternal("clipboardPasteError", data);
|
|
}
|
|
}
|
|
}
|
|
|
|
mutateData(data){
|
|
var output = [];
|
|
|
|
if(Array.isArray(data)){
|
|
data.forEach((row) => {
|
|
output.push(this.table.modules.mutator.transformRow(row, "clipboard"));
|
|
});
|
|
}else {
|
|
output = data;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
|
|
checkPasteOrigin(e){
|
|
var valid = true;
|
|
var blocked = this.confirm("clipboard-paste", [e]);
|
|
|
|
if(blocked || !["DIV", "SPAN"].includes(e.target.tagName)){
|
|
valid = false;
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
getPasteData(e){
|
|
var data;
|
|
|
|
if (window.clipboardData && window.clipboardData.getData) {
|
|
data = window.clipboardData.getData('Text');
|
|
} else if (e.clipboardData && e.clipboardData.getData) {
|
|
data = e.clipboardData.getData('text/plain');
|
|
} else if (e.originalEvent && e.originalEvent.clipboardData.getData) {
|
|
data = e.originalEvent.clipboardData.getData('text/plain');
|
|
}
|
|
|
|
return data;
|
|
}
|
|
}
|
|
|
|
class CalcComponent{
|
|
constructor (row){
|
|
this._row = row;
|
|
|
|
return new Proxy(this, {
|
|
get: function(target, name, receiver) {
|
|
if (typeof target[name] !== "undefined") {
|
|
return target[name];
|
|
}else {
|
|
return target._row.table.componentFunctionBinder.handle("row", target._row, name);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
getData(transform){
|
|
return this._row.getData(transform);
|
|
}
|
|
|
|
getElement(){
|
|
return this._row.getElement();
|
|
}
|
|
|
|
getTable(){
|
|
return this._row.table;
|
|
}
|
|
|
|
getCells(){
|
|
var cells = [];
|
|
|
|
this._row.getCells().forEach(function(cell){
|
|
cells.push(cell.getComponent());
|
|
});
|
|
|
|
return cells;
|
|
}
|
|
|
|
getCell(column){
|
|
var cell = this._row.getCell(column);
|
|
return cell ? cell.getComponent() : false;
|
|
}
|
|
|
|
_getSelf(){
|
|
return this._row;
|
|
}
|
|
}
|
|
|
|
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;
|
|
},
|
|
"unique":function(values, data, calcParams){
|
|
var unique = values.filter((value, index) => {
|
|
return (values || value === 0) && values.indexOf(value) === index;
|
|
});
|
|
|
|
return unique.length;
|
|
},
|
|
};
|
|
|
|
class ColumnCalcs extends Module{
|
|
|
|
static moduleName = "columnCalcs";
|
|
|
|
//load defaults
|
|
static calculations = defaultCalculations;
|
|
|
|
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(){
|
|
var fragment = document.createDocumentFragment();
|
|
|
|
if(!this.topInitialized){
|
|
|
|
fragment.appendChild(document.createElement("br"));
|
|
fragment.appendChild(this.topElement);
|
|
|
|
this.table.columnManager.getContentsElement().insertBefore(fragment, 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 = [],
|
|
hasDataTreeColumnCalcs = this.table.options.dataTree && this.table.options.dataTreeChildColumnCalcs,
|
|
dataTree = this.table.modules.dataTree;
|
|
|
|
rows.forEach((row) => {
|
|
data.push(row.getData());
|
|
|
|
if(hasDataTreeColumnCalcs && row.modules.dataTree?.open){
|
|
this.rowsToData(dataTree.getFilteredTreeChildren(row)).forEach(dataRow =>{
|
|
data.push(row);
|
|
});
|
|
}
|
|
});
|
|
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.lookupFormatter(column.definition[pos + "CalcFormatter"]),
|
|
params: column.definition[pos + "CalcFormatterParams"] || {},
|
|
};
|
|
}else {
|
|
this.genColumn.modules.format = {
|
|
formatter: this.table.modules.format.lookupFormatter("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";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class DataTree extends Module{
|
|
|
|
static moduleName = "dataTree";
|
|
|
|
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;
|
|
}
|
|
}
|
|
}else {
|
|
this.branchEl = document.createElement("div");
|
|
this.branchEl.classList.add("tabulator-data-tree-branch-empty");
|
|
}
|
|
|
|
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 = "<div class='tabulator-data-tree-control-collapse'></div>";
|
|
}
|
|
|
|
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 = "<div class='tabulator-data-tree-control-expand'></div>";
|
|
}
|
|
|
|
|
|
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-deleting", this.rowDeleting.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;
|
|
|
|
if(!config.index && config.children !== false){
|
|
children = this.getChildren(row, false, true);
|
|
|
|
children.forEach((child) => {
|
|
child.create();
|
|
output.push(child);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return output;
|
|
}
|
|
|
|
getChildren(row, allChildren, sortOnly){
|
|
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, sortOnly);
|
|
}
|
|
|
|
children.forEach((child) => {
|
|
output.push(child);
|
|
|
|
var subChildren = this.getChildren(child, false, true);
|
|
|
|
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;
|
|
}
|
|
|
|
rowDeleting(row){
|
|
var config = row.modules.dataTree;
|
|
|
|
if (config && config.children && Array.isArray(config.children)){
|
|
config.children.forEach((childRow) => {
|
|
if(childRow instanceof Row){
|
|
childRow.wipe();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
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 && 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){
|
|
this.getTreeChildren(childRow, component, recurse).forEach(child => {
|
|
output.push(child);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
function csv$1(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$2(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 : "",
|
|
jspdfLib, doc;
|
|
|
|
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
|
|
jspdfLib = this.dependencyRegistry.lookup("jspdf", "jsPDF");
|
|
doc = new jspdfLib(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$1(list, options, setFileContents){
|
|
var self = this,
|
|
sheetName = options.sheetName || "Sheet1",
|
|
XLSXLib = this.dependencyRegistry.lookup("XLSX"),
|
|
workbook = XLSXLib.utils.book_new(),
|
|
tableFeatures = new CoreFeature(this),
|
|
compression = 'compress' in options ? options.compress : true,
|
|
writeOptions = options.writeOptions || {bookType:'xlsx', bookSST:true, compression},
|
|
output;
|
|
|
|
writeOptions.type = 'binary';
|
|
|
|
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
|
|
XLSXLib.utils.sheet_add_aoa(worksheet, rows);
|
|
|
|
worksheet['!ref'] = XLSXLib.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 = XLSXLib.write(workbook, writeOptions);
|
|
|
|
setFileContents(s2ab(output), "application/octet-stream");
|
|
}
|
|
|
|
function html$1(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$1,
|
|
json:json$2,
|
|
jsonLines:jsonLines,
|
|
pdf:pdf,
|
|
xlsx:xlsx$1,
|
|
html:html$1,
|
|
};
|
|
|
|
class Download extends Module{
|
|
|
|
static moduleName = "download";
|
|
|
|
//load defaults
|
|
static downloaders = defaultDownloaders;
|
|
|
|
constructor(table){
|
|
super(table);
|
|
|
|
this.registerTableOption("downloadEncoder", function(data, mimeType){
|
|
return new Blob([data],{type:mimeType});
|
|
}); //function to manipulate download data
|
|
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(){
|
|
|
|
}
|
|
|
|
///////////////////////////////////
|
|
///////// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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.getType() === "cell"){
|
|
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$1(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.getType() === "cell"){
|
|
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$1(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.getType() === "cell"){
|
|
//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.getType() === "cell"){
|
|
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$1(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.getType() === "cell"){
|
|
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$1(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.getType() == "cell"){
|
|
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$2(cell, onRendered, success, cancel, editorParams){
|
|
var inputFormat = editorParams.format,
|
|
vertNav = editorParams.verticalNavigation || "editor",
|
|
DT = inputFormat ? (this.table.dependencyRegistry.lookup(["luxon", "DateTime"], "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.getType() === "cell"){
|
|
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;
|
|
}
|
|
|
|
let Edit$1 = 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.getType() === "header";
|
|
|
|
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
|
|
this._keyTab(e);
|
|
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 /////////
|
|
//////////////////////////////////////
|
|
|
|
_keyTab(e){
|
|
if(this.params.autocomplete && this.lastAction === "typing"){
|
|
this._resolveValue(true);
|
|
}else {
|
|
if(this.focusedItem){
|
|
this._chooseItem(this.focusedItem, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
_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(!this._emptyValueCheck(val)){
|
|
if(this.params.multiselect && Array.isArray(val)){
|
|
val.forEach((item) => {
|
|
if(!this._emptyValueCheck(item)){
|
|
output[item] = true;
|
|
}
|
|
});
|
|
}else {
|
|
output[val] = true;
|
|
}
|
|
|
|
}
|
|
});
|
|
}else {
|
|
console.warn("unable to find matching column to create select lookup list:", field);
|
|
output = [];
|
|
}
|
|
|
|
return Object.keys(output);
|
|
}
|
|
|
|
_emptyValueCheck(value){
|
|
return value === null || typeof value === "undefined" || value === "";
|
|
}
|
|
|
|
_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 list(cell, onRendered, success, cancel, editorParams){
|
|
var list = new Edit$1(this, cell, onRendered, success, cancel, editorParams);
|
|
|
|
return list.input;
|
|
}
|
|
|
|
//star rating
|
|
function star$1(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 = '<polygon fill="#488CE9" stroke="#014AAE" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>';
|
|
}else {
|
|
if(self.table.browser == "ie"){
|
|
star.setAttribute("class", "tabulator-star-inactive");
|
|
}else {
|
|
star.classList.replace("tabulator-star-active", "tabulator-star-inactive");
|
|
}
|
|
|
|
star.innerHTML = '<polygon fill="#010155" stroke="#686868" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>';
|
|
}
|
|
});
|
|
}
|
|
|
|
//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$1(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$1(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.getType() === "cell"){
|
|
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;
|
|
}
|
|
|
|
function adaptable$1(cell, onRendered, success, cancel, params){
|
|
var column = cell._getSelf().column,
|
|
lookup, editorFunc, editorParams;
|
|
|
|
function defaultLookup(cell){
|
|
var value = cell.getValue(),
|
|
editor = "input";
|
|
|
|
switch(typeof value){
|
|
case "number":
|
|
editor = "number";
|
|
break;
|
|
|
|
case "boolean":
|
|
editor = "tickCross";
|
|
break;
|
|
|
|
case "string":
|
|
if(value.includes("\n")){
|
|
editor = "textarea";
|
|
}
|
|
break;
|
|
}
|
|
|
|
return editor;
|
|
}
|
|
|
|
lookup = params.editorLookup ? params.editorLookup(cell) : defaultLookup(cell);
|
|
|
|
if(params.paramsLookup){
|
|
editorParams = typeof params.paramsLookup === "function" ? params.paramsLookup(lookup, cell) : params.paramsLookup[lookup];
|
|
}
|
|
|
|
editorFunc = this.table.modules.edit.lookupEditor(lookup, column);
|
|
|
|
return editorFunc.call(this, cell, onRendered, success, cancel, editorParams || {});
|
|
}
|
|
|
|
var defaultEditors = {
|
|
input:input,
|
|
textarea:textarea$1,
|
|
number:number$1,
|
|
range:range,
|
|
date:date$1,
|
|
time:time$1,
|
|
datetime:datetime$2,
|
|
list:list,
|
|
star:star$1,
|
|
progress:progress$1,
|
|
tickCross:tickCross$1,
|
|
adaptable:adaptable$1,
|
|
};
|
|
|
|
class Edit extends Module{
|
|
|
|
static moduleName = "edit";
|
|
|
|
//load defaults
|
|
static editors = defaultEditors;
|
|
|
|
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.convertEmptyValues = false;
|
|
|
|
this.editors = Edit.editors;
|
|
|
|
this.registerTableOption("editTriggerEvent", "focus");
|
|
this.registerTableOption("editorEmptyValue");
|
|
this.registerTableOption("editorEmptyValueFunc", this.emptyValueCheck.bind(this));
|
|
|
|
this.registerColumnOption("editable");
|
|
this.registerColumnOption("editor");
|
|
this.registerColumnOption("editorParams");
|
|
this.registerColumnOption("editorEmptyValue");
|
|
this.registerColumnOption("editorEmptyValueFunc");
|
|
|
|
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("clipboard-paste", this.pasteBlocker.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));
|
|
|
|
if(Object.keys(this.table.options).includes("editorEmptyValue")){
|
|
this.convertEmptyValues = true;
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////
|
|
///////// Paste Negation //////////
|
|
///////////////////////////////////
|
|
|
|
pasteBlocker(e){
|
|
if(this.currentCell){
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////
|
|
////// 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(!this.invalidEdit){
|
|
|
|
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 convertEmpty = Object.keys(column.definition).includes("editorEmptyValue");
|
|
|
|
var config = {
|
|
editor:false,
|
|
blocked:false,
|
|
check:column.definition.editable,
|
|
params:column.definition.editorParams || {},
|
|
convertEmptyValues:convertEmpty,
|
|
editorEmptyValue:column.definition.editorEmptyValue,
|
|
editorEmptyValueFunc:column.definition.editorEmptyValueFunc,
|
|
};
|
|
|
|
//set column editor
|
|
config.editor = this.lookupEditor(column.definition.editor, column);
|
|
|
|
if(config.editor){
|
|
column.modules.edit = config;
|
|
}
|
|
}
|
|
|
|
lookupEditor(editor, column){
|
|
var editorFunc;
|
|
|
|
switch(typeof editor){
|
|
case "string":
|
|
if(this.editors[editor]){
|
|
editorFunc = this.editors[editor];
|
|
}else {
|
|
console.warn("Editor Error - No such editor found: ", editor);
|
|
}
|
|
break;
|
|
|
|
case "function":
|
|
editorFunc = editor;
|
|
break;
|
|
|
|
case "boolean":
|
|
if(editor === true){
|
|
if(typeof column.definition.formatter !== "function"){
|
|
if(this.editors[column.definition.formatter]){
|
|
editorFunc = this.editors[column.definition.formatter];
|
|
}else {
|
|
editorFunc = this.editors["input"];
|
|
}
|
|
}else {
|
|
console.warn("Editor Error - Cannot auto lookup editor for a custom formatter: ", column.definition.formatter);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return editorFunc;
|
|
}
|
|
|
|
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("mousedown", function(e){
|
|
if (e.button === 2) {
|
|
e.preventDefault();
|
|
}else {
|
|
self.mouseClick = true;
|
|
}
|
|
});
|
|
|
|
if(this.options("editTriggerEvent") === "dblclick"){
|
|
element.addEventListener("dblclick", function(e){
|
|
if(!element.classList.contains("tabulator-editing")){
|
|
element.focus({preventScroll: true});
|
|
self.edit(cell, e, false);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
if(this.options("editTriggerEvent") === "focus" || this.options("editTriggerEvent") === "click"){
|
|
element.addEventListener("click", function(e){
|
|
if(!element.classList.contains("tabulator-editing")){
|
|
element.focus({preventScroll: true});
|
|
self.edit(cell, e, false);
|
|
}
|
|
});
|
|
}
|
|
|
|
if(this.options("editTriggerEvent") === "focus"){
|
|
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 || 0);
|
|
rightEdge -= parseInt(this.table.modules.frozenColumns.rightMargin || 0);
|
|
}
|
|
|
|
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(),
|
|
editFinished = false,
|
|
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 && !editFinished){
|
|
var valid = self.chain("edit-success", [cell, value], true, true);
|
|
|
|
if(valid === true || self.table.options.validationMode === "highlight"){
|
|
|
|
editFinished = true;
|
|
|
|
self.clearEditor();
|
|
|
|
if(!cell.modules.edit){
|
|
cell.modules.edit = {};
|
|
}
|
|
|
|
cell.modules.edit.edited = true;
|
|
|
|
if(self.editedCells.indexOf(cell) == -1){
|
|
self.editedCells.push(cell);
|
|
}
|
|
|
|
value = self.transformEmptyValues(value, cell);
|
|
|
|
cell.setValue(value, true);
|
|
|
|
return valid === true;
|
|
}else {
|
|
editFinished = true;
|
|
self.invalidEdit = true;
|
|
self.focusCellNoEvent(cell, true);
|
|
rendered();
|
|
|
|
setTimeout(() => {
|
|
editFinished = false;
|
|
}, 10);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//handle aborted edit
|
|
function cancel(){
|
|
// editFinished = true;
|
|
|
|
if(self.currentCell === cell && !editFinished){
|
|
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);
|
|
this.blur(element);
|
|
return false;
|
|
}
|
|
}else {
|
|
this.blur(element);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}else {
|
|
this.mouseClick = false;
|
|
this.blur(element);
|
|
return false;
|
|
}
|
|
}else {
|
|
this.mouseClick = false;
|
|
this.blur(element);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
emptyValueCheck(value){
|
|
return value === "" || value === null || typeof value === "undefined";
|
|
}
|
|
|
|
transformEmptyValues(value, cell){
|
|
var mod = cell.column.modules.edit,
|
|
convert = mod.convertEmptyValues || this.convertEmptyValues,
|
|
checkFunc;
|
|
|
|
if(convert){
|
|
checkFunc = mod.editorEmptyValueFunc || this.options("editorEmptyValueFunc");
|
|
|
|
if(checkFunc && checkFunc(value)){
|
|
value = mod.convertEmptyValues ? mod.editorEmptyValue : this.options("editorEmptyValue");
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
blur(element){
|
|
if(!this.confirm("edit-blur", [element]) ){
|
|
element.blur();
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
var columnLookups$1 = {
|
|
|
|
};
|
|
|
|
var rowLookups$1 = {
|
|
visible:function(){
|
|
return this.rowManager.getVisibleRows(false, true);
|
|
},
|
|
all:function(){
|
|
return this.rowManager.rows;
|
|
},
|
|
selected:function(){
|
|
return this.modules.selectRow.selectedRows;
|
|
},
|
|
active:function(){
|
|
if(this.options.pagination){
|
|
return this.rowManager.getDisplayRows(this.rowManager.displayRows.length - 2);
|
|
}else {
|
|
return this.rowManager.getDisplayRows();
|
|
}
|
|
},
|
|
};
|
|
|
|
class Export extends Module{
|
|
|
|
static moduleName = "export";
|
|
|
|
static columnLookups = columnLookups$1;
|
|
static rowLookups = rowLookups$1;
|
|
|
|
constructor(table){
|
|
super(table);
|
|
|
|
this.config = {};
|
|
this.cloneTableStyle = true;
|
|
this.colVisProp = "";
|
|
this.colVisPropAttach = "";
|
|
|
|
this.registerTableOption("htmlOutputConfig", false); //html output config
|
|
|
|
this.registerColumnOption("htmlOutput");
|
|
this.registerColumnOption("titleHtmlOutput");
|
|
}
|
|
|
|
initialize(){
|
|
this.registerTableFunction("getHtml", this.getHtml.bind(this));
|
|
}
|
|
|
|
///////////////////////////////////
|
|
///////// Internal Logic //////////
|
|
///////////////////////////////////
|
|
|
|
generateExportList(config, style, range, colVisProp){
|
|
var headers, body, columns, colLookup;
|
|
|
|
this.cloneTableStyle = style;
|
|
this.config = config || {};
|
|
this.colVisProp = colVisProp;
|
|
this.colVisPropAttach = this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1);
|
|
|
|
colLookup = Export.columnLookups[range];
|
|
|
|
if(colLookup){
|
|
columns = colLookup.call(this.table);
|
|
columns = columns.filter(col => this.columnVisCheck(col));
|
|
}
|
|
|
|
headers = this.config.columnHeaders !== false ? this.headersToExportRows(this.generateColumnGroupHeaders(columns)) : [];
|
|
|
|
if(columns){
|
|
columns = columns.map(col => col.getComponent());
|
|
}
|
|
|
|
body = this.bodyToExportRows(this.rowLookup(range), columns);
|
|
|
|
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 = [],
|
|
rowLookup;
|
|
|
|
if(typeof range == "function"){
|
|
range.call(this.table).forEach((row) =>{
|
|
row = this.table.rowManager.findRow(row);
|
|
|
|
if(row){
|
|
rows.push(row);
|
|
}
|
|
});
|
|
}else {
|
|
rowLookup = Export.rowLookups[range] || Export.rowLookups["active"];
|
|
|
|
rows = rowLookup.call(this.table);
|
|
}
|
|
|
|
return Object.assign([], rows);
|
|
}
|
|
|
|
generateColumnGroupHeaders(columns){
|
|
var output = [];
|
|
|
|
if (!columns) {
|
|
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.colVisPropAttach)] || 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(this.config.rowHeaders === false && column.isRowHeader){
|
|
return false;
|
|
}
|
|
|
|
if(typeof visProp === "function"){
|
|
visProp = visProp.call(this.table, column.getComponent());
|
|
}
|
|
|
|
if(visProp === false || visProp === true){
|
|
return visProp;
|
|
}
|
|
|
|
return column.visible && column.field;
|
|
}
|
|
|
|
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, columns = []){
|
|
var exportRows = [];
|
|
|
|
if (columns.length === 0) {
|
|
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.colVisPropAttach)],
|
|
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.colVisPropAttach)];
|
|
|
|
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.styleRowHeader = styles.firstRow.getElementsByClassName("tabulator-row-header")[0];
|
|
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-left", "border-right", "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 && column.component._column.parent.isGroup){
|
|
this.mapElementStyles(column.component._column.parent.groupElement, cellEl, ["border-top"]);
|
|
}else {
|
|
this.mapElementStyles(column.component.getElement(), cellEl, ["border-top"]);
|
|
}
|
|
|
|
if(column.component._column.isGroup){
|
|
this.mapElementStyles(column.component.getElement(), cellEl, ["border-bottom"]);
|
|
}else {
|
|
this.mapElementStyles(this.table.columnManager.getElement(), cellEl, ["border-bottom"]);
|
|
}
|
|
|
|
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,
|
|
table = this.table,
|
|
index = table.columnManager.findColumnIndex(column),
|
|
value = col.value,
|
|
cellStyle, styleProps;
|
|
|
|
var cellWrapper = {
|
|
modules:{},
|
|
getValue:function(){
|
|
return value;
|
|
},
|
|
getField:function(){
|
|
return column.definition.field;
|
|
},
|
|
getElement:function(){
|
|
return cellEl;
|
|
},
|
|
getType:function(){
|
|
return "cell";
|
|
},
|
|
getColumn:function(){
|
|
return column.getComponent();
|
|
},
|
|
getData:function(){
|
|
return row.component.getData();
|
|
},
|
|
getRow:function(){
|
|
return row.component;
|
|
},
|
|
getTable:function(){
|
|
return table;
|
|
},
|
|
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;
|
|
}
|
|
|
|
styleProps = ["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.isRowHeader){
|
|
cellStyle = styles.styleRowHeader;
|
|
styleProps.push("background-color");
|
|
}else {
|
|
cellStyle = styles.styleCells && styles.styleCells[index] ? styles.styleCells[index] : styles.firstCell;
|
|
}
|
|
|
|
if(cellStyle){
|
|
this.mapElementStyles(cellStyle, cellEl, styleProps);
|
|
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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{
|
|
|
|
static moduleName = "filter";
|
|
|
|
//load defaults
|
|
static filters = defaultFilters;
|
|
|
|
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.registerTableOption("placeholderHeaderFilter", false); //placeholder when header filter is empty
|
|
|
|
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));
|
|
this.subscribe("placeholder", this.generatePlaceholder.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;
|
|
}
|
|
|
|
generatePlaceholder(text){
|
|
if(this.table.options.placeholderHeaderFilter && Object.keys(this.headerFilters).length){
|
|
return this.table.options.placeholderHeaderFilter;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////
|
|
///////// 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;
|
|
},
|
|
getType:() => {
|
|
return "header";
|
|
},
|
|
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;
|
|
}
|
|
}
|
|
|
|
function plaintext(cell, formatterParams, onRendered){
|
|
return this.emptyToSpace(this.sanitizeHTML(cell.getValue()));
|
|
}
|
|
|
|
function html(cell, formatterParams, onRendered){
|
|
return cell.getValue();
|
|
}
|
|
|
|
function textarea(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, value;
|
|
|
|
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");
|
|
}
|
|
}
|
|
|
|
value = integer + decimal;
|
|
|
|
if(sign === true){
|
|
value = "(" + value + ")";
|
|
return after ? value + symbol : symbol + value;
|
|
}else {
|
|
return after ? sign + value + symbol : sign + symbol + value;
|
|
}
|
|
}
|
|
|
|
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 = Helpers.retrieveNestedData(this.table.options.nestedFieldSeparator, formatterParams.urlField, data);
|
|
}
|
|
|
|
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(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 : '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="#2DC214" clip-rule="evenodd" d="M21.652,3.211c-0.293-0.295-0.77-0.295-1.061,0L9.41,14.34 c-0.293,0.297-0.771,0.297-1.062,0L3.449,9.351C3.304,9.203,3.114,9.13,2.923,9.129C2.73,9.128,2.534,9.201,2.387,9.351 l-2.165,1.946C0.078,11.445,0,11.63,0,11.823c0,0.194,0.078,0.397,0.223,0.544l4.94,5.184c0.292,0.296,0.771,0.776,1.062,1.07 l2.124,2.141c0.292,0.293,0.769,0.293,1.062,0l14.366-14.34c0.293-0.294,0.293-0.777,0-1.071L21.652,3.211z" fill-rule="evenodd"/></svg>',
|
|
cross = typeof formatterParams.crossElement !== "undefined" ? formatterParams.crossElement : '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="#CE1515" d="M22.245,4.015c0.313,0.313,0.313,0.826,0,1.139l-6.276,6.27c-0.313,0.312-0.313,0.826,0,1.14l6.273,6.272 c0.313,0.313,0.313,0.826,0,1.14l-2.285,2.277c-0.314,0.312-0.828,0.312-1.142,0l-6.271-6.271c-0.313-0.313-0.828-0.313-1.141,0 l-6.276,6.267c-0.313,0.313-0.828,0.313-1.141,0l-2.282-2.28c-0.313-0.313-0.313-0.826,0-1.14l6.278-6.269 c0.313-0.312,0.313-0.826,0-1.14L1.709,5.147c-0.314-0.313-0.314-0.827,0-1.14l2.284-2.278C4.308,1.417,4.821,1.417,5.135,1.73 L11.405,8c0.314,0.314,0.828,0.314,1.141,0.001l6.276-6.267c0.312-0.312,0.826-0.312,1.141,0L22.245,4.015z"/></svg>';
|
|
|
|
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 = this.table.dependencyRegistry.lookup(["luxon", "DateTime"], "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 = this.table.dependencyRegistry.lookup(["luxon", "DateTime"], "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(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 = '<polygon fill="#FFEA00" stroke="#C1AB60" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>',
|
|
starInactive = '<polygon fill="#D2D2D2" stroke="#686868" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>';
|
|
|
|
//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(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 '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="#2DC214" clip-rule="evenodd" d="M21.652,3.211c-0.293-0.295-0.77-0.295-1.061,0L9.41,14.34 c-0.293,0.297-0.771,0.297-1.062,0L3.449,9.351C3.304,9.203,3.114,9.13,2.923,9.129C2.73,9.128,2.534,9.201,2.387,9.351 l-2.165,1.946C0.078,11.445,0,11.63,0,11.823c0,0.194,0.078,0.397,0.223,0.544l4.94,5.184c0.292,0.296,0.771,0.776,1.062,1.07 l2.124,2.141c0.292,0.293,0.769,0.293,1.062,0l14.366-14.34c0.293-0.294,0.293-0.777,0-1.071L21.652,3.211z" fill-rule="evenodd"/></svg>';
|
|
}
|
|
|
|
function buttonCross(cell, formatterParams, onRendered){
|
|
return '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="#CE1515" d="M22.245,4.015c0.313,0.313,0.313,0.826,0,1.139l-6.276,6.27c-0.313,0.312-0.313,0.826,0,1.14l6.273,6.272 c0.313,0.313,0.313,0.826,0,1.14l-2.285,2.277c-0.314,0.312-0.828,0.312-1.142,0l-6.271-6.271c-0.313-0.313-0.828-0.313-1.141,0 l-6.276,6.267c-0.313,0.313-0.828,0.313-1.141,0l-2.282-2.28c-0.313-0.313-0.313-0.826,0-1.14l6.278-6.269 c0.313-0.312,0.313-0.826,0-1.14L1.709,5.147c-0.314-0.313-0.314-0.827,0-1.14l2.284-2.278C4.308,1.417,4.821,1.417,5.135,1.73 L11.405,8c0.314,0.314,0.828,0.314,1.141,0.001l6.276-6.267c0.312-0.312,0.826-0.312,1.141,0L22.245,4.015z"/></svg>';
|
|
}
|
|
|
|
function toggle(cell, formatterParams, onRendered){
|
|
var value = cell.getValue(),
|
|
size = formatterParams.size ||15,
|
|
sizePx = size + "px",
|
|
containEl, switchEl,
|
|
onValue = formatterParams.hasOwnProperty("onValue") ? formatterParams.onValue : true,
|
|
offValue = formatterParams.hasOwnProperty("offValue") ? formatterParams.offValue : false,
|
|
|
|
|
|
state = formatterParams.onTruthy ? value : value === onValue;
|
|
|
|
|
|
containEl = document.createElement("div");
|
|
containEl.classList.add("tabulator-toggle");
|
|
|
|
if(state){
|
|
containEl.classList.add("tabulator-toggle-on");
|
|
containEl.style.flexDirection = "row-reverse";
|
|
|
|
if(formatterParams.onColor){
|
|
containEl.style.background = formatterParams.onColor;
|
|
}
|
|
}else {
|
|
if(formatterParams.offColor){
|
|
containEl.style.background = formatterParams.offColor;
|
|
}
|
|
}
|
|
|
|
containEl.style.width = (2.5 * size) + "px";
|
|
containEl.style.borderRadius = sizePx;
|
|
|
|
if(formatterParams.clickable){
|
|
containEl.addEventListener("click", (e) => {
|
|
cell.setValue(state ? offValue : onValue);
|
|
});
|
|
}
|
|
|
|
switchEl = document.createElement("div");
|
|
switchEl.classList.add("tabulator-toggle-switch");
|
|
|
|
switchEl.style.height = sizePx;
|
|
switchEl.style.width = sizePx;
|
|
switchEl.style.borderRadius = sizePx;
|
|
|
|
containEl.appendChild(switchEl);
|
|
|
|
return containEl;
|
|
}
|
|
|
|
function rownum(cell, formatterParams, onRendered){
|
|
var content = document.createElement("span");
|
|
var row = cell.getRow();
|
|
var table = cell.getTable();
|
|
|
|
row.watchPosition((position) => {
|
|
if (formatterParams.relativeToPage) {
|
|
position += table.modules.page.getPageSize() * (table.modules.page.getPage() - 1);
|
|
}
|
|
content.innerText = position;
|
|
});
|
|
|
|
return content;
|
|
}
|
|
|
|
function handle(cell, formatterParams, onRendered){
|
|
cell.getElement().classList.add("tabulator-row-handle");
|
|
return "<div class='tabulator-row-handle-box'><div class='tabulator-row-handle-bar'></div><div class='tabulator-row-handle-bar'></div><div class='tabulator-row-handle-bar'></div></div>";
|
|
}
|
|
|
|
function adaptable(cell, params, onRendered){
|
|
var lookup, formatterFunc, formatterParams;
|
|
|
|
function defaultLookup(cell){
|
|
var value = cell.getValue(),
|
|
formatter = "plaintext";
|
|
|
|
switch(typeof value){
|
|
case "boolean":
|
|
formatter = "tickCross";
|
|
break;
|
|
|
|
case "string":
|
|
if(value.includes("\n")){
|
|
formatter = "textarea";
|
|
}
|
|
break;
|
|
}
|
|
|
|
return formatter;
|
|
}
|
|
|
|
lookup = params.formatterLookup ? params.formatterLookup(cell) : defaultLookup(cell);
|
|
|
|
if(params.paramsLookup){
|
|
formatterParams = typeof params.paramsLookup === "function" ? params.paramsLookup(lookup, cell) : params.paramsLookup[lookup];
|
|
}
|
|
|
|
formatterFunc = this.table.modules.format.lookupFormatter(lookup);
|
|
|
|
return formatterFunc.call(this, cell, formatterParams || {}, onRendered);
|
|
}
|
|
|
|
function array$2(cell, formatterParams, onRendered){
|
|
var delimiter = formatterParams.delimiter || ",",
|
|
value = cell.getValue(),
|
|
table = this.table,
|
|
valueMap;
|
|
|
|
if(formatterParams.valueMap){
|
|
if(typeof formatterParams.valueMap === "string"){
|
|
valueMap = function(value){
|
|
return value.map((item) => {
|
|
return Helpers.retrieveNestedData(table.options.nestedFieldSeparator, formatterParams.valueMap, item);
|
|
});
|
|
};
|
|
}else {
|
|
valueMap = formatterParams.valueMap;
|
|
}
|
|
}
|
|
|
|
if(Array.isArray(value)){
|
|
if(valueMap){
|
|
value = valueMap(value);
|
|
}
|
|
|
|
return value.join(delimiter);
|
|
}else {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
function json$1(cell, formatterParams, onRendered){
|
|
var indent = formatterParams.indent || "\t",
|
|
multiline = typeof formatterParams.multiline === "undefined" ? true : formatterParams.multiline,
|
|
replacer = formatterParams.replacer || null,
|
|
value = cell.getValue();
|
|
|
|
if(multiline){
|
|
cell.getElement().style.whiteSpace = "pre-wrap";
|
|
}
|
|
|
|
return JSON.stringify(value, replacer, indent);
|
|
}
|
|
|
|
var defaultFormatters = {
|
|
plaintext:plaintext,
|
|
html:html,
|
|
textarea:textarea,
|
|
money:money,
|
|
link:link,
|
|
image:image,
|
|
tickCross:tickCross,
|
|
datetime:datetime$1,
|
|
datetimediff:datetimediff,
|
|
lookup:lookup,
|
|
star:star,
|
|
traffic:traffic,
|
|
progress:progress,
|
|
color:color,
|
|
buttonTick:buttonTick,
|
|
buttonCross:buttonCross,
|
|
toggle:toggle,
|
|
rownum:rownum,
|
|
handle:handle,
|
|
adaptable:adaptable,
|
|
array:array$2,
|
|
json:json$1,
|
|
};
|
|
|
|
class Format extends Module{
|
|
|
|
static moduleName = "format";
|
|
|
|
//load defaults
|
|
static formatters = defaultFormatters;
|
|
|
|
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.lookupTypeFormatter(column, "");
|
|
|
|
if(typeof column.definition.formatterPrint !== "undefined"){
|
|
column.modules.format.print = this.lookupTypeFormatter(column, "Print");
|
|
}
|
|
|
|
if(typeof column.definition.formatterClipboard !== "undefined"){
|
|
column.modules.format.clipboard = this.lookupTypeFormatter(column, "Clipboard");
|
|
}
|
|
|
|
if(typeof column.definition.formatterHtmlOutput !== "undefined"){
|
|
column.modules.format.htmlOutput = this.lookupTypeFormatter(column, "HtmlOutput");
|
|
}
|
|
}
|
|
|
|
lookupTypeFormatter(column, type){
|
|
var config = {params:column.definition["formatter" + type + "Params"] || {}},
|
|
formatter = column.definition["formatter" + type];
|
|
|
|
config.formatter = this.lookupFormatter(formatter);
|
|
|
|
return config;
|
|
}
|
|
|
|
|
|
lookupFormatter(formatter){
|
|
var formatterFunc;
|
|
|
|
//set column formatter
|
|
switch(typeof formatter){
|
|
case "string":
|
|
if(Format.formatters[formatter]){
|
|
formatterFunc = Format.formatters[formatter];
|
|
}else {
|
|
console.warn("Formatter Error - No such formatter found: ", formatter);
|
|
formatterFunc = Format.formatters.plaintext;
|
|
}
|
|
break;
|
|
|
|
case "function":
|
|
formatterFunc = formatter;
|
|
break;
|
|
|
|
default:
|
|
formatterFunc = Format.formatters.plaintext;
|
|
break;
|
|
}
|
|
|
|
return formatterFunc;
|
|
}
|
|
|
|
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.lookupFormatter(column.definition.titleFormatter);
|
|
|
|
onRendered = (callback) => {
|
|
column.titleFormatterRendered = callback;
|
|
};
|
|
|
|
mockCell = {
|
|
getValue:function(){
|
|
return title;
|
|
},
|
|
getElement:function(){
|
|
return el;
|
|
},
|
|
getType:function(){
|
|
return "header";
|
|
},
|
|
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;
|
|
}
|
|
|
|
}
|
|
|
|
class FrozenColumns extends Module{
|
|
|
|
static moduleName = "frozenColumns";
|
|
|
|
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-deleted", this.reinitializeColumns.bind(this));
|
|
this.subscribe("column-hide", this.reinitializeColumns.bind(this));
|
|
this.subscribe("column-show", this.reinitializeColumns.bind(this));
|
|
this.subscribe("columns-loaded", 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);
|
|
});
|
|
|
|
this.layout();
|
|
}
|
|
|
|
//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);
|
|
}
|
|
|
|
parentEl.classList.toggle("tabulator-frozen-left", column.modules.frozen.edge && column.modules.frozen.position === "left");
|
|
parentEl.classList.toggle("tabulator-frozen-right", column.modules.frozen.edge && column.modules.frozen.position === "right");
|
|
}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){
|
|
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");
|
|
|
|
element.classList.toggle("tabulator-frozen-left", column.modules.frozen.edge && column.modules.frozen.position === "left");
|
|
element.classList.toggle("tabulator-frozen-right", column.modules.frozen.edge && column.modules.frozen.position === "right");
|
|
}
|
|
}
|
|
|
|
adjustForScrollbar(width){
|
|
if(this.rightColumns.length){
|
|
this.table.columnManager.getContentsElement().style.width = "calc(100% - " + width + "px)";
|
|
}
|
|
}
|
|
|
|
getFrozenColumns(){
|
|
return this.leftColumns.concat(this.rightColumns);
|
|
}
|
|
|
|
_calcSpace(columns, index){
|
|
var width = 0;
|
|
|
|
for (let i = 0; i < index; i++){
|
|
if(columns[i].visible){
|
|
width += columns[i].getWidth();
|
|
}
|
|
}
|
|
|
|
return width;
|
|
}
|
|
}
|
|
|
|
class FrozenRows extends Module{
|
|
|
|
static moduleName = "frozenRows";
|
|
|
|
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(){
|
|
var fragment = document.createDocumentFragment();
|
|
|
|
this.rows = [];
|
|
|
|
this.topElement.classList.add("tabulator-frozen-rows-holder");
|
|
|
|
fragment.appendChild(document.createElement("br"));
|
|
fragment.appendChild(this.topElement);
|
|
|
|
// this.table.columnManager.element.append(this.topElement);
|
|
this.table.columnManager.getContentsElement().insertBefore(fragment, 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);
|
|
});
|
|
}
|
|
}
|
|
|
|
//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();
|
|
}
|
|
|
|
scrollTo(position, ifVisible){
|
|
return this._group.groupManager.table.rowManager.scrollToRow(this._group, position, ifVisible);
|
|
}
|
|
|
|
_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) => {
|
|
if(this.groupManager.table.options.groupToggleElement === "arrow"){
|
|
e.stopPropagation();
|
|
e.stopImmediatePropagation();
|
|
}
|
|
|
|
//allow click event to propagate before toggling visibility
|
|
setTimeout(() => {
|
|
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, includeChildren){
|
|
var output = [];
|
|
|
|
if(includeChildren && this.groupList.length){
|
|
this.groupList.forEach((group) => {
|
|
output = output.concat(group.getRows(component, includeChildren));
|
|
});
|
|
}else {
|
|
this.rows.forEach(function(row){
|
|
output.push(component ? row.getComponent() : row);
|
|
});
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
generateGroupHeaderContents(){
|
|
var data = [];
|
|
|
|
var rows = this.getRows(false, true);
|
|
|
|
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(){}
|
|
|
|
rendered(){}
|
|
|
|
//////////////// Object Generation /////////////////
|
|
getComponent(){
|
|
if(!this.component){
|
|
this.component = new GroupComponent(this);
|
|
}
|
|
|
|
return this.component;
|
|
}
|
|
}
|
|
|
|
class GroupRows extends Module{
|
|
|
|
static moduleName = "groupRows";
|
|
|
|
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) + "<span>(" + count + " " + ((count === 1) ? langValue : lang.groups.items) + ")</span>";
|
|
};
|
|
});
|
|
|
|
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 = "";
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
var defaultUndoers = {
|
|
cellEdit: function(action){
|
|
action.component.setValueProcessData(action.data.oldValue);
|
|
action.component.cellRendered();
|
|
},
|
|
|
|
rowAdd: function(action){
|
|
action.component.deleteActual();
|
|
|
|
this.table.rowManager.checkPlaceholder();
|
|
},
|
|
|
|
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);
|
|
|
|
this.table.rowManager.checkPlaceholder();
|
|
},
|
|
|
|
rowMove: function(action){
|
|
var after = (action.data.posFrom - action.data.posTo) > 0;
|
|
|
|
this.table.rowManager.moveRowActual(action.component, this.table.rowManager.getRowFromPosition(action.data.posFrom), after);
|
|
|
|
this.table.rowManager.regenerateRowPositions();
|
|
this.table.rowManager.reRenderInPosition();
|
|
},
|
|
};
|
|
|
|
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);
|
|
|
|
this.table.rowManager.checkPlaceholder();
|
|
},
|
|
|
|
rowDelete:function(action){
|
|
action.component.deleteActual();
|
|
|
|
this.table.rowManager.checkPlaceholder();
|
|
},
|
|
|
|
rowMove: function(action){
|
|
this.table.rowManager.moveRowActual(action.component, this.table.rowManager.getRowFromPosition(action.data.posTo), action.data.after);
|
|
|
|
this.table.rowManager.regenerateRowPositions();
|
|
this.table.rowManager.reRenderInPosition();
|
|
},
|
|
};
|
|
|
|
var bindings$1 = {
|
|
undo:["ctrl + 90", "meta + 90"],
|
|
redo:["ctrl + 89", "meta + 89"],
|
|
};
|
|
|
|
var actions$1 = {
|
|
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();
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
var extensions$3 = {
|
|
keybindings:{
|
|
bindings:bindings$1,
|
|
actions:actions$1
|
|
},
|
|
};
|
|
|
|
class History extends Module{
|
|
|
|
static moduleName = "history";
|
|
static moduleExtensions = extensions$3;
|
|
|
|
//load defaults
|
|
static undoers = defaultUndoers;
|
|
static redoers = defaultRedoers;
|
|
|
|
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(this.options("history") ? "History Undo Error - No more history to undo" : "History module not enabled");
|
|
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(this.options("history") ? "History Redo Error - No more history to redo" : "History module not enabled");
|
|
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);
|
|
}
|
|
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
class HtmlTableImport extends Module{
|
|
|
|
static moduleName = "htmlTableImport";
|
|
|
|
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().replaceAll(" ", "_");
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
function csv(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(input){
|
|
try {
|
|
return JSON.parse(input);
|
|
} catch(e) {
|
|
console.warn("JSON Import Error - File contents is invalid JSON", e);
|
|
return Promise.reject();
|
|
}
|
|
}
|
|
|
|
function array$1 (input){
|
|
return input;
|
|
}
|
|
|
|
function xlsx(input){
|
|
var XLSXLib = this.dependencyRegistry.lookup("XLSX"),
|
|
workbook2 = XLSXLib.read(input),
|
|
sheet = workbook2.Sheets[workbook2.SheetNames[0]];
|
|
|
|
return XLSXLib.utils.sheet_to_json(sheet, {header: 1 });
|
|
}
|
|
|
|
var defaultImporters = {
|
|
csv:csv,
|
|
json:json,
|
|
array:array$1,
|
|
xlsx:xlsx,
|
|
};
|
|
|
|
class Import extends Module{
|
|
|
|
static moduleName = "import";
|
|
|
|
//load defaults
|
|
static importers = defaultImporters;
|
|
|
|
constructor(table){
|
|
super(table);
|
|
|
|
this.registerTableOption("importFormat");
|
|
this.registerTableOption("importReader", "text");
|
|
this.registerTableOption("importHeaderTransform");
|
|
this.registerTableOption("importValueTransform");
|
|
this.registerTableOption("importDataValidator");
|
|
this.registerTableOption("importFileValidator");
|
|
}
|
|
|
|
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, importReader){
|
|
var importer = this.lookupImporter(importFormat);
|
|
|
|
if(importer){
|
|
return this.pickFile(extension, importReader)
|
|
.then(this.importData.bind(this, importer))
|
|
.then(this.structureData.bind(this))
|
|
.then(this.mutateData.bind(this))
|
|
.then(this.validateData.bind(this))
|
|
.then(this.setData.bind(this))
|
|
.catch((err) => {
|
|
this.dispatch("import-error", err);
|
|
this.dispatchExternal("importError", err);
|
|
|
|
console.error("Import Error:", err || "Unable to import file");
|
|
|
|
this.table.dataLoader.alertError();
|
|
|
|
setTimeout(() => {
|
|
this.table.dataLoader.clearAlert();
|
|
}, 3000);
|
|
|
|
return Promise.reject(err);
|
|
});
|
|
}
|
|
}
|
|
|
|
pickFile(extensions, importReader){
|
|
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(),
|
|
valid = this.validateFile(file);
|
|
|
|
if(valid === true){
|
|
|
|
this.dispatch("import-importing", input.files);
|
|
this.dispatchExternal("importImporting", input.files);
|
|
|
|
switch(importReader || 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(e);
|
|
};
|
|
}else {
|
|
reject(valid);
|
|
}
|
|
});
|
|
|
|
this.dispatch("import-choose");
|
|
this.dispatchExternal("importChoose");
|
|
input.click();
|
|
});
|
|
}
|
|
|
|
importData(importer, fileContents){
|
|
var data;
|
|
|
|
this.table.dataLoader.alertLoader();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
setTimeout(() => {
|
|
data = importer.call(this.table, fileContents);
|
|
|
|
if(data instanceof Promise){
|
|
resolve(data);
|
|
}else {
|
|
data ? resolve(data) : reject();
|
|
}
|
|
}, 10);
|
|
});
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
mutateData(data){
|
|
var output = [];
|
|
|
|
if(Array.isArray(data)){
|
|
data.forEach((row) => {
|
|
output.push(this.table.modules.mutator.transformRow(row, "import"));
|
|
});
|
|
}else {
|
|
output = data;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
transformHeader(headers){
|
|
var output = [];
|
|
|
|
if(this.table.options.importHeaderTransform){
|
|
headers.forEach((item) => {
|
|
output.push(this.table.options.importHeaderTransform.call(this.table, item, headers));
|
|
});
|
|
}else {
|
|
return headers;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
transformData(row){
|
|
var output = [];
|
|
|
|
if(this.table.options.importValueTransform){
|
|
row.forEach((item) => {
|
|
output.push(this.table.options.importValueTransform.call(this.table, item, row));
|
|
});
|
|
}else {
|
|
return row;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
structureArrayToObject(parsedData){
|
|
var columns = this.transformHeader(parsedData.shift());
|
|
|
|
var data = parsedData.map((values) => {
|
|
var row = {};
|
|
|
|
values = this.transformData(values);
|
|
|
|
columns.forEach((key, i) => {
|
|
row[key] = values[i];
|
|
});
|
|
|
|
return row;
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
structureArrayToColumns(parsedData){
|
|
var data = [],
|
|
firstRow = this.transformHeader(parsedData[0]),
|
|
columns = this.table.getColumns();
|
|
|
|
//remove first row if it is the column names
|
|
if(columns[0] && firstRow[0]){
|
|
if(columns[0].getDefinition().title === firstRow[0]){
|
|
parsedData.shift();
|
|
}
|
|
}
|
|
|
|
//convert row arrays to objects
|
|
parsedData.forEach((rowData) => {
|
|
var row = {};
|
|
|
|
rowData = this.transformData(rowData);
|
|
|
|
rowData.forEach((value, index) => {
|
|
var column = columns[index];
|
|
|
|
if(column){
|
|
row[column.getField()] = value;
|
|
}
|
|
});
|
|
|
|
data.push(row);
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
validateFile(file){
|
|
if(this.table.options.importFileValidator){
|
|
return this.table.options.importFileValidator.call(this.table, file);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
validateData(data){
|
|
var result;
|
|
|
|
if(this.table.options.importDataValidator){
|
|
result = this.table.options.importDataValidator.call(this.table, data);
|
|
|
|
if(result === true){
|
|
return data;
|
|
}else {
|
|
return Promise.reject(result);
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
setData(data){
|
|
this.dispatch("import-imported", data);
|
|
this.dispatchExternal("importImported", data);
|
|
|
|
this.table.dataLoader.clearAlert();
|
|
|
|
return this.table.setData(data);
|
|
}
|
|
}
|
|
|
|
class Interaction extends Module{
|
|
|
|
static moduleName = "interaction";
|
|
|
|
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));
|
|
this.subscribe("scroll-horizontal", this.clearTouchWatchers.bind(this));
|
|
this.subscribe("scroll-vertical", this.clearTouchWatchers.bind(this));
|
|
}
|
|
|
|
clearTouchWatchers(){
|
|
var types = Object.values(this.touchWatchers);
|
|
|
|
types.forEach((type) => {
|
|
for(let key in type){
|
|
type[key] = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
var defaultBindings = {
|
|
navPrev:"shift + 9",
|
|
navNext:9,
|
|
navUp:38,
|
|
navDown:40,
|
|
navLeft:37,
|
|
navRight:39,
|
|
scrollPageUp:33,
|
|
scrollPageDown:34,
|
|
scrollToStart:36,
|
|
scrollToEnd:35,
|
|
};
|
|
|
|
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);
|
|
},
|
|
};
|
|
|
|
class Keybindings extends Module{
|
|
|
|
static moduleName = "keybindings";
|
|
|
|
//load defaults
|
|
static bindings = defaultBindings;
|
|
static actions = defaultActions;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
class Menu extends Module{
|
|
|
|
static moduleName = "menu";
|
|
|
|
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.dispatch("menu-closed", menu, popup);
|
|
this.dispatchExternal("menuClosed", this.currentComponent.getComponent());
|
|
this.currentComponent = null;
|
|
}
|
|
});
|
|
|
|
this.currentComponent = component;
|
|
|
|
this.dispatch("menu-opened", menu, popup);
|
|
this.dispatchExternal("menuOpened", component.getComponent());
|
|
}
|
|
}
|
|
}
|
|
|
|
class MoveColumns extends Module{
|
|
|
|
static moduleName = "moveColumn";
|
|
|
|
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));
|
|
this.subscribe("alert-show", this.abortMove.bind(this));
|
|
}
|
|
}
|
|
|
|
abortMove(){
|
|
clearTimeout(this.checkTimeout);
|
|
}
|
|
|
|
initializeColumn(column){
|
|
var self = this,
|
|
config = {},
|
|
colEl;
|
|
|
|
if(!column.modules.frozen && !column.isGroup && !column.isRowHeader){
|
|
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();
|
|
|
|
//Prevent moving columns when range selection is active
|
|
if(this.table.modules.selectRange && this.table.modules.selectRange.columnSelection){
|
|
if(this.table.modules.selectRange.mousedown && this.table.modules.selectRange.selecting === "column"){
|
|
return;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
this.dispatch("column-moving", e, this.moving);
|
|
}
|
|
|
|
_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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var defaultSenders = {
|
|
delete:function(fromRow, toRow, toTable){
|
|
fromRow.delete();
|
|
}
|
|
};
|
|
|
|
var defaultReceivers = {
|
|
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;
|
|
},
|
|
};
|
|
|
|
class MoveRows extends Module{
|
|
|
|
static moduleName = "moveRow";
|
|
|
|
//load defaults
|
|
static senders = defaultSenders;
|
|
static receivers = defaultReceivers;
|
|
|
|
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 = MoveRows.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 = MoveRows.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);
|
|
}
|
|
}
|
|
}
|
|
|
|
var defaultMutators = {};
|
|
|
|
class Mutator extends Module{
|
|
|
|
static moduleName = "mutator";
|
|
|
|
//load defaults
|
|
static mutators = defaultMutators;
|
|
|
|
constructor(table){
|
|
super(table);
|
|
|
|
this.allowedTypes = ["", "data", "edit", "clipboard", "import"]; //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("mutatorImport");
|
|
this.registerColumnOption("mutatorImportParams");
|
|
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;
|
|
|
|
// console.log("key", key)
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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{
|
|
|
|
static moduleName = "page";
|
|
|
|
//load defaults
|
|
static pageCounters = defaultPageCounters;
|
|
|
|
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("paginationOutOfRange", false); //reset the current page when the last page < this.page, values: false|function|any value accepted by setPage()
|
|
|
|
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.table.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, paginationOutOfRange;
|
|
|
|
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 {
|
|
|
|
if(this.page > this.max){
|
|
console.warn( "Remote Pagination Error - Server returned last page value lower than the current page" );
|
|
|
|
paginationOutOfRange = this.options('paginationOutOfRange');
|
|
|
|
if(paginationOutOfRange){
|
|
return this.setPage(typeof paginationOutOfRange === 'function' ? paginationOutOfRange.call(this, this.page, this.max) : paginationOutOfRange);
|
|
}
|
|
}
|
|
|
|
// 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';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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{
|
|
|
|
static moduleName = "persistence";
|
|
|
|
static moduleInitOrder = -10;
|
|
|
|
//load defaults
|
|
static readers = defaultReaders;
|
|
static writers = defaultWriters;
|
|
|
|
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,
|
|
headerFilter:this.table.options.persistence === true || this.table.options.persistence.headerFilter,
|
|
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("filter-changed", this.eventSave.bind(this, "headerFilter"));
|
|
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, headerFilters;
|
|
|
|
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;
|
|
}
|
|
}
|
|
if(this.config.headerFilter){
|
|
headerFilters = this.load("headerFilter");
|
|
|
|
if(!headerFilters === false){
|
|
this.table.options.initialHeaderFilter = headerFilters;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
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, true));
|
|
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, mergeAllNew){
|
|
var output = [];
|
|
|
|
newCols = newCols || [];
|
|
|
|
newCols.forEach((column, to) => {
|
|
var from = this._findColumn(oldCols, column),
|
|
keys;
|
|
|
|
if(from){
|
|
if(mergeAllNew){
|
|
keys = Object.keys(column);
|
|
}else 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 "headerFilter":
|
|
data = this.table.modules.filter.getHeaderFilters();
|
|
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;
|
|
}
|
|
}
|
|
|
|
class Popup extends Module{
|
|
|
|
static moduleName = "popup";
|
|
|
|
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());
|
|
}
|
|
}
|
|
|
|
class Print extends Module{
|
|
|
|
static moduleName = "print";
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
class ReactiveData extends Module{
|
|
|
|
static moduleName = "reactiveData";
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
class ResizeColumns extends Module{
|
|
|
|
static moduleName = "resizeColumns";
|
|
|
|
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);
|
|
this.registerTableOption("resizableColumnGuide", 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.modules.resize.handleEl.style["z-index"] = 11;
|
|
}
|
|
|
|
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.dispatchExternal("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;
|
|
}
|
|
}
|
|
|
|
resize(e, column){
|
|
var x = typeof e.clientX === "undefined" ? e.touches[0].clientX : e.clientX,
|
|
startDiff = x - this.startX,
|
|
moveDiff = x - this.latestX,
|
|
blockedBefore, blockedAfter;
|
|
|
|
this.latestX = x;
|
|
|
|
if(this.table.rtl){
|
|
startDiff = -startDiff;
|
|
moveDiff = -moveDiff;
|
|
}
|
|
|
|
blockedBefore = column.width == column.minWidth || column.width == column.maxWidth;
|
|
|
|
column.setWidth(this.startWidth + startDiff);
|
|
|
|
blockedAfter = column.width == column.minWidth || column.width == column.maxWidth;
|
|
|
|
if(moveDiff < 0){
|
|
this.nextColumn = this.initialNextColumn;
|
|
}
|
|
|
|
if(this.table.options.resizableColumnFit && this.nextColumn && !(blockedBefore && blockedAfter)){
|
|
let colWidth = this.nextColumn.getWidth();
|
|
|
|
if(moveDiff > 0){
|
|
if(colWidth <= this.nextColumn.minWidth){
|
|
this.nextColumn = this.nextColumn.nextColumn();
|
|
}
|
|
}
|
|
|
|
if(this.nextColumn){
|
|
this.nextColumn.setWidth(this.nextColumn.getWidth() - moveDiff);
|
|
}
|
|
}
|
|
|
|
this.table.columnManager.rerenderColumns(true);
|
|
|
|
if(!this.table.browserSlow && column.modules.resize && column.modules.resize.variableHeight){
|
|
column.checkCellHeights();
|
|
}
|
|
}
|
|
|
|
calcGuidePosition(e, column, handle) {
|
|
var mouseX = typeof e.clientX === "undefined" ? e.touches[0].clientX : e.clientX,
|
|
handleX = handle.getBoundingClientRect().x - this.table.element.getBoundingClientRect().x,
|
|
tableX = this.table.element.getBoundingClientRect().x,
|
|
columnX = column.element.getBoundingClientRect().left - tableX,
|
|
mouseDiff = mouseX - this.startX,
|
|
pos = Math.max(handleX + mouseDiff, columnX + column.minWidth);
|
|
|
|
if(column.maxWidth){
|
|
pos = Math.min(pos, columnX + column.maxWidth);
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
_checkResizability(column){
|
|
return column.definition.resizable;
|
|
}
|
|
|
|
_mouseDown(e, column, handle){
|
|
var self = this,
|
|
guideEl;
|
|
|
|
this.dispatchExternal("columnResizing", column.getComponent());
|
|
|
|
if(self.table.options.resizableColumnGuide){
|
|
guideEl = document.createElement("span");
|
|
guideEl.classList.add('tabulator-col-resize-guide');
|
|
self.table.element.appendChild(guideEl);
|
|
setTimeout(() => {
|
|
guideEl.style.left = self.calcGuidePosition(e, column, handle) + "px";
|
|
});
|
|
}
|
|
|
|
self.table.element.classList.add("tabulator-block-select");
|
|
|
|
function mouseMove(e){
|
|
if(self.table.options.resizableColumnGuide){
|
|
guideEl.style.left = self.calcGuidePosition(e, column, handle) + "px";
|
|
}else {
|
|
self.resize(e, column);
|
|
}
|
|
}
|
|
|
|
function mouseUp(e){
|
|
if(self.table.options.resizableColumnGuide){
|
|
self.resize(e, column);
|
|
guideEl.remove();
|
|
}
|
|
|
|
//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.dispatchExternal("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.clientX === "undefined" ? e.touches[0].clientX : e.clientX;
|
|
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);
|
|
}
|
|
}
|
|
|
|
class ResizeRows extends Module{
|
|
|
|
static moduleName = "resizeRows";
|
|
|
|
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
|
|
this.registerTableOption("resizableRowGuide", false);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
resize(e, row) {
|
|
row.setHeight(this.startHeight + ((typeof e.screenY === "undefined" ? e.touches[0].screenY : e.screenY) - this.startY));
|
|
}
|
|
|
|
calcGuidePosition(e, row, handle) {
|
|
var mouseY = typeof e.screenY === "undefined" ? e.touches[0].screenY : e.screenY,
|
|
handleY = handle.getBoundingClientRect().y - this.table.element.getBoundingClientRect().y,
|
|
tableY = this.table.element.getBoundingClientRect().y,
|
|
rowY = row.element.getBoundingClientRect().top - tableY,
|
|
mouseDiff = mouseY - this.startY;
|
|
|
|
return Math.max(handleY + mouseDiff, rowY);
|
|
}
|
|
|
|
_mouseDown(e, row, handle){
|
|
var self = this,
|
|
guideEl;
|
|
|
|
self.dispatchExternal("rowResizing", row.getComponent());
|
|
|
|
if(self.table.options.resizableRowGuide){
|
|
guideEl = document.createElement("span");
|
|
guideEl.classList.add('tabulator-row-resize-guide');
|
|
self.table.element.appendChild(guideEl);
|
|
setTimeout(() => {
|
|
guideEl.style.top = self.calcGuidePosition(e, row, handle) + "px";
|
|
});
|
|
}
|
|
|
|
self.table.element.classList.add("tabulator-block-select");
|
|
|
|
function mouseMove(e){
|
|
if(self.table.options.resizableRowGuide){
|
|
guideEl.style.top = self.calcGuidePosition(e, row, handle) + "px";
|
|
}else {
|
|
self.resize(e, row);
|
|
}
|
|
}
|
|
|
|
function mouseUp(e){
|
|
if(self.table.options.resizableRowGuide){
|
|
self.resize(e, row);
|
|
guideEl.remove();
|
|
}
|
|
|
|
// //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);
|
|
}
|
|
}
|
|
|
|
class ResizeTable extends Module{
|
|
|
|
static moduleName = "resizeTable";
|
|
|
|
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 || (table.modules.edit && !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 || (table.modules.edit && !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 || (table.modules.edit && !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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = `<svg class='tabulator-responsive-collapse-toggle-open' viewbox="0 0 24 24">
|
|
<line x1="7" y1="12" x2="17" y2="12" fill="none" stroke-width="3" stroke-linecap="round" />
|
|
<line y1="7" x1="12" y2="17" x2="12" fill="none" stroke-width="3" stroke-linecap="round" />
|
|
</svg>
|
|
|
|
<svg class='tabulator-responsive-collapse-toggle-close' viewbox="0 0 24 24">
|
|
<line x1="7" y1="12" x2="17" y2="12" fill="none" stroke-width="3" stroke-linecap="round" />
|
|
</svg>`;
|
|
|
|
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;
|
|
}
|
|
|
|
var extensions$2 = {
|
|
format:{
|
|
formatters:{
|
|
responsiveCollapse:responsiveCollapse,
|
|
}
|
|
}
|
|
};
|
|
|
|
class ResponsiveLayout extends Module{
|
|
|
|
static moduleName = "responsiveLayout";
|
|
static moduleExtensions = extensions$2;
|
|
|
|
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 = [];
|
|
|
|
if(this.collapseFormatter){
|
|
this.collapseFormatter = this.collapseFormatter.bind(this.table);
|
|
}
|
|
|
|
//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);
|
|
}
|
|
row.calcHeight(true);
|
|
}
|
|
}
|
|
|
|
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;
|
|
},
|
|
getType:function(){
|
|
return "cell";
|
|
},
|
|
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((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.modules.localize.bind("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);
|
|
});
|
|
|
|
return Object.keys(data).length ? list : "";
|
|
}
|
|
}
|
|
|
|
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.selectableRowsRangeMode === "click"){
|
|
if(!blocked){
|
|
row.toggleSelect();
|
|
}else {
|
|
blocked = false;
|
|
}
|
|
}else {
|
|
row.toggleSelect();
|
|
}
|
|
});
|
|
|
|
if(this.table.options.selectableRowsRangeMode === "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 extensions$1 = {
|
|
format:{
|
|
formatters:{
|
|
rowSelection:rowSelection,
|
|
}
|
|
}
|
|
};
|
|
|
|
class SelectRow extends Module{
|
|
|
|
static moduleName = "selectRow";
|
|
static moduleExtensions = extensions$1;
|
|
|
|
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("selectableRows", "highlight"); //highlight rows on hover
|
|
this.registerTableOption("selectableRowsRangeMode", "drag"); //highlight rows on hover
|
|
this.registerTableOption("selectableRowsRollingSelection", true); //roll selection once maximum number of selectable rows is reached
|
|
this.registerTableOption("selectableRowsPersistence", true); // maintain selection when table view is updated
|
|
this.registerTableOption("selectableRowsCheck", 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(){
|
|
|
|
this.deprecatedOptionsCheck();
|
|
|
|
if(this.table.options.selectableRows === "highlight" && this.table.options.selectableRange){
|
|
this.table.options.selectableRows = false;
|
|
}
|
|
|
|
if(this.table.options.selectableRows !== 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.selectableRows && !this.table.options.selectableRowsPersistence){
|
|
this.subscribe("data-refreshing", this.deselectRows.bind(this));
|
|
}
|
|
}
|
|
}
|
|
|
|
deprecatedOptionsCheck(){
|
|
// this.deprecationCheck("selectable", "selectableRows", true);
|
|
// this.deprecationCheck("selectableRollingSelection", "selectableRowsRollingSelection", true);
|
|
// this.deprecationCheck("selectableRangeMode", "selectableRowsRangeMode", true);
|
|
// this.deprecationCheck("selectablePersistence", "selectableRowsPersistence", true);
|
|
// this.deprecationCheck("selectableCheck", "selectableRowsCheck", true);
|
|
}
|
|
|
|
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,
|
|
selectable = self.checkRowSelectability(row),
|
|
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};
|
|
|
|
element.classList.toggle("tabulator-selectable", selectable);
|
|
element.classList.toggle("tabulator-unselectable", !selectable);
|
|
|
|
//set row selection class
|
|
if(self.checkRowSelectability(row)){
|
|
if(self.table.options.selectableRows && self.table.options.selectableRows != "highlight"){
|
|
if(self.table.options.selectableRowsRangeMode === "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);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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.selectableRows !== true && !this.isRowSelected(row)){
|
|
if(this.selectedRows.length < this.table.options.selectableRows){
|
|
this.toggleRow(toggledRow);
|
|
}
|
|
}else {
|
|
this.toggleRow(toggledRow);
|
|
}
|
|
}
|
|
});
|
|
this.lastClickedRow = row;
|
|
}else {
|
|
this.deselectRows(undefined, true);
|
|
|
|
if(this.table.options.selectableRows !== true){
|
|
if(toggledRows.length > this.table.options.selectableRows){
|
|
toggledRows = toggledRows.slice(0, this.table.options.selectableRows);
|
|
}
|
|
}
|
|
|
|
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 && row.type === "row"){
|
|
return this.table.options.selectableRowsCheck.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 changes = [],
|
|
rowMatch, change;
|
|
|
|
switch(typeof rows){
|
|
case "undefined":
|
|
rowMatch = this.table.rowManager.rows;
|
|
break;
|
|
|
|
case "number":
|
|
rowMatch = this.table.rowManager.findRow(rows);
|
|
break;
|
|
|
|
case "string":
|
|
rowMatch = this.table.rowManager.findRow(rows);
|
|
|
|
if(!rowMatch){
|
|
rowMatch = this.table.rowManager.getRows(rows);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
rowMatch = rows;
|
|
break;
|
|
}
|
|
|
|
if(Array.isArray(rowMatch)){
|
|
if(rowMatch.length){
|
|
rowMatch.forEach((row) => {
|
|
change = this._selectRow(row, true, true);
|
|
|
|
if(change){
|
|
changes.push(change);
|
|
}
|
|
});
|
|
|
|
this._rowSelectionChanged(false, changes);
|
|
}
|
|
}else {
|
|
if(rowMatch){
|
|
this._selectRow(rowMatch, false, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
//select an individual row
|
|
_selectRow(rowInfo, silent, force){
|
|
//handle max row count
|
|
if(!isNaN(this.table.options.selectableRows) && this.table.options.selectableRows !== true && !force){
|
|
if(this.selectedRows.length >= this.table.options.selectableRows){
|
|
if(this.table.options.selectableRowsRollingSelection){
|
|
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, row);
|
|
|
|
return row;
|
|
}
|
|
}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 changes = [],
|
|
rowMatch, change;
|
|
|
|
switch(typeof rows){
|
|
case "undefined":
|
|
rowMatch = Object.assign([], this.selectedRows);
|
|
break;
|
|
|
|
case "number":
|
|
rowMatch = this.table.rowManager.findRow(rows);
|
|
break;
|
|
|
|
case "string":
|
|
rowMatch = this.table.rowManager.findRow(rows);
|
|
|
|
if(!rowMatch){
|
|
rowMatch = this.table.rowManager.getRows(rows);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
rowMatch = rows;
|
|
break;
|
|
}
|
|
|
|
if(Array.isArray(rowMatch)){
|
|
if(rowMatch.length){
|
|
rowMatch.forEach((row) => {
|
|
change = this._deselectRow(row, true, true);
|
|
|
|
if(change){
|
|
changes.push(change);
|
|
}
|
|
});
|
|
|
|
this._rowSelectionChanged(silent, [], changes);
|
|
}
|
|
}else {
|
|
if(rowMatch){
|
|
this._deselectRow(rowMatch, silent, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
//deselect an individual row
|
|
_deselectRow(rowInfo, silent){
|
|
var self = this,
|
|
row = self.table.rowManager.findRow(rowInfo),
|
|
index, element;
|
|
|
|
if(row){
|
|
index = self.selectedRows.findIndex(function(selectedRow){
|
|
return selectedRow == row;
|
|
});
|
|
|
|
if(index > -1){
|
|
|
|
element = row.getElement();
|
|
|
|
if(element){
|
|
element.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, undefined, row);
|
|
|
|
return row;
|
|
}
|
|
}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, selected = [], deselected = []){
|
|
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){
|
|
if(!Array.isArray(selected)){
|
|
selected = [selected];
|
|
}
|
|
|
|
selected = selected.map(row => row.getComponent());
|
|
|
|
if(!Array.isArray(deselected)){
|
|
deselected = [deselected];
|
|
}
|
|
|
|
deselected = deselected.map(row => row.getComponent());
|
|
|
|
this.dispatchExternal("rowSelectionChanged", this.getSelectedData(), this.getSelectedRows(), selected, deselected);
|
|
}
|
|
}
|
|
|
|
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, true);
|
|
|
|
if(select){
|
|
for(let child of children){
|
|
this._selectRow(child, true);
|
|
}
|
|
}else {
|
|
for(let child of children){
|
|
this._deselectRow(child, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class RangeComponent {
|
|
constructor(range) {
|
|
this._range = range;
|
|
|
|
return new Proxy(this, {
|
|
get: function (target, name, receiver) {
|
|
if (typeof target[name] !== "undefined") {
|
|
return target[name];
|
|
} else {
|
|
return target._range.table.componentFunctionBinder.handle("range", target._range, name);
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
getElement() {
|
|
return this._range.element;
|
|
}
|
|
|
|
getData() {
|
|
return this._range.getData();
|
|
}
|
|
|
|
getCells() {
|
|
return this._range.getCells(true, true);
|
|
}
|
|
|
|
getStructuredCells() {
|
|
return this._range.getStructuredCells();
|
|
}
|
|
|
|
getRows() {
|
|
return this._range.getRows().map((row) => row.getComponent());
|
|
}
|
|
|
|
getColumns() {
|
|
return this._range.getColumns().map((column) => column.getComponent());
|
|
}
|
|
|
|
getBounds() {
|
|
return this._range.getBounds();
|
|
}
|
|
|
|
getTopEdge() {
|
|
return this._range.top;
|
|
}
|
|
|
|
getBottomEdge() {
|
|
return this._range.bottom;
|
|
}
|
|
|
|
getLeftEdge() {
|
|
return this._range.left;
|
|
}
|
|
|
|
getRightEdge() {
|
|
return this._range.right;
|
|
}
|
|
|
|
setBounds(start, end){
|
|
if(this._range.destroyedGuard("setBounds")){
|
|
this._range.setBounds(start ? start._cell : start, end ? end._cell : end);
|
|
}
|
|
}
|
|
|
|
setStartBound(start){
|
|
if(this._range.destroyedGuard("setStartBound")){
|
|
this._range.setEndBound(start ? start._cell : start);
|
|
this._range.rangeManager.layoutElement();
|
|
}
|
|
}
|
|
|
|
setEndBound(end){
|
|
if(this._range.destroyedGuard("setEndBound")){
|
|
this._range.setEndBound(end ? end._cell : end);
|
|
this._range.rangeManager.layoutElement();
|
|
}
|
|
}
|
|
|
|
clearValues(){
|
|
if(this._range.destroyedGuard("clearValues")){
|
|
this._range.clearValues();
|
|
}
|
|
}
|
|
|
|
remove(){
|
|
if(this._range.destroyedGuard("remove")){
|
|
this._range.destroy(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
class Range extends CoreFeature{
|
|
constructor(table, rangeManager, start, end) {
|
|
super(table);
|
|
|
|
this.rangeManager = rangeManager;
|
|
this.element = null;
|
|
this.initialized = false;
|
|
this.initializing = {
|
|
start:false,
|
|
end:false,
|
|
};
|
|
this.destroyed = false;
|
|
|
|
this.top = 0;
|
|
this.bottom = 0;
|
|
this.left = 0;
|
|
this.right = 0;
|
|
|
|
this.table = table;
|
|
this.start = {row:0, col:0};
|
|
this.end = {row:0, col:0};
|
|
|
|
if(this.rangeManager.rowHeader){
|
|
this.left = 1;
|
|
this.right = 1;
|
|
this.start.col = 1;
|
|
this.end.col = 1;
|
|
}
|
|
|
|
this.initElement();
|
|
|
|
setTimeout(() => {
|
|
this.initBounds(start, end);
|
|
});
|
|
}
|
|
|
|
initElement(){
|
|
this.element = document.createElement("div");
|
|
this.element.classList.add("tabulator-range");
|
|
}
|
|
|
|
initBounds(start, end){
|
|
this._updateMinMax();
|
|
|
|
if(start){
|
|
this.setBounds(start, end || start);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////
|
|
/////// Boundary Setup ///////
|
|
///////////////////////////////////
|
|
|
|
setStart(row, col) {
|
|
if(this.start.row !== row || this.start.col !== col){
|
|
this.start.row = row;
|
|
this.start.col = col;
|
|
|
|
this.initializing.start = true;
|
|
this._updateMinMax();
|
|
}
|
|
}
|
|
|
|
setEnd(row, col) {
|
|
if(this.end.row !== row || this.end.col !== col){
|
|
this.end.row = row;
|
|
this.end.col = col;
|
|
|
|
this.initializing.end = true;
|
|
this._updateMinMax();
|
|
}
|
|
}
|
|
|
|
setBounds(start, end, visibleRows){
|
|
if(start){
|
|
this.setStartBound(start);
|
|
}
|
|
|
|
this.setEndBound(end || start);
|
|
this.rangeManager.layoutElement(visibleRows);
|
|
}
|
|
|
|
setStartBound(element){
|
|
var row, col;
|
|
|
|
if (element.type === "column") {
|
|
if(this.rangeManager.columnSelection){
|
|
this.setStart(0, element.getPosition() - 1);
|
|
}
|
|
}else {
|
|
row = element.row.position - 1;
|
|
col = element.column.getPosition() - 1;
|
|
|
|
if (element.column === this.rangeManager.rowHeader) {
|
|
this.setStart(row, 1);
|
|
} else {
|
|
this.setStart(row, col);
|
|
}
|
|
}
|
|
}
|
|
|
|
setEndBound(element){
|
|
var rowsCount = this._getTableRows().length,
|
|
row, col, isRowHeader;
|
|
|
|
if (element.type === "column") {
|
|
if(this.rangeManager.columnSelection){
|
|
if (this.rangeManager.selecting === "column") {
|
|
this.setEnd(rowsCount - 1, element.getPosition() - 1);
|
|
} else if (this.rangeManager.selecting === "cell") {
|
|
this.setEnd(0, element.getPosition() - 1);
|
|
}
|
|
}
|
|
}else {
|
|
row = element.row.position - 1;
|
|
col = element.column.getPosition() - 1;
|
|
isRowHeader = element.column === this.rangeManager.rowHeader;
|
|
|
|
if (this.rangeManager.selecting === "row") {
|
|
this.setEnd(row, this._getTableColumns().length - 1);
|
|
} else if (this.rangeManager.selecting !== "row" && isRowHeader) {
|
|
this.setEnd(row, 0);
|
|
} else if (this.rangeManager.selecting === "column") {
|
|
this.setEnd(rowsCount - 1, col);
|
|
} else {
|
|
this.setEnd(row, col);
|
|
}
|
|
}
|
|
}
|
|
|
|
_updateMinMax() {
|
|
this.top = Math.min(this.start.row, this.end.row);
|
|
this.bottom = Math.max(this.start.row, this.end.row);
|
|
this.left = Math.min(this.start.col, this.end.col);
|
|
this.right = Math.max(this.start.col, this.end.col);
|
|
|
|
if(this.initialized){
|
|
this.dispatchExternal("rangeChanged", this.getComponent());
|
|
}else {
|
|
if(this.initializing.start && this.initializing.end){
|
|
this.initialized = true;
|
|
this.dispatchExternal("rangeAdded", this.getComponent());
|
|
}
|
|
}
|
|
}
|
|
|
|
_getTableColumns() {
|
|
return this.table.columnManager.getVisibleColumnsByIndex();
|
|
}
|
|
|
|
_getTableRows() {
|
|
return this.table.rowManager.getDisplayRows().filter(row=> row.type === "row");
|
|
}
|
|
|
|
///////////////////////////////////
|
|
/////// Rendering ///////
|
|
///////////////////////////////////
|
|
|
|
layout() {
|
|
var _vDomTop = this.table.rowManager.renderer.vDomTop,
|
|
_vDomBottom = this.table.rowManager.renderer.vDomBottom,
|
|
_vDomLeft = this.table.columnManager.renderer.leftCol,
|
|
_vDomRight = this.table.columnManager.renderer.rightCol,
|
|
top, bottom, left, right, topLeftCell, bottomRightCell, topLeftCellEl, bottomRightCellEl, topLeftRowEl, bottomRightRowEl;
|
|
|
|
if(this.table.options.renderHorizontal === "virtual" && this.rangeManager.rowHeader) {
|
|
_vDomRight += 1;
|
|
}
|
|
|
|
if (_vDomTop == null) {
|
|
_vDomTop = 0;
|
|
}
|
|
|
|
if (_vDomBottom == null) {
|
|
_vDomBottom = Infinity;
|
|
}
|
|
|
|
if (_vDomLeft == null) {
|
|
_vDomLeft = 0;
|
|
}
|
|
|
|
if (_vDomRight == null) {
|
|
_vDomRight = Infinity;
|
|
}
|
|
|
|
if (this.overlaps(_vDomLeft, _vDomTop, _vDomRight, _vDomBottom)) {
|
|
top = Math.max(this.top, _vDomTop);
|
|
bottom = Math.min(this.bottom, _vDomBottom);
|
|
left = Math.max(this.left, _vDomLeft);
|
|
right = Math.min(this.right, _vDomRight);
|
|
|
|
topLeftCell = this.rangeManager.getCell(top, left);
|
|
bottomRightCell = this.rangeManager.getCell(bottom, right);
|
|
topLeftCellEl = topLeftCell.getElement();
|
|
bottomRightCellEl = bottomRightCell.getElement();
|
|
topLeftRowEl = topLeftCell.row.getElement();
|
|
bottomRightRowEl = bottomRightCell.row.getElement();
|
|
|
|
this.element.classList.add("tabulator-range-active");
|
|
// this.element.classList.toggle("tabulator-range-active", this === this.rangeManager.activeRange);
|
|
|
|
if(this.table.rtl){
|
|
this.element.style.right = topLeftRowEl.offsetWidth - topLeftCellEl.offsetLeft - topLeftCellEl.offsetWidth + "px";
|
|
this.element.style.width = topLeftCellEl.offsetLeft + topLeftCellEl.offsetWidth - bottomRightCellEl.offsetLeft + "px";
|
|
}else {
|
|
this.element.style.left = topLeftRowEl.offsetLeft + topLeftCellEl.offsetLeft + "px";
|
|
this.element.style.width = bottomRightCellEl.offsetLeft + bottomRightCellEl.offsetWidth - topLeftCellEl.offsetLeft + "px";
|
|
}
|
|
|
|
this.element.style.top = topLeftRowEl.offsetTop + "px";
|
|
this.element.style.height = bottomRightRowEl.offsetTop + bottomRightRowEl.offsetHeight - topLeftRowEl.offsetTop + "px";
|
|
}
|
|
}
|
|
|
|
atTopLeft(cell) {
|
|
return cell.row.position - 1 === this.top && cell.column.getPosition() - 1 === this.left;
|
|
}
|
|
|
|
atBottomRight(cell) {
|
|
return cell.row.position - 1 === this.bottom && cell.column.getPosition() - 1 === this.right;
|
|
}
|
|
|
|
occupies(cell) {
|
|
return this.occupiesRow(cell.row) && this.occupiesColumn(cell.column);
|
|
}
|
|
|
|
occupiesRow(row) {
|
|
return this.top <= row.position - 1 && row.position - 1 <= this.bottom;
|
|
}
|
|
|
|
occupiesColumn(col) {
|
|
return this.left <= col.getPosition() - 1 && col.getPosition() - 1 <= this.right;
|
|
}
|
|
|
|
overlaps(left, top, right, bottom) {
|
|
if ((this.left > right || left > this.right) || (this.top > bottom || top > this.bottom)){
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
getData() {
|
|
var data = [],
|
|
rows = this.getRows(),
|
|
columns = this.getColumns();
|
|
|
|
rows.forEach((row) => {
|
|
var rowData = row.getData(),
|
|
result = {};
|
|
|
|
columns.forEach((column) => {
|
|
result[column.field] = rowData[column.field];
|
|
});
|
|
|
|
data.push(result);
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
getCells(structured, component) {
|
|
var cells = [],
|
|
rows = this.getRows(),
|
|
columns = this.getColumns();
|
|
|
|
if (structured) {
|
|
cells = rows.map((row) => {
|
|
var arr = [];
|
|
|
|
row.getCells().forEach((cell) => {
|
|
if (columns.includes(cell.column)) {
|
|
arr.push(component ? cell.getComponent() : cell);
|
|
}
|
|
});
|
|
|
|
return arr;
|
|
});
|
|
} else {
|
|
rows.forEach((row) => {
|
|
row.getCells().forEach((cell) => {
|
|
if (columns.includes(cell.column)) {
|
|
cells.push(component ? cell.getComponent() : cell);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
return cells;
|
|
}
|
|
|
|
getStructuredCells() {
|
|
return this.getCells(true, true);
|
|
}
|
|
|
|
getRows() {
|
|
return this._getTableRows().slice(this.top, this.bottom + 1);
|
|
}
|
|
|
|
getColumns() {
|
|
return this._getTableColumns().slice(this.left, this.right + 1);
|
|
}
|
|
|
|
clearValues(){
|
|
var cells = this.getCells();
|
|
var clearValue = this.table.options.selectableRangeClearCellsValue;
|
|
|
|
this.table.blockRedraw();
|
|
|
|
cells.forEach((cell) => {
|
|
cell.setValue(clearValue);
|
|
});
|
|
|
|
this.table.restoreRedraw();
|
|
|
|
}
|
|
|
|
getBounds(component){
|
|
var cells = this.getCells(false, component),
|
|
output = {
|
|
start:null,
|
|
end:null,
|
|
};
|
|
|
|
if(cells.length){
|
|
output.start = cells[0];
|
|
output.end = cells[cells.length - 1];
|
|
}else {
|
|
console.warn("No bounds defined on range");
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
getComponent() {
|
|
if (!this.component) {
|
|
this.component = new RangeComponent(this);
|
|
}
|
|
return this.component;
|
|
}
|
|
|
|
destroy(notify) {
|
|
this.destroyed = true;
|
|
|
|
this.element.remove();
|
|
|
|
if(notify){
|
|
this.rangeManager.rangeRemoved(this);
|
|
}
|
|
|
|
if(this.initialized){
|
|
this.dispatchExternal("rangeRemoved", this.getComponent());
|
|
}
|
|
}
|
|
|
|
destroyedGuard(func){
|
|
if(this.destroyed){
|
|
console.warn("You cannot call the " + func + " function on a destroyed range");
|
|
}
|
|
|
|
return !this.destroyed;
|
|
}
|
|
}
|
|
|
|
var bindings = {
|
|
rangeJumpUp:["ctrl + 38", "meta + 38"],
|
|
rangeJumpDown:["ctrl + 40", "meta + 40"],
|
|
rangeJumpLeft:["ctrl + 37", "meta + 37"],
|
|
rangeJumpRight:["ctrl + 39", "meta + 39"],
|
|
rangeExpandUp:"shift + 38",
|
|
rangeExpandDown:"shift + 40",
|
|
rangeExpandLeft:"shift + 37",
|
|
rangeExpandRight:"shift + 39",
|
|
rangeExpandJumpUp:["ctrl + shift + 38", "meta + shift + 38"],
|
|
rangeExpandJumpDown:["ctrl + shift + 40", "meta + shift + 40"],
|
|
rangeExpandJumpLeft:["ctrl + shift + 37", "meta + shift + 37"],
|
|
rangeExpandJumpRight:["ctrl + shift + 39", "meta + shift + 39"],
|
|
};
|
|
|
|
var actions = {
|
|
rangeJumpLeft: function(e){
|
|
this.dispatch("keybinding-nav-range", e, "left", true, false);
|
|
},
|
|
rangeJumpRight: function(e){
|
|
this.dispatch("keybinding-nav-range", e, "right", true, false);
|
|
},
|
|
rangeJumpUp: function(e){
|
|
this.dispatch("keybinding-nav-range", e, "up", true, false);
|
|
},
|
|
rangeJumpDown: function(e){
|
|
this.dispatch("keybinding-nav-range", e, "down", true, false);
|
|
},
|
|
rangeExpandLeft: function(e){
|
|
this.dispatch("keybinding-nav-range", e, "left", false, true);
|
|
},
|
|
rangeExpandRight: function(e){
|
|
this.dispatch("keybinding-nav-range", e, "right", false, true);
|
|
},
|
|
rangeExpandUp: function(e){
|
|
this.dispatch("keybinding-nav-range", e, "up", false, true);
|
|
},
|
|
rangeExpandDown: function(e){
|
|
this.dispatch("keybinding-nav-range", e, "down", false, true);
|
|
},
|
|
rangeExpandJumpLeft: function(e){
|
|
this.dispatch("keybinding-nav-range", e, "left", true, true);
|
|
},
|
|
rangeExpandJumpRight: function(e){
|
|
this.dispatch("keybinding-nav-range", e, "right", true, true);
|
|
},
|
|
rangeExpandJumpUp: function(e){
|
|
this.dispatch("keybinding-nav-range", e, "up", true, true);
|
|
},
|
|
rangeExpandJumpDown: function(e){
|
|
this.dispatch("keybinding-nav-range", e, "down", true, true);
|
|
},
|
|
};
|
|
|
|
var pasteActions = {
|
|
range:function(data){
|
|
var rows = [],
|
|
range = this.table.modules.selectRange.activeRange,
|
|
singleCell = false,
|
|
bounds, startCell, startRow, rowWidth, dataLength;
|
|
|
|
dataLength = data.length;
|
|
|
|
if(range){
|
|
bounds = range.getBounds();
|
|
startCell = bounds.start;
|
|
|
|
if(bounds.start === bounds.end){
|
|
singleCell = true;
|
|
}
|
|
|
|
if(startCell){
|
|
rows = this.table.rowManager.activeRows.slice();
|
|
startRow = rows.indexOf(startCell.row);
|
|
|
|
if(singleCell){
|
|
rowWidth = data.length;
|
|
}else {
|
|
rowWidth = (rows.indexOf(bounds.end.row) - startRow) + 1;
|
|
}
|
|
|
|
|
|
if(startRow >-1){
|
|
this.table.blockRedraw();
|
|
|
|
rows = rows.slice(startRow, startRow + rowWidth);
|
|
|
|
rows.forEach((row, i) => {
|
|
row.updateData(data[i % dataLength]);
|
|
});
|
|
|
|
this.table.restoreRedraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
};
|
|
|
|
var pasteParsers = {
|
|
range:function(clipboard){
|
|
var data = [],
|
|
rows = [],
|
|
range = this.table.modules.selectRange.activeRange,
|
|
singleCell = false,
|
|
bounds, startCell, colWidth, columnMap, startCol;
|
|
|
|
if(range){
|
|
bounds = range.getBounds();
|
|
startCell = bounds.start;
|
|
|
|
if(bounds.start === bounds.end){
|
|
singleCell = true;
|
|
}
|
|
|
|
if(startCell){
|
|
//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){
|
|
columnMap = this.table.columnManager.getVisibleColumnsByIndex();
|
|
startCol = columnMap.indexOf(startCell.column);
|
|
|
|
if(startCol > -1){
|
|
if(singleCell){
|
|
colWidth = data[0].length;
|
|
}else {
|
|
colWidth = (columnMap.indexOf(bounds.end.column) - startCol) + 1;
|
|
}
|
|
|
|
columnMap = columnMap.slice(startCol, startCol + colWidth);
|
|
|
|
data.forEach((item) => {
|
|
var row = {};
|
|
var itemLength = item.length;
|
|
|
|
columnMap.forEach(function(col, i){
|
|
row[col.field] = item[i % itemLength];
|
|
});
|
|
|
|
rows.push(row);
|
|
});
|
|
|
|
return rows;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
var columnLookups = {
|
|
range:function(){
|
|
var columns = this.modules.selectRange.selectedColumns();
|
|
|
|
if(this.columnManager.rowHeader){
|
|
columns.unshift(this.columnManager.rowHeader);
|
|
}
|
|
|
|
return columns;
|
|
},
|
|
};
|
|
|
|
var rowLookups = {
|
|
range:function(){
|
|
return this.modules.selectRange.selectedRows();
|
|
},
|
|
};
|
|
|
|
var extensions = {
|
|
keybindings:{
|
|
bindings:bindings,
|
|
actions:actions
|
|
},
|
|
clipboard:{
|
|
pasteActions:pasteActions,
|
|
pasteParsers:pasteParsers
|
|
},
|
|
export:{
|
|
columnLookups:columnLookups,
|
|
rowLookups:rowLookups,
|
|
}
|
|
};
|
|
|
|
class SelectRange extends Module {
|
|
|
|
static moduleName = "selectRange";
|
|
static moduleInitOrder = 1;
|
|
static moduleExtensions = extensions;
|
|
|
|
constructor(table) {
|
|
super(table);
|
|
|
|
this.selecting = "cell";
|
|
this.mousedown = false;
|
|
this.ranges = [];
|
|
this.overlay = null;
|
|
this.rowHeader = null;
|
|
this.layoutChangeTimeout = null;
|
|
this.columnSelection = false;
|
|
this.rowSelection = false;
|
|
this.maxRanges = 0;
|
|
this.activeRange = false;
|
|
this.blockKeydown = false;
|
|
|
|
this.keyDownEvent = this._handleKeyDown.bind(this);
|
|
this.mouseUpEvent = this._handleMouseUp.bind(this);
|
|
|
|
this.registerTableOption("selectableRange", false); //enable selectable range
|
|
this.registerTableOption("selectableRangeColumns", false); //enable selectable range
|
|
this.registerTableOption("selectableRangeRows", false); //enable selectable range
|
|
this.registerTableOption("selectableRangeClearCells", false); //allow clearing of active range
|
|
this.registerTableOption("selectableRangeClearCellsValue", undefined); //value for cleared active range
|
|
this.registerTableOption("selectableRangeAutoFocus", true); //focus on a cell after resetRanges
|
|
|
|
this.registerTableFunction("getRangesData", this.getRangesData.bind(this));
|
|
this.registerTableFunction("getRanges", this.getRanges.bind(this));
|
|
this.registerTableFunction("addRange", this.addRangeFromComponent.bind(this));
|
|
|
|
this.registerComponentFunction("cell", "getRanges", this.cellGetRanges.bind(this));
|
|
this.registerComponentFunction("row", "getRanges", this.rowGetRanges.bind(this));
|
|
this.registerComponentFunction("column", "getRanges", this.colGetRanges.bind(this));
|
|
}
|
|
|
|
///////////////////////////////////
|
|
/////// Initialization ///////
|
|
///////////////////////////////////
|
|
|
|
initialize() {
|
|
if (this.options("selectableRange")) {
|
|
if(!this.options("selectableRows")){
|
|
this.maxRanges = this.options("selectableRange");
|
|
|
|
this.initializeTable();
|
|
this.initializeWatchers();
|
|
}else {
|
|
console.warn("SelectRange functionality cannot be used in conjunction with row selection");
|
|
}
|
|
|
|
if(this.options('columns').findIndex((column) => column.frozen) > 0) {
|
|
console.warn("Having frozen column in arbitrary position with selectRange option may result in unpredictable behavior.");
|
|
}
|
|
|
|
if(this.options('columns').filter((column) => column.frozen) > 1) {
|
|
console.warn("Having multiple frozen columns with selectRange option may result in unpredictable behavior.");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
initializeTable() {
|
|
this.overlay = document.createElement("div");
|
|
this.overlay.classList.add("tabulator-range-overlay");
|
|
|
|
this.rangeContainer = document.createElement("div");
|
|
this.rangeContainer.classList.add("tabulator-range-container");
|
|
|
|
this.activeRangeCellElement = document.createElement("div");
|
|
this.activeRangeCellElement.classList.add("tabulator-range-cell-active");
|
|
|
|
this.overlay.appendChild(this.rangeContainer);
|
|
this.overlay.appendChild(this.activeRangeCellElement);
|
|
|
|
this.table.rowManager.element.addEventListener("keydown", this.keyDownEvent);
|
|
|
|
this.resetRanges();
|
|
|
|
this.table.rowManager.element.appendChild(this.overlay);
|
|
this.table.columnManager.element.setAttribute("tabindex", 0);
|
|
this.table.element.classList.add("tabulator-ranges");
|
|
}
|
|
|
|
initializeWatchers() {
|
|
this.columnSelection = this.options("selectableRangeColumns");
|
|
this.rowSelection = this.options("selectableRangeRows");
|
|
|
|
this.subscribe("column-init", this.initializeColumn.bind(this));
|
|
this.subscribe("column-mousedown", this.handleColumnMouseDown.bind(this));
|
|
this.subscribe("column-mousemove", this.handleColumnMouseMove.bind(this));
|
|
this.subscribe("column-resized", this.handleColumnResized.bind(this));
|
|
this.subscribe("column-moving", this.handleColumnMoving.bind(this));
|
|
this.subscribe("column-moved", this.handleColumnMoved.bind(this));
|
|
this.subscribe("column-width", this.layoutChange.bind(this));
|
|
this.subscribe("column-height", this.layoutChange.bind(this));
|
|
this.subscribe("column-resized", this.layoutChange.bind(this));
|
|
this.subscribe("columns-loaded", this.updateHeaderColumn.bind(this));
|
|
|
|
this.subscribe("cell-height", this.layoutChange.bind(this));
|
|
this.subscribe("cell-rendered", this.renderCell.bind(this));
|
|
this.subscribe("cell-mousedown", this.handleCellMouseDown.bind(this));
|
|
this.subscribe("cell-mousemove", this.handleCellMouseMove.bind(this));
|
|
this.subscribe("cell-click", this.handleCellClick.bind(this));
|
|
this.subscribe("cell-editing", this.handleEditingCell.bind(this));
|
|
|
|
this.subscribe("page-changed", this.redraw.bind(this));
|
|
|
|
this.subscribe("scroll-vertical", this.layoutChange.bind(this));
|
|
this.subscribe("scroll-horizontal", this.layoutChange.bind(this));
|
|
|
|
this.subscribe("data-destroy", this.tableDestroyed.bind(this));
|
|
this.subscribe("data-processed", this.resetRanges.bind(this));
|
|
|
|
this.subscribe("table-layout", this.layoutElement.bind(this));
|
|
this.subscribe("table-redraw", this.redraw.bind(this));
|
|
this.subscribe("table-destroy", this.tableDestroyed.bind(this));
|
|
|
|
this.subscribe("edit-editor-clear", this.finishEditingCell.bind(this));
|
|
this.subscribe("edit-blur", this.restoreFocus.bind(this));
|
|
|
|
this.subscribe("keybinding-nav-prev", this.keyNavigate.bind(this, "left"));
|
|
this.subscribe("keybinding-nav-next", this.keyNavigate.bind(this, "right"));
|
|
this.subscribe("keybinding-nav-left", this.keyNavigate.bind(this, "left"));
|
|
this.subscribe("keybinding-nav-right", this.keyNavigate.bind(this, "right"));
|
|
this.subscribe("keybinding-nav-up", this.keyNavigate.bind(this, "up"));
|
|
this.subscribe("keybinding-nav-down", this.keyNavigate.bind(this, "down"));
|
|
this.subscribe("keybinding-nav-range", this.keyNavigateRange.bind(this));
|
|
}
|
|
|
|
|
|
initializeColumn(column) {
|
|
if(this.columnSelection && column.definition.headerSort && this.options("headerSortClickElement") !== "icon"){
|
|
console.warn("Using column headerSort with selectableRangeColumns option may result in unpredictable behavior. Consider using headerSortClickElement: 'icon'.");
|
|
}
|
|
|
|
if (column.modules.edit) ;
|
|
}
|
|
|
|
updateHeaderColumn(){
|
|
var frozenCols;
|
|
|
|
if(this.rowSelection){
|
|
this.rowHeader = this.table.columnManager.getVisibleColumnsByIndex()[0];
|
|
|
|
if(this.rowHeader){
|
|
this.rowHeader.definition.cssClass = this.rowHeader.definition.cssClass + " tabulator-range-row-header";
|
|
|
|
if(this.rowHeader.definition.headerSort){
|
|
console.warn("Using column headerSort with selectableRangeRows option may result in unpredictable behavior");
|
|
}
|
|
|
|
if(this.rowHeader.definition.editor){
|
|
console.warn("Using column editor with selectableRangeRows option may result in unpredictable behavior");
|
|
}
|
|
}
|
|
}
|
|
|
|
//warn if invalid frozen column configuration detected
|
|
if(this.table.modules.frozenColumns && this.table.modules.frozenColumns.active){
|
|
frozenCols = this.table.modules.frozenColumns.getFrozenColumns();
|
|
|
|
if(frozenCols.length > 1 || (frozenCols.length === 1 && frozenCols[0] !== this.rowHeader)){
|
|
console.warn("Using frozen columns that are not the range header in combination with the selectRange option may result in unpredictable behavior");
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////
|
|
/////// Table Functions ///////
|
|
///////////////////////////////////
|
|
|
|
getRanges(){
|
|
return this.ranges.map((range) => range.getComponent());
|
|
}
|
|
|
|
getRangesData() {
|
|
return this.ranges.map((range) => range.getData());
|
|
}
|
|
|
|
addRangeFromComponent(start, end){
|
|
start = start ? start._cell : null;
|
|
end = end ? end._cell : null;
|
|
|
|
return this.addRange(start, end);
|
|
}
|
|
|
|
///////////////////////////////////
|
|
/////// Component Functions ///////
|
|
///////////////////////////////////
|
|
|
|
cellGetRanges(cell){
|
|
var ranges = [];
|
|
|
|
if (cell.column === this.rowHeader) {
|
|
ranges = this.ranges.filter((range) => range.occupiesRow(cell.row));
|
|
} else {
|
|
ranges = this.ranges.filter((range) => range.occupies(cell));
|
|
}
|
|
|
|
return ranges.map((range) => range.getComponent());
|
|
}
|
|
|
|
rowGetRanges(row){
|
|
var ranges = this.ranges.filter((range) => range.occupiesRow(row));
|
|
|
|
return ranges.map((range) => range.getComponent());
|
|
}
|
|
|
|
colGetRanges(col){
|
|
var ranges = this.ranges.filter((range) => range.occupiesColumn(col));
|
|
|
|
return ranges.map((range) => range.getComponent());
|
|
}
|
|
|
|
///////////////////////////////////
|
|
////////// Event Handlers /////////
|
|
///////////////////////////////////
|
|
|
|
_handleMouseUp(e){
|
|
this.mousedown = false;
|
|
document.removeEventListener("mouseup", this.mouseUpEvent);
|
|
}
|
|
|
|
_handleKeyDown(e) {
|
|
if (!this.blockKeydown && (!this.table.modules.edit || (this.table.modules.edit && !this.table.modules.edit.currentCell))) {
|
|
if (e.key === "Enter") {
|
|
// is editing a cell?
|
|
if (this.table.modules.edit && this.table.modules.edit.currentCell) {
|
|
return;
|
|
}
|
|
|
|
this.table.modules.edit.editCell(this.getActiveCell());
|
|
|
|
e.preventDefault();
|
|
}
|
|
|
|
if ((e.key === "Backspace" || e.key === "Delete") && this.options("selectableRangeClearCells")) {
|
|
if(this.activeRange){
|
|
this.activeRange.clearValues();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
initializeFocus(cell){
|
|
var range;
|
|
|
|
this.restoreFocus();
|
|
|
|
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){}
|
|
}
|
|
|
|
restoreFocus(element){
|
|
this.table.rowManager.element.focus();
|
|
|
|
return true;
|
|
}
|
|
|
|
///////////////////////////////////
|
|
////// Column Functionality ///////
|
|
///////////////////////////////////
|
|
|
|
handleColumnResized(column) {
|
|
var selected;
|
|
|
|
if (this.selecting !== "column" && this.selecting !== "all") {
|
|
return;
|
|
}
|
|
|
|
selected = this.ranges.some((range) => range.occupiesColumn(column));
|
|
|
|
if (!selected) {
|
|
return;
|
|
}
|
|
|
|
this.ranges.forEach((range) => {
|
|
var selectedColumns = range.getColumns(true);
|
|
|
|
selectedColumns.forEach((selectedColumn) => {
|
|
if (selectedColumn !== column) {
|
|
selectedColumn.setWidth(column.width);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
handleColumnMoving(_event, column) {
|
|
this.resetRanges().setBounds(column);
|
|
this.overlay.style.visibility = "hidden";
|
|
}
|
|
|
|
handleColumnMoved(from, _to, _after) {
|
|
this.activeRange.setBounds(from);
|
|
this.layoutElement();
|
|
}
|
|
|
|
handleColumnMouseDown(event, column) {
|
|
if (event.button === 2 && (this.selecting === "column" || this.selecting === "all") && this.activeRange.occupiesColumn(column)) {
|
|
return;
|
|
}
|
|
|
|
//If columns are movable, allow dragging columns only if they are not
|
|
//selected. Dragging selected columns should move the columns instead.
|
|
if(this.table.options.movableColumns && this.selecting === "column" && this.activeRange.occupiesColumn(column)){
|
|
return;
|
|
}
|
|
|
|
this.mousedown = true;
|
|
|
|
document.addEventListener("mouseup", this.mouseUpEvent);
|
|
|
|
this.newSelection(event, column);
|
|
}
|
|
|
|
handleColumnMouseMove(e, column) {
|
|
if (column === this.rowHeader || !this.mousedown || this.selecting === 'all') {
|
|
return;
|
|
}
|
|
|
|
this.activeRange.setBounds(false, column, true);
|
|
}
|
|
|
|
///////////////////////////////////
|
|
//////// Cell Functionality ///////
|
|
///////////////////////////////////
|
|
|
|
renderCell(cell) {
|
|
var el = cell.getElement(),
|
|
rangeIdx = this.ranges.findIndex((range) => range.occupies(cell));
|
|
|
|
el.classList.toggle("tabulator-range-selected", rangeIdx !== -1);
|
|
el.classList.toggle("tabulator-range-only-cell-selected", this.ranges.length === 1 && this.ranges[0].atTopLeft(cell) && this.ranges[0].atBottomRight(cell));
|
|
|
|
el.dataset.range = rangeIdx;
|
|
}
|
|
|
|
handleCellMouseDown(event, cell) {
|
|
if (event.button === 2 && (this.activeRange.occupies(cell) || ((this.selecting === "row" || this.selecting === "all") && this.activeRange.occupiesRow(cell.row)))) {
|
|
return;
|
|
}
|
|
|
|
this.mousedown = true;
|
|
|
|
document.addEventListener("mouseup", this.mouseUpEvent);
|
|
|
|
this.newSelection(event, cell);
|
|
}
|
|
|
|
handleCellMouseMove(e, cell) {
|
|
if (!this.mousedown || this.selecting === "all") {
|
|
return;
|
|
}
|
|
|
|
this.activeRange.setBounds(false, cell, true);
|
|
}
|
|
|
|
handleCellClick(e, cell){
|
|
this.initializeFocus(cell);
|
|
}
|
|
|
|
handleEditingCell(cell) {
|
|
if(this.activeRange){
|
|
this.activeRange.setBounds(cell);
|
|
}
|
|
}
|
|
|
|
finishEditingCell() {
|
|
this.blockKeydown = true;
|
|
this.table.rowManager.element.focus();
|
|
|
|
setTimeout(() => {
|
|
this.blockKeydown = false;
|
|
}, 10);
|
|
}
|
|
|
|
///////////////////////////////////
|
|
/////// Navigation ///////
|
|
///////////////////////////////////
|
|
|
|
keyNavigate(dir, e){
|
|
if(this.navigate(false, false, dir));
|
|
e.preventDefault();
|
|
}
|
|
|
|
keyNavigateRange(e, dir, jump, expand){
|
|
if(this.navigate(jump, expand, dir));
|
|
e.preventDefault();
|
|
}
|
|
|
|
navigate(jump, expand, dir) {
|
|
var moved = false,
|
|
range, rangeEdge, prevRect, nextRow, nextCol, row, column,
|
|
rowRect, rowManagerRect, columnRect, columnManagerRect;
|
|
|
|
// Don't navigate while editing
|
|
if (this.table.modules.edit && this.table.modules.edit.currentCell) {
|
|
return false;
|
|
}
|
|
|
|
// If there are more than 1 range, use the active range and destroy the others
|
|
if (this.ranges.length > 1) {
|
|
this.ranges = this.ranges.filter((range) => {
|
|
if (range === this.activeRange) {
|
|
range.setEnd(range.start.row, range.start.col);
|
|
return true;
|
|
}
|
|
range.destroy();
|
|
return false;
|
|
});
|
|
}
|
|
|
|
range = this.activeRange;
|
|
prevRect = {
|
|
top: range.top,
|
|
bottom: range.bottom,
|
|
left: range.left,
|
|
right: range.right
|
|
};
|
|
|
|
rangeEdge = expand ? range.end : range.start;
|
|
nextRow = rangeEdge.row;
|
|
nextCol = rangeEdge.col;
|
|
|
|
if(jump){
|
|
switch(dir){
|
|
case "left":
|
|
nextCol = this.findJumpCellLeft(range.start.row, rangeEdge.col);
|
|
break;
|
|
case "right":
|
|
nextCol = this.findJumpCellRight(range.start.row, rangeEdge.col);
|
|
break;
|
|
case "up":
|
|
nextRow = this.findJumpCellUp(rangeEdge.row, range.start.col);
|
|
break;
|
|
case "down":
|
|
nextRow = this.findJumpCellDown(rangeEdge.row, range.start.col);
|
|
break;
|
|
}
|
|
}else {
|
|
if(expand){
|
|
if ((this.selecting === 'row' && (dir === 'left' || dir === 'right')) || (this.selecting === 'column' && (dir === 'up' || dir === 'down'))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch(dir){
|
|
case "left":
|
|
nextCol = Math.max(nextCol - 1, 0);
|
|
break;
|
|
case "right":
|
|
nextCol = Math.min(nextCol + 1, this.getTableColumns().length - 1);
|
|
break;
|
|
case "up":
|
|
nextRow = Math.max(nextRow - 1, 0);
|
|
break;
|
|
case "down":
|
|
nextRow = Math.min(nextRow + 1, this.getTableRows().length - 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(this.rowHeader && nextCol === 0) {
|
|
nextCol = 1;
|
|
}
|
|
|
|
if(!expand){
|
|
range.setStart(nextRow, nextCol);
|
|
}
|
|
|
|
range.setEnd(nextRow, nextCol);
|
|
|
|
if(!expand){
|
|
this.selecting = "cell";
|
|
}
|
|
|
|
moved = prevRect.top !== range.top || prevRect.bottom !== range.bottom || prevRect.left !== range.left || prevRect.right !== range.right;
|
|
|
|
if (moved) {
|
|
row = this.getRowByRangePos(range.end.row);
|
|
column = this.getColumnByRangePos(range.end.col);
|
|
rowRect = row.getElement().getBoundingClientRect();
|
|
columnRect = column.getElement().getBoundingClientRect();
|
|
rowManagerRect = this.table.rowManager.getElement().getBoundingClientRect();
|
|
columnManagerRect = this.table.columnManager.getElement().getBoundingClientRect();
|
|
|
|
if(!(rowRect.top >= rowManagerRect.top && rowRect.bottom <= rowManagerRect.bottom)){
|
|
if(row.getElement().parentNode && column.getElement().parentNode){
|
|
// Use faster autoScroll when the elements are on the DOM
|
|
this.autoScroll(range, row.getElement(), column.getElement());
|
|
}else {
|
|
row.getComponent().scrollTo(undefined, false);
|
|
}
|
|
}
|
|
|
|
if(!(columnRect.left >= columnManagerRect.left + this.getRowHeaderWidth() && columnRect.right <= columnManagerRect.right)){
|
|
if(row.getElement().parentNode && column.getElement().parentNode){
|
|
// Use faster autoScroll when the elements are on the DOM
|
|
this.autoScroll(range, row.getElement(), column.getElement());
|
|
}else {
|
|
column.getComponent().scrollTo(undefined, false);
|
|
}
|
|
}
|
|
|
|
this.layoutElement();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
rangeRemoved(removed){
|
|
this.ranges = this.ranges.filter((range) => range !== removed);
|
|
|
|
if(this.activeRange === removed){
|
|
if(this.ranges.length){
|
|
this.activeRange = this.ranges[this.ranges.length - 1];
|
|
}else {
|
|
this.addRange();
|
|
}
|
|
}
|
|
|
|
this.layoutElement();
|
|
}
|
|
|
|
findJumpRow(column, rows, reverse, emptyStart, emptySide){
|
|
if(reverse){
|
|
rows = rows.reverse();
|
|
}
|
|
|
|
return this.findJumpItem(emptyStart, emptySide, rows, function(row){return row.getData()[column.getField()];});
|
|
}
|
|
|
|
findJumpCol(row, columns, reverse, emptyStart, emptySide){
|
|
if(reverse){
|
|
columns = columns.reverse();
|
|
}
|
|
|
|
return this.findJumpItem(emptyStart, emptySide, columns, function(column){return row.getData()[column.getField()];});
|
|
}
|
|
|
|
findJumpItem(emptyStart, emptySide, items, valueResolver){
|
|
var nextItem;
|
|
|
|
for(let currentItem of items){
|
|
let currentValue = valueResolver(currentItem);
|
|
|
|
if(emptyStart){
|
|
nextItem = currentItem;
|
|
if(currentValue){
|
|
break;
|
|
}
|
|
}else {
|
|
if(emptySide){
|
|
nextItem = currentItem;
|
|
|
|
if(currentValue){
|
|
break;
|
|
}
|
|
}else {
|
|
if(currentValue){
|
|
nextItem = currentItem;
|
|
}else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nextItem;
|
|
}
|
|
|
|
findJumpCellLeft(rowPos, colPos){
|
|
var row = this.getRowByRangePos(rowPos),
|
|
columns = this.getTableColumns(),
|
|
isStartingCellEmpty = this.isEmpty(row.getData()[columns[colPos].getField()]),
|
|
isLeftOfStartingCellEmpty = columns[colPos - 1] ? this.isEmpty(row.getData()[columns[colPos - 1].getField()]) : false,
|
|
targetCols = this.rowHeader ? columns.slice(1, colPos) : columns.slice(0, colPos),
|
|
jumpCol = this.findJumpCol(row, targetCols, true, isStartingCellEmpty, isLeftOfStartingCellEmpty);
|
|
|
|
if(jumpCol){
|
|
return jumpCol.getPosition() - 1;
|
|
}
|
|
|
|
return colPos;
|
|
}
|
|
|
|
findJumpCellRight(rowPos, colPos){
|
|
var row = this.getRowByRangePos(rowPos),
|
|
columns = this.getTableColumns(),
|
|
isStartingCellEmpty = this.isEmpty(row.getData()[columns[colPos].getField()]),
|
|
isRightOfStartingCellEmpty = columns[colPos + 1] ? this.isEmpty(row.getData()[columns[colPos + 1].getField()]) : false,
|
|
jumpCol = this.findJumpCol(row, columns.slice(colPos + 1, columns.length), false, isStartingCellEmpty, isRightOfStartingCellEmpty);
|
|
|
|
if(jumpCol){
|
|
return jumpCol.getPosition() - 1;
|
|
}
|
|
|
|
return colPos;
|
|
}
|
|
|
|
findJumpCellUp(rowPos, colPos) {
|
|
var column = this.getColumnByRangePos(colPos),
|
|
rows = this.getTableRows(),
|
|
isStartingCellEmpty = this.isEmpty(rows[rowPos].getData()[column.getField()]),
|
|
isTopOfStartingCellEmpty = rows[rowPos - 1] ? this.isEmpty(rows[rowPos - 1].getData()[column.getField()]) : false,
|
|
jumpRow = this.findJumpRow(column, rows.slice(0, rowPos), true, isStartingCellEmpty, isTopOfStartingCellEmpty);
|
|
|
|
if(jumpRow){
|
|
return jumpRow.position - 1;
|
|
}
|
|
|
|
return rowPos;
|
|
}
|
|
|
|
findJumpCellDown(rowPos, colPos) {
|
|
var column = this.getColumnByRangePos(colPos),
|
|
rows = this.getTableRows(),
|
|
isStartingCellEmpty = this.isEmpty(rows[rowPos].getData()[column.getField()]),
|
|
isBottomOfStartingCellEmpty = rows[rowPos + 1] ? this.isEmpty(rows[rowPos + 1].getData()[column.getField()]) : false,
|
|
jumpRow = this.findJumpRow(column, rows.slice(rowPos + 1, rows.length), false, isStartingCellEmpty, isBottomOfStartingCellEmpty);
|
|
|
|
if(jumpRow){
|
|
return jumpRow.position - 1;
|
|
}
|
|
|
|
return rowPos;
|
|
}
|
|
|
|
///////////////////////////////////
|
|
/////// Selection ///////
|
|
///////////////////////////////////
|
|
newSelection(event, element) {
|
|
var range;
|
|
|
|
if (element.type === "column") {
|
|
if(!this.columnSelection){
|
|
return;
|
|
}
|
|
|
|
if (element === this.rowHeader) {
|
|
range = this.resetRanges();
|
|
this.selecting = "all";
|
|
|
|
var topLeftCell, bottomRightCell = this.getCell(-1, -1);
|
|
|
|
if(this.rowHeader){
|
|
topLeftCell = this.getCell(0, 1);
|
|
}else {
|
|
topLeftCell = this.getCell(0, 0);
|
|
}
|
|
|
|
range.setBounds(topLeftCell, bottomRightCell);
|
|
return;
|
|
} else {
|
|
this.selecting = "column";
|
|
}
|
|
} else if (element.column === this.rowHeader) {
|
|
this.selecting = "row";
|
|
} else {
|
|
this.selecting = "cell";
|
|
}
|
|
|
|
if (event.shiftKey) {
|
|
this.activeRange.setBounds(false, element);
|
|
} else if (event.ctrlKey) {
|
|
this.addRange().setBounds(element);
|
|
} else {
|
|
this.resetRanges().setBounds(element);
|
|
}
|
|
}
|
|
|
|
autoScroll(range, row, column) {
|
|
var tableHolder = this.table.rowManager.element,
|
|
rect, view, withinHorizontalView, withinVerticalView;
|
|
|
|
if (typeof row === 'undefined') {
|
|
row = this.getRowByRangePos(range.end.row).getElement();
|
|
}
|
|
|
|
if (typeof column === 'undefined') {
|
|
column = this.getColumnByRangePos(range.end.col).getElement();
|
|
}
|
|
|
|
rect = {
|
|
left: column.offsetLeft,
|
|
right: column.offsetLeft + column.offsetWidth,
|
|
top: row.offsetTop,
|
|
bottom: row.offsetTop + row.offsetHeight,
|
|
};
|
|
|
|
view = {
|
|
left: tableHolder.scrollLeft + this.getRowHeaderWidth(),
|
|
right: Math.ceil(tableHolder.scrollLeft + tableHolder.clientWidth),
|
|
top: tableHolder.scrollTop,
|
|
bottom: tableHolder.scrollTop + tableHolder.offsetHeight - this.table.rowManager.scrollbarWidth,
|
|
};
|
|
|
|
withinHorizontalView = view.left < rect.left && rect.left < view.right && view.left < rect.right && rect.right < view.right;
|
|
|
|
withinVerticalView = view.top < rect.top && rect.top < view.bottom && view.top < rect.bottom && rect.bottom < view.bottom;
|
|
|
|
if (!withinHorizontalView) {
|
|
if (rect.left < view.left) {
|
|
tableHolder.scrollLeft = rect.left - this.getRowHeaderWidth();
|
|
} else if (rect.right > view.right) {
|
|
tableHolder.scrollLeft = Math.min(rect.right - tableHolder.clientWidth, rect.left - this.getRowHeaderWidth());
|
|
}
|
|
}
|
|
|
|
if (!withinVerticalView) {
|
|
if (rect.top < view.top) {
|
|
tableHolder.scrollTop = rect.top;
|
|
} else if (rect.bottom > view.bottom) {
|
|
tableHolder.scrollTop = rect.bottom - tableHolder.clientHeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////
|
|
/////// Layout ///////
|
|
///////////////////////////////////
|
|
|
|
layoutChange(){
|
|
this.overlay.style.visibility = "hidden";
|
|
clearTimeout(this.layoutChangeTimeout);
|
|
this.layoutChangeTimeout = setTimeout(this.layoutRanges.bind(this), 200);
|
|
}
|
|
|
|
redraw(force) {
|
|
if (force) {
|
|
this.selecting = 'cell';
|
|
this.resetRanges();
|
|
this.layoutElement();
|
|
}
|
|
}
|
|
|
|
layoutElement(visibleRows) {
|
|
var rows;
|
|
|
|
if (visibleRows) {
|
|
rows = this.table.rowManager.getVisibleRows(true);
|
|
} else {
|
|
rows = this.table.rowManager.getRows();
|
|
}
|
|
|
|
rows.forEach((row) => {
|
|
if (row.type === "row") {
|
|
this.layoutRow(row);
|
|
row.cells.forEach((cell) => this.renderCell(cell));
|
|
}
|
|
});
|
|
|
|
this.getTableColumns().forEach((column) => {
|
|
this.layoutColumn(column);
|
|
});
|
|
|
|
this.layoutRanges();
|
|
}
|
|
|
|
layoutRow(row) {
|
|
var el = row.getElement(),
|
|
selected = false,
|
|
occupied = this.ranges.some((range) => range.occupiesRow(row));
|
|
|
|
if (this.selecting === "row") {
|
|
selected = occupied;
|
|
} else if (this.selecting === "all") {
|
|
selected = true;
|
|
}
|
|
|
|
el.classList.toggle("tabulator-range-selected", selected);
|
|
el.classList.toggle("tabulator-range-highlight", occupied);
|
|
}
|
|
|
|
layoutColumn(column) {
|
|
var el = column.getElement(),
|
|
selected = false,
|
|
occupied = this.ranges.some((range) => range.occupiesColumn(column));
|
|
|
|
if (this.selecting === "column") {
|
|
selected = occupied;
|
|
} else if (this.selecting === "all") {
|
|
selected = true;
|
|
}
|
|
|
|
el.classList.toggle("tabulator-range-selected", selected);
|
|
el.classList.toggle("tabulator-range-highlight", occupied);
|
|
}
|
|
|
|
layoutRanges() {
|
|
var activeCell, activeCellEl, activeRowEl;
|
|
|
|
if (!this.table.initialized) {
|
|
return;
|
|
}
|
|
|
|
activeCell = this.getActiveCell();
|
|
|
|
if (!activeCell) {
|
|
return;
|
|
}
|
|
|
|
activeCellEl = activeCell.getElement();
|
|
activeRowEl = activeCell.row.getElement();
|
|
|
|
if(this.table.rtl){
|
|
this.activeRangeCellElement.style.right = activeRowEl.offsetWidth - activeCellEl.offsetLeft - activeCellEl.offsetWidth + "px";
|
|
}else {
|
|
this.activeRangeCellElement.style.left = activeRowEl.offsetLeft + activeCellEl.offsetLeft + "px";
|
|
}
|
|
|
|
this.activeRangeCellElement.style.top = activeRowEl.offsetTop + "px";
|
|
this.activeRangeCellElement.style.width = activeCellEl.offsetWidth + "px";
|
|
this.activeRangeCellElement.style.height = activeRowEl.offsetHeight + "px";
|
|
|
|
this.ranges.forEach((range) => range.layout());
|
|
|
|
this.overlay.style.visibility = "visible";
|
|
}
|
|
|
|
|
|
///////////////////////////////////
|
|
/////// Helper Functions ///////
|
|
///////////////////////////////////
|
|
|
|
getCell(rowIdx, colIdx) {
|
|
var row;
|
|
|
|
if (colIdx < 0) {
|
|
colIdx = this.getTableColumns().length + colIdx;
|
|
if (colIdx < 0) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (rowIdx < 0) {
|
|
rowIdx = this.getTableRows().length + rowIdx;
|
|
}
|
|
|
|
row = this.table.rowManager.getRowFromPosition(rowIdx + 1);
|
|
|
|
return row ? row.getCells(false, true).filter((cell) => cell.column.visible)[colIdx] : null;
|
|
}
|
|
|
|
|
|
getActiveCell() {
|
|
return this.getCell(this.activeRange.start.row, this.activeRange.start.col);
|
|
}
|
|
|
|
getRowByRangePos(pos) {
|
|
return this.getTableRows()[pos];
|
|
}
|
|
|
|
getColumnByRangePos(pos) {
|
|
return this.getTableColumns()[pos];
|
|
}
|
|
|
|
getTableRows() {
|
|
return this.table.rowManager.getDisplayRows().filter(row=> row.type === "row");
|
|
}
|
|
|
|
getTableColumns() {
|
|
return this.table.columnManager.getVisibleColumnsByIndex();
|
|
}
|
|
|
|
addRange(start, end) {
|
|
var range;
|
|
|
|
if(this.maxRanges !== true && this.ranges.length >= this.maxRanges){
|
|
this.ranges.shift().destroy();
|
|
}
|
|
|
|
range = new Range(this.table, this, start, end);
|
|
|
|
this.activeRange = range;
|
|
this.ranges.push(range);
|
|
this.rangeContainer.appendChild(range.element);
|
|
|
|
return range;
|
|
}
|
|
|
|
resetRanges() {
|
|
var range, cell, visibleCells;
|
|
|
|
this.ranges.forEach((range) => range.destroy());
|
|
this.ranges = [];
|
|
|
|
range = this.addRange();
|
|
|
|
if(this.table.rowManager.activeRows.length){
|
|
visibleCells = this.table.rowManager.activeRows[0].cells.filter((cell) => cell.column.visible);
|
|
cell = visibleCells[this.rowHeader ? 1 : 0];
|
|
|
|
if(cell){
|
|
range.setBounds(cell);
|
|
if(this.options("selectableRangeAutoFocus")){
|
|
this.initializeFocus(cell);
|
|
}
|
|
}
|
|
}
|
|
|
|
return range;
|
|
}
|
|
|
|
tableDestroyed(){
|
|
document.removeEventListener("mouseup", this.mouseUpEvent);
|
|
this.table.rowManager.element.removeEventListener("keydown", this.keyDownEvent);
|
|
}
|
|
|
|
selectedRows(component) {
|
|
return component ? this.activeRange.getRows().map((row) => row.getComponent()) : this.activeRange.getRows();
|
|
}
|
|
|
|
selectedColumns(component) {
|
|
return component ? this.activeRange.getColumns().map((col) => col.getComponent()) : this.activeRange.getColumns();
|
|
}
|
|
|
|
getRowHeaderWidth(){
|
|
if(!this.rowHeader){
|
|
return 0;
|
|
}
|
|
return this.rowHeader.getElement().offsetWidth;
|
|
}
|
|
|
|
isEmpty(value) {
|
|
return value === null || value === undefined || value === "";
|
|
}
|
|
}
|
|
|
|
//sort numbers
|
|
function number(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(a, b, aRow, bRow, column, dir, params){
|
|
var DT = this.table.dependencyRegistry.lookup(["luxon", "DateTime"], "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(a, b, aRow, bRow, column, dir, params){
|
|
if(!params.format){
|
|
params.format = "dd/MM/yyyy";
|
|
}
|
|
|
|
return datetime.call(this, a, b, aRow, bRow, column, dir, params);
|
|
}
|
|
|
|
//sort times
|
|
function time(a, b, aRow, bRow, column, dir, params){
|
|
if(!params.format){
|
|
params.format = "HH:mm";
|
|
}
|
|
|
|
return datetime.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,
|
|
table = this.table,
|
|
valueMap;
|
|
|
|
if(params.valueMap){
|
|
if(typeof params.valueMap === "string"){
|
|
valueMap = function(value){
|
|
return value.map((item) => {
|
|
return Helpers.retrieveNestedData(table.options.nestedFieldSeparator, params.valueMap, item);
|
|
});
|
|
};
|
|
}else {
|
|
valueMap = params.valueMap;
|
|
}
|
|
}
|
|
|
|
function calc(value){
|
|
var result;
|
|
|
|
if(valueMap){
|
|
value = valueMap(value);
|
|
}
|
|
|
|
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;
|
|
|
|
case "string":
|
|
result = value.join("");
|
|
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 {
|
|
if(type === "string"){
|
|
return String(calc(a)).toLowerCase().localeCompare(String(calc(b)).toLowerCase());
|
|
}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,
|
|
string:string,
|
|
date:date,
|
|
time:time,
|
|
datetime:datetime,
|
|
boolean:boolean,
|
|
array:array,
|
|
exists:exists,
|
|
alphanum:alphanum
|
|
};
|
|
|
|
class Sort extends Module{
|
|
|
|
static moduleName = "sort";
|
|
|
|
//load defaults
|
|
static sorters = defaultSorters;
|
|
|
|
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", "<div class='tabulator-arrow'></div>"); //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");
|
|
|
|
if(this.table.options.headerSortClickElement === "icon"){
|
|
arrowEl.addEventListener("mousedown", (e) => {
|
|
e.stopPropagation();
|
|
});
|
|
}
|
|
|
|
//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, sortOnly){
|
|
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());
|
|
}
|
|
|
|
if(!sortOnly) {
|
|
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);
|
|
}
|
|
|
|
if(!sortOnly) {
|
|
self.setColumnHeader(item.column, item.dir);
|
|
}
|
|
}
|
|
});
|
|
|
|
//sort data
|
|
if (sortListActual.length) {
|
|
self._sortItems(data, sortListActual);
|
|
}
|
|
|
|
}else if(!sortOnly) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
class GridCalculator{
|
|
constructor(columns, rows){
|
|
this.columnCount = columns;
|
|
this.rowCount = rows;
|
|
|
|
this.columnString = [];
|
|
this.columns = [];
|
|
this.rows = [];
|
|
}
|
|
|
|
genColumns(data){
|
|
var colCount = Math.max(this.columnCount, Math.max(...data.map(item => item.length)));
|
|
|
|
this.columnString = [];
|
|
this.columns = [];
|
|
|
|
for(let i = 1; i <= colCount; i++){
|
|
this.incrementChar(this.columnString.length - 1);
|
|
this.columns.push(this.columnString.join(""));
|
|
}
|
|
|
|
return this.columns;
|
|
}
|
|
|
|
genRows(data){
|
|
var rowCount = Math.max(this.rowCount, data.length);
|
|
|
|
this.rows = [];
|
|
|
|
for(let i = 1; i <= rowCount; i++){
|
|
this.rows.push(i);
|
|
}
|
|
|
|
return this.rows;
|
|
}
|
|
|
|
incrementChar(i){
|
|
let char = this.columnString[i];
|
|
|
|
if(char){
|
|
if(char !== "Z"){
|
|
this.columnString[i] = String.fromCharCode(this.columnString[i].charCodeAt(0) + 1);
|
|
}else {
|
|
this.columnString[i] = "A";
|
|
|
|
if(i){
|
|
this.incrementChar(i-1);
|
|
}else {
|
|
this.columnString.push("A");
|
|
}
|
|
}
|
|
}else {
|
|
this.columnString.push("A");
|
|
}
|
|
}
|
|
|
|
setRowCount(count){
|
|
this.rowCount = count;
|
|
}
|
|
|
|
setColumnCount(count){
|
|
this.columnCount = count;
|
|
}
|
|
}
|
|
|
|
class SheetComponent {
|
|
constructor(sheet) {
|
|
this._sheet = sheet;
|
|
|
|
return new Proxy(this, {
|
|
get: function (target, name, receiver) {
|
|
if (typeof target[name] !== "undefined") {
|
|
return target[name];
|
|
} else {
|
|
return target._sheet.table.componentFunctionBinder.handle("sheet", target._sheet, name);
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
getTitle(){
|
|
return this._sheet.title;
|
|
}
|
|
|
|
getKey(){
|
|
return this._sheet.key;
|
|
}
|
|
|
|
getDefinition(){
|
|
return this._sheet.getDefinition();
|
|
}
|
|
|
|
getData() {
|
|
return this._sheet.getData();
|
|
}
|
|
|
|
setData(data) {
|
|
return this._sheet.setData(data);
|
|
}
|
|
|
|
clear(){
|
|
return this._sheet.clear();
|
|
}
|
|
|
|
remove(){
|
|
return this._sheet.remove();
|
|
}
|
|
|
|
active(){
|
|
return this._sheet.active();
|
|
}
|
|
|
|
setTitle(title){
|
|
return this._sheet.setTitle(title);
|
|
}
|
|
|
|
setRows(rows){
|
|
return this._sheet.setRows(rows);
|
|
}
|
|
|
|
setColumns(columns){
|
|
return this._sheet.setColumns(columns);
|
|
}
|
|
}
|
|
|
|
class Sheet extends CoreFeature{
|
|
constructor(spreadsheetManager, definition) {
|
|
super(spreadsheetManager.table);
|
|
|
|
this.spreadsheetManager = spreadsheetManager;
|
|
this.definition = definition;
|
|
|
|
this.title = this.definition.title || "";
|
|
this.key = this.definition.key || this.definition.title;
|
|
this.rowCount = this.definition.rows;
|
|
this.columnCount = this.definition.columns;
|
|
this.data = this.definition.data || [];
|
|
this.element = null;
|
|
this.isActive = false;
|
|
|
|
this.grid = new GridCalculator(this.columnCount, this.rowCount);
|
|
|
|
this.defaultColumnDefinition = {width:100, headerHozAlign:"center", headerSort:false};
|
|
this.columnDefinition = Object.assign(this.defaultColumnDefinition, this.options("spreadsheetColumnDefinition"));
|
|
|
|
this.columnDefs = [];
|
|
this.rowDefs = [];
|
|
this.columnFields = [];
|
|
this.columns = [];
|
|
this.rows = [];
|
|
|
|
this.scrollTop = null;
|
|
this.scrollLeft = null;
|
|
|
|
this.initialize();
|
|
|
|
this.dispatchExternal("sheetAdded", this.getComponent());
|
|
}
|
|
|
|
///////////////////////////////////
|
|
///////// Initialization //////////
|
|
///////////////////////////////////
|
|
|
|
initialize(){
|
|
this.initializeElement();
|
|
this.initializeColumns();
|
|
this.initializeRows();
|
|
}
|
|
|
|
reinitialize(){
|
|
this.initializeColumns();
|
|
this.initializeRows();
|
|
}
|
|
|
|
initializeElement(){
|
|
this.element = document.createElement("div");
|
|
this.element.classList.add("tabulator-spreadsheet-tab");
|
|
this.element.innerText = this.title;
|
|
|
|
this.element.addEventListener("click", () => {
|
|
this.spreadsheetManager.loadSheet(this);
|
|
});
|
|
}
|
|
|
|
initializeColumns(){
|
|
this.grid.setColumnCount(this.columnCount);
|
|
this.columnFields = this.grid.genColumns(this.data);
|
|
|
|
this.columnDefs = [];
|
|
|
|
this.columnFields.forEach((ref) => {
|
|
var def = Object.assign({}, this.columnDefinition);
|
|
def.field = ref;
|
|
def.title = ref;
|
|
|
|
this.columnDefs.push(def);
|
|
});
|
|
}
|
|
|
|
initializeRows(){
|
|
var refs;
|
|
|
|
this.grid.setRowCount(this.rowCount);
|
|
|
|
refs = this.grid.genRows(this.data);
|
|
|
|
this.rowDefs = [];
|
|
|
|
refs.forEach((ref, i) => {
|
|
var def = {"_id":ref};
|
|
var data = this.data[i];
|
|
|
|
if(data){
|
|
data.forEach((val, j) => {
|
|
var field = this.columnFields[j];
|
|
|
|
if(field){
|
|
def[field] = val;
|
|
}
|
|
});
|
|
}
|
|
|
|
this.rowDefs.push(def);
|
|
});
|
|
}
|
|
|
|
unload(){
|
|
this.isActive = false;
|
|
this.scrollTop = this.table.rowManager.scrollTop;
|
|
this.scrollLeft = this.table.rowManager.scrollLeft;
|
|
this.data = this.getData(true);
|
|
this.element.classList.remove("tabulator-spreadsheet-tab-active");
|
|
}
|
|
|
|
load(){
|
|
|
|
var wasInactive = !this.isActive;
|
|
|
|
this.isActive = true;
|
|
this.table.blockRedraw();
|
|
this.table.setData([]);
|
|
this.table.setColumns(this.columnDefs);
|
|
this.table.setData(this.rowDefs);
|
|
this.table.restoreRedraw();
|
|
|
|
if(wasInactive && this.scrollTop !== null){
|
|
this.table.rowManager.element.scrollLeft = this.scrollLeft;
|
|
this.table.rowManager.element.scrollTop = this.scrollTop;
|
|
}
|
|
|
|
this.element.classList.add("tabulator-spreadsheet-tab-active");
|
|
|
|
this.dispatchExternal("sheetLoaded", this.getComponent());
|
|
}
|
|
|
|
///////////////////////////////////
|
|
//////// Helper Functions /////////
|
|
///////////////////////////////////
|
|
|
|
getComponent(){
|
|
return new SheetComponent(this);
|
|
}
|
|
|
|
getDefinition(){
|
|
return {
|
|
title:this.title,
|
|
key:this.key,
|
|
rows:this.rowCount,
|
|
columns:this.columnCount,
|
|
data:this.getData(),
|
|
};
|
|
}
|
|
|
|
getData(full){
|
|
var output = [],
|
|
rowWidths,
|
|
outputWidth, outputHeight;
|
|
|
|
//map data to array format
|
|
this.rowDefs.forEach((rowData) => {
|
|
var row = [];
|
|
|
|
this.columnFields.forEach((field) => {
|
|
row.push(rowData[field]);
|
|
});
|
|
|
|
output.push(row);
|
|
});
|
|
|
|
//trim output
|
|
if(!full && !this.options("spreadsheetOutputFull")){
|
|
|
|
//calculate used area of data
|
|
rowWidths = output.map(row => row.findLastIndex(val => typeof val !== 'undefined') + 1);
|
|
outputWidth = Math.max(...rowWidths);
|
|
outputHeight = rowWidths.findLastIndex(width => width > 0) + 1;
|
|
|
|
output = output.slice(0, outputHeight);
|
|
output = output.map(row => row.slice(0, outputWidth));
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
setData(data){
|
|
this.data = data;
|
|
this.reinitialize();
|
|
|
|
this.dispatchExternal("sheetUpdated", this.getComponent());
|
|
|
|
if(this.isActive){
|
|
this.load();
|
|
}
|
|
}
|
|
|
|
clear(){
|
|
this.setData([]);
|
|
}
|
|
|
|
setTitle(title){
|
|
this.title = title;
|
|
this.element.innerText = title;
|
|
|
|
this.dispatchExternal("sheetUpdated", this.getComponent());
|
|
}
|
|
|
|
setRows(rows){
|
|
this.rowCount = rows;
|
|
this.initializeRows();
|
|
|
|
this.dispatchExternal("sheetUpdated", this.getComponent());
|
|
|
|
if(this.isActive){
|
|
this.load();
|
|
}
|
|
}
|
|
|
|
setColumns(columns){
|
|
this.columnCount = columns;
|
|
this.reinitialize();
|
|
|
|
this.dispatchExternal("sheetUpdated", this.getComponent());
|
|
|
|
if(this.isActive){
|
|
this.load();
|
|
}
|
|
}
|
|
|
|
remove(){
|
|
this.spreadsheetManager.removeSheet(this);
|
|
}
|
|
|
|
destroy(){
|
|
if(this.element.parentNode){
|
|
this.element.parentNode.removeChild(this.element);
|
|
}
|
|
|
|
this.dispatchExternal("sheetRemoved", this.getComponent());
|
|
}
|
|
|
|
active(){
|
|
this.spreadsheetManager.loadSheet(this);
|
|
}
|
|
}
|
|
|
|
class Spreadsheet extends Module{
|
|
|
|
static moduleName = "spreadsheet";
|
|
|
|
constructor(table){
|
|
super(table);
|
|
|
|
this.sheets = [];
|
|
this.element = null;
|
|
|
|
this.registerTableOption("spreadsheet", false);
|
|
this.registerTableOption("spreadsheetRows", 50);
|
|
this.registerTableOption("spreadsheetColumns", 50);
|
|
this.registerTableOption("spreadsheetColumnDefinition", {});
|
|
this.registerTableOption("spreadsheetOutputFull", false);
|
|
this.registerTableOption("spreadsheetData", false);
|
|
this.registerTableOption("spreadsheetSheets", false);
|
|
this.registerTableOption("spreadsheetSheetTabs", false);
|
|
this.registerTableOption("spreadsheetSheetTabsElement", false);
|
|
|
|
this.registerTableFunction("setSheets", this.setSheets.bind(this));
|
|
this.registerTableFunction("addSheet", this.addSheet.bind(this));
|
|
this.registerTableFunction("getSheets", this.getSheets.bind(this));
|
|
this.registerTableFunction("getSheetDefinitions", this.getSheetDefinitions.bind(this));
|
|
this.registerTableFunction("setSheetData", this.setSheetData.bind(this));
|
|
this.registerTableFunction("getSheet", this.getSheet.bind(this));
|
|
this.registerTableFunction("getSheetData", this.getSheetData.bind(this));
|
|
this.registerTableFunction("clearSheet", this.clearSheet.bind(this));
|
|
this.registerTableFunction("removeSheet", this.removeSheetFunc.bind(this));
|
|
this.registerTableFunction("activeSheet", this.activeSheetFunc.bind(this));
|
|
}
|
|
|
|
///////////////////////////////////
|
|
////// Module Initialization //////
|
|
///////////////////////////////////
|
|
|
|
|
|
initialize(){
|
|
if(this.options("spreadsheet")){
|
|
this.subscribe("table-initialized", this.tableInitialized.bind(this));
|
|
this.subscribe("data-loaded", this.loadRemoteData.bind(this));
|
|
|
|
this.table.options.index = "_id";
|
|
|
|
if(this.options("spreadsheetData") && this.options("spreadsheetSheets")){
|
|
console.warn("You cannot use spreadsheetData and spreadsheetSheets at the same time, ignoring spreadsheetData");
|
|
|
|
this.table.options.spreadsheetData = false;
|
|
}
|
|
|
|
this.compatibilityCheck();
|
|
|
|
if(this.options("spreadsheetSheetTabs")){
|
|
this.initializeTabset();
|
|
}
|
|
}
|
|
}
|
|
|
|
compatibilityCheck(){
|
|
if(this.options("data")){
|
|
console.warn("Do not use the data option when working with spreadsheets, use either spreadsheetData or spreadsheetSheets to pass data into the table");
|
|
}
|
|
|
|
if(this.options("pagination")){
|
|
console.warn("The spreadsheet module is not compatible with the pagination module");
|
|
}
|
|
|
|
if(this.options("groupBy")){
|
|
console.warn("The spreadsheet module is not compatible with the row grouping module");
|
|
}
|
|
|
|
if(this.options("responsiveCollapse")){
|
|
console.warn("The spreadsheet module is not compatible with the responsive collapse module");
|
|
}
|
|
}
|
|
initializeTabset(){
|
|
this.element = document.createElement("div");
|
|
this.element.classList.add("tabulator-spreadsheet-tabs");
|
|
var altContainer = this.options("spreadsheetSheetTabsElement");
|
|
|
|
if(altContainer && !(altContainer instanceof HTMLElement)){
|
|
altContainer = document.querySelector(altContainer);
|
|
|
|
if(!altContainer){
|
|
console.warn("Unable to find element matching spreadsheetSheetTabsElement selector:", this.options("spreadsheetSheetTabsElement"));
|
|
}
|
|
}
|
|
|
|
if(altContainer){
|
|
altContainer.appendChild(this.element);
|
|
}else {
|
|
this.footerAppend(this.element);
|
|
}
|
|
}
|
|
|
|
tableInitialized(){
|
|
if(this.sheets.length){
|
|
this.loadSheet(this.sheets[0]);
|
|
}else {
|
|
|
|
if(this.options("spreadsheetSheets")){
|
|
this.loadSheets(this.options("spreadsheetSheets"));
|
|
}else if(this.options("spreadsheetData")){
|
|
this.loadData(this.options("spreadsheetData"));
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////
|
|
/////////// Ajax Parsing //////////
|
|
///////////////////////////////////
|
|
|
|
loadRemoteData(data, data1, data2){
|
|
console.log("data", data, data1, data2);
|
|
|
|
if(Array.isArray(data)){
|
|
|
|
this.table.dataLoader.clearAlert();
|
|
this.dispatchExternal("dataLoaded", data);
|
|
|
|
if(!data.length || Array.isArray(data[0])){
|
|
this.loadData(data);
|
|
}else {
|
|
this.loadSheets(data);
|
|
}
|
|
}else {
|
|
console.error("Spreadsheet Loading Error - Unable to process remote data due to invalid data type \nExpecting: array \nReceived: ", typeof data, "\nData: ", data);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////
|
|
///////// Sheet Management ////////
|
|
///////////////////////////////////
|
|
|
|
|
|
loadData(data){
|
|
var def = {
|
|
data:data,
|
|
};
|
|
|
|
this.loadSheet(this.newSheet(def));
|
|
}
|
|
|
|
destroySheets(){
|
|
this.sheets.forEach((sheet) => {
|
|
sheet.destroy();
|
|
});
|
|
|
|
this.sheets = [];
|
|
this.activeSheet = null;
|
|
}
|
|
|
|
loadSheets(sheets){
|
|
if(!Array.isArray(sheets)){
|
|
sheets = [];
|
|
}
|
|
|
|
this.destroySheets();
|
|
|
|
sheets.forEach((def) => {
|
|
this.newSheet(def);
|
|
});
|
|
|
|
this.loadSheet(this.sheets[0]);
|
|
}
|
|
|
|
loadSheet(sheet){
|
|
if(this.activeSheet !== sheet){
|
|
if(this.activeSheet){
|
|
this.activeSheet.unload();
|
|
}
|
|
|
|
this.activeSheet = sheet;
|
|
|
|
sheet.load();
|
|
}
|
|
}
|
|
|
|
newSheet(definition = {}){
|
|
var sheet;
|
|
|
|
if(!definition.rows){
|
|
definition.rows = this.options("spreadsheetRows");
|
|
}
|
|
|
|
if(!definition.columns){
|
|
definition.columns = this.options("spreadsheetColumns");
|
|
}
|
|
|
|
sheet = new Sheet(this, definition);
|
|
|
|
this.sheets.push(sheet);
|
|
|
|
if(this.element){
|
|
this.element.appendChild(sheet.element);
|
|
}
|
|
|
|
return sheet;
|
|
}
|
|
|
|
removeSheet(sheet){
|
|
var index = this.sheets.indexOf(sheet),
|
|
prevSheet;
|
|
|
|
if(this.sheets.length > 1){
|
|
if(index > -1){
|
|
this.sheets.splice(index, 1);
|
|
sheet.destroy();
|
|
|
|
if(this.activeSheet === sheet){
|
|
|
|
prevSheet = this.sheets[index - 1] || this.sheets[0];
|
|
|
|
if(prevSheet){
|
|
this.loadSheet(prevSheet);
|
|
}else {
|
|
this.activeSheet = null;
|
|
}
|
|
}
|
|
}
|
|
}else {
|
|
console.warn("Unable to remove sheet, at least one sheet must be active");
|
|
}
|
|
}
|
|
|
|
lookupSheet(key){
|
|
if(!key){
|
|
return this.activeSheet;
|
|
}else if(key instanceof Sheet){
|
|
return key;
|
|
}else if(key instanceof SheetComponent){
|
|
return key._sheet;
|
|
}else {
|
|
return this.sheets.find(sheet => sheet.key === key) || false;
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////
|
|
//////// Public Functions /////////
|
|
///////////////////////////////////
|
|
|
|
setSheets(sheets){
|
|
this.loadSheets(sheets);
|
|
|
|
return this.getSheets();
|
|
}
|
|
|
|
addSheet(sheet){
|
|
return this.newSheet(sheet).getComponent();
|
|
}
|
|
|
|
getSheetDefinitions(){
|
|
return this.sheets.map(sheet => sheet.getDefinition());
|
|
}
|
|
|
|
getSheets(){
|
|
return this.sheets.map(sheet => sheet.getComponent());
|
|
}
|
|
|
|
getSheet(key){
|
|
var sheet = this.lookupSheet(key);
|
|
|
|
return sheet ? sheet.getComponent() : false;
|
|
}
|
|
|
|
setSheetData(key, data){
|
|
if (key && !data){
|
|
data = key;
|
|
key = false;
|
|
}
|
|
|
|
var sheet = this.lookupSheet(key);
|
|
|
|
return sheet ? sheet.setData(data) : false;
|
|
}
|
|
|
|
getSheetData(key){
|
|
var sheet = this.lookupSheet(key);
|
|
|
|
return sheet ? sheet.getData() : false;
|
|
}
|
|
|
|
clearSheet(key){
|
|
var sheet = this.lookupSheet(key);
|
|
|
|
return sheet ? sheet.clear() : false;
|
|
}
|
|
|
|
removeSheetFunc(key){
|
|
var sheet = this.lookupSheet(key);
|
|
|
|
if(sheet){
|
|
this.removeSheet(sheet);
|
|
}
|
|
}
|
|
|
|
activeSheetFunc(key){
|
|
var sheet = this.lookupSheet(key);
|
|
|
|
return sheet ? this.loadSheet(sheet) : false;
|
|
}
|
|
}
|
|
|
|
class Tooltip extends Module{
|
|
|
|
static moduleName = "tooltip";
|
|
|
|
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());
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
},
|
|
|
|
//must be alphanumeric
|
|
alphanumeric: function(cell, value, parameters){
|
|
if(value === "" || value === null || typeof value === "undefined"){
|
|
return true;
|
|
}
|
|
|
|
var reg = new RegExp(/^[a-z0-9]+$/i);
|
|
|
|
return reg.test(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{
|
|
|
|
static moduleName = "validate";
|
|
|
|
//load defaults
|
|
static validators = defaultValidators;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var allModules = /*#__PURE__*/Object.freeze({
|
|
__proto__: null,
|
|
AccessorModule: Accessor,
|
|
AjaxModule: Ajax,
|
|
ClipboardModule: Clipboard,
|
|
ColumnCalcsModule: ColumnCalcs,
|
|
DataTreeModule: DataTree,
|
|
DownloadModule: Download,
|
|
EditModule: Edit,
|
|
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,
|
|
PrintModule: Print,
|
|
ReactiveDataModule: ReactiveData,
|
|
ResizeColumnsModule: ResizeColumns,
|
|
ResizeRowsModule: ResizeRows,
|
|
ResizeTableModule: ResizeTable,
|
|
ResponsiveLayoutModule: ResponsiveLayout,
|
|
SelectRangeModule: SelectRange,
|
|
SelectRowModule: SelectRow,
|
|
SortModule: Sort,
|
|
SpreadsheetModule: Spreadsheet,
|
|
TooltipModule: Tooltip,
|
|
ValidateModule: Validate
|
|
});
|
|
|
|
//tabulator with all modules installed
|
|
|
|
class TabulatorFull extends Tabulator {
|
|
static extendModule(){
|
|
Tabulator.initializeModuleBinder(allModules);
|
|
Tabulator._extendModule(...arguments);
|
|
}
|
|
|
|
static registerModule(){
|
|
Tabulator.initializeModuleBinder(allModules);
|
|
Tabulator._registerModule(...arguments);
|
|
}
|
|
|
|
constructor(element, options, modules){
|
|
super(element, options, allModules);
|
|
}
|
|
}
|
|
|
|
var TabulatorFull$1 = TabulatorFull;
|
|
|
|
return TabulatorFull$1;
|
|
|
|
}));
|
|
//# sourceMappingURL=tabulator.js.map
|