UNPKG

jquery.tabulator

Version:

Interactive table generation plugin for jQuery UI

2,342 lines (1,326 loc) 423 kB
'use strict'; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /* Tabulator v3.5.3 (c) Oliver Folkerd */ /* * This file is part of the Tabulator package. * * (c) Oliver Folkerd <oliver.folkerd@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. * * Full Documentation & Demos can be found at: http://olifolkerd.github.io/tabulator/ * */ (function (factory) { "use strict"; if (typeof define === 'function' && define.amd) { define(['jquery'], factory); } else if (typeof module !== 'undefined' && module.exports) { module.exports = factory(require('jquery')); } else { factory(jQuery); } })(function ($, undefined) { (function () { 'use strict'; // https://tc39.github.io/ecma262/#sec-array.prototype.findIndex if (!Array.prototype.findIndex) { Object.defineProperty(Array.prototype, 'findIndex', { value: function value(predicate) { // 1. Let O be ? ToObject(this value). if (this == null) { throw new TypeError('"this" is null or not defined'); } var o = Object(this); // 2. Let len be ? ToLength(? Get(O, "length")). var len = o.length >>> 0; // 3. If IsCallable(predicate) is false, throw a TypeError exception. if (typeof predicate !== 'function') { throw new TypeError('predicate must be a function'); } // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. var thisArg = arguments[1]; // 5. Let k be 0. var k = 0; // 6. Repeat, while k < len while (k < len) { // a. Let Pk be ! ToString(k). // b. Let kValue be ? Get(O, Pk). // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). // d. If testResult is true, return k. var kValue = o[k]; if (predicate.call(thisArg, kValue, k, o)) { return k; } // e. Increase k by 1. k++; } // 7. Return -1. return -1; } }); } // https://tc39.github.io/ecma262/#sec-array.prototype.find if (!Array.prototype.find) { Object.defineProperty(Array.prototype, 'find', { value: function value(predicate) { // 1. Let O be ? ToObject(this value). if (this == null) { throw new TypeError('"this" is null or not defined'); } var o = Object(this); // 2. Let len be ? ToLength(? Get(O, "length")). var len = o.length >>> 0; // 3. If IsCallable(predicate) is false, throw a TypeError exception. if (typeof predicate !== 'function') { throw new TypeError('predicate must be a function'); } // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. var thisArg = arguments[1]; // 5. Let k be 0. var k = 0; // 6. Repeat, while k < len while (k < len) { // a. Let Pk be ! ToString(k). // b. Let kValue be ? Get(O, Pk). // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). // d. If testResult is true, return kValue. var kValue = o[k]; if (predicate.call(thisArg, kValue, k, o)) { return kValue; } // e. Increase k by 1. k++; } // 7. Return undefined. return undefined; } }); } var ColumnManager = function ColumnManager(table) { this.table = table; //hold parent table this.headersElement = $("<div class='tabulator-headers'></div>"); this.element = $("<div class='tabulator-header'></div>"); //containing element this.rowManager = null; //hold row manager object this.columns = []; // column definition object this.columnsByIndex = []; //columns by index this.columnsByField = []; //columns by field this.scrollLeft = 0; this.element.prepend(this.headersElement); }; ////////////// Setup Functions ///////////////// //link to row manager ColumnManager.prototype.setRowManager = function (manager) { this.rowManager = manager; }; //return containing element ColumnManager.prototype.getElement = function () { return this.element; }; //return header containing element ColumnManager.prototype.getHeadersElement = function () { return this.headersElement; }; //scroll horizontally to match table body ColumnManager.prototype.scrollHorizontal = function (left) { var hozAdjust = 0, scrollWidth = this.element[0].scrollWidth - this.table.element.innerWidth(); this.element.scrollLeft(left); //adjust for vertical scrollbar moving table when present if (left > scrollWidth) { hozAdjust = left - scrollWidth; this.element.css("margin-left", -hozAdjust); } else { this.element.css("margin-left", 0); } //keep frozen columns fixed in position //this._calcFrozenColumnsPos(hozAdjust + 3); this.scrollLeft = left; if (this.table.extExists("frozenColumns")) { this.table.extensions.frozenColumns.layout(); } }; ///////////// Column Setup Functions ///////////// ColumnManager.prototype.setColumns = function (cols, row) { var self = this; self.headersElement.empty(); self.columns = []; self.columnsByIndex = []; self.columnsByField = []; //reset frozen columns if (self.table.extExists("frozenColumns")) { self.table.extensions.frozenColumns.reset(); } cols.forEach(function (def, i) { self._addColumn(def); }); self._reIndexColumns(); if (self.table.options.responsiveLayout && self.table.extExists("responsiveLayout", true)) { self.table.extensions.responsiveLayout.initialize(); } self.redraw(true); }; ColumnManager.prototype._addColumn = function (definition, before, nextToColumn) { var column = new Column(definition, this); var index = nextToColumn ? this.findColumnIndex(nextToColumn) : nextToColumn; if (nextToColumn && index > -1) { var parentIndex = this.columns.indexOf(nextToColumn.getTopColumn()); if (before) { this.columns.splice(parentIndex, 0, column); nextToColumn.getElement().before(column.getElement()); } else { this.columns.splice(parentIndex + 1, 0, column); nextToColumn.getElement().after(column.getElement()); } } else { if (before) { this.columns.unshift(column); this.headersElement.prepend(column.getElement()); } else { this.columns.push(column); this.headersElement.append(column.getElement()); } } return column; }; ColumnManager.prototype.registerColumnField = function (col) { if (col.definition.field) { this.columnsByField[col.definition.field] = col; } }; ColumnManager.prototype.registerColumnPosition = function (col) { this.columnsByIndex.push(col); }; ColumnManager.prototype._reIndexColumns = function () { this.columnsByIndex = []; this.columns.forEach(function (column) { column.reRegisterPosition(); }); }; //ensure column headers take up the correct amount of space in column groups ColumnManager.prototype._verticalAlignHeaders = function () { var self = this; self.columns.forEach(function (column) { column.clearVerticalAlign(); }); self.columns.forEach(function (column) { column.verticalAlign(self.table.options.columnVertAlign); }); self.rowManager.adjustTableSize(); }; //////////////// Column Details ///////////////// ColumnManager.prototype.findColumn = function (subject) { var self = this; if ((typeof subject === 'undefined' ? 'undefined' : _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 (subject instanceof jQuery) { //subject is a jquery element of the column header var match = self.columns.find(function (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; }; ColumnManager.prototype.getColumnByField = function (field) { return this.columnsByField[field]; }; ColumnManager.prototype.getColumnByIndex = function (index) { return this.columnsByIndex[index]; }; ColumnManager.prototype.getColumns = function () { return this.columns; }; ColumnManager.prototype.findColumnIndex = function (column) { return this.columnsByIndex.findIndex(function (col) { return column === col; }); }; //return all columns that are not groups ColumnManager.prototype.getRealColumns = function () { return this.columnsByIndex; }; //travers across columns and call action ColumnManager.prototype.traverse = function (callback) { var self = this; self.columnsByIndex.forEach(function (column, i) { callback(column, i); }); }; //get defintions of actual columns ColumnManager.prototype.getDefinitions = function (active) { var self = this, output = []; self.columnsByIndex.forEach(function (column) { if (!active || active && column.visible) { output.push(column.getDefinition()); } }); return output; }; //get full nested definition tree ColumnManager.prototype.getDefinitionTree = function () { var self = this, output = []; self.columns.forEach(function (column) { output.push(column.getDefinition(true)); }); return output; }; ColumnManager.prototype.getComponents = function (structured) { var self = this, output = [], columns = structured ? self.columns : self.columnsByIndex; columns.forEach(function (column) { output.push(column.getComponent()); }); return output; }; ColumnManager.prototype.getWidth = function () { var width = 0; this.columnsByIndex.forEach(function (column) { if (column.visible) { width += column.getWidth(); } }); return width; }; ColumnManager.prototype.moveColumn = function (from, to, after) { this._moveColumnInArray(this.columns, from, to, after); this._moveColumnInArray(this.columnsByIndex, from, to, after, true); if (this.table.options.responsiveLayout && this.table.extExists("responsiveLayout", true)) { this.table.extensions.responsiveLayout.initialize(); } if (this.table.options.columnMoved) { this.table.options.columnMoved(from.getComponent(), this.table.columnManager.getComponents()); } if (this.table.options.persistentLayout && this.table.extExists("persistence", true)) { this.table.extensions.persistence.save("columns"); } }; ColumnManager.prototype._moveColumnInArray = function (columns, from, to, after, updateRows) { var fromIndex = columns.indexOf(from), toIndex; 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) { this.table.rowManager.rows.forEach(function (row) { if (row.cells.length) { var cell = row.cells.splice(fromIndex, 1)[0]; row.cells.splice(toIndex, 0, cell); } }); } } }; ColumnManager.prototype.scrollToColumn = function (column, position, ifVisible) { var left = 0, offset = 0, adjust = 0; 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[0].clientWidth / 2; break; case "right": adjust = column.element.innerWidth() - this.headersElement.innerWidth(); break; } //check column visibility if (!ifVisible) { offset = column.element.position().left; if (offset > 0 && offset + column.element.outerWidth() < this.element[0].clientWidth) { return false; } } //calculate scroll position left = column.element.position().left + this.element.scrollLeft() + adjust; left = Math.max(Math.min(left, this.table.rowManager.element[0].scrollWidth - this.table.rowManager.element[0].clientWidth), 0); this.table.rowManager.scrollHorizontal(left); this.scrollHorizontal(left); return true; } else { console.warn("Scroll Error - Column not visible"); return false; } }; //////////////// Cell Management ///////////////// ColumnManager.prototype.generateCells = function (row) { var self = this; var cells = []; self.columnsByIndex.forEach(function (column) { cells.push(column.generateCell(row)); }); return cells; }; //////////////// Column Management ///////////////// ColumnManager.prototype.getFlexBaseWidth = function () { var self = this, totalWidth = self.table.element.innerWidth(), //table element width fixedWidth = 0; //adjust for vertical scrollbar if present if (self.rowManager.element[0].scrollHeight > self.rowManager.element.innerHeight()) { totalWidth -= self.rowManager.element[0].offsetWidth - self.rowManager.element[0].clientWidth; } this.columnsByIndex.forEach(function (column) { var width, minWidth, colWidth; if (column.visible) { width = column.definition.width || 0; minWidth = typeof column.minWidth == "undefined" ? self.table.options.columnMinWidth : 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; }; ColumnManager.prototype.addColumn = function (definition, before, nextToColumn) { var column = this._addColumn(definition, before, nextToColumn); this._reIndexColumns(); if (this.table.options.responsiveLayout && this.table.extExists("responsiveLayout", true)) { this.table.extensions.responsiveLayout.initialize(); } if (this.table.extExists("columnCalcs")) { this.table.extensions.columnCalcs.recalc(this.table.rowManager.activeRows); } this.redraw(); if (this.table.extensions.layout.getMode() != "fitColumns") { column.reinitializeWidth(); } this._verticalAlignHeaders(); this.table.rowManager.reinitialize(); }; //remove column from system ColumnManager.prototype.deregisterColumn = function (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); } if (this.table.options.responsiveLayout && this.table.extExists("responsiveLayout", true)) { this.table.extensions.responsiveLayout.initialize(); } this.redraw(); }; //redraw columns ColumnManager.prototype.redraw = function (force) { if (force) { if (this.element.is(":visible")) { this._verticalAlignHeaders(); } this.table.rowManager.resetScroll(); this.table.rowManager.reinitialize(); } if (this.table.extensions.layout.getMode() == "fitColumns") { this.table.extensions.layout.layout(); } else { if (force) { this.table.extensions.layout.layout(); } else { if (this.table.options.responsiveLayout && this.table.extExists("responsiveLayout", true)) { this.table.extensions.responsiveLayout.update(); } } } if (this.table.extExists("frozenColumns")) { this.table.extensions.frozenColumns.layout(); } if (this.table.extExists("columnCalcs")) { this.table.extensions.columnCalcs.recalc(this.table.rowManager.activeRows); } if (force) { if (this.table.options.persistentLayout && this.table.extExists("persistence", true)) { this.table.extensions.persistence.save("columns"); } if (this.table.extExists("columnCalcs")) { this.table.extensions.columnCalcs.redraw(); } } this.table.footerManager.redraw(); }; //public column object var ColumnComponent = function ColumnComponent(column) { this.column = column; this.type = "ColumnComponent"; }; ColumnComponent.prototype.getElement = function () { return this.column.getElement(); }; ColumnComponent.prototype.getDefinition = function () { return this.column.getDefinition(); }; ColumnComponent.prototype.getField = function () { return this.column.getField(); }; ColumnComponent.prototype.getCells = function () { var cells = []; this.column.cells.forEach(function (cell) { cells.push(cell.getComponent()); }); return cells; }; ColumnComponent.prototype.getVisibility = function () { return this.column.visible; }; ColumnComponent.prototype.show = function () { if (this.column.isGroup) { this.column.columns.forEach(function (column) { column.show(); }); } else { this.column.show(); } }; ColumnComponent.prototype.hide = function () { if (this.column.isGroup) { this.column.columns.forEach(function (column) { column.hide(); }); } else { this.column.hide(); } }; ColumnComponent.prototype.toggle = function () { if (this.column.visible) { this.hide(); } else { this.show(); } }; ColumnComponent.prototype.delete = function () { this.column.delete(); }; ColumnComponent.prototype.getSubColumns = function () { var output = []; if (this.column.columns.length) { this.column.columns.forEach(function (column) { output.push(column.getComponent()); }); } return output; }; ColumnComponent.prototype.getParentColumn = function () { return this.column.parent instanceof Column ? this.column.parent.getComponent() : false; }; ColumnComponent.prototype._getSelf = function () { return this.column; }; ColumnComponent.prototype.scrollTo = function () { this.column.table.columManager.scrollToColumn(this.column); }; var Column = function Column(def, parent) { var self = this; this.table = parent.table; this.definition = def; //column definition this.parent = parent; //hold parent object this.type = "column"; //type of element this.columns = []; //child columns this.cells = []; //cells bound to this column this.element = $("<div class='tabulator-col' role='columnheader' aria-sort='none'></div>"); //column header element this.contentElement = false; this.groupElement = $("<div class='tabulator-col-group-cols'></div>"); //column group holder element this.isGroup = false; this.tooltip = false; //hold column tooltip this.hozAlign = ""; //horizontal text alignment //multi dimentional filed handling this.field = ""; this.fieldStructure = ""; this.getFieldValue = ""; this.setFieldValue = ""; this.setField(this.definition.field); this.extensions = {}; //hold extension variables; this.cellEvents = { cellClick: false, cellDblClick: false, cellContext: false, cellTap: false, cellDblTap: false, cellTapHold: false }; this.width = null; //column width this.minWidth = null; //column minimum width this.widthFixed = false; //user has specified a width for this column this.visible = true; //default visible state //initialize column if (def.columns) { this.isGroup = true; def.columns.forEach(function (def, i) { var newCol = new Column(def, self); self.attachColumn(newCol); }); self.checkColumnVisibility(); } else { parent.registerColumnField(this); } if (def.rowHandle && this.table.options.movableRows !== false && this.table.extExists("moveRow")) { this.table.extensions.moveRow.setHandle(true); } this._mapDepricatedFunctionality(); this._buildHeader(); }; //////////////// Setup Functions ///////////////// Column.prototype._mapDepricatedFunctionality = function (field) { if (this.definition.tooltipHeader) { console.warn("The%c tooltipHeader%c column definition property has been depricated and will be removed in version 4.0, use %c headerTooltip%c instead.", "font-weight:bold;", "font-weight:regular;", "font-weight:bold;", "font-weight:regular;"); if (typeof this.definition.headerTooltip == "undefined") { this.definition.headerTooltip = this.definition.tooltipHeader; } } }; Column.prototype.setField = function (field) { this.field = field; this.fieldStructure = field ? field.split(".") : []; this.getFieldValue = this.fieldStructure.length > 1 ? this._getNestedData : this._getFlatData; this.setFieldValue = this.fieldStructure.length > 1 ? this._setNesteData : this._setFlatData; }; //register column position with column manager Column.prototype.registerColumnPosition = function (column) { this.parent.registerColumnPosition(column); }; //register column position with column manager Column.prototype.registerColumnField = function (column) { this.parent.registerColumnField(column); }; //trigger position registration Column.prototype.reRegisterPosition = function () { if (this.isGroup) { this.columns.forEach(function (column) { column.reRegisterPosition(); }); } else { this.registerColumnPosition(this); } }; Column.prototype.setTooltip = function () { var self = this, def = self.definition; //set header tooltips var tooltip = def.headerTooltip || def.tooltip === false ? def.headerTooltip : self.table.options.tooltipsHeader; if (tooltip) { if (tooltip === true) { if (def.field) { self.table.extensions.localize.bind("columns|" + def.field, function (value) { self.element.attr("title", value || def.title); }); } else { self.element.attr("title", def.title); } } else { if (typeof tooltip == "function") { tooltip = tooltip(self.getComponent()); if (tooltip === false) { tooltip = ""; } } self.element.attr("title", tooltip); } } else { self.element.attr("title", ""); } }; //build header element Column.prototype._buildHeader = function () { var self = this, def = self.definition, dblTap, tapHold, tap; self.element.empty(); self.contentElement = self._buildColumnHeaderContent(); self.element.append(self.contentElement); if (self.isGroup) { self._buildGroupHeader(); } else { self._buildColumnHeader(); } self.setTooltip(); //set resizable handles if (self.table.options.resizableColumns && self.table.extExists("resizeColumns")) { self.table.extensions.resizeColumns.initializeColumn("header", self, self.element); } //set resizable handles if (def.headerFilter && self.table.extExists("filter") && self.table.extExists("edit")) { if (typeof def.headerFilterPlaceholder !== "undefined" && def.field) { self.table.extensions.localize.setHeaderFilterColumnPlaceholder(def.field, def.headerFilterPlaceholder); } self.table.extensions.filter.initializeColumn(self); } //set resizable handles if (self.table.extExists("frozenColumns")) { self.table.extensions.frozenColumns.initializeColumn(self); } //set movable column if (self.table.options.movableColumns && !self.isGroup && self.table.extExists("moveColumn")) { self.table.extensions.moveColumn.initializeColumn(self); } //set calcs column if ((def.topCalc || def.bottomCalc) && self.table.extExists("columnCalcs")) { self.table.extensions.columnCalcs.initializeColumn(self); } //update header tooltip on mouse enter self.element.on("mouseenter", function (e) { self.setTooltip(); }); //setup header click event bindings if (typeof def.headerClick == "function") { self.element.on("click", function (e) { def.headerClick(e, self.getComponent()); }); } if (typeof def.headerDblClick == "function") { self.element.on("dblclick", function (e) { def.headerDblClick(e, self.getComponent()); }); } if (typeof def.headerContext == "function") { self.element.on("contextmenu", function (e) { def.headerContext(e, self.getComponent()); }); } //setup header tap event bindings if (typeof def.headerTap == "function") { tap = false; self.element.on("touchstart", function (e) { tap = true; }); self.element.on("touchend", function (e) { if (tap) { def.headerTap(e, self.getComponent()); } tap = false; }); } if (typeof def.headerDblTap == "function") { dblTap = null; self.element.on("touchend", function (e) { if (dblTap) { clearTimeout(dblTap); dblTap = null; def.headerDblTap(e, self.getComponent()); } else { dblTap = setTimeout(function () { clearTimeout(dblTap); dblTap = null; }, 300); } }); } if (typeof def.headerTapHold == "function") { tapHold = null; self.element.on("touchstart", function (e) { clearTimeout(tapHold); tapHold = setTimeout(function () { clearTimeout(tapHold); tapHold = null; tap = false; def.headerTapHold(e, self.getComponent()); }, 1000); }); self.element.on("touchend", function (e) { clearTimeout(tapHold); tapHold = null; }); } //store column cell click event bindings if (typeof def.cellClick == "function") { self.cellEvents.cellClick = def.cellClick; } if (typeof def.cellDblClick == "function") { self.cellEvents.cellDblClick = def.cellDblClick; } if (typeof def.cellContext == "function") { self.cellEvents.cellContext = def.cellContext; } //setup column cell tap event bindings if (typeof def.cellTap == "function") { self.cellEvents.cellTap = def.cellTap; } if (typeof def.cellDblTap == "function") { self.cellEvents.cellDblTap = def.cellDblTap; } if (typeof def.cellTapHold == "function") { self.cellEvents.cellTapHold = def.cellTapHold; } //setup column cell edit callbacks if (typeof def.cellEdited == "function") { self.cellEvents.cellEdited = def.cellEdited; } if (typeof def.cellEditing == "function") { self.cellEvents.cellEditing = def.cellEditing; } if (typeof def.cellEditCancelled == "function") { self.cellEvents.cellEditCancelled = def.cellEditCancelled; } }; //build header element for header Column.prototype._buildColumnHeader = function () { var self = this, def = self.definition, table = self.table, sortable; //set column sorter if (table.extExists("sort")) { table.extensions.sort.initializeColumn(self, self.contentElement); } //set column formatter if (table.extExists("format")) { table.extensions.format.initializeColumn(self); } //set column editor if (typeof def.editor != "undefined" && table.extExists("edit")) { table.extensions.edit.initializeColumn(self); } //set colum validator if (typeof def.validator != "undefined" && table.extExists("validate")) { table.extensions.validate.initializeColumn(self); } //set column mutator if (table.extExists("mutator")) { table.extensions.mutator.initializeColumn(self); } //set column accessor if (table.extExists("accessor")) { table.extensions.accessor.initializeColumn(self); } //set respoviveLayout if (_typeof(table.options.responsiveLayout) && table.extExists("responsiveLayout")) { table.extensions.responsiveLayout.initializeColumn(self); } //set column visibility if (typeof def.visible != "undefined") { if (def.visible) { self.show(true); } else { self.hide(true); } } //asign additional css classes to column header if (def.cssClass) { self.element.addClass(def.cssClass); } if (def.field) { this.element.attr("tabulator-field", def.field); } //set min width if present self.setMinWidth(typeof def.minWidth == "undefined" ? self.table.options.columnMinWidth : def.minWidth); self.reinitializeWidth(); //set tooltip if present self.tooltip = self.definition.tooltip || self.definition.tooltip === false ? self.definition.tooltip : self.table.options.tooltips; //set orizontal text alignment self.hozAlign = typeof self.definition.align == "undefined" ? "" : self.definition.align; }; Column.prototype._buildColumnHeaderContent = function () { var self = this, def = self.definition, table = self.table; var contentElement = $("<div class='tabulator-col-content'></div>"); contentElement.append(self._buildColumnHeaderTitle()); return contentElement; }; //build title element of column Column.prototype._buildColumnHeaderTitle = function () { var self = this, def = self.definition, table = self.table, title; var titleHolderElement = $("<div class='tabulator-col-title'></div>"); if (def.editableTitle) { var titleElement = $("<input class='tabulator-title-editor'>"); titleElement.on("click", function (e) { e.stopPropagation(); $(this).focus(); }); titleElement.on("change", function () { var newTitle = $(this).val(); def.title = newTitle; table.options.columnTitleChanged(self.getComponent()); }); titleHolderElement.append(titleElement); if (def.field) { table.extensions.localize.bind("columns|" + def.field, function (text) { titleElement.val(text || def.title || "&nbsp"); }); } else { titleElement.val(def.title || "&nbsp"); } } else { if (def.field) { table.extensions.localize.bind("columns|" + def.field, function (text) { self._formatColumnHeaderTitle(titleHolderElement, text || def.title || "&nbsp"); }); } else { self._formatColumnHeaderTitle(titleHolderElement, def.title || "&nbsp"); } } return titleHolderElement; }; Column.prototype._formatColumnHeaderTitle = function (el, title) { var formatter, contents; if (this.definition.titleFormatter && this.table.extExists("format")) { formatter = this.table.extensions.format.getFormatter(this.definition.titleFormatter); contents = formatter.call(this.table.extensions.format, { getValue: function getValue() { return title; }, getElement: function getElement() { return el; } }, this.definition.titleFormatterParams || {}); el.append(contents); } else { el.html(title); } }; //build header element for column group Column.prototype._buildGroupHeader = function () { var self = this, def = self.definition, table = self.table; self.element.addClass("tabulator-col-group").attr("role", "columngroup").attr("aria-title", def.title); self.element.append(self.groupElement); }; //flat field lookup Column.prototype._getFlatData = function (data) { return data[this.field]; }; //nested field lookup Column.prototype._getNestedData = function (data) { var dataObj = data, structure = this.fieldStructure, length = structure.length, output; for (var i = 0; i < length; i++) { dataObj = dataObj[structure[i]]; output = dataObj; if (!dataObj) { break; } } return output; }; //flat field set Column.prototype._setFlatData = function (data, value) { data[this.field] = value; }; //nested field set Column.prototype._setNesteData = function (data, value) { var dataObj = data, structure = this.fieldStructure, length = structure.length; for (var i = 0; i < length; i++) { if (i == length - 1) { dataObj[structure[i]] = value; } else { if (!dataObj[structure[i]]) { dataObj[structure[i]] = {}; } dataObj = dataObj[structure[i]]; } } }; //attach column to this group Column.prototype.attachColumn = function (column) { var self = this; if (self.groupElement) { self.columns.push(column); self.groupElement.append(column.getElement()); } else { console.warn("Column Warning - Column being attached to another column instead of column group"); } }; //vertically align header in column Column.prototype.verticalAlign = function (alignment) { //calculate height of column header and group holder element var parentHeight = this.parent.isGroup ? this.parent.getGroupElement().innerHeight() : this.parent.getHeadersElement().innerHeight(); this.element.css("height", parentHeight); if (this.isGroup) { this.groupElement.css("min-height", parentHeight - this.contentElement.outerHeight()); } //vertically align cell contents if (!this.isGroup && alignment !== "top") { if (alignment === "bottom") { this.element.css({ "padding-top": this.element.innerHeight() - this.contentElement.outerHeight() }); } else { this.element.css({ "padding-top": (this.element.innerHeight() - this.contentElement.outerHeight()) / 2 }); } } this.columns.forEach(function (column) { column.verticalAlign(alignment); }); }; //clear vertical alignmenet Column.prototype.clearVerticalAlign = function () { this.element.css("padding-top", ""); this.element.css("height", ""); this.element.css("min-height", ""); this.columns.forEach(function (column) { column.clearVerticalAlign(); }); }; //// Retreive Column Information //// //return column header element Column.prototype.getElement = function () { return this.element; }; //return colunm group element Column.prototype.getGroupElement = function () { return this.groupElement; }; //return field name Column.prototype.getField = function () { return this.field; }; //return the first column in a group Column.prototype.getFirstColumn = function () { 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 Column.prototype.getLastColumn = function () { 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 Column.prototype.getColumns = function () { return this.columns; }; //return all columns in a group Column.prototype.getCells = function () { return this.cells; }; //retreive the top column in a group of columns Column.prototype.getTopColumn = function () { if (this.parent.isGroup) { return this.parent.getTopColumn(); } else { return this; } }; //return column definition object Column.prototype.getDefinition = function (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 //////////////////// Column.prototype.checkColumnVisibility = function () { var visible = false; this.columns.forEach(function (column) { if (column.visible) { visible = true; } }); if (visible) { this.show(); this.parent.table.options.columnVisibilityChanged(this.getComponent(), false); } else { this.hide(); } }; //show column Column.prototype.show = function (silent, responsiveToggle) { if (!this.visible) { this.visible = true; this.element.css({ "display": "" }); this.table.columnManager._verticalAlignHeaders(); if (this.parent.isGroup) { this.parent.checkColumnVisibility(); } this.cells.forEach(function (cell) { cell.show(); }); if (this.table.options.persistentLayout && this.table.extExists("responsiveLayout", true)) { this.table.extensions.persistence.save("columns"); } if (!responsiveToggle && this.table.options.responsiveLayout && this.table.extExists("responsiveLayout", true)) { this.table.extensions.responsiveLayout.updateColumnVisibility(this, this.visible); } if (!silent) { this.table.options.columnVisibilityChanged(this.getComponent(), true); } } }; //hide column Column.prototype.hide = function (silent, responsiveToggle) { if (this.visible) { this.visible = false; this.element.css({ "display": "none" }); this.table.columnManager._verticalAlignHeaders(); if (this.parent.isGroup) { this.parent.checkColumnVisibility(); } this.cells.forEach(function (cell) { cell.hide(); }); if (this.table.options.persistentLayout && this.table.extExists("persistence", true)) { this.table.extensions.persistence.save("columns"); } if (!responsiveToggle && this.table.options.responsiveLayout && this.table.extExists("responsiveLayout", true)) { this.table.extensions.responsiveLayout.updateColumnVisibility(this, this.visible); } if (!silent) { this.table.options.columnVisibilityChanged(this.getComponent(), false); } } }; Column.prototype.matchChildWidths = function () { var childWidth = 0; if (this.contentElement && this.columns.length) { this.columns.forEach(function (column) { childWidth += column.getWidth(); }); this.contentElement.css("max-width", childWidth - 1); } }; Column.prototype.setWidth = function (width) { this.widthFixed = true; this.setWidthActual(width); }; Column.prototype.setWidthActual = function (width) { if (isNaN(width)) { width = Math.floor(this.table.element.innerWidth() / 100 * parseInt(width)); } width = Math.max(this.minWidth, width); this.width = width; this.element.css("width", width || ""); if (!this.isGroup) { this.cells.forEach(function (cell) { cell.setWidth(width); }); } if (this.parent.isGroup) { this.parent.matchChildWidths(); } //set resizable handles if (this.table.extExists("frozenColumns")) { this.table.extensions.frozenColumns.layout(); } }; Column.prototype.checkCellHeights = function () { var rows = []; this.cells.forEach(function (cell) { if (cell.row.heightInitialized) { if (cell.row.element[0].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(); }); }; Column.prototype.getWidth = function () { return this.element.outerWidth(); }; Column.prototype.getHeight = function () { return this.element.outerHeight(); }; Column.prototype.setMinWidth = function (minWidth) { this.minWidth = minWidth; this.element.css("min-width", minWidth || ""); this.cells.forEach(function (cell) { cell.setMinWidth(minWidth); }); }; Column.prototype.delete = function () { if (this.isGroup) { this.columns.forEach(function (column) { column.delete(); }); } var cellCount = this.cells.length; for (var i = 0; i < cellCount; i++) { this.cells[0].delete(); } this.element.detach(); this.table.columnManager.deregisterColumn(this); }; //////////////// Cell Management ///////////////// //generate cell for this column Column.prototype.generateCell = function (row) { var self = this; var cell = new Cell(self, row); this.cells.push(cell); return cell; }; Column.prototype.reinitializeWidth = function (force) { this.widthFixed = false; //set width if present if (typeof this.definition.width !== "undefined" && !force) { this.setWidth(this.definition.width); } //hide header filters to prevent them altering column width if (this.table.extExists("filter")) { this.table.extensions.filter.hideHeaderFilterElements(); } this.fitToData(); //show header filters again after layout is complete if (this.table.extExists("filter")) { this.table.extensions.filter.showHeaderFilterElements(); } }; //set column width to maximum cell width Column.prototype.fitToData = function () { var self = this; if (!this.widthFixed) { this.element.css("width", ""); self.cells.forEach(function (cell) { cell.setWidth(""); }); } var maxWidth = this.element.outerWidth(); if (!self.width || !this.widthFixed) { self.cells.forEach(function (cell) { var width = cell.getWidth(); if (width > maxWidth) { maxWidth = width; } }); if (maxWidth) { self.setWidthActual(maxWidth + 1); } } }; Column.prototype.deleteCell = function (cell) { var index = this.cells.indexOf(cell); if (index > -1) { this.cells.splice(index, 1); } }; //////////////// Event Bindings ///////////////// //////////////// Object Generation ///////////////// Column.prototype.getComponent = function () { return new ColumnComponent(this); }; var RowManager = function RowManager(table) { this.table = table; this.element = $("<div class='tabulator-tableHolder' tabindex='0'></div>"); //containing element this.tableElement = $("<div class='tabulator-table'></div>"); //table element this.columnManager = null; //hold column manager object this.height = 0; //hold height of table element this.firstRender = false; //handle first render this.renderMode = "classic"; //current rendering mode this.rows = []; //hold row data objects 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.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 possition 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 }; //////////////// Setup Functions ///////////////// //return containing element RowManager.prototype.getElement = function () { return this.element; }; //return table element RowManager.prototype.getTableElement = function () { return this.tableElement; }; //return position of row in table RowManager.prototype.getRowPosition = function (row, active) { if (active) { return this.activeRows.indexOf(row); } else { return this.rows.indexOf(row); } }; //link to column manager RowManager.prototype.setColumnManager = function (manager) { this.columnManager = manager; }; RowManager.prototype.initialize = function () { var self = this; self.setRenderMode(); //initialize manager self.element.append(self.tableElement); self.firstRender = true