/**
* jquery.sorTable
* -----------
* Add some basic functions like sorting and selecting of rows to an existing table.
*
* For this to work, there needs to be a header row that controls the sorting. This is either:
* - "table>thead>tr>th","table>thead>tr>td" or "table>tr[eq:0]>td"
* can be specified in the "headSelect" option
* the rows sorted are either "table>tbody>tr", "table>tr" or "table>tr[pos()>0]" depending on the html layout (i.e. with a thead).
* the can be specified in the "bodyRowSelect" option. the cells are ALWAYS td for the body!
*
* Every time you call refresh the body will be selected new, so you can use this even with dynamically added tables.
*
* Note that the header cells must be matched by body cells in order for sorting to work. If there is a different amount of body cells
* there will be unintended effects.
*
* @version 1.0
* @class
* @author Niko Berger
* @license MIT License GPL
*/
;(function( $, window, undefined ){
"use strict";
const SORTABLE_INIT_FUNCTIONS = {}; // remember initialization functions
const SORTABLE_MAP = {}; // remember all sorTables
/**
* @param element {Node} the cotnainer table that should be converted to a sorTable
* @param options {object} the configuraton object
* @constructor
*/
function SorTable (element, options) {
// create the options
this.options = $.extend({}, {
/**
* add icons in the header: add html snipped.
* If empty, nothing will be shown
*/
icon: [' ', ' ', ' '],
/**
* selector to get the header cells
*/
headSelect: "thead>tr>th",
/**
* select the body
*/
bodySelect: "tbody",
/**
* the class in the head that specifies a row that is sortable
*/
classSortable: "sortable",
/**
* quickfilter element
*/
quickfilter: null,
/**
* remember the sort order in a cookie (non-null = name of the cookie)
*/
remember: null
}, options);
// the table to sort
this.element = element;
// the sorted last
this.lastSort = null;
this._init();
}
/**
* init and load the config
* @private
*/
SorTable.prototype._init = function() {
const that = this;
$(this.options.headSelect, this.element).each(function(){
if(!$(this).hasClass(that.options.classSortable))
return;
let sortIcon = null;
if(that.options.icon) {
sortIcon = $(''+that.options.icon[0]+'');
$(this).append(sortIcon);
}
$(this).click(function(){
const curSort = that._sort($(this).prevAll().length, that.lastSort !== sortIcon ? 0 : $(this).data().sortOrder);
$(this).data().sortOrder = curSort;
if(sortIcon) {
if(that.lastSort) that.lastSort.html(that.options.icon[0]);
// remember the current row and set the icon correctly
that.lastSort = sortIcon;
sortIcon.html(that.options.icon[curSort]);
}
});
});
const qf = $(this.options.headSelect, this.element).find("input.quickfilter");
if(qf.length > 0) {
qf.on("keyup change", function(){
const val = $(this).val().trim();
that._filter(val);
});
}
};
/**
* filter the body table based on a value
*/
SorTable.prototype._filter = function(value) {
const that = this;
const $body = $(this.options.bodySelect, this.element);
value = value.toLowerCase();
$($body).children("tr").each(function(){
if(!value || value === "") {
$(this).show();
return;
}
// check the text content
const content = that._rowVal($(this)).toLowerCase();
if(content.indexOf(value) === -1)
$(this).hide();
else
$(this).show();
});
};
/**
* get the content of a cell
*/
SorTable.prototype._cellVal= function($cell) {
let rowVal = $("select", $cell).val();
// no text - check input
if(typeof rowVal === "undefined") {
rowVal = $("input", $cell).val();
}
if(typeof rowVal === "undefined") {
rowVal = $cell.text().trim();
}
return rowVal;
}
/**
* get the content of a row
*/
SorTable.prototype._rowVal= function($row) {
const that = this;
let rowVal = "";
$row.children().each(function(){
rowVal += that._cellVal($(this));
});
return rowVal;
}
/**
* repaint the sorTable with new data
* @param pos the row to sort
* @param order the current order of this row
* @return 0-no sort; 1-down; 2-up
* @private
*/
SorTable.prototype._sort = function(pos, order) {
const that = this;
const $body = $(this.options.bodySelect, this.element);
let dataType = "string";
if(order == 1)
order = 2;
else
order = 1;
const he = $($(this.options.headSelect, this.element).get(pos));
if(he.hasClass("num") || he.hasClass("number"))
dataType = "number";
else if(he.hasClass("trimnum"))
dataType = "findnumber";
else if(he.hasClass("date"))
dataType = "date";
console.log("sort "+pos+" by", dataType);
// collect all tr
const data = [];
$($body).children("tr").each(function(){
const $cell = $($(this).children()[pos]);
const rowVal = that._cellVal($cell);
data.push({
val: rowVal,
row: $(this).detach()
});
});
let valFunc = he.data().sorter;
if(!valFunc)
switch(dataType){
case "number":
valFunc = (a)=>{
return Number(a.val);
}; break;
case "findnumber":
valFunc = (a)=>{
const aval = a.val.replace(/[^0-9.,]+/g, "");
return Number(aval);
}; break;
case "date":
valFunc = (a)=>{
const aval = new Date(a);
return aval.getTime();
}; break;
default:
valFunc = (a)=>{
return a.val;
}; break;
}
// sort
data.sort(function(a,b){
const aVal = valFunc(a);
const bVal = valFunc(b);
if (aVal < bVal)
return order == 1 ? -1 : 1;
if (aVal > bVal)
return order == 1 ? 1 : -1;
return 0;
});
// readd
$.each(data, function(){
$body.append(this.row);
});
return order;
};
SorTable.prototype.sort = function (col) {
if(!isNaN(col)) {
this._sort(col);
return;
}
this._sort(col.prevAll().length);
};
/**
* destroy the jsform and its resources.
* @private
*/
SorTable.prototype.destroy = function( ) {
return $(this.element).each(function(){
$(window).unbind('.sorTable');
$(this).removeData('sorTable');
});
};
// init and call methods
$.fn.sorTable = function ( method ) {
// Method calling logic
if ( typeof method === 'object' || ! method ) {
return this.each(function () {
if (!$(this).data('sorTable')) {
$(this).data('sorTable', new SorTable( this, method ));
}
});
} else {
const args = Array.prototype.slice.call( arguments, 1 );
let sorTable;
// none found
if(this.length === 0) {
return null;
}
// only one - return directly
if(this.length === 1) {
sorTable = $(this).data('sorTable');
if (sorTable) {
if(method.indexOf("_") !== 0 && sorTable[method]) {
return sorTable[method].apply(sorTable, args);
}
$.error( 'Method ' + method + ' does not exist on jQuery.sorTable' );
return false;
}
}
return this.each(function () {
sorTable = $.data(this, 'sorTable');
if (sorTable) {
if(method.indexOf("_") !== 0 && sorTable[method]) {
return sorTable[method].apply(sorTable, args);
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.sorTable' );
return false;
}
}
});
}
};
/**
* global sorTable function for initialization
*/
$.sorTable = function ( name, initFunc ) {
const sorTables = SORTABLE_MAP[name];
// initFunc is a function -> initialize
if($.isFunction(initFunc)) {
// call init if already initialized
if(sorTables) {
$.each(sorTables, function(){
initFunc(this, $(this.element));
});
}
// remember for future initializations
SORTABLE_INIT_FUNCTIONS[name] = initFunc;
} else {
// call init if already initialized
if(sorTables) {
const method = initFunc;
const args = Array.prototype.slice.call( arguments, 2 );
$.each(portlets, function(){
this[method].apply(this, args);
});
}
}
};
})( jQuery, window );