jquery.tabulator
Version:
Interactive table generation plugin for jQuery UI
2,006 lines (1,491 loc) • 127 kB
JavaScript
/*
* 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(){
'use strict';
//polyfill for Array.find method
if (!Array.prototype.find) {
Array.prototype.find = function (predicate, thisValue) {
var arr = Object(this);
if (typeof predicate !== 'function') {
throw new TypeError();
}
for(var i=0; i < arr.length; i++) {
if (i in arr) {
var elem = arr[i];
if (predicate.call(thisValue, elem, i, arr)) {
return elem;
}
}
}
return undefined;
}
}
$.widget("ui.tabulator", {
data:[],//array to hold data for table
activeData:[],//array to hold data that is active in the DOM
selectedRows:[], //array to hold currently selected rows
selecting:false, //is selection currently happening
selectPrev:[], //hold jQuery element for previously selected row to handle changing direction when selecting
firstRender:true, //layout table widths correctly on first render
mouseDrag:false, //mouse drag tracker;
mouseDragWidth:false, //starting width of colum on mouse drag
mouseDragElement:false, //column being dragged
mouseDragOut:false, //catch to prevent mouseup on col drag triggering click on sort
sortCurCol:null,//column name of currently sorted column
sortCurDir:null,//column name of currently sorted column
filterField:null, //field to be filtered on data render
filterValue:null, //value to match on filter
filterType:null, //filter type
paginationCurrentPage:1, // pagination page
paginationMaxPage:1, // pagination maxpage
progressiveRenderTimer:null, //timer for progressiver rendering
progressiveRenderFill:false, //initial fill of a progressive rendering table
columnList:[], //an array of all acutal columns ignoring column groupings
responsiveColumnList:[], //array of responsive columns
responsiveColumnIndex:0, //current possition in responsive array
columnFrozenLeft:[], //list of frozen columns left
columnFrozenRight:[], //list of frozen columns right
loaderDiv: $("<div class='tablulator-loader'><div class='tabulator-loader-msg'></div></div>"), //loader blockout div
lang:{}, // hold current locale text
defaultLang:{ //hold default locale text
"columns":{
},
"pagination":{
"first":"First",
"first_title":"First Page",
"last":"Last",
"last_title":"Last Page",
"prev":"Prev",
"prev_title":"Prev Page",
"next":"Next",
"next_title":"Next Page",
},
"headerFilters":{
"default":"filter column...",
"columns":{
}
}
},
//setup options
options: {
colMinWidth:"40px", //minimum global width for a column
colResizable:true, //resizable columns
colVertAlign:"top", //vertical alignment of column headers
height:false, //height of tabulator
fitColumns:false, //fit colums to width of screen;
movableCols:false, //enable movable columns
movableRows:false, //enable movable rows
movableRowHandle:"<div></div><div></div><div></div>", //handle for movable rows
locale:"en-gb", //durrent system language
langs:{},
persistentLayout:false, //store cookie with column _styles
persistentLayoutID:"", //id for stored cookie
pagination:false, //enable pagination
paginationSize:false, //size of pages
paginationElement:false, //element to hold pagination numbers
paginationDataReceived:{ //pagination data received from the server
"current_page":"current_page",
"last_page":"last_page",
"data":"data",
},
paginationDataSent:{ //pagination data sent to the server
"page":"page",
"size":"size",
"sort":"sort",
"sort_dir":"sort_dir",
"filter":"filter",
"filter_value":"filter_value",
"filter_type":"fitler_type",
},
paginator:false, //pagination url string builder
progressiveRender:false, //enable progressive rendering
progressiveRenderSize:20, //block size for progressive rendering
progressiveRenderMargin:200, //disance in px before end of scroll before progressive render is triggered
headerFilterPlaceholder: "", //placeholder text to display in header filters
headerFilterColumnPlaceholders:{}, //placeholders by column
tooltips: false, //Tool tip value
tooltipsHeader: false, //Tool tip for headers
columns:false,//store for colum header info
data:false, //store for initial table data if set at construction
index:"id",
sortable:true, //global default for sorting
dateFormat: "dd/mm/yyyy", //date format to be used for sorting
sortBy:"id", //defualt column to sort by
sortDir:"desc", //default sort direction
groupBy:false, //enable table grouping and set field to group by
groupStartOpen:true, //starting state of group
groupHeader:function(value, count, data){ //header layout function
return value + "<span>(" + count + " " + ((count === 1) ? "item" : "items") + ")</span>";
},
rowFormatter:false, //row formatter callback
addRowPos:"bottom", //position to insert blank rows, top|bottom
selectable:"highlight", //highlight rows on hover
selectableRollingSelection:true, //roll selection once maximum number of selectable rows is reached
selectablePersistence:true, // maintain selection when table view is updated
selectableCheck:function(data, row){return true;}, //check wheather row is selectable
responsiveLayout:false, //enable responsive column layout
ajaxURL:false, //url for ajax loading
ajaxParams:{}, //params for ajax loading
ajaxConfig:"get", //ajax request type
showLoader:true, //show loader while data loading
loader:"<div class='tabulator-loading'>Loading Data</div>", //loader element
loaderError:"<div class='tabulator-error'>Loading Error</div>", //loader element
//Callbacks from events
rowClick:function(){},
rowDblClick:function(){},
rowAdded:function(){},
rowDeleted:function(){},
rowContext:function(){},
rowMoved:function(){},
rowUpdated:function(){},
rowSelectionChanged:function(){},
cellEdited:function(){},
colMoved:function(){},
colTitleChanged:function(){},
dataLoading:function(){},
dataLoaded:function(){},
dataLoadError:function(){},
dataEdited:function(){},
ajaxResponse:false,
dataFiltering:function(){},
dataFiltered:function(){},
dataSorting:function(){},
dataSorted:function(){},
renderStarted:function(){},
renderComplete:function(){},
pageLoaded:function(){},
localized:function(){},
tableBuilding:function(){},
tableBuilt:function(){},
},
////////////////// Element Construction //////////////////
//constructor
_create: function(){
var self = this;
var element = self.element;
//initialize arrays
self.selectedRows = [];
self.selectPrev = [];
self.columnList = [];
//prevent column array being copied over when not explicitly set
if(!self.options.columns){
self.options.columns = [];
}
if(element.is("table")){
self._parseTable();
}else{
self._buildElement();
}
},
//parse table element to create data set
_parseTable:function(){
var self = this;
var element = self.element;
var options = self.options;
var rows = $("tbody tr", element);
var headers = $("th", element);
var hasIndex = false;
var columns = options.columns;
//find column if it has already been defined
function search(title){
var match = false;
$.each(columns, function(index, column) {
if(column.title === title){
match = column;
return false;
}
});
return match;
}
//get attributes of cell
var attributes = element[0].attributes;
function attribValue(value){
if(value === "true"){
return true;
}
if(value === "false"){
return false;
}
return value;
}
//check for tablator inline options
for(var index in attributes){
var attrib = attributes[index];
var name;
if(attrib && attrib.name && attrib.name.indexOf("tabulator-") === 0){
name = attrib.name.replace("tabulator-", "");
for(var key in options){
if(key.toLowerCase() == name){
options[key] = attribValue(attrib.value);
}
}
}
}
//build columns from table header if they havnt been set;
if(headers.length){
//list of possible attributes
var attribList = ["title", "field", "align", "width", "minWidth", "frozen", "sortable", "sorter", "formatter", "onClick", "onDblClick", "onContext", "editable", "editor", "visible", "cssClass", "tooltip", "tooltipHeader", "editableTitle", "headerFilter", "mutator", "mutateType", "accessor"];
//create column array from headers
headers.each(function(index){
var header = $(this);
var exists = false;
var attributes = header[0].attributes;
var col = search(header.text());
if(col){
exists = true;
}else{
col = {title:header.text()};
}
if(!col.field) {
col.field = header.text().toLowerCase().replace(" ", "_");
}
$("td:eq(" + index + ")", rows).data("field", col.field)
var width = header.attr("width");
if(width && !col.width) {
col.width = width;
}
if(col.field == options.index){
hasIndex = true;
}
//check for tablator inline options
for(var index in attributes){
var attrib = attributes[index];
var name;
if(attrib && attrib.name && attrib.name.indexOf("tabulator-") === 0){
name = attrib.name.replace("tabulator-", "");
attribList.forEach(function(key){
if(key.toLowerCase() == name){
col[key] = attribValue(attrib.value);
}
});
}
}
if(!exists){
columns.push(col)
}
});
}else{
//create blank table headers
headers = $("tr:first td", element);
headers.each(function(index){
var col = {title:"", field:"col" + index};
$("td:eq(" + index + ")", rows).data("field", col.field)
var width = $(this).attr("width");
if(width){
col.width = width;
}
columns.push(col);
});
}
self.data = [];
//iterate through table rows and build data set
rows.each(function(rowIndex){
var item = {};
//create index if the dont exist in table
if(!hasIndex){
item[options.index] = rowIndex;
}
//add row data to item
$("td", $(this)).each(function(colIndex){
item[$(this).data("field")] = $(this).html();
});
self.data.push(item);
});
//create new element
var newElement = $("<div></div>");
//transfer attributes to new element
var attributes = element.prop("attributes");
// loop through attributes and apply them on div
$.each(attributes, function(){
newElement.attr(this.name, this.value);
});
// replace table with div element
element.replaceWith(newElement);
options.data = self.data;
newElement.tabulator(options);
},
//build tabulator element
_buildElement: function(){
var self = this;
var options = self.options;
var element = self.element;
options.tableBuilding();
//set current locale
self.setLocale(self.options.locale);
//// backwards compatability options adjustments ////
//old persistan column layout adjustment
if( typeof options.columnLayoutCookie != 'undefined'){
options.persistentLayout = options.columnLayoutCookie;
options.persistentLayoutID = options.columnLayoutCookieID;
}
//ajax type backwards compatability
if(options.ajaxType){
options.ajaxConfig = options.ajaxType;
}
/////////////////////////////////////////////////////
//setup persistent layout storage if needed
if(self.options.persistentLayout){
//determine persistent layout storage type
self.options.persistentLayout = self.options.persistentLayout !== true ? self.options.persistentLayout : (typeof window.localStorage !== 'undefined' ? "local" : "cookie");
//set storage tag
self.options.persistentLayoutID = "tabulator-" + (self.options.persistentLayoutID ? self.options.persistentLayoutID : self.element.attr("id") ? self.element.attr("id") : "");
}
options.colMinWidth = isNaN(options.colMinWidth) ? options.colMinWidth : options.colMinWidth + "px";
if(options.height){
options.height = isNaN(options.height) ? options.height : options.height + "px";
element.css({"height": options.height});
}
element.addClass("tabulator").attr("role", "grid");
element.empty();
self.header = $("<div class='tabulator-header'></div>")
self.tableHolder = $("<div class='tabulator-tableHolder'></div>");
var scrollTop = 0;
var scrollLeft = 0;
self.tableHolder.scroll(function(){
//scroll header along with table body
var holder = $(this);
var left = holder.scrollLeft();
self.header.scrollLeft(left);
var hozAdjust = 0;
//adjust for vertical scrollbar moving table when present
var scrollWidth = self.header[0].scrollWidth - self.element.innerWidth();
if(left > scrollWidth){
hozAdjust = left - scrollWidth
self.header.css("margin-left", -(hozAdjust));
}else{
self.header.css("margin-left", 0);
}
//keep frozen columns fixed in position
self._calcFrozenColumnsPos(hozAdjust + 3);
//trigger progressive rendering on scroll
if(self.options.progressiveRender && scrollTop != holder.scrollTop() && scrollTop < holder.scrollTop()){
if(holder[0].scrollHeight - holder.innerHeight() - holder.scrollTop() < self.options.progressiveRenderMargin){
if(self.options.progressiveRender == "remote"){
if(self.paginationCurrentPage <= self.paginationMaxPage){
self._renderTable(true);
}
}else{
if(self.paginationCurrentPage < self.paginationMaxPage){
self.paginationCurrentPage++;
self._renderTable(true);
}
}
}
}
scrollTop = holder.scrollTop();
});
//create scrollable table holder
self.table = $("<div class='tabulator-table'></div>");
//build pagination footer if needed
if(options.pagination){
if(options.pagination === true){
options.pagination = "local"; //convert old pagination style to new
}
if(!options.paginationElement){
options.paginationElement = $("<div class='tabulator-footer'></div>");
self.footer = options.paginationElement;
}
self.paginator = $("<span class='tabulator-paginator'><span class='tabulator-page' data-page='first' role='button' aria-label='" + self.lang.pagination.first_title + "' title='" + self.lang.pagination.first_title + "'>" + self.lang.pagination.first + "</span><span class='tabulator-page' data-page='prev' role='button' aria-label='" + self.lang.pagination.prev_title + "' title='" + self.lang.pagination.prev_title + "'>" + self.lang.pagination.prev + "</span><span class='tabulator-pages'></span><span class='tabulator-page' data-page='next' role='button' aria-label='" + self.lang.pagination.next_title + "' title='" + self.lang.pagination.next_title + "'>" + self.lang.pagination.next + "</span><span class='tabulator-page' data-page='last' role='button' aria-label='" + self.lang.pagination.last_title + "' title='" + self.lang.pagination.last_title + "'>" + self.lang.pagination.last + "</span></span>");
self.paginator.on("click", ".tabulator-page", function(){
if(!$(this).hasClass("disabled")){
self.setPage($(this).data("page"));
}
});
options.paginationElement.append(self.paginator);
}
//layout columns
if(options.persistentLayout){
self._getPersistentCol();
}else{
self._colLayout();
}
},
//set options
_setOption: function(option, value){
var self = this;
//block update if option cannot be updated this way
if(["columns"].indexOf(option) > -1){
return false;
}
//set option to value
$.Widget.prototype._setOption.apply(this, arguments);
//trigger appropriate table response
if(["colMinWidth", "colResizable", "fitColumns", "movableCols", "movableRows", "movableRowHandle", "sortable", "groupBy", "groupHeader", "rowFormatter", "selectable"].indexOf(option) > -1){
//triger rerender
self._renderTable();
}else if(["height", "pagination", "paginationSize", "tooltips"].indexOf(option) > -1){
//triger render/reset page
if(self.options.pagination){
self.setPage(1);
}else{
self._renderTable();
}
}else if(["dateFormat", "sortBy", "sortDir"].indexOf(option) > -1){
//trigger sort
if(self.sortCurCol){
self.sort(self.sortCurCol, self.sortCurDir);
}
}else if(["index"].indexOf(option) > -1){
//trigger reparse data
self._parseData(self.data);
}else if(["paginationElement"].indexOf(option) > -1){
//trigger complete redraw
}
},
////////////////// Localization Functions //////////////////
//set current locale
setLocale:function(desiredLocale){
var self = this;
var locale = false; //hold the matching locale
//fill in any matching languge 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];
}
}
}
//determing correct locale to load
if(desiredLocale === true && navigator.language){
//get local from system
desiredLocale = navigator.language.toLowerCase();
}
if(desiredLocale){
if(self.options.langs[desiredLocale]){
locale = desiredLocale;
}else{
//see if matching top level local is present
var prefix = desiredLocale.split("-")[0];
if(self.options.langs[prefix]){
locale = prefix;
}
}
}
if(self.options.headerFilterPlaceholder){
self.defaultLang.headerFilters.default = self.options.headerFilterPlaceholder;
}
if(self.options.headerFilterColumnPlaceholders){
self.defaultLang.headerFilters.columns = self.options.headerFilterColumnPlaceholders;
}
//load default lang template
self.lang = $.extend(true, [], self.defaultLang);
if(locale){
traverseLang(self.options.langs[locale], self.lang);
}
self.options.locale = locale;
//update ui elements that need translating
if(!self.firstRender){
self._updateLocaleText();
}
//triger localized callback
self.options.localized(locale, self.lang);
return locale;
},
//quick update of elements with local based text
_updateLocaleText:function(){
var self = this;
//update column titles
self.columnList.forEach(function(column){
if(column.field){
$(".tabulator-col[data-field=" + column.field + "] .tabulator-col-title", self.header).text(self.lang.columns[column.field] || column.title);
}
});
//update pagination if enabled
if(self.options.pagination){
for(var prop in self.lang.pagination){
var propParts = prop.split("_");
var element = $(".tabulator-paginator .tabulator-page[data-page=" + propParts[0] + "]", self.element);
if (propParts.length > 1){
element.attr("title",self.lang.pagination[prop])
.attr("aria-label",self.lang.pagination[prop]);
}else{
element.text(self.lang.pagination[prop]);
}
}
}
//redraw incase column headers change width
self.redraw(true);
},
//return the current locale
getLocale:function(){
var self = this;
return self.options.locale;
},
//return the language definitions for the curent locale
getLang:function(){
var self = this;
var lang = self.options.langs[self.options.locale]
return lang ? lang : false;
},
////////////////// General Public Functions //////////////////
//get number of elements in dataset
dataCount:function(){
return this.data.length;
},
//redraw list without updating data
redraw:function(fullRedraw){
var self = this;
//redraw columns
if(self.options.fitColumns || self.options.responsiveLayout || fullRedraw){
self._colRender();
}
//reposition loader if present
if(self.element.innerHeight() > 0){
var msg = $(".tabulator-loader-msg", self.loaderDiv);
msg.css({"margin-top":(self.element.innerHeight() / 2) - (msg.outerHeight()/2)})
}
self._calcFrozenColumnsPos();
//trigger row restyle
self._styleRows(true);
if(fullRedraw){
self._renderTable();
}
},
//trigger file download
download:function(type, filename, options){
var self = this;
//create temporary link element to trigger download
var element = document.createElement('a');
if(typeof type === "function"){
//create the download link
element.setAttribute('href', type(self.columnList, self.activeData, options));
}else{
switch(type){
case "csv":
var delimiter = options && options.delimiter ? options.delimiter : ",";
//get field lists
var titles = [];
var fields = [];
self.columnList.forEach(function(column){
if(column.field){
titles.push('"' + String(column.title).split('"').join('""') + '"');
fields.push(column.field);
}
})
//generate header row
var fileContents = [titles.join(delimiter)];
//generate each row of the table
self.activeData.forEach(function(row){
var rowData = [];
fields.forEach(function(field){
var value = typeof row[field] == "object" ? JSON.stringify(row[field]) : row[field];
//escape uotation marks
rowData.push('"' + String(value).split('"').join('""') + '"');
})
fileContents.push(rowData.join(delimiter));
});
//create the download link
element.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(fileContents.join("\n")));
break;
case "json":
var fileContents = JSON.stringify(self.activeData, null, '\t');
//create the download link
element.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(fileContents));
break;
default:
return false;
break;
}
}
//set file title
element.setAttribute('download', filename || "Tabulator." + (typeof type === "function" ? "txt" : type));
//trigger download
element.style.display = 'none';
document.body.appendChild(element);
element.click();
//remove temporary link element
document.body.removeChild(element);
return true;
},
////////////////// Column Manipulation //////////////////
//set column style cookie
_setPersistentCol:function(){
var self = this;
//parse styles from columns
function parseCols(columns){
var cols = [];
columns.forEach(function(column){
var style = {
field: column.field,
width: column.width,
visible: column.visible,
};
if(column.columns){
style.title = column.title;
style.columns = parseCols(column.columns);
}
cols.push(style);
})
return cols;
}
//create array of column styles only
var columnStyles = parseCols(self.options.columns);
//JSON format column data
var data = JSON.stringify(columnStyles);
if(self.options.persistentLayout == "cookie"){
//set cookie expiration far in the future
var expDate = new Date();
expDate.setDate(expDate.getDate() + 10000);
//save cookie
document.cookie = self.options.persistentLayoutID + "=" + data + "; expires=" + expDate.toUTCString();
}else{
//save data to local storage
localStorage.setItem(self.options.persistentLayoutID, data);
}
},
//set Column style cookie
_getPersistentCol:function(){
var self = this;
var colString = "";
if(self.options.persistentLayout == "cookie"){
//find cookie
var cookie = document.cookie;
var cookiePos = cookie.indexOf(self.options.persistentLayoutID + "=");
//if cookie exists, decode and load column data into tabulator
if(cookiePos > -1){
cookie = cookie.substr(cookiePos);
var end = cookie.indexOf(";");
if(end > -1){
cookie = cookie.substr(0, end);
}
colString = cookie.replace(self.options.persistentLayoutID + "=", "");
}
}else{
//find loocal storage value
var colString = localStorage.getItem(self.options.persistentLayoutID);
}
if(colString){
self.setColumns(JSON.parse(colString), true);
}else{
self._colLayout();
}
},
//set tabulator columns
setColumns: function(columns, update){
var self = this;
//update column properties
function updateCols(oldCols, newCols){
newCols.forEach(function(item, to){
var type = item.columns ? "group" : (item.field ? "field" : "object");
var from = search(oldCols, item, type);
if(from !== false){
var column = oldCols.splice(from, 1)[0];
column.width = item.width;
column.visible = item.visible;
oldCols.splice(to , 0, column);
if(type == "group"){
updateCols(column.columns, item.columns);
}
}
});
}
//find matching column
function search(columns, col, type){
var match = false;
$.each(columns, function(i, column){;
switch(type){
case "group":
if(col.title === column.title && col.columns.length === column.columns.length){
match = i;
}
break;
case "field":
if(col.field === column.field){
match = i;
}
break;
case "object":
if(col === column){
match = i;
}
break;
}
if(match !== false){
return false;
}
});
return match;
}
if(Array.isArray(columns)){
//if updating columns work through exisiting column data
if(update){
updateCols(self.options.columns, columns);
}else{
// if replaceing columns, replace columns array with new
self.options.columns = columns;
}
//Trigger Redraw
self._colLayout(true);
if(self.options.persistentLayout){
self._setPersistentCol();
}
}
},
//get tabulator columns
getColumns: function(){
var self = this;
return self.options.columns;
},
//add column
addColumn:function(newCol, before, field){
var self = this;
if(newCol){
if(field){
if(typeof field == "string"){
var match = self._findColumn(field);
if(match){
match.parent.splice(before ? match.index : match.index + 1, 0, newCol);
}
}else{
var match = self._findColumn(self.columnList[field]);
if(match){
match.parent.splice(before ? match.index : match.index + 1, 0, newCol);
}
}
}else{
self.options.columns.splice(before ? 0 : self.options.columns.length + 1, 0, newCol);
}
self.setColumns(self.options.columns);
}
},
//delete column
deleteColumn:function(field){
var self = this;
if(field){
var match = self._findColumn(field);
if(match){
match.parent.splice(match.index, 1);
}
self.setColumns(self.options.columns);
}
},
//traverse all columns and trigger callback on each column
_traverseColumns:function(callback, columns){
var self = this;
columns = columns ? columns : self.options.columns;
$.each(columns, function(i, column){
if(column.columns){
self._traverseColumns(callback, column.columns);
}else{
callback(column);
}
});
},
//find column
_findColumn:function(field, columns){
var self = this;
var match = false;
columns = columns ? columns : self.options.columns;
$.each(columns, function(i, column){
switch(typeof field){
case "object":
if(column == field){
match = {parent:columns, column:column, index:i};
return false;
}
break;
case "function":
if(field(column)){
match = {parent:columns, column:column, index:i};
return false;
}
break;
default:
if(column.field == field){
match = {parent:columns, column:column, index:i};
return false;
}
}
if(column.columns){
var children = self._findColumn(field, column.columns);
if(children){
match = children;
return false;
}
}
});
return match;
},
_setColGroupVisibility:function(column, visibility){
var self = this;
var group = column.parent().closest(".tabulator-col-group");
console.log("group", visibility, group.length, group)
if(group.length){
var visCols = $(".tabulator-col:visible", group);
if(visCols.length || visibility){
group.show();
}else{
group.hide();
}
self._setColGroupVisibility(group, visibility);
}
},
_setColVisibility:function(column, visibility){
var self = this;
if(column){
column.visible = visibility;
var elements = $(".tabulator-col[data-index=" + column.index + "], .tabulator-cell[data-index=" + column.index + "]", self.element)
if(visibility){
elements.show();
}else{
elements.hide();
}
self._setColGroupVisibility($(".tabulator-col[data-index=" + column.index + "]"), visibility);
self._renderTable();
if(self.options.persistentLayout){
self._setPersistentCol();
}
}
},
//hide column
hideCol: function(field){
var self = this;
var match = self._findColumn(field);
if(match){
self._setColVisibility(match.column, false);
}
},
//show column
showCol: function(field){
var self = this;
var match = self._findColumn(field);
if(match){
self._setColVisibility(match.column, true);
}
},
//toggle column visibility
toggleCol: function(field){
var self = this;
var match = self._findColumn(field);
if(match){
self._setColVisibility(match.column, !match.column.visible);
}
},
////////////////// Row Manipulation //////////////////
//find row in data set
_findRow: function(id){
var self = this;
var row;
if(typeof id == "object"){
row = self.data.find(function(item){
return item === id;
});
}else{
row = self.data.find(function(item){
return item[self.options.index] == id;
});
}
return row;
},
//delete row from table by id
deleteRow: function(item){
var self = this;
var itemType = typeof item;
var id = (itemType == "number" || itemType == "string") ? item : item.data("data")[self.options.index];
var row = (itemType == "number" || itemType == "string") ? $("[data-id='" + item + "']", self.element) : item;
if(row.length){
var rowData = row.data("data");
rowData.tabulator_delete_row = true;
self.deselectRow(row);
//remove from data
var line = self.data.find(function(item){
return item.tabulator_delete_row;
});
if(line){
line = self.data.indexOf(line);
if(line > -1){
//remove row from data
self.data.splice(line, 1);
}
}
//remove from active data
line = self.activeData.find(function(item){
return item.tabulator_delete_row;
});
if(line){
line = self.activeData.indexOf(line);
if(line > -1){
//remove row from data
self.activeData.splice(line, 1);
}
}
var group = row.closest(".tabulator-group");
row.remove();
if(self.options.groupBy){
var rows = $(".tabulator-row", group);
var length = rows.length;
if(length){
var data = [];
rows.each(function(){
data.push($(this).data("data"));
});
var header = $(".tabulator-group-header", group);
var arrow = $(".tabulator-arrow", header).clone(true,true);
header.empty();
header.append(arrow).append(self.options.groupHeader(group.data("value"), length, data));
}else{
group.remove();
}
}
//style table rows
self._styleRows();
//align column widths
self._colRender(!self.firstRender);
self.options.rowDeleted(id);
self.options.dataEdited(self.data);
if(self.options.pagination){
self.paginationMaxPage = Math.max(1, Math.ceil(self.activeData.length/self.options.paginationSize));
self._layoutPageSelector();
self.setPage(self.paginationCurrentPage);
}
}
},
//add blank row to table
addRow:function(item, top, indexRowVal){
var self = this;
var line, activeLine, indexRow, indexID;
if(item){
item[self.options.index] = item[self.options.index] ? item[self.options.index] : 0;
}else{
item = {id:0};
}
//Apply mutators if present
item = self._mutateData(item);
//create blank row
var row = self._renderRow(item);
var top = typeof top == "undefined" ? self.options.addRowPos : top;
if(top === "top"){
top = true;
}
if(top === "bottom"){
top = false;
}
//work out row to put new row next to
if(typeof indexRowVal !== "undefined"){
indexRow = indexRowVal instanceof jQuery ? indexRowVal : $(".tabulator-row[data-id='" + indexRowVal + "']", self.element);
indexID = indexRowVal instanceof jQuery ? indexRowVal.data("id") : indexRowVal;
console.log("index defined", indexID, indexRow)
}
// initial place to append the row in the table, by default in the table row container
var newRowContainer = self.table;
// if group are used, look for the corresponding row of the group.
if(!indexRow || !indexRow.length){
//create new line at top or bottom of table or group
if(self.options.groupBy){
var groupVal = typeof(self.options.groupBy) == "function" ? self.options.groupBy(item) : item[self.options.groupBy];
var group = $(".tabulator-group[data-value='" + groupVal + "']", self.table);
//if group does not exist, build it
if(group.length == 0){
group = self._renderGroup(groupVal);
self.table.append(group);
self._renderGroupHeader(group);
}
//set the place to append the row to the corresponding group container.
newRowContainer = group;
}
//append to top or bottom of table based on preference
if(top){
if (self.activeData !== self.data){
self.activeData.unshift(item);
}
self.data.unshift(item);
newRowContainer.prepend(row);
}else{
if (self.activeData !== self.data){
self.activeData.push(item);
}
self.data.push(item);
newRowContainer.append(row);
}
}else{
//insert new line next to existing row
if (self.activeData !== self.data){
//remove from active data
activeLine = self.activeData.find(function(item){
return item[self.options.index] == indexID;
});
activeLine = self.activeData.indexOf(activeLine);
}
line = self.data.find(function(item){
return item[self.options.index] == indexID;
});
line = self.data.indexOf(line);
if(line > -1){
if(top){
if (self.activeData !== self.data){
self.activeData.splice(activeLine, 0, item);
}
self.data.splice(line, 0, item);
if(indexRow){
indexRow.before(row);
}
}else{
if (self.activeData !== self.data){
self.activeData.splice(activeLine+1, 0, item);
}
self.data.splice(line+1, 0, item);
if(indexRow){
indexRow.after(row);
}
}
}
}
//align column widths
self._colRender(!self.firstRender);
//style table rows
self._styleRows();
//triger event
self.options.rowAdded(item, row);
self.options.dataEdited(self.data);
},
//update row if it exits or create if it dosnt
updateOrAddRow:function(index, item){
var self = this;
if(!self.updateRow(index, item)){
self.addRow(item);
}
},
//update row data
updateRow:function(index, item, bulk){
var self = this;
var itemType = typeof index;
console.log("type", itemType, index)
var id = (itemType == "number" || itemType == "string") ? index : index.data("data")[self.options.index];
var row = (itemType == "number" || itemType == "string") ? $("[data-id='" + index + "']", self.element) : index;
if(row.length){
var rowData = row.data("data");
//Apply mutators if present
item = self._mutateData(item);
//makesure there are differences between the new and old data before updating
if(JSON.stringify(rowData) !== JSON.stringify(item)){
//update row data
for (var attrname in item) { rowData[attrname] = item[attrname]; }
//render new row
var newRow = self._renderRow(rowData);
//replace old row with new row
row.replaceWith(newRow);
if(!bulk){
//align column widths
self._colRender(!self.firstRender);
//style table rows
self._styleRows();
}
//triger event
self.options.rowUpdated(id, item, newRow);
if(!bulk){
self.options.dataEdited(self.data);
}
}
return true;
}
return false;
},
//return jQuery object for row
getRow:function(index){
var self = this;
var row = $(".tabulator-row[data-id='" + index + "']", self.element);
return row.length ? row : false;
},
//scroll to sepcified row
scrollToRow:function(item){
var self = this;
var itemType = typeof item;
var id = (itemType == "number" || itemType == "string") ? item : item.data("data")[self.options.index];
var row = (itemType == "number" || itemType == "string") ? $("[data-id='" + item + "']", self.element) : item;
if(row){
self.tableHolder.stop();
self.tableHolder.animate({
scrollTop: row.offset().top - self.tableHolder.offset().top + self.tableHolder.scrollTop()
}, 1000);
}
},
////////////////// Data Manipulation //////////////////
//get array of data from the table
getData:function(filteredData){
var self = this;
//clone data array with deep copy to isolate internal data from returned result
var outputData = $.extend(true, [], filteredData === true ? self.activeData: self.data);
//check for accessors and return the processed data
return self._applyAccessors(outputData);
},
getRowData:function(row){
var self = this;
var rowData;
if(row instanceof jQuery){
rowData = row.data("data");
}else{
rowData = self.data.find(function(item){
return item[self.options.index] === row;
});
}
//clone data object with deep copy to isolate internal data from returned result
var outputData = $.extend(true, [], rowData || []);
//check for accessors and return the processed data
return self._applyAccessors([outputData])[0];
},
//update existing data
updateData:function(data, orAdd){
var self = this;
if(data){
data.forEach(function(item){
//update each row in turn
var success = self.updateRow(item[self.options.index], item, true);
if(!success){
self.addRow(item);
}
});
//align column widths
self._colRender(!self.firstRender);
//style table rows
self._styleRows();
self.options.dataEdited(self.data);
}
},
//update data if it exits or create if it dosnt
updateOrAddData:function(data){
this.updateData(data, true);
},
//apply any column accessors to the data before returing the result
_applyAccessors:function(data){
var self = this;
self._traverseColumns(function(col){
if(col.accessor){
var accessor = typeof col.accessor === "function" ? col.accessor : self.accessors[col.accessor];
data.forEach(function(item, j){
item[col.field] = accessor(item[col.field], item);
});
}
})
return data;
},
///////////////// Pagination Data Loading //////////////////
//parse paginated data
_parsePageData:function(data, update){
var self = this;
var options = self.options;
self.paginationMaxPage = parseInt(data[options.paginationDataReceived.last_page]);
self._layoutPageSelector();
self._parseData(data[options.paginationDataReceived.data], update);
},
_getRemotePageData:function(update){
var self = this;
var options = self.options;
if(typeof options.paginator == "function"){
var url = options.paginator(options.ajaxURL, self.paginationCurrentPage, options.paginationSize, options.ajaxParams);
self._getAjaxData(url, {}, null, update);
}else{
var pageParams = {};
//clone ajax params into page request params
for (var attrname in options.ajaxParams) { pageParams[attrname] = options.ajaxParams[attrname]; }
//set page number
pageParams[options.paginationDataSent.page] = self.paginationCurrentPage;
//set page size if defined
if(options.paginationSize){
pageParams[options.paginationDataSent.size] = options.paginationSize;
}
//set sort data if defined
if(self.sortCurCol && typeof self.sortCurCol.field === "string"){
pageParams[options.paginationDataSent.sort] = self.sortCurCol.field;
pageParams[options.paginationDataSent.sort_dir] = self.sortCurDir;
}
//set filter data if defined
if(self.filterField && typeof self.filterField === "string"){
pageParams[options.paginationDataSent.filter] = self.filterField;
pageParams[options.paginationDataSent.filter_value] = self.filterValue;
pageParams[options.paginationDataSent.filter_type] = self.filterType;
}
self._getAjaxData(options.ajaxURL, pageParams, null, update);
}
},
////////////////// Data Loading //////////////////
//load data
setData:function(data, params, config){
var self = this;
self.options.dataLoading(data, params);
if(params){
self.options.ajaxParams = params;
}
//show loader if needed
self._showLoader(this, this.options.loader);
if(typeof(data) === "string"){
if (data.indexOf("{") == 0 || data.indexOf("[") == 0){
//data is a json encoded string
self._parseData(jQuery.parseJSON(data));
}else{
self.options.ajaxURL = data;
if(self.options.pagination == "remote"){
self.setPage(1);
}else{
//assume data is url, make ajax call to url to get data
self._getAjaxData(data, self.options.ajaxParams, config);
}
}
}else{
if(data){
//asume data is already an object
self._parseData(data);
}else{
//no data provided, check if ajaxURL is present;
if(this.options.ajaxURL){
if(self.options.pagination == "remote"){
self.setPage(1);
}else{
self._getAjaxData(this.options.ajaxURL, self.options.ajaxParams, config);
}
}else{
//empty data
self._parseData([]);
}
}
}
},
//clear data
clear:function(){
this.table.empty();
this.data = [];
this._filterData();
},
//get json data via ajax
_getAjaxData:function(url, params, config, update){
var self = this;
var options = self.options;
var ajaxConfig = {
url: url,
type: "GET",
data: params || self.options.ajaxParams,
async: true,
dataType:"json",
success: function (data){
var returnedData = typeof self.options.ajaxResponse === "function" ? self.options.ajaxResponse(url, params ? params : self.options.ajaxParams, data) : data;
if(self.options.pagination == "remote" || self.options.progressiveRender == "remote"){
self._parsePageData(returnedData, update);
}else{
self._parseData(returnedData, update);
}
},
error: function (xhr, textStatus, errorThrown){
console.error("Tablulator ERROR (ajax get): " + xhr.status + " - " + errorThrown);
self.options.dataLoadError(xhr, textStatus, errorThrown);
self._showLoader(self, self.options.loaderError);
},
}
function configUpdate(configData){
if (configData){
if(typeof configData == "string"){
ajaxConfig.type = configData;
}else if(typeof configData == "object"){
for(var key in configData){
ajaxConfig[key] = configData[key];
}
}
}
}
//configure ajax request
configUpdate(self.options.ajaxConfig);
configUpdate(config);
$.ajax(ajaxConfig);
},
//parse and index data
_parseData:function(data, update){
var self = this;
if(Array.isArray(data)){
var newData = [];
if(data.length){
if(typeof(data[0][self.options.index]) == "undefined"){
self.options.index = "_index";
$.each(data, function(i, item){
newData[i] = item;
newData[i]["_index"] = i;
});
}else{
$.each(data, function(i, item){
newData.push(item);
});
}
}
self.options.dataLoaded(newData);
var mutatedData = self._mutateData(newData);
if(update){
self.data = mutatedData;
self.data = self.data.concat(mutatedData);
self.activeData = self.activeData.concat(mutatedData);
if(self.progressiveRenderFill){
self._renderTable(true);
}
}else{
self.data = mutatedData;
self._clearSelection();
//filter incomming data
self._filterData();
}
}
},
//applu mutators if present
_mutateData:function(data){
var self = this;
self._traverseColumns(function(col){
if(col.mutator && col.mutateType !== "edit"){
var mutator = typeof col.mutator === "function" ? col.mutator : self.mutators[col.mutator];
if(Array.isArray(data)){
data.forEach(function(item, j){
if(typeof item[col.field] != "undefined"){
item[col.field] = mutator(item[col.field], "data", item);
}
});
}else{
if(typeof data[col.field] != "undefined"){
data[col.field] = mutator(data[col.field], "data", data);
}
}
}
});
return data;
},
////////////////// Data Filtering //////////////////
//filter data in table
setFilter:function(field, type, value){
var self = this;
if(!self.options.selectablePersistence){
self._clearSelection();
}
self.options.dataFiltering(field, type, value);
//set filter
if(field){
//set filter
self.filterField = field;
self.filterType = typeof(value) == "undefined" ? "=" : type;
self.filterValue = typeof(value) == "undefined" ? type : value;
}else{
//clear filter
self.filterField = null;
self.filterType = null;
self.filterValue = null;
}
//render table
this._filterData();
},
//clear filter
clearFilter:function(){
var self = this;
self.filterField = null;
self.filterType = null;
self.filterValue = null;
//clear header filter values
var headerFilters = $(".tabulator-header-filter", self.header);
$("input, select", headerFilters).val("");
//render table
this._filterData();
},
//get current filter info
getFilter:function(){
var self = this;
if(self.filterField){
var filter = {
"field":self.filterField,
"type":self.filterType,
"value":self.filterValue,
};
return filter;
}else{
return false;
}
},
//filter data set
_filterData:function(){
var self = this;
//filter data set
if(self.filterField){
self.activeData = self.data.filter(function(row){
return self._filterRow(row);
});
}else{
self.activeData = self.data;
}
//set the max pages available given the filter results
if(self.options.pagination == "local"){
self.paginationMaxPage = Math.max(1, Math.ceil(self.activeData.length/self.options.paginationSize));
}
self.options.dataFiltered(self.activeData, self.filterField, self.filterType, self.filterValue);
//sort or render data
if(self.sortCurCol && self.options.pagination != "remote"){
self.sort(self.sortCurCol, self.sortCurDir);
}else{
//determine pagination information / render table
if(self.options.pagination == "local"){
self.setPage(1);
}else{
self._renderTable();
}
}
},
//check if row data matches filter
_filterRow:function(row){
var self = this;
// if no filter set display row
if(!self.filterField){
return true;
}else{
if(typeof(self.filterField) == "function"){
return self.filterField(row);
}else{
var value = row[self.filterField];
var term = self.filterValue;
switch(self.filterType){
case "=": //equal to
return value == term ? true : false;
break;
case "<": //less than
return value < term ? true : false;
break;
case "<=": //less than or equal too
return value <= term ? true : false;
break;
case ">": //greater than
return value > term ? true : false;
break;
case ">=": //greater than or equal too
return value >= term ? true : false;
break;
case "!=": //not equal to
return value != term ? true : false;
break;
case "like": //text like
if(value === null){
return term === value ? true : false;
}else{
return value.toLowerCase().indexOf(term.toLowerCase()) > -1 ? true : false;
}
break;
default:
return false;
}
}
}
return false;
},
////////////////// Data Sorting //////////////////
//return the current sorter
getSort:function(){
var self = this;
return {
"field" : self.sortCurCol ? self.sortCurCol.field : false,
"dir" : self.sortCurDir || false,
};
},
//handle user clicking on column header sort
_sortClick: function(column, element){
var self