jquery.tabulator
Version:
Interactive table generation plugin for jQuery UI
1,012 lines (777 loc) • 24.1 kB
JavaScript
//public column object
var ColumnComponent = function (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(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 || " "));
});
}else{
titleElement.val(def.title || " ");
}
}else{
if(def.field){
table.extensions.localize.bind("columns|" + def.field, function(text){
self._formatColumnHeaderTitle(titleHolderElement, text || (def.title || " "));
});
}else{
self._formatColumnHeaderTitle(titleHolderElement, def.title || " ");
}
}
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(){
return title;
},
getElement:function(){
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(let 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(let 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(let 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);
};