tabulator-tables
Version:
Interactive table generation JavaScript library
459 lines (350 loc) • 11.1 kB
JavaScript
import Module from '../../core/Module.js';
import defaultSorters from './defaults/sorters.js';
class Sort extends Module{
constructor(table){
super(table);
this.sortList = []; //holder current sort
this.changed = false; //has the sort changed since last render
this.registerTableOption("sortMode", "local"); //local or remote sorting
this.registerTableOption("initialSort", false); //initial sorting criteria
this.registerTableOption("columnHeaderSortMulti", true); //multiple or single column sorting
this.registerTableOption("sortOrderReverse", false); //reverse internal sort ordering
this.registerTableOption("headerSortElement", "<div class='tabulator-arrow'></div>"); //header sort element
this.registerColumnOption("sorter");
this.registerColumnOption("sorterParams");
this.registerColumnOption("headerSort", true);
this.registerColumnOption("headerSortStartingDir");
this.registerColumnOption("headerSortTristate");
}
initialize(){
this.subscribe("column-layout", this.initializeColumn.bind(this));
this.subscribe("table-built", this.tableBuilt.bind(this));
this.registerDataHandler(this.sort.bind(this), 20);
this.registerTableFunction("setSort", this.userSetSort.bind(this));
this.registerTableFunction("getSorters", this.getSort.bind(this));
this.registerTableFunction("clearSort", this.clearSort.bind(this));
if(this.table.options.sortMode === "remote"){
this.subscribe("data-params", this.remoteSortParams.bind(this));
}
}
tableBuilt(){
if(this.table.options.initialSort){
this.setSort(this.table.options.initialSort);
}
}
remoteSortParams(data, config, silent, params){
var sorters = this.getSort();
sorters.forEach((item) => {
delete item.column;
});
params.sort = sorters;
return params;
}
///////////////////////////////////
///////// Table Functions /////////
///////////////////////////////////
userSetSort(sortList, dir){
this.setSort(sortList, dir);
// this.table.rowManager.sorterRefresh();
this.refreshSort();
}
clearSort(){
this.clear();
// this.table.rowManager.sorterRefresh();
this.refreshSort();
}
///////////////////////////////////
///////// Internal Logic //////////
///////////////////////////////////
//initialize column header for sorting
initializeColumn(column){
var sorter = false,
colEl,
arrowEl;
switch(typeof column.definition.sorter){
case "string":
if(Sort.sorters[column.definition.sorter]){
sorter = Sort.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: column.definition.headerSortTristate,
};
if(column.definition.headerSort !== false){
colEl = column.getElement();
colEl.classList.add("tabulator-sortable");
arrowEl = document.createElement("div");
arrowEl.classList.add("tabulator-col-sorter");
switch(this.table.options.headerSortElement){
case "function":
//do nothing
break;
case "object":
arrowEl.appendChild(this.table.options.headerSortElement);
break;
default:
arrowEl.innerHTML = this.table.options.headerSortElement;
}
//create sorter arrow
column.titleHolderElement.appendChild(arrowEl);
column.modules.sort.element = arrowEl;
this.setColumnHeaderSortIcon(column, "none");
//sort on click
colEl.addEventListener("click", (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 (this.table.options.columnHeaderSortMulti && (e.shiftKey || e.ctrlKey)) {
sorters = this.getSort();
match = sorters.findIndex((sorter) => {
return sorter.field === column.getField();
});
if(match > -1){
sorters[match].dir = dir;
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
this.setSort(sorters);
}else{
if(dir == "none"){
this.clear();
}else{
//sort by column only
this.setSort(column, dir);
}
}
// this.table.rowManager.sorterRefresh(!this.sortList.length);
this.refreshSort();
}
});
}
}
refreshSort(){
if(this.table.options.sortMode === "remote"){
this.reloadData(null, false, false);
}else{
this.refreshData(true);
}
//TODO - Persist left position of row manager
// left = this.scrollLeft;
// this.scrollHorizontal(left);
}
//check if the sorters have changed since last use
hasChanged(){
var changed = this.changed;
this.changed = false;
return changed;
}
//return current sorters
getSort(){
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
setSort(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;
this.dispatch("sort-changed");
}
//clear sorters
clear(){
this.setSort([]);
}
//find appropriate sorter for column
findSorter(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 Sort.sorters[sorter];
}
//work through sort list sorting data
sort(data){
var self = this,
sortList = this.table.options.sortOrderReverse ? self.sortList.slice().reverse() : self.sortList,
sortListActual = [],
rowComponents = [];
if(this.subscribedExternal("dataSorting")){
this.dispatchExternal("dataSorting", self.getSort());
}
self.clearColumnHeaders();
if(this.table.options.sortMode !== "remote"){
//build list of valid sorters and trigger column specific callbacks before sort begins
sortList.forEach(function(item, i){
var sortObj;
if(item.column){
sortObj = item.column.modules.sort;
if(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(this.subscribedExternal("dataSorted")){
data.forEach((row) => {
rowComponents.push(row.getComponent());
});
this.dispatchExternal("dataSorted", self.getSort(), rowComponents);
}
return data;
}
//clear sort arrows on columns
clearColumnHeaders(){
this.table.columnManager.getRealColumns().forEach((column) => {
if(column.modules.sort){
column.modules.sort.dir = "none";
column.getElement().setAttribute("aria-sort", "none");
this.setColumnHeaderSortIcon(column, "none");
}
});
}
//set the column header sort direction
setColumnHeader(column, dir){
column.modules.sort.dir = dir;
column.getElement().setAttribute("aria-sort", dir === "asc" ? "ascending" : "descending");
this.setColumnHeaderSortIcon(column, dir);
}
setColumnHeaderSortIcon(column, dir){
var sortEl = column.modules.sort.element,
arrowEl;
if(column.definition.headerSort && typeof this.table.options.headerSortElement === "function"){
while(sortEl.firstChild) sortEl.removeChild(sortEl.firstChild);
arrowEl = this.table.options.headerSortElement.call(this.table, column.getComponent(), dir);
if(typeof arrowEl === "object"){
sortEl.appendChild(arrowEl);
}else{
sortEl.innerHTML = arrowEl;
}
}
}
//sort each item in sort list
_sortItems(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
_sortRow(a, b, column, dir, params){
var el1Comp, el2Comp;
//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);
}
}
Sort.moduleName = "sort";
//load defaults
Sort.sorters = defaultSorters;
export default Sort;