/*
*
* TableSorter - Client-side table sorting with ease!
*
* Copyright (c) 2006 Christian Bach (http://motherrussia.polyester.se)
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*
* jQueryDate:
* jQueryAuthor: Christian jQuery
*
*/
(function($) {
$.fn.tableSorter = function(o) {
var defaults = {
sortDir: 0,
sortColumn: null,
sortClassAsc: 'ascending',
sortClassDesc: 'descending',
headerClass: null,
stripingRowClass: false,
highlightClass: false,
rowLimit: 0,
minRowsForWaitingMsg: 0,
disableHeader: -1,
stripeRowsOnStartUp: false,
columnParser: false,
rowHighlightClass: false,
useCache: true,
debug: false,
textExtraction: 'simple',
textExtractionCustom: false,
textExtractionType: false,
bind: true,
addHeaderLink: false,
lockedSortDir: false,
enableResize: false,
dateFormat: 'mm/dd/yyyy' /** us default, uk dd/mm/yyyy */
};
return this.each(function(){
/** merge default with custom options */
$.extend(defaults, o);
/** Private vars */
var COLUMN_DATA; /** array for storing columns */
var COLUMN_CACHE; /** array for storing sort caches.*/
var COLUMN_INDEX; /** int for storing current cell index */
var COLUMN_SORTER_CACHE = []; /** array for sorter parser cache */
var COLUMN_CELL; /** stores the current cell object */
var COLUMN_DIR; /** stores the current soring direction */
var COLUMN_HEADER_LENGTH; /** stores the columns header length */
var COLUMN_ROW_LENGTH;
var ROW_LAST_HIGHLIGHT_OBJ = false;
var COLUMN_LAST_INDEX = -1;
var COLUMN_LAST_DIR = defaults.sortDir;
/** table object holder.*/
var oTable = this;
if(defaults.stripeRowsOnStartUp && defaults.stripingRowClass) {
$.tableSorter.utils.stripeRows(defaults,oTable);
}
/** bind events to the tablesorter element */
$(this).bind("resort",doSorting);
$(this).bind("flushCache",function(event) {
COLUMN_CACHE = [];
});
$(this).bind("updateColumnData",buildColumnDataIndex);
/** Store length of table rows. */
var tableRowLength = (oTable.tBodies[0] && oTable.tBodies[0].rows.length-1) || 0;
/** Index column data. */
buildColumnDataIndex();
/** when done, build headers. */
buildColumnHeaders();
function buildColumnHeaders() {
var oFirstTableRow = oTable.rows[0];
var oDataSampleRow = oTable.rows[1];
/** store column length */
COLUMN_HEADER_LENGTH = oFirstTableRow.cells.length;
/** loop column headers */
for( var i=0; i < COLUMN_HEADER_LENGTH; i++ ) {
var oCell = oFirstTableRow.cells[i];
if(oDataSampleRow && !$.tableSorter.utils.isHeaderDisabled(defaults,oCell,defaults.disableHeader,i)) {
/** get current cell from columns headers */
var oCellValue = $.tableSorter.utils.getElementText(defaults,oDataSampleRow.cells[i],'columns',i);
/** check for default column. */
if(typeof(defaults.sortColumn) == "string") {
if(defaults.sortColumn.toLowerCase() == $.tableSorter.utils.getElementText(defaults,oCell,'header',i).toLowerCase()) {
defaults.sortColumn = i;
}
}
/** get sorting method for column. */
COLUMN_SORTER_CACHE[i] = $.tableSorter.analyzer.analyseString(defaults,oCellValue);
/** if we have a column parser, set it manual. */
if(defaults.columnParser) {
var a = defaults.columnParser;
var l = a.length;
for(var j=0; j < l; j++) {
if(i == a[j][0]) {
COLUMN_SORTER_CACHE[i] = $.tableSorter.analyzer.getById(a[j][1]);
continue;
}
}
}
if(defaults.headerClass) {
$(oCell).addClass(defaults.headerClass);
}
if(defaults.addHeaderLink) {
$(oCell).wrapInner({element: '<a href="#">', name: 'a', className: 'sorter'});
$(".sorter",oCell).click(function(e) {
sortOnColumn( $(this).parent(), ((defaults.lockedSortDir) ? defaults.lockedSortDir : $(this).parent()[0].count++) % 2, $(this).parent()[0].index );
return false;
});
} else {
$(oCell).click(function(e) {
sortOnColumn( $(this), ((defaults.lockedSortDir) ? defaults.lockedSortDir : $(this)[0].count++) % 2, $(this)[0].index );
return false;
});
}
oCell.index = i;
oCell.count = defaults.sortDir;
}
}
/** comming feature. */
if(defaults.enableResize) {
addColGroup(oFirstTableRow);
}
/** if we have a init sorting, fire it! */
if(defaults.sortColumn != null) {
$(oFirstTableRow.cells[defaults.sortColumn]).trigger("click");
}
if(defaults.rowHighlightClass) {
$("> tbody:first/tr",oTable).click(function() {
if(ROW_LAST_HIGHLIGHT_OBJ) {
ROW_LAST_HIGHLIGHT_OBJ.removeClass(defaults.rowHighlightClass);
}
ROW_LAST_HIGHLIGHT_OBJ = $(this).addClass(defaults.rowHighlightClass);
});
}
}
/** break out and put i $.tableSorter? */
function buildColumnDataIndex() {
/** make colum data. */
COLUMN_DATA = [];
COLUMN_CACHE = [];
COLUMN_ROW_LENGTH = (oTable.tBodies[0] && oTable.tBodies[0].rows.length) || 0;
var l = COLUMN_ROW_LENGTH;
for (var i=0;i < l; i++) {
/** Add the table data to main data array */
COLUMN_DATA.push(oTable.tBodies[0].rows[i]);
}
}
function addColGroup(columnsHeader) {
var oSampleTableRow = oTable.rows[1];
/** adjust header to the sample rows */
for(var i=0; i < COLUMN_HEADER_LENGTH; i++) {
if(oSampleTableRow && oSampleTableRow.cells[i])
$(columnsHeader.cells[i]).css("width",oSampleTableRow.cells[i].clientWidth + "px");
}
}
function sortOnColumn(oCell,dir,index) {
/** trigger event sort start. */
if(tableRowLength > defaults.minRowsForWaitingMsg) {
$(oTable).trigger( "sortStart");
}
/** define globals for current sorting. */
COLUMN_INDEX = index;
COLUMN_CELL = oCell;
COLUMN_DIR = dir;
/** clear all classes, need to be optimized. */
$("thead th",oTable).removeClass(defaults.sortClassAsc).removeClass(defaults.sortClassDesc);
/**add active class and append image. */
$(COLUMN_CELL).addClass((dir % 2 ? defaults.sortClassAsc : defaults.sortClassDesc));
/** if this is fired, with a straight call, sortStart / Stop would never be fired. */
setTimeout(doSorting,0);
}
function doSorting() {
/** added check to see if COLUMN_INDEX is set */
if(COLUMN_INDEX >= 0) {
/** array for storing sorted data. */
var columns;
/** sorting exist in cache, get it. */
if($.tableSorter.cache.exist(COLUMN_CACHE,COLUMN_INDEX) && defaults.useCache) {
/** get from cache */
var cache = $.tableSorter.cache.get(COLUMN_CACHE,COLUMN_INDEX);
/** figure out the way to sort. */
if(cache.dir == COLUMN_DIR) {
columns = cache.data;
cache.dir = COLUMN_DIR;
} else {
columns = cache.data.reverse();
cache.dir = COLUMN_DIR;
}
/** sort and cache */
} else {
/** return flat data, and then sort it. */
var flatData = $.tableSorter.data.flatten(defaults,COLUMN_DATA,COLUMN_SORTER_CACHE,COLUMN_INDEX);
/** do sorting, only onces per column. */
flatData.sort(COLUMN_SORTER_CACHE[COLUMN_INDEX].sorter);
/** if we have a sortDir, reverse the damn thing. */
if(COLUMN_LAST_DIR != COLUMN_DIR) {
flatData.reverse();
}
/** rebuild data from flat. */
columns = $.tableSorter.data.rebuild(COLUMN_DATA,flatData,COLUMN_INDEX,COLUMN_LAST_INDEX);
/** append to table cache. */
$.tableSorter.cache.add(COLUMN_CACHE,COLUMN_INDEX,COLUMN_DIR,columns);
/** good practise */
flatData = null;
}
/** append to table > tbody */
$.tableSorter.utils.appendToTable(defaults,oTable,columns,COLUMN_INDEX,COLUMN_LAST_INDEX);
/** good practise i guess */
columns = null;
/** trigger stop event. */
if(tableRowLength > defaults.minRowsForWaitingMsg) {
$(oTable).trigger("sortStop",[COLUMN_INDEX]);
}
COLUMN_LAST_INDEX = COLUMN_INDEX;
}
}
});
};
$.fn.sortStart = function(fn) {
return this.bind("sortStart",fn);
};
$.fn.sortReload = function(fn) {
return this.bind("sortStart",fn);
};
$.fn.sortStop = function(fn) {
return this.bind("sortStop",fn);
};
$.tableSorter = {
params: {},
/** cache functions, okey for now. */
cache: {
add: function(cache,index,dir,data) {
var oCache = {};
oCache.dir = dir;
oCache.data = data;
cache[index] = oCache;
},
get: function (cache,index) {
return cache[index];
},
exist: function(cache,index) {
var oCache = cache[index];
if(!oCache) {
return false
} else {
return true
}
},
clear: function(cache) {
cache = [];
}
},
data: {
flatten: function(defaults,columnData,columnCache,columnIndex) {
var flatData = [];
var l = columnData.length;
for (var i=0;i < l; i++) {
flatData.push([i,columnCache[columnIndex].format($.tableSorter.utils.getElementText(defaults,columnData[i].cells[columnIndex],'columns',columnIndex),defaults)]);
}
return flatData;
},
rebuild: function(columnData,flatData,columnIndex,columnLastIndex) {
var l = flatData.length;
var sortedData = [];
for (var i=0;i < l; i++) {
sortedData.push(columnData[flatData[i][0]]);
}
return sortedData;
}
},
sorters: {},
parsers: {},
analyzer: {
analyzers: [],
add: function(analyzer) {
this.analyzers.push(analyzer);
},
add_to_front: function(analyzer) {
this.analyzers.unshift(analyzer);
},
analyseString: function(defaults,s) {
/** set defaults params. */
var found = false;
var analyzer = $.tableSorter.parsers.generic;
var list = this.analyzers;
$.each(list, function(i) {
if(!found) {
if(list[i].is(s)) {
found = true;
analyzer = list[i];
}
}
});
return analyzer;
},
getById: function(s) {
var list = this.analyzers;
var analyzer = $.tableSorter.parsers.generic;
$.each(list, function(i) {
if(list[i].id == s) {
analyzer = list[i];
}
});
return analyzer;
}
},
utils: {
getElementText: function(defaults,o,type,index) {
if(!o) return "";
var elementText = "";
if(type == 'header') {
elementText = $(o).text();
} else if(type == 'columns') {
if(defaults.textExtractionCustom && typeof(defaults.textExtractionCustom[index]) == "function") {
elementText = defaults.textExtractionCustom[index](o);
} else {
if(defaults.textExtraction == 'simple') {
if(typeof(defaults.textExtractionType) == "object") {
var d = defaults.textExtractionType;
$.each(d,function(i) {
var val = o[d[i]];
if(val && val.length > 0) {
elementText = val;
}
});
} else {
if(o.childNodes[0] && o.childNodes[0].hasChildNodes()) {
elementText = o.childNodes[0].innerHTML;
} else {
elementText = o.innerHTML;
}
}
} else if(defaults.textExtraction == 'complex') {
// make a jquery object, this will take forever with large tables.
elementText = $(o).text();
}
}
}
return elementText;
},
appendToTable: function(defaults,o,c,index,lastIndex) {
var l = c.length;
$("> tbody:first",o).empty().append(c);
/** jquery way, need to be benched mark! */
if(defaults.stripingRowClass) {
/** remove old! */
$("> tbody:first/tr",o).removeClass(defaults.stripingRowClass[0]).removeClass(defaults.stripingRowClass[1]);
/** add new! */
$.tableSorter.utils.stripeRows(defaults,o);
}
if(defaults.highlightClass) {
$.tableSorter.utils.highlightColumn(defaults,o,index,lastIndex);
}
/** empty object, good practice! */
c=null;
},
highlightColumn : function(defaults,o,index, lastIndex) {
$("> tbody:first/tr", o).find("td:eq(" + lastIndex+ ")").removeClass(defaults.highlightClass);
$("> tbody:first/tr", o).find("td:eq(" + index + ")").addClass(defaults.highlightClass);
},
stripeRows: function(defaults,o) {
$("> tbody:first/tr:visible:even",o).addClass(defaults.stripingRowClass[0]);
$("> tbody:first/tr:visible:odd",o).addClass(defaults.stripingRowClass[1]);
},
isHeaderDisabled: function(defaults,o,arg,index) {
if(typeof(arg) == "number") {
return (arg == index)? true : false;
} else if(typeof(arg) == "string") {
return (arg.toLowerCase() == $.tableSorter.utils.getElementText(defaults,o,'header',index).toLowerCase()) ? true : false;
} else if(arg.parentNode) {
return (o == arg) ? true : false
} else if(typeof(arg) == "object") {
var l = arg.length;
if(!this.lastFound) { this.lastFound = -1; }
for(var i=0; i < l; i++) {
var val = $.tableSorter.utils.isHeaderDisabled(defaults,o,arg[i],index);
if(this.lastFound != i && val) {
this.lastFound = i;
return val;
}
}
} else {
return false
}
}
},
sorters: {
generic: function(a,b) {
return ((a[1] < b[1]) ? -1 : ((a[1] > b[1]) ? 1 : 0));
},
numeric: function(a,b) {
return a[1]-b[1];
}
}
};
$.tableSorter.parsers.generic = {
id: 'generic',
is: function(s) {
return true;
},
format: function(s) {
return s.toLowerCase();
},
sorter: $.tableSorter.sorters.generic
};
$.tableSorter.parsers.currency = {
id: 'currency',
is: function(s) {
return s.match(new RegExp(/^[£$?.]/g));
},
format: function(s) {
return parseFloat(s.replace(new RegExp(/[^0-9.]/g),''));
},
sorter: $.tableSorter.sorters.numeric
};
$.tableSorter.parsers.integer = {
id: 'integer',
is: function(s) {
return s.match(new RegExp(/^\d+$/));
},
format: function(s) {
return parseFloat(s);
},
sorter: $.tableSorter.sorters.numeric
};
$.tableSorter.parsers.floating = {
id: 'floating',
is: function(s) {
return s.match(new RegExp(/^(\+|-)?[0-9]+\.[0-9]+((E|e)(\+|-)?[0-9]+)?$/));
},
format: function(s) {
return parseFloat(s.replace(new RegExp(/,/),''));
},
sorter: $.tableSorter.sorters.numeric
};
$.tableSorter.parsers.ipAddress = {
id: 'ipAddress',
is: function(s) {
return s.match(/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/);
},
format: function(s) {
var a = s.split('.');
var r = '';
for (var i = 0, item; item = a[i]; i++) {
if(item.length == 2) {
r += '0' + item;
} else {
r += item;
}
}
return parseFloat(r);
},
sorter: $.tableSorter.sorters.numeric
};
$.tableSorter.parsers.url = {
id: 'url',
is: function(s) {
return s.match(new RegExp(/(https?|ftp|file):\/\//));
},
format: function(s) {
return s.replace(new RegExp(/(https?|ftp|file):\/\//),'');
},
sorter: $.tableSorter.sorters.generic
};
$.tableSorter.parsers.isoDate = {
id: 'isoDate',
is: function(s) {
return s.match(new RegExp(/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/));
},
format: function(s) {
return parseFloat((s != "") ? new Date(s.replace(new RegExp(/-/g),'/')).getTime() : "0");
},
sorter: $.tableSorter.sorters.numeric
};
$.tableSorter.parsers.usLongDate = {
id: 'usLongDate',
is: function(s) {
return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));
},
format: function(s) {
return parseFloat((new Date(s)).getTime());
},
sorter: $.tableSorter.sorters.numeric
};
$.tableSorter.parsers.shortDate = {
id: 'shortDate',
is: function(s) {
return s.match(new RegExp(/\d{1,2}[\/-]\d{1,2}[\/-]\d{2,4}/));
},
format: function(s,defaults) {
s = s.replace(new RegExp(/-/g),'/');
if(defaults.dateFormat == "mm/dd/yyyy" || defaults.dateFormat == "mm-dd-yyyy") {
/** reformat the string in ISO format */
s = s.replace(new RegExp(/(\d{1,2})[\/-](\d{1,2})[\/-](\d{4})/), '$3/$1/$2');
} else if(defaults.dateFormat == "dd/mm/yyyy" || defaults.dateFormat == "dd-mm-yyyy") {
/** reformat the string in ISO format */
s = s.replace(new RegExp(/(\d{1,2})[\/-](\d{1,2})[\/-](\d{4})/), '$3/$2/$1');
}
return parseFloat((new Date(s)).getTime());
},
sorter: $.tableSorter.sorters.numeric
};
$.tableSorter.parsers.time = {
id: 'time',
is: function(s) {
return s.toUpperCase().match(new RegExp(/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));
},
format: function(s) {
return parseFloat((new Date("2000/01/01 " + s)).getTime());
},
sorter: $.tableSorter.sorters.numeric
};
/** add parsers */
$.tableSorter.analyzer.add($.tableSorter.parsers.currency);
$.tableSorter.analyzer.add($.tableSorter.parsers.integer);
$.tableSorter.analyzer.add($.tableSorter.parsers.isoDate);
$.tableSorter.analyzer.add($.tableSorter.parsers.shortDate);
$.tableSorter.analyzer.add($.tableSorter.parsers.usLongDate);
$.tableSorter.analyzer.add($.tableSorter.parsers.ipAddress);
$.tableSorter.analyzer.add($.tableSorter.parsers.url);
$.tableSorter.analyzer.add($.tableSorter.parsers.time);
$.tableSorter.analyzer.add($.tableSorter.parsers.floating);
})(jQuery);