tabulator-tables
Version:
Interactive table generation JavaScript library
591 lines (466 loc) • 14.2 kB
JavaScript
var Sort = function(table){
this.table = table; //hold Tabulator object
this.sortList = []; //holder current sort
this.changed = false; //has the sort changed since last render
};
//initialize column header for sorting
Sort.prototype.initializeColumn = function(column, content){
var self = this,
sorter = false,
colEl,
arrowEl;
switch(typeof column.definition.sorter){
case "string":
if(self.sorters[column.definition.sorter]){
sorter = self.sorters[column.definition.sorter];
}else{
console.warn("Sort Error - No such sorter found: ", column.definition.sorter);
}
break;
case "function":
sorter = column.definition.sorter;
break;
}
column.modules.sort = {
sorter:sorter, dir:"none",
params:column.definition.sorterParams || {},
startingDir:column.definition.headerSortStartingDir || "asc",
tristate: typeof column.definition.headerSortTristate !== "undefined" ? column.definition.headerSortTristate : this.table.options.headerSortTristate,
};
if(typeof column.definition.headerSort === "undefined" ? (this.table.options.headerSort !== false) : column.definition.headerSort !== false){
colEl = column.getElement();
colEl.classList.add("tabulator-sortable");
arrowEl = document.createElement("div");
arrowEl.classList.add("tabulator-col-sorter");
if(typeof this.table.options.headerSortElement == "object"){
arrowEl.appendChild(this.table.options.headerSortElement);
}else{
arrowEl.innerHTML = this.table.options.headerSortElement;
}
//create sorter arrow
content.appendChild(arrowEl);
column.modules.sort.element = arrowEl;
//sort on click
colEl.addEventListener("click", function(e){
var dir = "",
sorters=[],
match = false;
if(column.modules.sort){
if(column.modules.sort.tristate){
if(column.modules.sort.dir == "none"){
dir = column.modules.sort.startingDir;
}else{
if(column.modules.sort.dir == column.modules.sort.startingDir){
dir = column.modules.sort.dir == "asc" ? "desc" : "asc";
}else{
dir = "none";
}
}
}else{
switch(column.modules.sort.dir){
case "asc":
dir = "desc";
break;
case "desc":
dir = "asc";
break;
default:
dir = column.modules.sort.startingDir;
}
}
if (self.table.options.columnHeaderSortMulti && (e.shiftKey || e.ctrlKey)) {
sorters = self.getSort();
match = sorters.findIndex(function(sorter){
return sorter.field === column.getField();
});
if(match > -1){
sorters[match].dir = dir;
if(match != sorters.length -1){
match = sorters.splice(match, 1)[0];
if(dir != "none"){
sorters.push(match);
}
}
}else{
if(dir != "none"){
sorters.push({column:column, dir:dir});
}
}
//add to existing sort
self.setSort(sorters);
}else{
if(dir == "none"){
self.clear();
}else{
//sort by column only
self.setSort(column, dir);
}
}
self.table.rowManager.sorterRefresh(!self.sortList.length);
}
});
}
};
//check if the sorters have changed since last use
Sort.prototype.hasChanged = function(){
var changed = this.changed;
this.changed = false;
return changed;
};
//return current sorters
Sort.prototype.getSort = function(){
var self = this,
sorters = [];
self.sortList.forEach(function(item){
if(item.column){
sorters.push({column:item.column.getComponent(), field:item.column.getField(), dir:item.dir});
}
});
return sorters;
};
//change sort list and trigger sort
Sort.prototype.setSort = function(sortList, dir){
var self = this,
newSortList = [];
if(!Array.isArray(sortList)){
sortList = [{column: sortList, dir:dir}];
}
sortList.forEach(function(item){
var column;
column = self.table.columnManager.findColumn(item.column);
if(column){
item.column = column;
newSortList.push(item);
self.changed = true;
}else{
console.warn("Sort Warning - Sort field does not exist and is being ignored: ", item.column);
}
});
self.sortList = newSortList;
if(this.table.options.persistence && this.table.modExists("persistence", true) && this.table.modules.persistence.config.sort){
this.table.modules.persistence.save("sort");
}
};
//clear sorters
Sort.prototype.clear = function(){
this.setSort([]);
};
//find appropriate sorter for column
Sort.prototype.findSorter = function(column){
var row = this.table.rowManager.activeRows[0],
sorter = "string",
field, value;
if(row){
row = row.getData();
field = column.getField();
if(field){
value = column.getFieldValue(row);
switch(typeof value){
case "undefined":
sorter = "string";
break;
case "boolean":
sorter = "boolean";
break;
default:
if(!isNaN(value) && value !== ""){
sorter = "number";
}else{
if(value.match(/((^[0-9]+[a-z]+)|(^[a-z]+[0-9]+))+$/i)){
sorter = "alphanum";
}
}
break;
}
}
}
return this.sorters[sorter];
};
//work through sort list sorting data
Sort.prototype.sort = function(data){
var self = this,
sortList = this.table.options.sortOrderReverse ? self.sortList.slice().reverse() : self.sortList,
sortListActual = [],
rowComponents = [],
lastSort;
if(self.table.options.dataSorting){
self.table.options.dataSorting.call(self.table, self.getSort());
}
self.clearColumnHeaders();
if(!self.table.options.ajaxSorting){
//build list of valid sorters and trigger column specific callbacks before sort begins
sortList.forEach(function(item, i){
var sortObj = item.column.modules.sort;
if(item.column && sortObj){
//if no sorter has been defined, take a guess
if(!sortObj.sorter){
sortObj.sorter = self.findSorter(item.column);
}
item.params = typeof sortObj.params === "function" ? sortObj.params(item.column.getComponent(), item.dir) : sortObj.params;
sortListActual.push(item);
}
self.setColumnHeader(item.column, item.dir);
});
//sort data
if (sortListActual.length) {
self._sortItems(data, sortListActual);
}
}else{
sortList.forEach(function(item, i){
self.setColumnHeader(item.column, item.dir);
});
}
if(self.table.options.dataSorted){
data.forEach((row) => {
rowComponents.push(row.getComponent());
});
self.table.options.dataSorted.call(self.table, self.getSort(), rowComponents);
}
};
//clear sort arrows on columns
Sort.prototype.clearColumnHeaders = function(){
this.table.columnManager.getRealColumns().forEach(function(column){
if(column.modules.sort){
column.modules.sort.dir = "none";
column.getElement().setAttribute("aria-sort", "none");
}
});
};
//set the column header sort direction
Sort.prototype.setColumnHeader = function(column, dir){
column.modules.sort.dir = dir;
column.getElement().setAttribute("aria-sort", dir);
};
//sort each item in sort list
Sort.prototype._sortItems = function(data, sortList){
var sorterCount = sortList.length - 1;
data.sort((a, b) => {
var result;
for(var i = sorterCount; i>= 0; i--){
let sortItem = sortList[i];
result = this._sortRow(a, b, sortItem.column, sortItem.dir, sortItem.params);
if(result !== 0){
break;
}
}
return result;
});
};
//process individual rows for a sort function on active data
Sort.prototype._sortRow = function(a, b, column, dir, params){
var el1Comp, el2Comp, colComp;
//switch elements depending on search direction
var el1 = dir == "asc" ? a : b;
var el2 = dir == "asc" ? b : a;
a = column.getFieldValue(el1.getData());
b = column.getFieldValue(el2.getData());
a = typeof a !== "undefined" ? a : "";
b = typeof b !== "undefined" ? b : "";
el1Comp = el1.getComponent();
el2Comp = el2.getComponent();
return column.modules.sort.sorter.call(this, a, b, el1Comp, el2Comp, column.getComponent(), dir, params);
};
//default data sorters
Sort.prototype.sorters = {
//sort numbers
number:function(a, b, aRow, bRow, column, dir, params){
var alignEmptyValues = params.alignEmptyValues;
var decimal = params.decimalSeparator;
var thousand = params.thousandSeparator;
var emptyAlign = 0;
a = String(a);
b = String(b);
if(thousand){
a = a.split(thousand).join("");
b = b.split(thousand).join("");
}
if(decimal){
a = a.split(decimal).join(".");
b = b.split(decimal).join(".");
}
a = parseFloat(a);
b = parseFloat(b);
//handle non numeric values
if(isNaN(a)){
emptyAlign = isNaN(b) ? 0 : -1;
}else if(isNaN(b)){
emptyAlign = 1;
}else{
//compare valid values
return a - b;
}
//fix empty values in position
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
emptyAlign *= -1;
}
return emptyAlign;
},
//sort strings
string:function(a, b, aRow, bRow, column, dir, params){
var alignEmptyValues = params.alignEmptyValues;
var emptyAlign = 0;
var locale;
//handle empty values
if(!a){
emptyAlign = !b ? 0 : -1;
}else if(!b){
emptyAlign = 1;
}else{
//compare valid values
switch(typeof params.locale){
case "boolean":
if(params.locale){
locale = this.table.modules.localize.getLocale();
}
break;
case "string":
locale = params.locale;
break;
}
return String(a).toLowerCase().localeCompare(String(b).toLowerCase(), locale);
}
//fix empty values in position
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
emptyAlign *= -1;
}
return emptyAlign;
},
//sort date
date:function(a, b, aRow, bRow, column, dir, params){
if(!params.format){
params.format = "DD/MM/YYYY";
}
return this.sorters.datetime.call(this, a, b, aRow, bRow, column, dir, params);
},
//sort HH:mm formatted times
time:function(a, b, aRow, bRow, column, dir, params){
if(!params.format){
params.format = "HH:mm";
}
return this.sorters.datetime.call(this, a, b, aRow, bRow, column, dir, params);
},
//sort datetime
datetime:function(a, b, aRow, bRow, column, dir, params){
var format = params.format || "DD/MM/YYYY HH:mm:ss",
alignEmptyValues = params.alignEmptyValues,
emptyAlign = 0;
if(typeof moment != "undefined"){
a = moment(a, format);
b = moment(b, format);
if(!a.isValid()){
emptyAlign = !b.isValid() ? 0 : -1;
}else if(!b.isValid()){
emptyAlign = 1;
}else{
//compare valid values
return a - b;
}
//fix empty values in position
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
emptyAlign *= -1;
}
return emptyAlign;
}else{
console.error("Sort Error - 'datetime' sorter is dependant on moment.js");
}
},
//sort booleans
boolean:function(a, b, aRow, bRow, column, dir, params){
var el1 = a === true || a === "true" || a === "True" || a === 1 ? 1 : 0;
var el2 = b === true || b === "true" || b === "True" || b === 1 ? 1 : 0;
return el1 - el2;
},
//sort if element contains any data
array:function(a, b, aRow, bRow, column, dir, params){
var el1 = 0;
var el2 = 0;
var type = params.type || "length";
var alignEmptyValues = params.alignEmptyValues;
var emptyAlign = 0;
function calc(value){
switch(type){
case "length":
return value.length;
break;
case "sum":
return value.reduce(function(c, d){
return c + d;
});
break;
case "max":
return Math.max.apply(null, value) ;
break;
case "min":
return Math.min.apply(null, value) ;
break;
case "avg":
return value.reduce(function(c, d){
return c + d;
}) / value.length;
break;
}
}
//handle non array values
if(!Array.isArray(a)){
alignEmptyValues = !Array.isArray(b) ? 0 : -1;
}else if(!Array.isArray(b)){
alignEmptyValues = 1;
}else{
//compare valid values
el1 = a ? calc(a) : 0;
el2 = b ? calc(b) : 0;
return el1 - el2;
}
//fix empty values in position
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
emptyAlign *= -1;
}
return emptyAlign;
},
//sort if element contains any data
exists:function(a, b, aRow, bRow, column, dir, params){
var el1 = typeof a == "undefined" ? 0 : 1;
var el2 = typeof b == "undefined" ? 0 : 1;
return el1 - el2;
},
//sort alpha numeric strings
alphanum:function(as, bs, aRow, bRow, column, dir, params){
var a, b, a1, b1, i= 0, L, rx = /(\d+)|(\D+)/g, rd = /\d/;
var alignEmptyValues = params.alignEmptyValues;
var emptyAlign = 0;
//handle empty values
if(!as && as!== 0){
emptyAlign = !bs && bs!== 0 ? 0 : -1;
}else if(!bs && bs!== 0){
emptyAlign = 1;
}else{
if(isFinite(as) && isFinite(bs)) return as - bs;
a = String(as).toLowerCase();
b = String(bs).toLowerCase();
if(a === b) return 0;
if(!(rd.test(a) && rd.test(b))) return a > b ? 1 : -1;
a = a.match(rx);
b = b.match(rx);
L = a.length > b.length ? b.length : a.length;
while(i < L){
a1= a[i];
b1= b[i++];
if(a1 !== b1){
if(isFinite(a1) && isFinite(b1)){
if(a1.charAt(0) === "0") a1 = "." + a1;
if(b1.charAt(0) === "0") b1 = "." + b1;
return a1 - b1;
}
else return a1 > b1 ? 1 : -1;
}
}
return a.length > b.length;
}
//fix empty values in position
if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
emptyAlign *= -1;
}
return emptyAlign;
},
};
Tabulator.prototype.registerModule("sort", Sort);