UNPKG

tabulator-tables

Version:

Interactive table generation JavaScript library

591 lines (466 loc) 14.2 kB
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);