UNPKG

tabulator-tables

Version:

Interactive table generation JavaScript library

714 lines (533 loc) 16.7 kB
import CoreFeature from './CoreFeature.js'; import Column from './column/Column.js'; import ColumnComponent from './column/ColumnComponent.js'; import Helpers from './tools/Helpers.js'; import OptionsList from './tools/OptionsList.js'; import RendererBasicHorizontal from './rendering/renderers/BasicHorizontal.js'; import RendererVirtualDomHorizontal from './rendering/renderers/VirtualDomHorizontal.js'; import defaultColumnOptions from './column/defaults/options.js'; export default class ColumnManager extends CoreFeature { constructor (table){ super(table); this.blockHozScrollEvent = false; this.headersElement = 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.element = this.createHeaderElement(); this.element.insertBefore(this.headersElement, this.element.firstChild); this.subscribe("scroll-horizontal", this.scrollHorizontal.bind(this)); } initializeRenderer(){ var renderClass; var renderers = { "virtual": RendererVirtualDomHorizontal, "basic": RendererBasicHorizontal, }; 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; } 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 header containing element getHeadersElement(){ return this.headersElement; } //scroll horizontally to match table body scrollHorizontal(left){ var hozAdjust = 0, scrollWidth = this.element.scrollWidth - this.table.element.clientWidth; // this.tempScrollBlock(); this.element.scrollLeft = left; //adjust for vertical scrollbar moving table when present if(left > scrollWidth){ hozAdjust = left - scrollWidth; this.element.style.marginLeft = (-(hozAdjust)) + "px"; }else{ this.element.style.marginLeft = 0; } this.scrollLeft = left; this.renderer.scrollColumns(left); } ///////////// Column Setup Functions ///////////// generateColumnsFromRowData(data){ var cols = [], definitions = this.table.options.autoColumnsDefinitions, row, sorter; if(data && data.length){ row = data[0]; for(var key in row){ let col = { field:key, title:key, }; let value = row[key]; switch(typeof value){ case "undefined": sorter = "string"; break; case "boolean": sorter = "boolean"; break; case "object": if(Array.isArray(value)){ sorter = "array"; }else{ sorter = "string"; } break; default: if(!isNaN(value) && value !== ""){ sorter = "number"; }else{ if(value.match(/((^[0-9]+[a-z]+)|(^[a-z]+[0-9]+))+$/i)){ sorter = "alphanum"; }else{ sorter = "string"; } } break; } col.sorter = sorter; cols.push(col); } if(definitions){ switch(typeof definitions){ case "function": this.table.options.columns = definitions.call(this.table, cols); break; case "object": if(Array.isArray(definitions)){ cols.forEach((col) => { var match = definitions.find((def) => { return def.field === col.field; }); if(match){ Object.assign(col, match); } }); }else{ cols.forEach((col) => { if(definitions[col.field]){ Object.assign(col, definitions[col.field]); } }); } this.table.options.columns = cols; break; } }else{ this.table.options.columns = cols; } this.setColumns(this.table.options.columns); } } setColumns(cols, row){ while(this.headersElement.firstChild) this.headersElement.removeChild(this.headersElement.firstChild); this.columns = []; this.columnsByIndex = []; this.columnsByField = {}; this.dispatch("columns-loading"); cols.forEach((def, i) => { this._addColumn(def); }); this._reIndexColumns(); this.dispatch("columns-loaded"); this.rerenderColumns(false, true); this.redraw(true); } _addColumn(definition, before, nextToColumn){ var column = new Column(definition, this), colEl = column.getElement(), index = nextToColumn ? this.findColumnIndex(nextToColumn) : nextToColumn; if(nextToColumn && index > -1){ var topColumn = nextToColumn.getTopColumn(); var parentIndex = this.columns.indexOf(topColumn); var nextEl = topColumn.getElement(); if(before){ this.columns.splice(parentIndex, 0, column); nextEl.parentNode.insertBefore(colEl, nextEl); }else{ this.columns.splice(parentIndex + 1, 0, column); nextEl.parentNode.insertBefore(colEl, nextEl.nextSibling); } }else{ if(before){ this.columns.unshift(column); this.headersElement.insertBefore(column.getElement(), this.headersElement.firstChild); }else{ this.columns.push(column); this.headersElement.appendChild(column.getElement()); } } column.columnRendered(); return column; } registerColumnField(col){ if(col.definition.field){ this.columnsByField[col.definition.field] = col; } } registerColumnPosition(col){ this.columnsByIndex.push(col); } _reIndexColumns(){ this.columnsByIndex = []; this.columns.forEach(function(column){ column.reRegisterPosition(); }); } //ensure column headers take up the correct amount of space in column groups verticalAlignHeaders(){ var minHeight = 0; if(!this.redrawBlock){ this.columns.forEach((column) => { column.clearVerticalAlign(); }); this.columns.forEach((column) => { var height = column.getHeight(); if(height > minHeight){ minHeight = height; } }); this.columns.forEach((column) => { column.verticalAlign(this.table.options.columnHeaderVertAlign, minHeight); }); this.table.rowManager.adjustTableSize(); } } //////////////// Column Details ///////////////// findColumn(subject){ var columns; if(typeof subject == "object"){ if(subject instanceof Column){ //subject is column element return subject; }else if(subject instanceof ColumnComponent){ //subject is public column component return subject._getSelf() || false; }else if(typeof HTMLElement !== "undefined" && subject instanceof HTMLElement){ columns = []; this.columns.forEach((column) => { columns.push(column); columns = columns.concat(column.getColumns(true)); }); //subject is a HTML element of the column header let match = columns.find((column) => { return column.element === subject; }); return match || false; } }else{ //subject should be treated as the field name of the column return this.columnsByField[subject] || false; } //catch all for any other type of input return false; } getColumnByField(field){ return this.columnsByField[field]; } getColumnsByFieldRoot(root){ var matches = []; Object.keys(this.columnsByField).forEach((field) => { var fieldRoot = field.split(".")[0]; if(fieldRoot === root){ matches.push(this.columnsByField[field]); } }); return matches; } getColumnByIndex(index){ return this.columnsByIndex[index]; } getFirstVisibleColumn(){ var index = this.columnsByIndex.findIndex((col) => { return col.visible; }); return index > -1 ? this.columnsByIndex[index] : false; } getColumns(){ return this.columns; } findColumnIndex(column){ return this.columnsByIndex.findIndex((col) => { return column === col; }); } //return all columns that are not groups getRealColumns(){ return this.columnsByIndex; } //traverse across columns and call action traverse(callback){ this.columnsByIndex.forEach((column,i) =>{ callback(column, i); }); } //get definitions of actual columns getDefinitions(active){ var output = []; this.columnsByIndex.forEach((column) => { if(!active || (active && column.visible)){ output.push(column.getDefinition()); } }); return output; } //get full nested definition tree getDefinitionTree(){ var output = []; this.columns.forEach((column) => { output.push(column.getDefinition(true)); }); return output; } getComponents(structured){ var output = [], columns = structured ? this.columns : this.columnsByIndex; columns.forEach((column) => { output.push(column.getComponent()); }); return output; } getWidth(){ var width = 0; this.columnsByIndex.forEach((column) => { if(column.visible){ width += column.getWidth(); } }); return width; } moveColumn(from, to, after){ to.element.parentNode.insertBefore(from.element, to.element); if(after){ to.element.parentNode.insertBefore(to.element, from.element); } this.moveColumnActual(from, to, after); this.verticalAlignHeaders(); this.table.rowManager.reinitialize(); } moveColumnActual(from, to, after){ if(from.parent.isGroup){ this._moveColumnInArray(from.parent.columns, from, to, after); }else{ this._moveColumnInArray(this.columns, from, to, after); } this._moveColumnInArray(this.columnsByIndex, from, to, after, true); this.rerenderColumns(true); this.dispatch("column-moved", from, to, after); if(this.subscribedExternal("columnMoved")){ this.dispatchExternal("columnMoved", from.getComponent(), this.table.columnManager.getComponents()); } } _moveColumnInArray(columns, from, to, after, updateRows){ var fromIndex = columns.indexOf(from), toIndex, rows = []; if (fromIndex > -1) { columns.splice(fromIndex, 1); toIndex = columns.indexOf(to); if (toIndex > -1) { if(after){ toIndex = toIndex+1; } }else{ toIndex = fromIndex; } columns.splice(toIndex, 0, from); if(updateRows){ rows = this.chain("column-moving-rows", [from, to, after], null, []) || []; rows = rows.concat(this.table.rowManager.rows); rows.forEach(function(row){ if(row.cells.length){ var cell = row.cells.splice(fromIndex, 1)[0]; row.cells.splice(toIndex, 0, cell); } }); } } } scrollToColumn(column, position, ifVisible){ var left = 0, offset = 0, 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){ offset = colEl.offsetLeft; if(offset > 0 && offset + colEl.offsetWidth < this.element.clientWidth){ return false; } } //calculate scroll position left = colEl.offsetLeft + 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(); } }