app/assets/javascripts/pager.js
// ################################# Table sorter filter #################################
$(document).ready(function() {
$(function table_sorter_filter() {
var pagerOptions = {
// target the pager markup - see the HTML block below
container: $(".pager"),
// use this url format "http:/mydatabase.com?page={page}&size={size}&{sortList:col}"
ajaxUrl: null,
// modify the url after all processing has been applied
customAjaxUrl: function(table, url) { return url; },
// process ajax so that the data object is returned along with the total number of rows
// example: { "data" : [{ "ID": 1, "Name": "Foo", "Last": "Bar" }], "total_rows" : 100 }
ajaxProcessing: function(ajax){
if (ajax && ajax.hasOwnProperty('data')) {
// return [ "data", "total_rows" ];
return [ ajax.total_rows, ajax.data ];
}
},
// output string - default is '{page}/{totalPages}'
// possible variables: {page}, {totalPages}, {filteredPages}, {startRow}, {endRow}, {filteredRows} and {totalRows}
output: '{startRow} - {endRow} / {filteredRows} ({totalRows})',
// apply disabled classname to the pager arrows when the rows at either extreme is visible - default is true
updateArrows: true,
// starting page of the pager (zero based index)
page: 0,
// Number of visible rows - default is 10
size: 10,
// if true, the table will remain the same height no matter how many records are displayed. The space is made up by an empty
// table row set to a height to compensate; default is false
fixedHeight: false,
// remove rows from the table to speed up the sort of large tables.
// setting this to false, only hides the non-visible rows; needed if you plan to add/remove rows with the pager enabled.
removeRows: false,
// css class names of pager arrows
cssNext: '.next', // next page arrow
cssPrev: '.prev', // previous page arrow
cssFirst: '.first', // go to first page arrow
cssLast: '.last', // go to last page arrow
cssGoto : ".pagenum",
cssPageDisplay: '.pagedisplay', // location of where the "output" is displayed
cssPageSize: '.pagesize', // page size selector - select dropdown that sets the "size" option
// class added to arrows when at the extremes (i.e. prev/first arrows are "disabled" when on the first page)
cssDisabled: 'disabled', // Note there is no period "." in front of this class name
cssErrorRow: 'tablesorter-errorRow' // ajax error information row
};
$.extend($.tablesorter.themes.bootstrap, {
// these classes are added to the table. To see other table classes available,
// look here: http://twitter.github.com/bootstrap/base-css.html#tables
table : 'table table-bordered',
header : 'bootstrap-header', // give the header a gradient background
footerRow : '',
footerCells: '',
icons : '', // add "icon-white" to make them white; this icon class is added to the <i> in the header
sortNone : 'bootstrap-icon-unsorted',
sortAsc : 'icon-chevron-up',
sortDesc : 'icon-chevron-down',
active : '', // applied when column is sorted
hover : '', // use custom css here - bootstrap class may not override it
filterRow : '', // filter row class
even : '', // odd row zebra striping
odd : '' // even row zebra striping
});
$("table thead th.action").data({
sorter: false,
filter: false
});
// call the tablesorter plugin and apply the uitheme widget
$("table")
.tablesorter({
// this will apply the bootstrap theme if "uitheme" widget is included
// the widgetOptions.uitheme is no longer required to be set
theme : "bootstrap",
widthFixed: true,
sortList: [[0,0]],
headerTemplate : '{content} {icon}', // new in v2.7. Needed to add the bootstrap icon!
debug: true,
dateFormat : 'ddmmyyyy',
// widget code contained in the jquery.tablesorter.widgets.js file
// use the zebra stripe widget if you plan on hiding any rows (filter widget)
widgets: [ 'uitheme', 'zebra', 'filter'],
widgetOptions : {
filter_cssFilter : 'tablesorter-filter',
zebra : ["even", "odd"],
// jQuery selector string of an element used to reset the filters
filter_reset : '.reset',
filter_functions : {
}
}
})
// bind to pager events
// *********************
.bind('pagerChange pagerComplete pagerInitialized pageMoved', function(e, c){
var msg = '"</span> event triggered, ' + (e.type === 'pagerChange' ? 'going to' : 'now on') +
' page <span class="typ">' + (c.page + 1) + '/' + c.totalPages + '</span>';
$('#display')
.append('<li><span class="str">"' + e.type + msg + '</li>')
.find('li:first').remove();
})
// initialize the pager plugin
// ****************************
.tablesorterPager(pagerOptions);
//Update the current TAB footer
var current_tab_index = $(".tabs").tabs('option', 'selected');
var current_tab_name = current_tab_index+1;
if($("#table_list_"+current_tab_name).length){
$("#table_list_"+current_tab_name).tablesorterPager(pagerOptions);
}
// Get the current tab
//var current_tab_index = $(".tabs").tabs('option', 'active');
//var current_tab_index = $(".tabs").tabs('option', 'selected');
$('.tabs').tabs({
select: function(event, ui) { //bind click event to link
var tab_index = ui.index+1;
var anchor_value = "";
$(".current_tab").val("tabs-" + tab_index);
var re = /#/;
window.location.hash = ui.tab.hash.replace(re, "#");
anchor_value = ui.tab.hash;
$.ajax({
url:"/",
method: 'GET',
data: {
anchor_value: anchor_value
}
});
// ADD TABLESORTER TO ALL TABLES WITH ID
$("#table_list_"+tab_index)
.tablesorter({
// this will apply the bootstrap theme if "uitheme" widget is included
// the widgetOptions.uitheme is no longer required to be set
theme : "bootstrap",
widthFixed: true,
sortList: [[0,0]],
headerTemplate : '{content} {icon}', // new in v2.7. Needed to add the bootstrap icon!
debug: true,
dateFormat : 'ddmmyyyy',
// widget code contained in the jquery.tablesorter.widgets.js file
// use the zebra stripe widget if you plan on hiding any rows (filter widget)
widgets: [ 'uitheme', 'zebra', 'filter'],
widgetOptions : {
filter_cssFilter : 'tablesorter-filter',
zebra : ["even", "odd"],
// jQuery selector string of an element used to reset the filters
filter_reset : '.reset',
filter_functions : {
}
}
})
// bind to pager events
// *********************
.bind('pagerChange pagerComplete pagerInitialized pageMoved', function(e, c){
var msg = '"</span> event triggered, ' + (e.type === 'pagerChange' ? 'going to' : 'now on') +
' page <span class="typ">' + (c.page + 1) + '/' + c.totalPages + '</span>';
$('#display')
.append('<li><span class="str">"' + e.type + msg + '</li>')
.find('li:first').remove();
})
// initialize the pager plugin
// ****************************
.tablesorterPager(pagerOptions);
}
});
});
// ################################# TABLE SORTER #################################
/*!
* TableSorter 2.10.8 - Client-side table sorting with ease!
* @requires jQuery v1.2.6+
*
* Copyright (c) 2007 Christian Bach
* Examples and docs at: http://tablesorter.com
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* @type jQuery
* @name tablesorter
* @cat Plugins/Tablesorter
* @author Christian Bach/christian.bach@polyester.se
* @contributor Rob Garrison/https://github.com/Mottie/tablesorter
*/
/*jshint browser:true, jquery:true, unused:false, expr: true */
/*global console:false, alert:false */
!(function($) {
"use strict";
$.extend({
/*jshint supernew:true */
tablesorter: new function() {
var ts = this;
ts.version = "2.10.8";
ts.parsers = [];
ts.widgets = [];
ts.defaults = {
// *** appearance
theme : 'default', // adds tablesorter-{theme} to the table for styling
widthFixed : false, // adds colgroup to fix widths of columns
showProcessing : false, // show an indeterminate timer icon in the header when the table is sorted or filtered.
headerTemplate : '{content}',// header layout template (HTML ok); {content} = innerHTML, {icon} = <i/> (class from cssIcon)
onRenderTemplate : null, // function(index, template){ return template; }, (template is a string)
onRenderHeader : null, // function(index){}, (nothing to return)
// *** functionality
cancelSelection : true, // prevent text selection in the header
dateFormat : 'mmddyyyy', // other options: "ddmmyyy" or "yyyymmdd"
sortMultiSortKey : 'shiftKey', // key used to select additional columns
sortResetKey : 'ctrlKey', // key used to remove sorting on a column
usNumberFormat : true, // false for German "1.234.567,89" or French "1 234 567,89"
delayInit : false, // if false, the parsed table contents will not update until the first sort
serverSideSorting: false, // if true, server-side sorting should be performed because client-side sorting will be disabled, but the ui and events will still be used.
// *** sort options
headers : {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc.
ignoreCase : true, // ignore case while sorting
sortForce : null, // column(s) first sorted; always applied
sortList : [], // Initial sort order; applied initially; updated when manually sorted
sortAppend : null, // column(s) sorted last; always applied
sortInitialOrder : 'asc', // sort direction on first click
sortLocaleCompare: false, // replace equivalent character (accented characters)
sortReset : false, // third click on the header will reset column to default - unsorted
sortRestart : false, // restart sort to "sortInitialOrder" when clicking on previously unsorted columns
emptyTo : 'bottom', // sort empty cell to bottom, top, none, zero
stringTo : 'max', // sort strings in numerical column as max, min, top, bottom, zero
textExtraction : 'simple', // text extraction method/function - function(node, table, cellIndex){}
textSorter : null, // use custom text sorter - function(a,b){ return a.sort(b); } // basic sort
// *** widget options
widgets: [], // method to add widgets, e.g. widgets: ['zebra']
widgetOptions : {
zebra : [ 'even', 'odd' ] // zebra widget alternating row class names
},
initWidgets : true, // apply widgets on tablesorter initialization
// *** callbacks
initialized : null, // function(table){},
// *** css class names
tableClass : 'tablesorter',
cssAsc : 'tablesorter-headerAsc',
cssChildRow : 'tablesorter-childRow', // previously "expand-child"
cssDesc : 'tablesorter-headerDesc',
cssHeader : 'tablesorter-header',
cssHeaderRow : 'tablesorter-headerRow',
cssIcon : 'tablesorter-icon', // if this class exists, a <i> will be added to the header automatically
cssInfoBlock : 'tablesorter-infoOnly', // don't sort tbody with this class name
cssProcessing : 'tablesorter-processing', // processing icon applied to header during sort/filter
// *** selectors
selectorHeaders : '> thead th, > thead td',
selectorSort : 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort
selectorRemove : '.remove-me',
// *** advanced
debug : false,
// *** Internal variables
headerList: [],
empties: {},
strings: {},
parsers: []
// deprecated; but retained for backwards compatibility
// widgetZebra: { css: ["even", "odd"] }
};
/* debuging utils */
function log(s) {
if (typeof console !== "undefined" && typeof console.log !== "undefined") {
// console.log(s);
} else {
alert(s);
}
}
function benchmark(s, d) {
log(s + " (" + (new Date().getTime() - d.getTime()) + "ms)");
}
ts.log = log;
ts.benchmark = benchmark;
function getElementText(table, node, cellIndex) {
if (!node) { return ""; }
var c = table.config,
t = c.textExtraction, text = "";
if (t === "simple") {
if (c.supportsTextContent) {
text = node.textContent; // newer browsers support this
} else {
text = $(node).text();
}
} else {
if (typeof t === "function") {
text = t(node, table, cellIndex);
} else if (typeof t === "object" && t.hasOwnProperty(cellIndex)) {
text = t[cellIndex](node, table, cellIndex);
} else {
text = c.supportsTextContent ? node.textContent : $(node).text();
}
}
return $.trim(text);
}
function detectParserForColumn(table, rows, rowIndex, cellIndex) {
var cur,
i = ts.parsers.length,
node = false,
nodeValue = '',
keepLooking = true;
while (nodeValue === '' && keepLooking) {
rowIndex++;
if (rows[rowIndex]) {
node = rows[rowIndex].cells[cellIndex];
nodeValue = getElementText(table, node, cellIndex);
if (table.config.debug) {
log('Checking if value was empty on row ' + rowIndex + ', column: ' + cellIndex + ': "' + nodeValue + '"');
}
} else {
keepLooking = false;
}
}
while (--i >= 0) {
cur = ts.parsers[i];
// ignore the default text parser because it will always be true
if (cur && cur.id !== 'text' && cur.is && cur.is(nodeValue, table, node)) {
return cur;
}
}
// nothing found, return the generic parser (text)
return ts.getParserById('text');
}
function buildParserCache(table) {
var c = table.config,
// update table bodies in case we start with an empty table
tb = c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')'),
rows, list, l, i, h, ch, p, parsersDebug = "";
if ( tb.length === 0) {
return c.debug ? log('*Empty table!* Not building a parser cache') : '';
}
rows = tb[0].rows;
if (rows[0]) {
list = [];
l = rows[0].cells.length;
for (i = 0; i < l; i++) {
// tons of thanks to AnthonyM1229 for working out the following selector (issue #74) to make this work in IE8!
// More fixes to this selector to work properly in iOS and jQuery 1.8+ (issue #132 & #174)
h = c.$headers.filter(':not([colspan])');
h = h.add( c.$headers.filter('[colspan="1"]') ) // ie8 fix
.filter('[data-column="' + i + '"]:last');
ch = c.headers[i];
// get column parser
p = ts.getParserById( ts.getData(h, ch, 'sorter') );
// empty cells behaviour - keeping emptyToBottom for backwards compatibility
c.empties[i] = ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' );
// text strings behaviour in numerical sorts
c.strings[i] = ts.getData(h, ch, 'string') || c.stringTo || 'max';
if (!p) {
p = detectParserForColumn(table, rows, -1, i);
}
if (c.debug) {
parsersDebug += "column:" + i + "; parser:" + p.id + "; string:" + c.strings[i] + '; empty: ' + c.empties[i] + "\n";
}
list.push(p);
}
}
if (c.debug) {
log(parsersDebug);
}
c.parsers = list;
}
/* utils */
function buildCache(table) {
var b = table.tBodies,
tc = table.config,
totalRows,
totalCells,
parsers = tc.parsers,
t, v, i, j, k, c, cols, cacheTime, colMax = [];
tc.cache = {};
// if no parsers found, return - it's an empty table.
if (!parsers) {
return tc.debug ? log('*Empty table!* Not building a cache') : '';
}
if (tc.debug) {
cacheTime = new Date();
}
// processing icon
if (tc.showProcessing) {
ts.isProcessing(table, true);
}
for (k = 0; k < b.length; k++) {
tc.cache[k] = { row: [], normalized: [] };
// ignore tbodies with class name from css.cssInfoBlock
if (!$(b[k]).hasClass(tc.cssInfoBlock)) {
totalRows = (b[k] && b[k].rows.length) || 0;
totalCells = (b[k].rows[0] && b[k].rows[0].cells.length) || 0;
for (i = 0; i < totalRows; ++i) {
/** Add the table data to main data array */
c = $(b[k].rows[i]);
cols = [];
// if this is a child row, add it to the last row's children and continue to the next row
if (c.hasClass(tc.cssChildRow)) {
tc.cache[k].row[tc.cache[k].row.length - 1] = tc.cache[k].row[tc.cache[k].row.length - 1].add(c);
// go to the next for loop
continue;
}
tc.cache[k].row.push(c);
for (j = 0; j < totalCells; ++j) {
t = getElementText(table, c[0].cells[j], j);
// allow parsing if the string is empty, previously parsing would change it to zero,
// in case the parser needs to extract data from the table cell attributes
v = parsers[j].format(t, table, c[0].cells[j], j);
cols.push(v);
if ((parsers[j].type || '').toLowerCase() === "numeric") {
colMax[j] = Math.max(Math.abs(v) || 0, colMax[j] || 0); // determine column max value (ignore sign)
}
}
cols.push(tc.cache[k].normalized.length); // add position for rowCache
tc.cache[k].normalized.push(cols);
}
tc.cache[k].colMax = colMax;
}
}
if (tc.showProcessing) {
ts.isProcessing(table); // remove processing icon
}
if (tc.debug) {
benchmark("Building cache for " + totalRows + " rows", cacheTime);
}
}
// init flag (true) used by pager plugin to prevent widget application
function appendToTable(table, init) {
var c = table.config,
b = table.tBodies,
rows = [],
c2 = c.cache,
r, n, totalRows, checkCell, $bk, $tb,
i, j, k, l, pos, appendTime;
if (!c2[0]) { return; } // empty table - fixes #206
if (c.debug) {
appendTime = new Date();
}
for (k = 0; k < b.length; k++) {
$bk = $(b[k]);
if ($bk.length && !$bk.hasClass(c.cssInfoBlock)) {
// get tbody
$tb = ts.processTbody(table, $bk, true);
r = c2[k].row;
n = c2[k].normalized;
totalRows = n.length;
checkCell = totalRows ? (n[0].length - 1) : 0;
for (i = 0; i < totalRows; i++) {
pos = n[i][checkCell];
rows.push(r[pos]);
// removeRows used by the pager plugin
if (!c.appender || !c.removeRows) {
l = r[pos].length;
for (j = 0; j < l; j++) {
$tb.append(r[pos][j]);
}
}
}
// restore tbody
ts.processTbody(table, $tb, false);
}
}
if (c.appender) {
c.appender(table, rows);
}
if (c.debug) {
benchmark("Rebuilt table", appendTime);
}
// apply table widgets
if (!init) { ts.applyWidget(table); }
// trigger sortend
$(table).trigger("sortEnd", table);
}
// computeTableHeaderCellIndexes from:
// http://www.javascripttoolbox.com/lib/table/examples.php
// http://www.javascripttoolbox.com/temp/table_cellindex.html
function computeThIndexes(t) {
var matrix = [],
lookup = {},
cols = 0, // determine the number of columns
trs = $(t).find('thead:eq(0), tfoot').children('tr'), // children tr in tfoot - see issue #196
i, j, k, l, c, cells, rowIndex, cellId, rowSpan, colSpan, firstAvailCol, matrixrow;
for (i = 0; i < trs.length; i++) {
cells = trs[i].cells;
for (j = 0; j < cells.length; j++) {
c = cells[j];
rowIndex = c.parentNode.rowIndex;
cellId = rowIndex + "-" + c.cellIndex;
rowSpan = c.rowSpan || 1;
colSpan = c.colSpan || 1;
if (typeof(matrix[rowIndex]) === "undefined") {
matrix[rowIndex] = [];
}
// Find first available column in the first row
for (k = 0; k < matrix[rowIndex].length + 1; k++) {
if (typeof(matrix[rowIndex][k]) === "undefined") {
firstAvailCol = k;
break;
}
}
lookup[cellId] = firstAvailCol;
cols = Math.max(firstAvailCol, cols);
// add data-column
$(c).attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex
for (k = rowIndex; k < rowIndex + rowSpan; k++) {
if (typeof(matrix[k]) === "undefined") {
matrix[k] = [];
}
matrixrow = matrix[k];
for (l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
matrixrow[l] = "x";
}
}
}
}
t.config.columns = cols; // may not be accurate if # header columns !== # tbody columns
return lookup;
}
function formatSortingOrder(v) {
// look for "d" in "desc" order; return true
return (/^d/i.test(v) || v === 1);
}
function buildHeaders(table) {
var header_index = computeThIndexes(table), ch, $t,
h, i, t, lock, time, c = table.config;
c.headerList = [];
c.headerContent = [];
if (c.debug) {
time = new Date();
}
i = c.cssIcon ? '<i class="' + c.cssIcon + '"></i>' : ''; // add icon if cssIcon option exists
c.$headers = $(table).find(c.selectorHeaders).each(function(index) {
$t = $(this);
ch = c.headers[index];
c.headerContent[index] = this.innerHTML; // save original header content
// set up header template
t = c.headerTemplate.replace(/\{content\}/g, this.innerHTML).replace(/\{icon\}/g, i);
if (c.onRenderTemplate) {
h = c.onRenderTemplate.apply($t, [index, t]);
if (h && typeof h === 'string') { t = h; } // only change t if something is returned
}
this.innerHTML = '<div class="tablesorter-header-inner">' + t + '</div>'; // faster than wrapInner
if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index]); }
this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex];
this.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2];
this.count = -1; // set to -1 because clicking on the header automatically adds one
this.lockedOrder = false;
lock = ts.getData($t, ch, 'lockedOrder') || false;
if (typeof lock !== 'undefined' && lock !== false) {
this.order = this.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0];
}
$t.addClass(c.cssHeader);
// add cell to headerList
c.headerList[index] = this;
// add to parent in case there are multiple rows
$t.parent().addClass(c.cssHeaderRow);
// allow keyboard cursor to focus on element
$t.attr("tabindex", 0);
});
// enable/disable sorting
updateHeader(table);
if (c.debug) {
benchmark("Built headers:", time);
log(c.$headers);
}
}
function commonUpdate(table, resort, callback) {
var c = table.config;
// remove rows/elements before update
c.$table.find(c.selectorRemove).remove();
// rebuild parsers
buildParserCache(table);
// rebuild the cache map
buildCache(table);
checkResort(c.$table, resort, callback);
}
function updateHeader(table) {
var s, c = table.config;
c.$headers.each(function(index, th){
s = ts.getData( th, c.headers[index], 'sorter' ) === 'false';
th.sortDisabled = s;
$(th)[ s ? 'addClass' : 'removeClass' ]('sorter-false');
});
}
function setHeadersCss(table) {
var f, i, j, l,
c = table.config,
list = c.sortList,
css = [c.cssAsc, c.cssDesc],
// find the footer
$t = $(table).find('tfoot tr').children().removeClass(css.join(' '));
// remove all header information
c.$headers.removeClass(css.join(' '));
l = list.length;
for (i = 0; i < l; i++) {
// direction = 2 means reset!
if (list[i][1] !== 2) {
// multicolumn sorting updating - choose the :last in case there are nested columns
f = c.$headers.not('.sorter-false').filter('[data-column="' + list[i][0] + '"]' + (l === 1 ? ':last' : '') );
if (f.length) {
for (j = 0; j < f.length; j++) {
if (!f[j].sortDisabled) {
f.eq(j).addClass(css[list[i][1]]);
// add sorted class to footer, if it exists
if ($t.length) {
$t.filter('[data-column="' + list[i][0] + '"]').eq(j).addClass(css[list[i][1]]);
}
}
}
}
}
}
}
// automatically add col group, and column sizes if set
function fixColumnWidth(table) {
if (table.config.widthFixed && $(table).find('colgroup').length === 0) {
var colgroup = $('<colgroup>'),
overallWidth = $(table).width();
$(table.tBodies[0]).find("tr:first").children("td").each(function() {
colgroup.append($('<col>').css('width', parseInt(($(this).width()/overallWidth)*1000, 10)/10 + '%'));
});
$(table).prepend(colgroup);
}
}
function updateHeaderSortCount(table, list) {
var s, t, o, c = table.config,
sl = list || c.sortList;
c.sortList = [];
$.each(sl, function(i,v){
// ensure all sortList values are numeric - fixes #127
s = [ parseInt(v[0], 10), parseInt(v[1], 10) ];
// make sure header exists
o = c.headerList[s[0]];
if (o) { // prevents error if sorton array is wrong
c.sortList.push(s);
t = $.inArray(s[1], o.order); // fixes issue #167
o.count = t >= 0 ? t : s[1] % (c.sortReset ? 3 : 2);
}
});
}
function getCachedSortType(parsers, i) {
return (parsers && parsers[i]) ? parsers[i].type || '' : '';
}
function initSort(table, cell, e){
var a, i, j, o, s,
c = table.config,
k = !e[c.sortMultiSortKey],
$this = $(table);
// Only call sortStart if sorting is enabled
$this.trigger("sortStart", table);
// get current column sort order
cell.count = e[c.sortResetKey] ? 2 : (cell.count + 1) % (c.sortReset ? 3 : 2);
// reset all sorts on non-current column - issue #30
if (c.sortRestart) {
i = cell;
c.$headers.each(function() {
// only reset counts on columns that weren't just clicked on and if not included in a multisort
if (this !== i && (k || !$(this).is('.' + c.cssDesc + ',.' + c.cssAsc))) {
this.count = -1;
}
});
}
// get current column index
i = cell.column;
// user only wants to sort on one column
if (k) {
// flush the sort list
c.sortList = [];
if (c.sortForce !== null) {
a = c.sortForce;
for (j = 0; j < a.length; j++) {
if (a[j][0] !== i) {
c.sortList.push(a[j]);
}
}
}
// add column to sort list
o = cell.order[cell.count];
if (o < 2) {
c.sortList.push([i, o]);
// add other columns if header spans across multiple
if (cell.colSpan > 1) {
for (j = 1; j < cell.colSpan; j++) {
c.sortList.push([i + j, o]);
}
}
}
// multi column sorting
} else {
// get rid of the sortAppend before adding more - fixes issue #115
if (c.sortAppend && c.sortList.length > 1) {
if (ts.isValueInArray(c.sortAppend[0][0], c.sortList)) {
c.sortList.pop();
}
}
// the user has clicked on an already sorted column
if (ts.isValueInArray(i, c.sortList)) {
// reverse the sorting direction for all tables
for (j = 0; j < c.sortList.length; j++) {
s = c.sortList[j];
o = c.headerList[s[0]];
if (s[0] === i) {
s[1] = o.order[o.count];
if (s[1] === 2) {
c.sortList.splice(j,1);
o.count = -1;
}
}
}
} else {
// add column to sort list array
o = cell.order[cell.count];
if (o < 2) {
c.sortList.push([i, o]);
// add other columns if header spans across multiple
if (cell.colSpan > 1) {
for (j = 1; j < cell.colSpan; j++) {
c.sortList.push([i + j, o]);
}
}
}
}
}
if (c.sortAppend !== null) {
a = c.sortAppend;
for (j = 0; j < a.length; j++) {
if (a[j][0] !== i) {
c.sortList.push(a[j]);
}
}
}
// sortBegin event triggered immediately before the sort
$this.trigger("sortBegin", table);
// setTimeout needed so the processing icon shows up
setTimeout(function(){
// set css for headers
setHeadersCss(table);
multisort(table);
appendToTable(table);
}, 1);
}
// sort multiple columns
function multisort(table) { /*jshint loopfunc:true */
var dir = 0, tc = table.config,
sortList = tc.sortList, l = sortList.length, bl = table.tBodies.length,
sortTime, i, k, c, colMax, cache, lc, s, order, orgOrderCol;
if (tc.serverSideSorting || !tc.cache[0]) { // empty table - fixes #206
return;
}
if (tc.debug) { sortTime = new Date(); }
for (k = 0; k < bl; k++) {
colMax = tc.cache[k].colMax;
cache = tc.cache[k].normalized;
lc = cache.length;
orgOrderCol = (cache && cache[0]) ? cache[0].length - 1 : 0;
cache.sort(function(a, b) {
// cache is undefined here in IE, so don't use it!
for (i = 0; i < l; i++) {
c = sortList[i][0];
order = sortList[i][1];
// fallback to natural sort since it is more robust
s = /n/i.test(getCachedSortType(tc.parsers, c)) ? "Numeric" : "Text";
s += order === 0 ? "" : "Desc";
if (/Numeric/.test(s) && tc.strings[c]) {
// sort strings in numerical columns
if (typeof (tc.string[tc.strings[c]]) === 'boolean') {
dir = (order === 0 ? 1 : -1) * (tc.string[tc.strings[c]] ? -1 : 1);
} else {
dir = (tc.strings[c]) ? tc.string[tc.strings[c]] || 0 : 0;
}
}
var sort = $.tablesorter["sort" + s](table, a[c], b[c], c, colMax[c], dir);
if (sort) { return sort; }
}
return a[orgOrderCol] - b[orgOrderCol];
});
}
if (tc.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time", sortTime); }
}
function resortComplete($table, callback){
$table.trigger('updateComplete');
if (typeof callback === "function") {
callback($table[0]);
}
}
function checkResort($table, flag, callback) {
// don't try to resort if the table is still processing
// this will catch spamming of the updateCell method
if (flag !== false && !$table[0].isProcessing) {
$table.trigger("sorton", [$table[0].config.sortList, function(){
resortComplete($table, callback);
}]);
} else {
resortComplete($table, callback);
}
}
function bindEvents(table){
var c = table.config,
$this = c.$table,
j, downTime;
// apply event handling to headers
c.$headers
// http://stackoverflow.com/questions/5312849/jquery-find-self;
.find(c.selectorSort).add( c.$headers.filter(c.selectorSort) )
.unbind('mousedown.tablesorter mouseup.tablesorter sort.tablesorter keypress.tablesorter')
.bind('mousedown.tablesorter mouseup.tablesorter sort.tablesorter keypress.tablesorter', function(e, external) {
// only recognize left clicks or enter
if ( ((e.which || e.button) !== 1 && !/sort|keypress/.test(e.type)) || (e.type === 'keypress' && e.which !== 13) ) {
return false;
}
// ignore long clicks (prevents resizable widget from initializing a sort)
if (e.type === 'mouseup' && external !== true && (new Date().getTime() - downTime > 250)) { return false; }
// set timer on mousedown
if (e.type === 'mousedown') {
downTime = new Date().getTime();
return e.target.tagName === "INPUT" ? '' : !c.cancelSelection;
}
if (c.delayInit && !c.cache) { buildCache(table); }
// jQuery v1.2.6 doesn't have closest()
var $cell = /TH|TD/.test(this.tagName) ? $(this) : $(this).parents('th, td').filter(':first'), cell = $cell[0];
if (!cell.sortDisabled) {
initSort(table, cell, e);
}
});
if (c.cancelSelection) {
// cancel selection
c.$headers
.attr('unselectable', 'on')
.bind('selectstart', false)
.css({
'user-select': 'none',
'MozUserSelect': 'none' // not needed for jQuery 1.8+
});
}
// apply easy methods that trigger bound events
$this
.unbind('sortReset update updateRows updateCell updateAll addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave '.split(' ').join('.tablesorter '))
.bind("sortReset.tablesorter", function(e){
e.stopPropagation();
c.sortList = [];
setHeadersCss(table);
multisort(table);
appendToTable(table);
})
.bind("updateAll.tablesorter", function(e, resort, callback){
e.stopPropagation();
ts.refreshWidgets(table, true, true);
ts.restoreHeaders(table);
buildHeaders(table);
bindEvents(table);
commonUpdate(table, resort, callback);
})
.bind("update.tablesorter updateRows.tablesorter", function(e, resort, callback) {
e.stopPropagation();
// update sorting (if enabled/disabled)
updateHeader(table);
commonUpdate(table, resort, callback);
})
.bind("updateCell.tablesorter", function(e, cell, resort, callback) {
e.stopPropagation();
$this.find(c.selectorRemove).remove();
// get position from the dom
var l, row, icell,
$tb = $this.find('tbody'),
// update cache - format: function(s, table, cell, cellIndex)
// no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
tbdy = $tb.index( $(cell).parents('tbody').filter(':first') ),
$row = $(cell).parents('tr').filter(':first');
cell = $(cell)[0]; // in case cell is a jQuery object
// tbody may not exist if update is initialized while tbody is removed for processing
if ($tb.length && tbdy >= 0) {
row = $tb.eq(tbdy).find('tr').index( $row );
icell = cell.cellIndex;
l = c.cache[tbdy].normalized[row].length - 1;
c.cache[tbdy].row[table.config.cache[tbdy].normalized[row][l]] = $row;
c.cache[tbdy].normalized[row][icell] = c.parsers[icell].format( getElementText(table, cell, icell), table, cell, icell );
checkResort($this, resort, callback);
}
})
.bind("addRows.tablesorter", function(e, $row, resort, callback) {
e.stopPropagation();
var i, rows = $row.filter('tr').length,
dat = [], l = $row[0].cells.length,
tbdy = $this.find('tbody').index( $row.parents('tbody').filter(':first') );
// fixes adding rows to an empty table - see issue #179
if (!c.parsers) {
buildParserCache(table);
}
// add each row
for (i = 0; i < rows; i++) {
// add each cell
for (j = 0; j < l; j++) {
dat[j] = c.parsers[j].format( getElementText(table, $row[i].cells[j], j), table, $row[i].cells[j], j );
}
// add the row index to the end
dat.push(c.cache[tbdy].row.length);
// update cache
c.cache[tbdy].row.push([$row[i]]);
c.cache[tbdy].normalized.push(dat);
dat = [];
}
// resort using current settings
checkResort($this, resort, callback);
})
.bind("sorton.tablesorter", function(e, list, callback, init) {
e.stopPropagation();
$this.trigger("sortStart", this);
// update header count index
updateHeaderSortCount(table, list);
// set css for headers
setHeadersCss(table);
$this.trigger("sortBegin", this);
// sort the table and append it to the dom
multisort(table);
appendToTable(table, init);
if (typeof callback === "function") {
callback(table);
}
})
.bind("appendCache.tablesorter", function(e, callback, init) {
e.stopPropagation();
appendToTable(table, init);
if (typeof callback === "function") {
callback(table);
}
})
.bind("applyWidgetId.tablesorter", function(e, id) {
e.stopPropagation();
ts.getWidgetById(id).format(table, c, c.widgetOptions);
})
.bind("applyWidgets.tablesorter", function(e, init) {
e.stopPropagation();
// apply widgets
ts.applyWidget(table, init);
})
.bind("refreshWidgets.tablesorter", function(e, all, dontapply){
e.stopPropagation();
ts.refreshWidgets(table, all, dontapply);
})
.bind("destroy.tablesorter", function(e, c, cb){
e.stopPropagation();
ts.destroy(table, c, cb);
});
}
/* public methods */
ts.construct = function(settings) {
return this.each(function() {
// if no thead or tbody, or tablesorter is already present, quit
if (!this.tHead || this.tBodies.length === 0 || this.hasInitialized === true) {
return (this.config && this.config.debug) ? log('stopping initialization! No thead, tbody or tablesorter has already been initialized') : '';
}
// declare
var $this = $(this), table = this,
c, k = '',
m = $.metadata;
// initialization flag
table.hasInitialized = false;
// table is being processed flag
table.isProcessing = true;
// new blank config object
table.config = {};
// merge and extend
c = $.extend(true, table.config, ts.defaults, settings);
// save the settings where they read
$.data(table, "tablesorter", c);
if (c.debug) { $.data( table, 'startoveralltimer', new Date()); }
// constants
c.supportsTextContent = $('<span>x</span>')[0].textContent === 'x';
c.supportsDataObject = parseFloat($.fn.jquery) >= 1.4;
// digit sort text location; keeping max+/- for backwards compatibility
c.string = { 'max': 1, 'min': -1, 'max+': 1, 'max-': -1, 'zero': 0, 'none': 0, 'null': 0, 'top': true, 'bottom': false };
// add table theme class only if there isn't already one there
if (!/tablesorter\-/.test($this.attr('class'))) {
k = (c.theme !== '' ? ' tablesorter-' + c.theme : '');
}
c.$table = $this.addClass(c.tableClass + k);
c.$tbodies = $this.children('tbody:not(.' + c.cssInfoBlock + ')');
// build headers
buildHeaders(table);
// fixate columns if the users supplies the fixedWidth option
// do this after theme has been applied
fixColumnWidth(table);
// try to auto detect column type, and store in tables config
buildParserCache(table);
// build the cache for the tbody cells
// delayInit will delay building the cache until the user starts a sort
if (!c.delayInit) { buildCache(table); }
// bind all header events and methods
bindEvents(table);
// get sort list from jQuery data or metadata
// in jQuery < 1.4, an error occurs when calling $this.data()
if (c.supportsDataObject && typeof $this.data().sortlist !== 'undefined') {
c.sortList = $this.data().sortlist;
} else if (m && ($this.metadata() && $this.metadata().sortlist)) {
c.sortList = $this.metadata().sortlist;
}
// apply widget init code
ts.applyWidget(table, true);
// if user has supplied a sort list to constructor
if (c.sortList.length > 0) {
$this.trigger("sorton", [c.sortList, {}, !c.initWidgets]);
} else if (c.initWidgets) {
// apply widget format
ts.applyWidget(table);
}
// show processesing icon
if (c.showProcessing) {
$this
.unbind('sortBegin.tablesorter sortEnd.tablesorter')
.bind('sortBegin.tablesorter sortEnd.tablesorter', function(e) {
ts.isProcessing(table, e.type === 'sortBegin');
});
}
// initialized
table.hasInitialized = true;
table.isProcessing = false;
if (c.debug) {
ts.benchmark("Overall initialization time", $.data( table, 'startoveralltimer'));
}
$this.trigger('tablesorter-initialized', table);
if (typeof c.initialized === 'function') { c.initialized(table); }
});
};
// *** Process table ***
// add processing indicator
ts.isProcessing = function(table, toggle, $ths) {
table = $(table);
var c = table[0].config,
// default to all headers
$h = $ths || table.find('.' + c.cssHeader);
if (toggle) {
if (c.sortList.length > 0) {
// get headers from the sortList
$h = $h.filter(function(){
// get data-column from attr to keep compatibility with jQuery 1.2.6
return this.sortDisabled ? false : ts.isValueInArray( parseFloat($(this).attr('data-column')), c.sortList);
});
}
$h.addClass(c.cssProcessing);
} else {
$h.removeClass(c.cssProcessing);
}
};
// detach tbody but save the position
// don't use tbody because there are portions that look for a tbody index (updateCell)
ts.processTbody = function(table, $tb, getIt){
var holdr;
if (getIt) {
table.isProcessing = true;
$tb.before('<span class="tablesorter-savemyplace"/>');
holdr = ($.fn.detach) ? $tb.detach() : $tb.remove();
return holdr;
}
holdr = $(table).find('span.tablesorter-savemyplace');
$tb.insertAfter( holdr );
holdr.remove();
table.isProcessing = false;
};
ts.clearTableBody = function(table) {
$(table)[0].config.$tbodies.empty();
};
// restore headers
ts.restoreHeaders = function(table){
var c = table.config;
// don't use c.$headers here in case header cells were swapped
c.$table.find(c.selectorHeaders).each(function(i){
// only restore header cells if it is wrapped
// because this is also used by the updateAll method
if ($(this).find('.tablesorter-header-inner').length){
$(this).html( c.headerContent[i] );
}
});
};
ts.destroy = function(table, removeClasses, callback){
table = $(table)[0];
if (!table.hasInitialized) { return; }
// remove all widgets
ts.refreshWidgets(table, true, true);
var $t = $(table), c = table.config,
$h = $t.find('thead:first'),
$r = $h.find('tr.' + c.cssHeaderRow).removeClass(c.cssHeaderRow),
$f = $t.find('tfoot:first > tr').children('th, td');
// remove widget added rows, just in case
$h.find('tr').not($r).remove();
// disable tablesorter
$t
.removeData('tablesorter')
.unbind('sortReset update updateAll updateRows updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd '.split(' ').join('.tablesorter '));
c.$headers.add($f)
.removeClass(c.cssHeader + ' ' + c.cssAsc + ' ' + c.cssDesc)
.removeAttr('data-column');
$r.find(c.selectorSort).unbind('mousedown.tablesorter mouseup.tablesorter keypress.tablesorter');
ts.restoreHeaders(table);
if (removeClasses !== false) {
$t.removeClass(c.tableClass + ' tablesorter-' + c.theme);
}
// clear flag in case the plugin is initialized again
table.hasInitialized = false;
if (typeof callback === 'function') {
callback(table);
}
};
// *** sort functions ***
// regex used in natural sort
ts.regex = [
/(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi, // chunk/tokenize numbers & letters
/(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, //date
/^0x[0-9a-f]+$/i // hex
];
// Natural sort - https://github.com/overset/javascript-natural-sort
ts.sortText = function(table, a, b, col) {
if (a === b) { return 0; }
var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ],
r = ts.regex, xN, xD, yN, yD, xF, yF, i, mx;
if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
if (typeof c.textSorter === 'function') { return c.textSorter(a, b, table, col); }
// chunk/tokenize
xN = a.replace(r[0], '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0');
yN = b.replace(r[0], '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0');
// numeric, hex or date detection
xD = parseInt(a.match(r[2]),16) || (xN.length !== 1 && a.match(r[1]) && Date.parse(a));
yD = parseInt(b.match(r[2]),16) || (xD && b.match(r[1]) && Date.parse(b)) || null;
// first try and sort Hex codes or Dates
if (yD) {
if ( xD < yD ) { return -1; }
if ( xD > yD ) { return 1; }
}
mx = Math.max(xN.length, yN.length);
// natural sorting through split numeric strings and default strings
for (i = 0; i < mx; i++) {
// find floats not starting with '0', string or 0 if not defined
xF = isNaN(xN[i]) ? xN[i] || 0 : parseFloat(xN[i]) || 0;
yF = isNaN(yN[i]) ? yN[i] || 0 : parseFloat(yN[i]) || 0;
// handle numeric vs string comparison - number < string - (Kyle Adams)
if (isNaN(xF) !== isNaN(yF)) { return (isNaN(xF)) ? 1 : -1; }
// rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
if (typeof xF !== typeof yF) {
xF += '';
yF += '';
}
if (xF < yF) { return -1; }
if (xF > yF) { return 1; }
}
return 0;
};
ts.sortTextDesc = function(table, a, b, col) {
if (a === b) { return 0; }
var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
if (typeof c.textSorter === 'function') { return c.textSorter(b, a, table, col); }
return ts.sortText(table, b, a);
};
// return text string value by adding up ascii value
// so the text is somewhat sorted when using a digital sort
// this is NOT an alphanumeric sort
ts.getTextValue = function(a, mx, d) {
if (mx) {
// make sure the text value is greater than the max numerical value (mx)
var i, l = a ? a.length : 0, n = mx + d;
for (i = 0; i < l; i++) {
n += a.charCodeAt(i);
}
return d * n;
}
return 0;
};
ts.sortNumeric = function(table, a, b, col, mx, d) {
if (a === b) { return 0; }
var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
if (isNaN(a)) { a = ts.getTextValue(a, mx, d); }
if (isNaN(b)) { b = ts.getTextValue(b, mx, d); }
return a - b;
};
ts.sortNumericDesc = function(table, a, b, col, mx, d) {
if (a === b) { return 0; }
var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
if (isNaN(a)) { a = ts.getTextValue(a, mx, d); }
if (isNaN(b)) { b = ts.getTextValue(b, mx, d); }
return b - a;
};
// used when replacing accented characters during sorting
ts.characterEquivalents = {
"a" : "\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5", // áàâãäąå
"A" : "\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5", // ÁÀÂÃÄĄÅ
"c" : "\u00e7\u0107\u010d", // çćč
"C" : "\u00c7\u0106\u010c", // ÇĆČ
"e" : "\u00e9\u00e8\u00ea\u00eb\u011b\u0119", // éèêëěę
"E" : "\u00c9\u00c8\u00ca\u00cb\u011a\u0118", // ÉÈÊËĚĘ
"i" : "\u00ed\u00ec\u0130\u00ee\u00ef\u0131", // íìİîïı
"I" : "\u00cd\u00cc\u0130\u00ce\u00cf", // ÍÌİÎÏ
"o" : "\u00f3\u00f2\u00f4\u00f5\u00f6", // óòôõö
"O" : "\u00d3\u00d2\u00d4\u00d5\u00d6", // ÓÒÔÕÖ
"ss": "\u00df", // ß (s sharp)
"SS": "\u1e9e", // ẞ (Capital sharp s)
"u" : "\u00fa\u00f9\u00fb\u00fc\u016f", // úùûüů
"U" : "\u00da\u00d9\u00db\u00dc\u016e" // ÚÙÛÜŮ
};
ts.replaceAccents = function(s) {
var a, acc = '[', eq = ts.characterEquivalents;
if (!ts.characterRegex) {
ts.characterRegexArray = {};
for (a in eq) {
if (typeof a === 'string') {
acc += eq[a];
ts.characterRegexArray[a] = new RegExp('[' + eq[a] + ']', 'g');
}
}
ts.characterRegex = new RegExp(acc + ']');
}
if (ts.characterRegex.test(s)) {
for (a in eq) {
if (typeof a === 'string') {
s = s.replace( ts.characterRegexArray[a], a );
}
}
}
return s;
};
// *** utilities ***
ts.isValueInArray = function(v, a) {
var i, l = a.length;
for (i = 0; i < l; i++) {
if (a[i][0] === v) {
return true;
}
}
return false;
};
ts.addParser = function(parser) {
var i, l = ts.parsers.length, a = true;
for (i = 0; i < l; i++) {
if (ts.parsers[i].id.toLowerCase() === parser.id.toLowerCase()) {
a = false;
}
}
if (a) {
ts.parsers.push(parser);
}
};
ts.getParserById = function(name) {
var i, l = ts.parsers.length;
for (i = 0; i < l; i++) {
if (ts.parsers[i].id.toLowerCase() === (name.toString()).toLowerCase()) {
return ts.parsers[i];
}
}
return false;
};
ts.addWidget = function(widget) {
ts.widgets.push(widget);
};
ts.getWidgetById = function(name) {
var i, w, l = ts.widgets.length;
for (i = 0; i < l; i++) {
w = ts.widgets[i];
if (w && w.hasOwnProperty('id') && w.id.toLowerCase() === name.toLowerCase()) {
return w;
}
}
};
ts.applyWidget = function(table, init) {
table = $(table)[0]; // in case this is called externally
var c = table.config,
wo = c.widgetOptions,
widgets = [],
time, i, w, wd;
if (c.debug) { time = new Date(); }
if (c.widgets.length) {
// ensure unique widget ids
c.widgets = $.grep(c.widgets, function(v, k){
return $.inArray(v, c.widgets) === k;
});
// build widget array & add priority as needed
$.each(c.widgets || [], function(i,n){
wd = ts.getWidgetById(n);
if (wd && wd.id) {
// set priority to 10 if not defined
if (!wd.priority) { wd.priority = 10; }
widgets[i] = wd;
}
});
// sort widgets by priority
widgets.sort(function(a, b){
return a.priority < b.priority ? -1 : a.priority === b.priority ? 0 : 1;
});
// add/update selected widgets
$.each(widgets, function(i,w){
if (w) {
if (init) {
if (w.hasOwnProperty('options')) {
wo = table.config.widgetOptions = $.extend( true, {}, w.options, wo );
}
if (w.hasOwnProperty('init')) {
w.init(table, w, c, wo);
}
} else if (!init && w.hasOwnProperty('format')) {
w.format(table, c, wo, false);
}
}
});
}
if (c.debug) {
w = c.widgets.length;
benchmark("Completed " + (init === true ? "initializing " : "applying ") + w + " widget" + (w !== 1 ? "s" : ""), time);
}
};
ts.refreshWidgets = function(table, doAll, dontapply) {
table = $(table)[0]; // see issue #243
var i, c = table.config,
cw = c.widgets,
w = ts.widgets, l = w.length;
// remove previous widgets
for (i = 0; i < l; i++){
if ( w[i] && w[i].id && (doAll || $.inArray( w[i].id, cw ) < 0) ) {
if (c.debug) { log( 'Refeshing widgets: Removing ' + w[i].id ); }
if (w[i].hasOwnProperty('remove')) { w[i].remove(table, c, c.widgetOptions); }
}
}
if (dontapply !== true) {
ts.applyWidget(table, doAll);
}
};
// get sorter, string, empty, etc options for each column from
// jQuery data, metadata, header option or header class name ("sorter-false")
// priority = jQuery data > meta > headers option > header class name
ts.getData = function(h, ch, key) {
var val = '', $h = $(h), m, cl;
if (!$h.length) { return ''; }
m = $.metadata ? $h.metadata() : false;
cl = ' ' + ($h.attr('class') || '');
if (typeof $h.data(key) !== 'undefined' || typeof $h.data(key.toLowerCase()) !== 'undefined'){
// "data-lockedOrder" is assigned to "lockedorder"; but "data-locked-order" is assigned to "lockedOrder"
// "data-sort-initial-order" is assigned to "sortInitialOrder"
val += $h.data(key) || $h.data(key.toLowerCase());
} else if (m && typeof m[key] !== 'undefined') {
val += m[key];
} else if (ch && typeof ch[key] !== 'undefined') {
val += ch[key];
} else if (cl !== ' ' && cl.match(' ' + key + '-')) {
// include sorter class name "sorter-text", etc; now works with "sorter-my-custom-parser"
val = cl.match( new RegExp('\\s' + key + '-([\\w-]+)') )[1] || '';
}
return $.trim(val);
};
ts.formatFloat = function(s, table) {
if (typeof s !== 'string' || s === '') { return s; }
// allow using formatFloat without a table; defaults to US number format
var i,
t = table && table.config ? table.config.usNumberFormat !== false :
typeof table !== "undefined" ? table : true;
if (t) {
// US Format - 1,234,567.89 -> 1234567.89
s = s.replace(/,/g,'');
} else {
// German Format = 1.234.567,89 -> 1234567.89
// French Format = 1 234 567,89 -> 1234567.89
s = s.replace(/[\s|\.]/g,'').replace(/,/g,'.');
}
if(/^\s*\([.\d]+\)/.test(s)) {
// make (#) into a negative number -> (10) = -10
s = s.replace(/^\s*\(/,'-').replace(/\)/,'');
}
i = parseFloat(s);
// return the text instead of zero
return isNaN(i) ? $.trim(s) : i;
};
ts.isDigit = function(s) {
// replace all unwanted chars and match
return isNaN(s) ? (/^[\-+(]?\d+[)]?$/).test(s.toString().replace(/[,.'"\s]/g, '')) : true;
};
}()
});
// make shortcut
var ts = $.tablesorter;
// extend plugin scope
$.fn.extend({
tablesorter: ts.construct
});
// add default parsers
ts.addParser({
id: "text",
is: function() {
return true;
},
format: function(s, table) {
var c = table.config;
if (s) {
s = $.trim( c.ignoreCase ? s.toLocaleLowerCase() : s );
s = c.sortLocaleCompare ? ts.replaceAccents(s) : s;
}
return s;
},
type: "text"
});
ts.addParser({
id: "digit",
is: function(s) {
return ts.isDigit(s);
},
format: function(s, table) {
var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ""), table);
return s && typeof n === 'number' ? n : s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
},
type: "numeric"
});
ts.addParser({
id: "currency",
is: function(s) {
return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[,. ]/g,'')); // £$€¤¥¢
},
format: function(s, table) {
var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ""), table);
return s && typeof n === 'number' ? n : s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
},
type: "numeric"
});
ts.addParser({
id: "ipAddress",
is: function(s) {
return (/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/).test(s);
},
format: function(s, table) {
var i, a = s ? s.split(".") : '',
r = "",
l = a.length;
for (i = 0; i < l; i++) {
r += ("00" + a[i]).slice(-3);
}
return s ? ts.formatFloat(r, table) : s;
},
type: "numeric"
});
ts.addParser({
id: "url",
is: function(s) {
return (/^(https?|ftp|file):\/\//).test(s);
},
format: function(s) {
return s ? $.trim(s.replace(/(https?|ftp|file):\/\//, '')) : s;
},
type: "text"
});
ts.addParser({
id: "isoDate",
is: function(s) {
return (/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/).test(s);
},
format: function(s, table) {
return s ? ts.formatFloat((s !== "") ? (new Date(s.replace(/-/g, "/")).getTime() || "") : "", table) : s;
},
type: "numeric"
});
ts.addParser({
id: "percent",
is: function(s) {
return (/(\d\s*?%|%\s*?\d)/).test(s) && s.length < 15;
},
format: function(s, table) {
return s ? ts.formatFloat(s.replace(/%/g, ""), table) : s;
},
type: "numeric"
});
ts.addParser({
id: "usLongDate",
is: function(s) {
// two digit years are not allowed cross-browser
// Jan 01, 2013 12:34:56 PM or 01 Jan 2013
return (/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i).test(s) || (/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i).test(s);
},
format: function(s, table) {
return s ? ts.formatFloat( (new Date(s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ''), table) : s;
},
type: "numeric"
});
ts.addParser({
id: "shortDate", // "mmddyyyy", "ddmmyyyy" or "yyyymmdd"
is: function(s) {
// testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g," ").replace(/[\-.,]/g, "/"));
},
format: function(s, table, cell, cellIndex) {
if (s) {
var c = table.config, ci = c.headerList[cellIndex],
format = ci.dateFormat || ts.getData( ci, c.headers[cellIndex], 'dateFormat') || c.dateFormat;
s = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/"); // escaped - because JSHint in Firefox was showing it as an error
if (format === "mmddyyyy") {
s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2");
} else if (format === "ddmmyyyy") {
s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$2/$1");
} else if (format === "yyyymmdd") {
s = s.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$1/$2/$3");
}
}
return s ? ts.formatFloat( (new Date(s).getTime() || ''), table) : s;
},
type: "numeric"
});
ts.addParser({
id: "time",
is: function(s) {
return (/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s);
},
format: function(s, table) {
return s ? ts.formatFloat( (new Date("2000/01/01 " + s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ""), table) : s;
},
type: "numeric"
});
ts.addParser({
id: "metadata",
is: function() {
return false;
},
format: function(s, table, cell) {
var c = table.config,
p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
return $(cell).metadata()[p];
},
type: "numeric"
});
// add default widgets
ts.addWidget({
id: "zebra",
priority: 90,
format: function(table, c, wo) {
var $tb, $tv, $tr, row, even, time, k, l,
child = new RegExp(c.cssChildRow, 'i'),
b = c.$tbodies;
if (c.debug) {
time = new Date();
}
for (k = 0; k < b.length; k++ ) {
// loop through the visible rows
$tb = b.eq(k);
l = $tb.children('tr').length;
if (l > 1) {
row = 0;
$tv = $tb.children('tr:visible');
// revered back to using jQuery each - strangely it's the fastest method
/*jshint loopfunc:true */
$tv.each(function(){
$tr = $(this);
// style children rows the same way the parent row was styled
if (!child.test(this.className)) { row++; }
even = (row % 2 === 0);
$tr.removeClass(wo.zebra[even ? 1 : 0]).addClass(wo.zebra[even ? 0 : 1]);
});
}
}
if (c.debug) {
ts.benchmark("Applying Zebra widget", time);
}
},
remove: function(table, c, wo){
var k, $tb,
b = c.$tbodies,
rmv = (wo.zebra || [ "even", "odd" ]).join(' ');
for (k = 0; k < b.length; k++ ){
$tb = $.tablesorter.processTbody(table, b.eq(k), true); // remove tbody
$tb.children().removeClass(rmv);
$.tablesorter.processTbody(table, $tb, false); // restore tbody
}
}
});
})(jQuery);
// ################################# TABLE SORTER PLUGIN #################################
/*! tablesorter pager plugin minified - updated 5/27/2013 */
;(function(f){f.extend({tablesorterPager:new function(){this.defaults={container:null,ajaxUrl:null,customAjaxUrl:function(b,a){return a},ajaxObject:{dataType:"json"},ajaxProcessing:function(){return[0,[],null]},output:"{startRow} to {endRow} of {totalRows} rows",updateArrows:!0,page:0,size:10,fixedHeight:!1,removeRows:!1,cssFirst:".first",cssPrev:".prev",cssNext:".next",cssLast:".last",cssGoto:".gotoPage",cssPageDisplay:".pagedisplay",cssPageSize:".pagesize",cssErrorRow:"tablesorter-errorRow",cssDisabled:"disabled", totalRows:0,totalPages:0,filteredRows:0,filteredPages:0};var t=this,u=function(b,a){var d=b.cssDisabled,e=!!a,c=Math.min(b.totalPages,b.filteredPages);b.updateArrows&&(b.$container.find(b.cssFirst+","+b.cssPrev)[e||0===b.page?"addClass":"removeClass"](d),b.$container.find(b.cssNext+","+b.cssLast)[e||b.page===c-1?"addClass":"removeClass"](d))},q=function(b,a,d){var e,c,g;e=b.config;c=f(b).hasClass("hasFilters")&&!a.ajaxUrl;a.totalPages=Math.ceil(a.totalRows/a.size);a.filteredRows=c?e.$tbodies.eq(0).children("tr:not(."+ (e.widgetOptions&&e.widgetOptions.filter_filteredRow||"filtered")+","+e.selectorRemove+")").length:a.totalRows;a.filteredPages=c?Math.ceil(a.filteredRows/a.size)||1:a.totalPages;if(0<=Math.min(a.totalPages,a.filteredPages)&&(g=a.size*a.page>a.filteredRows,a.startRow=g?1:0===a.filteredRows?0:a.size*a.page+1,a.page=g?0:a.page,a.endRow=Math.min(a.filteredRows,a.totalRows,a.size*(a.page+1)),c=a.$container.find(a.cssPageDisplay),e=a.output.replace(/\{(page|filteredRows|filteredPages|totalPages|startRow|endRow|totalRows)\}/gi, function(b){return{"{page}":a.page+1,"{filteredRows}":a.filteredRows,"{filteredPages}":a.filteredPages,"{totalPages}":a.totalPages,"{startRow}":a.startRow,"{endRow}":a.endRow,"{totalRows}":a.totalRows}[b]}),c.length&&(c["INPUT"===c[0].tagName?"val":"html"](e),a.$goto.length))){g="";c=Math.min(a.totalPages,a.filteredPages);for(e=1;e<=c;e++)g+="<option>"+e+"</option>";a.$goto.html(g).val(a.page+1)}u(a);a.initialized&&!1!==d&&f(b).trigger("pagerComplete",a)},r=function(b,a){var d,e=b.config.$tbodies.eq(0); if(a.fixedHeight&&(e.find("tr.pagerSavedHeightSpacer").remove(),d=f.data(b,"pagerSavedHeight")))d-=e.height(),5<d&&(f.data(b,"pagerLastSize")===a.size&&e.children("tr:visible").length<a.size)&&e.append('<tr class="pagerSavedHeightSpacer '+b.config.selectorRemove.replace(/(tr)?\./g,"")+'" style="height:'+d+'px;"></tr>')},x=function(b,a){var d=b.config.$tbodies.eq(0);d.find("tr.pagerSavedHeightSpacer").remove();f.data(b,"pagerSavedHeight",d.height());r(b,a);f.data(b,"pagerLastSize",a.size)},s=function(b, a){if(!a.ajaxUrl){var d;d=b.config;var e=d.$tbodies.eq(0).children("tr:not(."+d.cssChildRow+")"),c=e.length,f=a.page*a.size,p=f+a.size,k=d.widgetOptions&&d.widgetOptions.filter_filteredRow||"filtered",h=0;for(d=0;d<c;d++)e[d].className.match(k)||(e[d].style.display=h>=f&&h<p?"":"none",h++)}},y=function(b,a){a.size=parseInt(a.$size.val(),10)||a.size;f.data(b,"pagerLastSize",a.size);u(a);a.removeRows||(s(b,a),f(b).bind("sortEnd.pager filterEnd.pager",function(){s(b,a)}))},A=function(b,a,d,e,c){if("function"=== typeof d.ajaxProcessing){var g,p,k,h,l,m=f(a),n=a.config,z=m.find("thead th").length,j="";b=d.ajaxProcessing(b,a)||[0,[]];g=isNaN(b[0])&&!isNaN(b[1]);m.find("thead tr."+d.cssErrorRow).remove();if(c)f('<tr class="'+d.cssErrorRow+'"><td style="text-align:center;" colspan="'+z+'">'+(0===e.status?"Not connected, verify Network":404===e.status?"Requested page not found [404]":500===e.status?"Internal Server Error [500]":"parsererror"===c?"Requested JSON parse failed":"timeout"===c?"Time out error":"abort"=== c?"Ajax Request aborted":"Uncaught error: "+e.statusText+" ["+e.status+"]")+"</td></tr>").click(function(){f(this).remove()}).appendTo(m.find("thead:first")),n.$tbodies.eq(0).empty();else{d.totalRows=b[g?1:0]||d.totalRows||0;e=b[g?0:1]||[];c=e.length;l=b[2];if(e instanceof jQuery)n.$tbodies.eq(0).empty().append(e);else if(e.length){if(0<c)for(b=0;b<c;b++){j+="<tr>";for(g=0;g<e[b].length;g++)j+="<td>"+e[b][g]+"</td>";j+="</tr>"}n.$tbodies.eq(0).html(j)}l&&l.length===z&&(h=(p=m.hasClass("hasStickyHeaders"))? n.$sticky.children("thead:first").children().children():"",k=m.find("tfoot tr:first").children(),m.find("th."+n.cssHeader).each(function(a){var b=f(this),c;b.find("."+n.cssIcon).length?(c=b.find("."+n.cssIcon).clone(!0),b.find(".tablesorter-header-inner").html(l[a]).append(c),p&&h.length&&(c=h.eq(a).find("."+n.cssIcon).clone(!0),h.eq(a).find(".tablesorter-header-inner").html(l[a]).append(c))):(b.find(".tablesorter-header-inner").html(l[a]),p&&h.length&&h.eq(a).find(".tablesorter-header-inner").html(l[a])); k.eq(a).html(l[a])}))}n.showProcessing&&f.tablesorter.isProcessing(a);m.trigger("update");d.totalPages=Math.ceil(d.totalRows/d.size);q(a,d);r(a,d);d.initialized&&m.trigger("pagerChange",d)}d.initialized||(d.initialized=!0,f(a).trigger("pagerInitialized",d))},v=function(b,a,d){d.isDisabled=!1;var e,c,g,j,k=a.length;e=d.page*d.size;var h=e+d.size;if(!(1>k)){d.initialized&&f(b).trigger("pagerChange",d);if(d.removeRows){h>a.length&&(h=a.length);f.tablesorter.clearTableBody(b);for(j=f.tablesorter.processTbody(b, b.config.$tbodies.eq(0),!0);e<h;e++){g=a[e];k=g.length;for(c=0;c<k;c++)j.appendChild(g[c])}f.tablesorter.processTbody(b,j,!1)}else s(b,d);d.page>=d.totalPages&&B(b,d);q(b,d);d.isDisabled||r(b,d);f(b).trigger("applyWidgets")}},C=function(b,a){a.ajax?u(a,!0):(a.isDisabled=!0,f.data(b,"pagerLastPage",a.page),f.data(b,"pagerLastSize",a.size),a.page=0,a.size=a.totalRows,a.totalPages=1,f(b).find("tr.pagerSavedHeightSpacer").remove(),v(b,b.config.rowsCopy,a));a.$size.add(a.$goto).each(function(){f(this).addClass(a.cssDisabled)[0].disabled= !0})},j=function(b,a,d){if(!a.isDisabled){var e=Math.min(a.totalPages,a.filteredPages);0>a.page&&(a.page=0);a.page>e-1&&0!==e&&(a.page=e-1);if(a.ajax){var c,e=a.ajaxUrl?a.ajaxUrl.replace(/\{page([\-+]\d+)?\}/,function(b,c){return a.page+(c?parseInt(c,10):0)}).replace(/\{size\}/g,a.size):"",g=b.config.sortList,j=a.currentFilters||[],k=e.match(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/),h=e.match(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/),l=[];k&&(k=k[1],f.each(g,function(a,b){l.push(k+"["+b[0]+"]="+b[1])}), e=e.replace(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/g,l.length?l.join("&"):k),l=[]);h&&(h=h[1],f.each(j,function(a,b){b&&l.push(h+"["+a+"]="+encodeURIComponent(b))}),e=e.replace(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/g,l.length?l.join("&"):h));"function"===typeof a.customAjaxUrl&&(e=a.customAjaxUrl(b,e));c=e;var m=f(document),e=b.config;""!==c&&(e.showProcessing&&f.tablesorter.isProcessing(b,!0),m.bind("ajaxError.pager",function(d,e,f,g){c.match(f.url)&&(A(null,b,a,e,g),m.unbind("ajaxError.pager"))}), a.ajaxObject.url=c,a.ajaxObject.success=function(c){A(c,b,a);m.unbind("ajaxError.pager");"function"===typeof a.oldAjaxSuccess&&a.oldAjaxSuccess(c)},f.ajax(a.ajaxObject))}else a.ajax||v(b,b.config.rowsCopy,a);f.data(b,"pagerLastPage",a.page);f.data(b,"pagerUpdateTriggered",!0);a.initialized&&!1!==d&&f(b).trigger("pageMoved",a)}},w=function(b,a,d){d.size=a;d.$size.val(a);f.data(b,"pagerLastPage",d.page);f.data(b,"pagerLastSize",d.size);d.totalPages=Math.ceil(d.totalRows/d.size);j(b,d)},E=function(b, a){a.page=0;j(b,a)},B=function(b,a){a.page=Math.min(a.totalPages,a.filteredPages)-1;j(b,a)},F=function(b,a){a.page++;a.page>=Math.min(a.totalPages,a.filteredPages)-1&&(a.page=Math.min(a.totalPages,a.filteredPages)-1);j(b,a)},G=function(b,a){a.page--;0>=a.page&&(a.page=0);j(b,a)},D=function(b,a,d){var e=a.$size.removeClass(a.cssDisabled).removeAttr("disabled");a.$goto.removeClass(a.cssDisabled).removeAttr("disabled");a.isDisabled=!1;a.page=f.data(b,"pagerLastPage")||a.page||0;a.size=f.data(b,"pagerLastSize")|| parseInt(e.find("option[selected]").val(),10)||a.size;e.val(a.size);a.totalPages=Math.ceil(Math.min(a.totalPages,a.filteredPages)/a.size);d&&(f(b).trigger("update"),w(b,a.size,a),y(b,a),r(b,a))};t.appender=function(b,a){var d=b.config.pager;d.ajax||(b.config.rowsCopy=a,d.totalRows=a.length,d.size=f.data(b,"pagerLastSize")||d.size,d.totalPages=Math.ceil(d.totalRows/d.size),v(b,a,d))};t.construct=function(b){return this.each(function(){if(this.config&&this.hasInitialized){var a,d,e=this.config,c=e.pager= f.extend({},f.tablesorterPager.defaults,b),g=this,p=g.config,k=f(g),h=c.$container=f(c.container).addClass("tablesorter-pager").show();c.oldAjaxSuccess=c.oldAjaxSuccess||c.ajaxObject.success;e.appender=t.appender;k.unbind("filterStart.pager filterEnd.pager sortEnd.pager disable.pager enable.pager destroy.pager update.pager pageSize.pager").bind("filterStart.pager",function(a,b){f.data(g,"pagerUpdateTriggered",!1);c.currentFilters=b}).bind("filterEnd.pager sortEnd.pager",function(){f.data(g,"pagerUpdateTriggered")? f.data(g,"pagerUpdateTriggered",!1):(j(g,c,!1),q(g,c,!1),r(g,c))}).bind("disable.pager",function(a){a.stopPropagation();C(g,c)}).bind("enable.pager",function(a){a.stopPropagation();D(g,c,!0)}).bind("destroy.pager",function(a){a.stopPropagation();C(g,c);c.$container.hide();g.config.appender=null;f(g).unbind("destroy.pager sortEnd.pager filterEnd.pager enable.pager disable.pager")}).bind("update.pager",function(a){a.stopPropagation();s(g,c)}).bind("pageSize.pager",function(a,b){a.stopPropagation(); w(g,parseInt(b,10)||10,c);s(g,c);q(g,c,!1);c.$size.length&&c.$size.val(c.size)}).bind("pageSet.pager",function(a,b){a.stopPropagation();c.page=(parseInt(b,10)||1)-1;c.$goto.length&&c.$goto.val(c.size);j(g,c);q(g,c,!1)});a=[c.cssFirst,c.cssPrev,c.cssNext,c.cssLast];d=[E,G,F,B];h.find(a.join(",")).unbind("click.pager").bind("click.pager",function(){var b,e=f(this),h=a.length;if(!e.hasClass(c.cssDisabled))for(b=0;b<h;b++)if(e.is(a[b])){d[b](g,c);break}return!1});c.$goto=h.find(c.cssGoto);c.$goto.length&& (c.$goto.unbind("change").bind("change",function(){c.page=f(this).val()-1;j(g,c)}),q(g,c,!1));c.$size=h.find(c.cssPageSize);c.$size.length&&c.$size.unbind("change.pager").bind("change.pager",function(){c.$size.val(f(this).val());f(this).hasClass(c.cssDisabled)||(w(g,parseInt(f(this).val(),10),c),x(g,c));return!1});c.initialized=!1;k.trigger("pagerBeforeInitialized",c);D(g,c,!1);"string"===typeof c.ajaxUrl?(c.ajax=!0,p.widgetOptions.filter_serversideFiltering=!0,p.serverSideSorting=!0,j(g,c)):(c.ajax= !1,f(this).trigger("appendCache",!0),y(g,c));x(g,c);c.ajax||(c.initialized=!0,f(g).trigger("pagerInitialized",c))}})}}});f.fn.extend({tablesorterPager:f.tablesorterPager.construct})})(jQuery);
// ################################# TABLE SORTER WIDGET #################################
/*! tableSorter 2.8+ widgets - updated 6/4/2013
*
* Column Styles
* Column Filters
* Column Resizing
* Sticky Header
* UI Theme (generalized)
* Save Sort
* [ "columns", "filter", "resizable", "stickyHeaders", "uitheme", "saveSort" ]
*/
/*jshint browser:true, jquery:true, unused:false, loopfunc:true */
/*global jQuery: false, localStorage: false, navigator: false */
;(function($){
"use strict";
var ts = $.tablesorter = $.tablesorter || {};
ts.themes = {
"bootstrap" : {
table : 'table table-bordered table-striped',
header : 'bootstrap-header', // give the header a gradient background
footerRow : '',
footerCells: '',
icons : '', // add "icon-white" to make them white; this icon class is added to the <i> in the header
sortNone : 'bootstrap-icon-unsorted',
sortAsc : 'icon-chevron-up',
sortDesc : 'icon-chevron-down',
active : '', // applied when column is sorted
hover : '', // use custom css here - bootstrap class may not override it
filterRow : '', // filter row class
even : '', // even row zebra striping
odd : '' // odd row zebra striping
},
"jui" : {
table : 'ui-widget ui-widget-content ui-corner-all', // table classes
header : 'ui-widget-header ui-corner-all ui-state-default', // header classes
footerRow : '',
footerCells: '',
icons : 'ui-icon', // icon class added to the <i> in the header
sortNone : 'ui-icon-carat-2-n-s',
sortAsc : 'ui-icon-carat-1-n',
sortDesc : 'ui-icon-carat-1-s',
active : 'ui-state-active', // applied when column is sorted
hover : 'ui-state-hover', // hover class
filterRow : '',
even : 'ui-widget-content', // even row zebra striping
odd : 'ui-state-default' // odd row zebra striping
}
};
// *** Store data in local storage, with a cookie fallback ***
/* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json)
if you need it, then include https://github.com/douglascrockford/JSON-js
$.parseJSON is not available is jQuery versions older than 1.4.1, using older
versions will only allow storing information for one page at a time
// *** Save data (JSON format only) ***
// val must be valid JSON... use http://jsonlint.com/ to ensure it is valid
var val = { "mywidget" : "data1" }; // valid JSON uses double quotes
// $.tablesorter.storage(table, key, val);
$.tablesorter.storage(table, 'tablesorter-mywidget', val);
// *** Get data: $.tablesorter.storage(table, key); ***
v = $.tablesorter.storage(table, 'tablesorter-mywidget');
// val may be empty, so also check for your data
val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : '';
alert(val); // "data1" if saved, or "" if not
*/
ts.storage = function(table, key, val){
var d, k, ls = false, v = {},
id = table.id || $('.tablesorter').index( $(table) ),
url = window.location.pathname;
// https://gist.github.com/paulirish/5558557
if ("localStorage" in window) {
try {
window.localStorage.setItem('_tmptest', 'temp');
ls = true;
window.localStorage.removeItem('_tmptest');
} catch(e) {}
}
// *** get val ***
if ($.parseJSON){
if (ls){
v = $.parseJSON(localStorage[key] || '{}');
} else {
k = document.cookie.split(/[;\s|=]/); // cookie
d = $.inArray(key, k) + 1; // add one to get from the key to the value
v = (d !== 0) ? $.parseJSON(k[d] || '{}') : {};
}
}
// allow val to be an empty string to
if ((val || val === '') && window.JSON && JSON.hasOwnProperty('stringify')){
// add unique identifiers = url pathname > table ID/index on page > data
if (!v[url]) {
v[url] = {};
}
v[url][id] = val;
// *** set val ***
if (ls){
localStorage[key] = JSON.stringify(v);
} else {
d = new Date();
d.setTime(d.getTime() + (31536e+6)); // 365 days
document.cookie = key + '=' + (JSON.stringify(v)).replace(/\"/g,'\"') + '; expires=' + d.toGMTString() + '; path=/';
}
} else {
return v && v[url] ? v[url][id] : {};
}
};
// Add a resize event to table headers
// **************************
ts.addHeaderResizeEvent = function(table, disable, options){
var defaults = {
timer : 250
},
o = $.extend({}, defaults, options),
c = table.config,
wo = c.widgetOptions,
headers,
checkSizes = function(){
wo.resize_flag = true;
headers = [];
c.$headers.each(function(){
var d = $.data(this, 'savedSizes'),
w = this.offsetWidth,
h = this.offsetHeight;
if (w !== d[0] || h !== d[1]) {
$.data(this, 'savedSizes', [ w, h ]);
headers.push(this);
}
});
if (headers.length) { c.$table.trigger('resize', [ headers ]); }
wo.resize_flag = false;
};
clearInterval(wo.resize_timer);
if (disable) {
wo.resize_flag = false;
return false;
}
c.$headers.each(function(){
$.data(this, 'savedSizes', [ this.offsetWidth, this.offsetHeight ]);
});
wo.resize_timer = setInterval(function(){
if (wo.resize_flag) { return; }
checkSizes();
}, o.timer);
};
// Widget: General UI theme
// "uitheme" option in "widgetOptions"
// **************************
ts.addWidget({
id: "uitheme",
priority: 10,
options: {
uitheme : 'jui'
},
format: function(table, c, wo){
var time, klass, $el, $tar,
t = ts.themes,
$t = c.$table,
theme = c.theme !== 'default' ? c.theme : wo.uitheme || 'jui',
o = t[ t[theme] ? theme : t[wo.uitheme] ? wo.uitheme : 'jui'],
$h = c.$headers,
sh = 'tr.' + (wo.stickyHeaders || 'tablesorter-stickyHeader'),
rmv = o.sortNone + ' ' + o.sortDesc + ' ' + o.sortAsc;
if (c.debug) { time = new Date(); }
if (!$t.hasClass('tablesorter-' + theme) || c.theme === theme || !table.hasInitialized){
// update zebra stripes
if (o.even !== '') { wo.zebra[0] += ' ' + o.even; }
if (o.odd !== '') { wo.zebra[1] += ' ' + o.odd; }
// add table/footer class names
t = $t
// remove other selected themes; use widgetOptions.theme_remove
.removeClass( c.theme === '' ? '' : 'tablesorter-' + c.theme )
.addClass('tablesorter-' + theme + ' ' + o.table) // add theme widget class name
.find('tfoot');
if (t.length) {
t
.find('tr').addClass(o.footerRow)
.children('th, td').addClass(o.footerCells);
}
// update header classes
$h
.addClass(o.header)
.filter(':not(.sorter-false)')
.bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(e){
// toggleClass with switch added in jQuery 1.3
$(this)[ e.type === 'mouseenter' ? 'addClass' : 'removeClass' ](o.hover);
});
if (!$h.find('.tablesorter-wrapper').length) {
// Firefox needs this inner div to position the resizer correctly
$h.wrapInner('<div class="tablesorter-wrapper" style="position:relative;height:100%;width:100%"></div>');
}
if (c.cssIcon){
// if c.cssIcon is '', then no <i> is added to the header
$h.find('.' + c.cssIcon).addClass(o.icons);
}
if ($t.hasClass('hasFilters')){
$h.find('.tablesorter-filter-row').addClass(o.filterRow);
}
}
$.each($h, function(i){
$el = $(this);
$tar = (c.cssIcon) ? $el.find('.' + c.cssIcon) : $el;
if (this.sortDisabled){
// no sort arrows for disabled columns!
$el.removeClass(rmv);
$tar.removeClass(rmv + ' tablesorter-icon ' + o.icons);
} else {
t = ($t.hasClass('hasStickyHeaders')) ? $t.find(sh).find('th').eq(i).add($el) : $el;
klass = ($el.hasClass(c.cssAsc)) ? o.sortAsc : ($el.hasClass(c.cssDesc)) ? o.sortDesc : $el.hasClass(c.cssHeader) ? o.sortNone : '';
$el[klass === o.sortNone ? 'removeClass' : 'addClass'](o.active);
$tar.removeClass(rmv).addClass(klass);
}
});
if (c.debug){
ts.benchmark("Applying " + theme + " theme", time);
}
},
remove: function(table, c, wo){
var $t = c.$table,
theme = typeof wo.uitheme === 'object' ? 'jui' : wo.uitheme || 'jui',
o = typeof wo.uitheme === 'object' ? wo.uitheme : ts.themes[ ts.themes.hasOwnProperty(theme) ? theme : 'jui'],
$h = $t.children('thead').children(),
rmv = o.sortNone + ' ' + o.sortDesc + ' ' + o.sortAsc;
$t
.removeClass('tablesorter-' + theme + ' ' + o.table)
.find(c.cssHeader).removeClass(o.header);
$h
.unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover
.removeClass(o.hover + ' ' + rmv + ' ' + o.active)
.find('.tablesorter-filter-row').removeClass(o.filterRow);
$h.find('.tablesorter-icon').removeClass(o.icons);
}
});
// Widget: Column styles
// "columns", "columns_thead" (true) and
// "columns_tfoot" (true) options in "widgetOptions"
// **************************
ts.addWidget({
id: "columns",
priority: 30,
options : {
columns : [ "primary", "secondary", "tertiary" ]
},
format: function(table, c, wo){
var $tb, $tr, $td, $t, time, last, rmv, i, k, l,
$tbl = c.$table,
b = c.$tbodies,
list = c.sortList,
len = list.length,
// keep backwards compatibility, for now
css = (c.widgetColumns && c.widgetColumns.hasOwnProperty('css')) ? c.widgetColumns.css || css :
(wo && wo.hasOwnProperty('columns')) ? wo.columns || css : css;
last = css.length-1;
rmv = css.join(' ');
if (c.debug){
time = new Date();
}
// check if there is a sort (on initialization there may not be one)
for (k = 0; k < b.length; k++ ){
$tb = ts.processTbody(table, b.eq(k), true); // detach tbody
$tr = $tb.children('tr');
l = $tr.length;
// loop through the visible rows
$tr.each(function(){
$t = $(this);
if (this.style.display !== 'none'){
// remove all columns class names
$td = $t.children().removeClass(rmv);
// add appropriate column class names
if (list && list[0]){
// primary sort column class
$td.eq(list[0][0]).addClass(css[0]);
if (len > 1){
for (i = 1; i < len; i++){
// secondary, tertiary, etc sort column classes
$td.eq(list[i][0]).addClass( css[i] || css[last] );
}
}
}
}
});
ts.processTbody(table, $tb, false);
}
// add classes to thead and tfoot
$tr = wo.columns_thead !== false ? 'thead tr' : '';
if (wo.columns_tfoot !== false) {
$tr += ($tr === '' ? '' : ',') + 'tfoot tr';
}
if ($tr.length) {
$t = $tbl.find($tr).children().removeClass(rmv);
if (list && list[0]){
// primary sort column class
$t.filter('[data-column="' + list[0][0] + '"]').addClass(css[0]);
if (len > 1){
for (i = 1; i < len; i++){
// secondary, tertiary, etc sort column classes
$t.filter('[data-column="' + list[i][0] + '"]').addClass(css[i] || css[last]);
}
}
}
}
if (c.debug){
ts.benchmark("Applying Columns widget", time);
}
},
remove: function(table, c, wo){
var k, $tb,
b = c.$tbodies,
rmv = (wo.columns || [ "primary", "secondary", "tertiary" ]).join(' ');
c.$headers.removeClass(rmv);
c.$table.children('tfoot').children('tr').children('th, td').removeClass(rmv);
for (k = 0; k < b.length; k++ ){
$tb = ts.processTbody(table, b.eq(k), true); // remove tbody
$tb.children('tr').each(function(){
$(this).children().removeClass(rmv);
});
ts.processTbody(table, $tb, false); // restore tbody
}
}
});
// Widget: filter
// **************************
ts.addWidget({
id: "filter",
priority: 50,
options : {
filter_childRows : false, // if true, filter includes child row content in the search
filter_columnFilters : true, // if true, a filter will be added to the top of each table column
filter_cssFilter : 'tablesorter-filter', // css class name added to the filter row & each input in the row
filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin
filter_formatter : null, // add custom filter elements to the filter row
filter_functions : null, // add custom filter functions using this option
filter_hideFilters : false, // collapse filter row when mouse leaves the area
filter_ignoreCase : true, // if true, make all searches case-insensitive
filter_liveSearch : true, // if true, search column content while the user types (with a delay)
filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available (visible) options within the drop down
filter_reset : null, // jQuery selector string of an element used to reset the filters
filter_searchDelay : 300, // typing delay in milliseconds before starting a search
filter_startsWith : false, // if true, filter start from the beginning of the cell contents
filter_useParsedData : false, // filter all data using parsed content
filter_serversideFiltering : false, // if true, server-side filtering should be performed because client-side filtering will be disabled, but the ui and events will still be used.
filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value
// regex used in filter "check" functions - not for general use and not documented
filter_regex : {
"regex" : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex
"child" : /tablesorter-childRow/, // child row class name; this gets updated in the script
"filtered" : /filtered/, // filtered (hidden) row class name; updated in the script
"type" : /undefined|number/, // check type
"exact" : /(^[\"|\'|=])|([\"|\'|=]$)/g, // exact match
"nondigit" : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser)
"operators" : /[<>=]/g // replace operators
}
},
format: function(table, c, wo){
if (c.parsers && !c.$table.hasClass('hasFilters')){
var i, j, k, l, val, ff, x, xi, st, sel, str,
ft, ft2, $th, rg, s, t, dis, col,
fmt = ts.formatFloat,
last = '', // save last filter search
$ths = c.$headers,
css = wo.filter_cssFilter,
$t = c.$table.addClass('hasFilters'),
b = $t.find('tbody'),
cols = c.parsers.length,
parsed, time, timer,
// dig fer gold
checkFilters = function(filter){
var arry = $.isArray(filter),
v = (arry) ? filter : ts.getFilters(table),
cv = (v || []).join(''); // combined filter values
// add filter array back into inputs
if (arry) {
ts.setFilters( $t, v );
}
if (wo.filter_hideFilters){
// show/hide filter row as needed
$t.find('.tablesorter-filter-row').trigger( cv === '' ? 'mouseleave' : 'mouseenter' );
}
// return if the last search is the same; but filter === false when updating the search
// see example-widget-filter.html filter toggle buttons
if (last === cv && filter !== false) { return; }
$t.trigger('filterStart', [v]);
if (c.showProcessing) {
// give it time for the processing icon to kick in
setTimeout(function(){
findRows(filter, v, cv);
return false;
}, 30);
} else {
findRows(filter, v, cv);
return false;
}
},
findRows = function(filter, v, cv){
var $tb, $tr, $td, cr, r, l, ff, time, r1, r2, searchFiltered;
if (c.debug) { time = new Date(); }
for (k = 0; k < b.length; k++ ){
if (b.eq(k).hasClass(c.cssInfoBlock)) { continue; } // ignore info blocks, issue #264
$tb = ts.processTbody(table, b.eq(k), true);
$tr = $tb.children('tr:not(.' + c.cssChildRow + ')');
l = $tr.length;
if (cv === '' || wo.filter_serversideFiltering){
$tb.children().show().removeClass(wo.filter_filteredRow);
} else {
// optimize searching only through already filtered rows - see #313
searchFiltered = true;
r = $t.data('lastSearch') || [];
$.each(v, function(i,val){
// check for changes from beginning of filter; but ignore if there is a logical "or" in the string
searchFiltered = (val || '').indexOf(r[i] || '') === 0 && searchFiltered && !/(\s+or\s+|\|)/g.test(val || '');
});
// can't search when all rows are hidden - this happens when looking for exact matches
if (searchFiltered && $tr.filter(':visible').length === 0) { searchFiltered = false; }
// loop through the rows
for (j = 0; j < l; j++){
r = $tr[j].className;
// skip child rows & already filtered rows
if ( wo.filter_regex.child.test(r) || (searchFiltered && wo.filter_regex.filtered.test(r)) ) { continue; }
r = true;
cr = $tr.eq(j).nextUntil('tr:not(.' + c.cssChildRow + ')');
// so, if "table.config.widgetOptions.filter_childRows" is true and there is
// a match anywhere in the child row, then it will make the row visible
// checked here so the option can be changed dynamically
t = (cr.length && wo.filter_childRows) ? cr.text() : '';
t = wo.filter_ignoreCase ? t.toLocaleLowerCase() : t;
$td = $tr.eq(j).children('td');
for (i = 0; i < cols; i++){
// ignore if filter is empty or disabled
if (v[i]){
// check if column data should be from the cell or from parsed data
if (wo.filter_useParsedData || parsed[i]){
x = c.cache[k].normalized[j][i];
} else {
// using older or original tablesorter
x = $.trim($td.eq(i).text());
}
xi = !wo.filter_regex.type.test(typeof x) && wo.filter_ignoreCase ? x.toLocaleLowerCase() : x;
ff = r; // if r is true, show that row
// val = case insensitive, v[i] = case sensitive
val = wo.filter_ignoreCase ? v[i].toLocaleLowerCase() : v[i];
if (wo.filter_functions && wo.filter_functions[i]){
if (wo.filter_functions[i] === true){
// default selector; no "filter-select" class
ff = ($ths.filter('[data-column="' + i + '"]:last').hasClass('filter-match')) ? xi.search(val) >= 0 : v[i] === x;
} else if (typeof wo.filter_functions[i] === 'function'){
// filter callback( exact cell content, parser normalized content, filter input value, column index )
ff = wo.filter_functions[i](x, c.cache[k].normalized[j][i], v[i], i);
} else if (typeof wo.filter_functions[i][v[i]] === 'function'){
// selector option function
ff = wo.filter_functions[i][v[i]](x, c.cache[k].normalized[j][i], v[i], i);
}
// Look for regex
} else if (wo.filter_regex.regex.test(val)){
rg = wo.filter_regex.regex.exec(val);
try {
ff = new RegExp(rg[1], rg[2]).test(xi);
} catch (err){
ff = false;
}
// Look for quotes or equals to get an exact match; ignore type since xi could be numeric
/*jshint eqeqeq:false */
} else if (val.replace(wo.filter_regex.exact, '') == xi){
ff = true;
// Look for a not match
} else if (/^\!/.test(val)){
val = val.replace('!','');
s = xi.search($.trim(val));
ff = val === '' ? true : !(wo.filter_startsWith ? s === 0 : s >= 0);
// Look for operators >, >=, < or <=
} else if (/^[<>]=?/.test(val)){
s = fmt(val.replace(wo.filter_regex.nondigit, '').replace(wo.filter_regex.operators,''), table);
// parse filter value in case we're comparing numbers (dates)
if (parsed[i] || c.parsers[i].type === 'numeric') {
rg = c.parsers[i].format('' + val.replace(wo.filter_regex.operators,''), table, $ths.eq(i), i);
s = (rg !== '' && !isNaN(rg)) ? rg : s;
}
// xi may be numeric - see issue #149;
// check if c.cache[k].normalized[j] is defined, because sometimes j goes out of range? (numeric columns)
rg = ( parsed[i] || c.parsers[i].type === 'numeric' ) && !isNaN(s) && c.cache[k].normalized[j] ? c.cache[k].normalized[j][i] :
isNaN(xi) ? fmt(xi.replace(wo.filter_regex.nondigit, ''), table) : fmt(xi, table);
if (/>/.test(val)) { ff = />=/.test(val) ? rg >= s : rg > s; }
if (/</.test(val)) { ff = /<=/.test(val) ? rg <= s : rg < s; }
if (s === '') { ff = true; } // keep showing all rows if nothing follows the operator
// Look for an AND or && operator (logical and)
} else if (/\s+(AND|&&)\s+/g.test(v[i])) {
s = val.split(/(?:\s+(?:and|&&)\s+)/g);
ff = xi.search($.trim(s[0])) >= 0;
r1 = s.length - 1;
while (ff && r1) {
ff = ff && xi.search($.trim(s[r1])) >= 0;
r1--;
}
// Look for a range (using " to " or " - ") - see issue #166; thanks matzhu!
} else if (/\s+(-|to)\s+/.test(val)){
s = val.split(/(?: - | to )/); // make sure the dash is for a range and not indicating a negative number
r1 = fmt(s[0].replace(wo.filter_regex.nondigit, ''), table);
r2 = fmt(s[1].replace(wo.filter_regex.nondigit, ''), table);
// parse filter value in case we're comparing numbers (dates)
if (parsed[i] || c.parsers[i].type === 'numeric') {
rg = c.parsers[i].format('' + s[0], table, $ths.eq(i), i);
r1 = (rg !== '' && !isNaN(rg)) ? rg : r1;
rg = c.parsers[i].format('' + s[1], table, $ths.eq(i), i);
r2 = (rg !== '' && !isNaN(rg)) ? rg : r2;
}
rg = ( parsed[i] || c.parsers[i].type === 'numeric' ) && !isNaN(r1) && !isNaN(r2) ? c.cache[k].normalized[j][i] :
isNaN(xi) ? fmt(xi.replace(wo.filter_regex.nondigit, ''), table) : fmt(xi, table);
if (r1 > r2) { ff = r1; r1 = r2; r2 = ff; } // swap
ff = (rg >= r1 && rg <= r2) || (r1 === '' || r2 === '') ? true : false;
// Look for wild card: ? = single, * = multiple, or | = logical OR
} else if ( /[\?|\*]/.test(val) || /\s+OR\s+/.test(v[i]) ){
s = val.replace(/\s+OR\s+/gi,"|");
// look for an exact match with the "or" unless the "filter-match" class is found
if (!$ths.filter('[data-column="' + i + '"]:last').hasClass('filter-match') && /\|/.test(s)) {
s = '^(' + s + ')$';
}
ff = new RegExp( s.replace(/\?/g, '\\S{1}').replace(/\*/g, '\\S*') ).test(xi);
// Look for match, and add child row data for matching
} else {
x = (xi + t).indexOf(val);
ff = ( (!wo.filter_startsWith && x >= 0) || (wo.filter_startsWith && x === 0) );
}
r = (ff) ? (r ? true : false) : false;
}
}
$tr[j].style.display = (r ? '' : 'none');
$tr.eq(j)[r ? 'removeClass' : 'addClass'](wo.filter_filteredRow);
if (cr.length) { cr[r ? 'show' : 'hide'](); }
}
}
ts.processTbody(table, $tb, false);
}
last = cv; // save last search
$t.data('lastSearch', v);
if (c.debug){
ts.benchmark("Completed filter widget search", time);
}
$t.trigger('applyWidgets'); // make sure zebra widget is applied
$t.trigger('filterEnd');
},
buildSelect = function(i, updating, onlyavail){
var o, t, arry = [], currentVal;
i = parseInt(i, 10);
t = $ths.filter('[data-column="' + i + '"]:last');
// t.data('placeholder') won't work in jQuery older than 1.4.3
o = '<option value="">' + (t.data('placeholder') || t.attr('data-placeholder') || '') + '</option>';
for (k = 0; k < b.length; k++ ){
l = c.cache[k].row.length;
// loop through the rows
for (j = 0; j < l; j++){
// check if has class filtered
if (onlyavail && c.cache[k].row[j][0].className.match(wo.filter_filteredRow)) { continue; }
// get non-normalized cell content
if (wo.filter_useParsedData){
arry.push( '' + c.cache[k].normalized[j][i] );
} else {
t = c.cache[k].row[j][0].cells[i];
if (t){
arry.push( $.trim(c.supportsTextContent ? t.textContent : $(t).text()) );
}
}
}
}
// get unique elements and sort the list
// if $.tablesorter.sortText exists (not in the original tablesorter),
// then natural sort the list otherwise use a basic sort
arry = $.grep(arry, function(v, k){
return $.inArray(v, arry) === k;
});
arry = (ts.sortText) ? arry.sort(function(a, b){ return ts.sortText(table, a, b, i); }) : arry.sort(true);
// Get curent filter value
currentVal = $t.find('thead').find('select.' + css + '[data-column="' + i + '"]').val();
// build option list
for (k = 0; k < arry.length; k++){
t = arry[k].replace(/\"/g, """);
// replace quotes - fixes #242 & ignore empty strings - see http://stackoverflow.com/q/14990971/145346
o += arry[k] !== '' ? '<option value="' + t + '"' + (currentVal === t ? ' selected="selected"' : '') +'>' + arry[k] + '</option>' : '';
}
$t.find('thead').find('select.' + css + '[data-column="' + i + '"]')[ updating ? 'html' : 'append' ](o);
},
buildDefault = function(updating){
// build default select dropdown
for (i = 0; i < cols; i++){
t = $ths.filter('[data-column="' + i + '"]:last');
// look for the filter-select class; build/update it if found
if ((t.hasClass('filter-select') || wo.filter_functions && wo.filter_functions[i] === true) && !t.hasClass('filter-false')){
if (!wo.filter_functions) { wo.filter_functions = {}; }
wo.filter_functions[i] = true; // make sure this select gets processed by filter_functions
buildSelect(i, updating, t.hasClass(wo.filter_onlyAvail));
}
}
},
searching = function(filter){
if (typeof filter === 'undefined' || filter === true){
// delay filtering
clearTimeout(timer);
timer = setTimeout(function(){
checkFilters(filter);
}, wo.filter_liveSearch ? wo.filter_searchDelay : 10);
} else {
// skip delay
checkFilters(filter);
}
};
if (c.debug){
time = new Date();
}
wo.filter_regex.child = new RegExp(c.cssChildRow);
wo.filter_regex.filtered = new RegExp(wo.filter_filteredRow);
// don't build filter row if columnFilters is false or all columns are set to "filter-false" - issue #156
if (wo.filter_columnFilters !== false && $ths.filter('.filter-false').length !== $ths.length){
// build filter row
t = '<tr class="tablesorter-filter-row">';
for (i = 0; i < cols; i++){
t += '<td></td>';
}
c.$filters = $(t += '</tr>').appendTo( $t.find('thead').eq(0) ).find('td');
// build each filter input
for (i = 0; i < cols; i++){
dis = false;
$th = $ths.filter('[data-column="' + i + '"]:last'); // assuming last cell of a column is the main column
sel = (wo.filter_functions && wo.filter_functions[i] && typeof wo.filter_functions[i] !== 'function') || $th.hasClass('filter-select');
// use header option - headers: { 1: { filter: false } } OR add class="filter-false"
if (ts.getData){
// get data from jQuery data, metadata, headers option or header class name
dis = ts.getData($th[0], c.headers[i], 'filter') === 'false';
} else {
// only class names and header options - keep this for compatibility with tablesorter v2.0.5
dis = (c.headers[i] && c.headers[i].hasOwnProperty('filter') && c.headers[i].filter === false) || $th.hasClass('filter-false');
}
if (sel){
t = $('<select>').appendTo( c.$filters.eq(i) );
} else {
if (wo.filter_formatter && $.isFunction(wo.filter_formatter[i])) {
t = wo.filter_formatter[i]( c.$filters.eq(i), i );
// no element returned, so lets go find it
if (t && t.length === 0) { t = c.$filters.eq(i).children('input'); }
// element not in DOM, so lets attach it
if (t && (t.parent().length === 0 || (t.parent().length && t.parent()[0] !== c.$filters[i]))) {
c.$filters.eq(i).append(t);
}
} else {
t = $('<input type="search">').appendTo( c.$filters.eq(i) );
}
if (t) {
t.attr('placeholder', $th.data('placeholder') || $th.attr('data-placeholder') || '');
}
}
if (t) {
t.addClass(css).attr('data-column', i);
if (dis) {
t.addClass('disabled')[0].disabled = true; // disabled!
}
}
}
}
$t
.bind('addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '.split(' ').join('.tsfilter '), function(e, filter){
if (!/(search|filterReset|filterEnd)/.test(e.type)){
e.stopPropagation();
buildDefault(true);
}
if (e.type === 'filterReset') {
$t.find('.' + css).val('');
}
if (e.type === 'filterEnd') {
buildDefault(true);
} else {
// send false argument to force a new search; otherwise if the filter hasn't changed, it will return
filter = e.type === 'search' ? filter : e.type === 'updateComplete' ? $t.data('lastSearch') : '';
searching(filter);
}
return false;
})
.find('input.' + css).bind('keyup search', function(e, filter){
// emulate what webkit does.... escape clears the filter
if (e.which === 27) {
this.value = '';
// liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace
} else if ( (typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch && this.value !== '') || ( e.type === 'keyup' &&
( (e.which < 32 && e.which !== 8 && wo.filter_liveSearch === true && e.which !== 13) || (e.which >= 37 && e.which <=40) || (e.which !== 13 && wo.filter_liveSearch === false) ) ) ) {
return;
}
searching(filter);
});
// parse columns after formatter, in case the class is added at that point
parsed = $ths.map(function(i){
return (ts.getData) ? ts.getData($ths.filter('[data-column="' + i + '"]:last'), c.headers[i], 'filter') === 'parsed' : $(this).hasClass('filter-parsed');
}).get();
// reset button/link
if (wo.filter_reset && $(wo.filter_reset).length){
$(wo.filter_reset).bind('click.tsfilter', function(){
$t.trigger('filterReset');
});
}
if (wo.filter_functions){
// i = column # (string)
for (col in wo.filter_functions){
if (wo.filter_functions.hasOwnProperty(col) && typeof col === 'string'){
t = $ths.filter('[data-column="' + col + '"]:last');
ff = '';
if (wo.filter_functions[col] === true && !t.hasClass('filter-false')){
buildSelect(col);
} else if (typeof col === 'string' && !t.hasClass('filter-false')){
// add custom drop down list
for (str in wo.filter_functions[col]){
if (typeof str === 'string'){
ff += ff === '' ? '<option value="">' + (t.data('placeholder') || t.attr('data-placeholder') || '') + '</option>' : '';
ff += '<option value="' + str + '">' + str + '</option>';
}
}
$t.find('thead').find('select.' + css + '[data-column="' + col + '"]').append(ff);
}
}
}
}
// not really updating, but if the column has both the "filter-select" class & filter_functions set to true,
// it would append the same options twice.
buildDefault(true);
$t.find('select.' + css).bind('change search', function(e, filter){
checkFilters(filter);
});
if (wo.filter_hideFilters){
$t
.find('.tablesorter-filter-row')
.addClass('hideme')
.bind('mouseenter mouseleave', function(e){
// save event object - http://bugs.jquery.com/ticket/12140
var all, evt = e;
ft = $(this);
clearTimeout(st);
st = setTimeout(function(){
if (/enter|over/.test(evt.type)){
ft.removeClass('hideme');
} else {
// don't hide if input has focus
// $(':focus') needs jQuery 1.6+
if ($(document.activeElement).closest('tr')[0] !== ft[0]){
// get all filter values
all = $t.find('.' + wo.filter_cssFilter).map(function(){
return $(this).val() || '';
}).get().join('');
// don't hide row if any filter has a value
if (all === ''){
ft.addClass('hideme');
}
}
}
}, 200);
})
.find('input, select').bind('focus blur', function(e){
ft2 = $(this).closest('tr');
clearTimeout(st);
st = setTimeout(function(){
// don't hide row if any filter has a value
if ($t.find('.' + wo.filter_cssFilter).map(function(){ return $(this).val() || ''; }).get().join('') === ''){
ft2[ e.type === 'focus' ? 'removeClass' : 'addClass']('hideme');
}
}, 200);
});
}
// show processing icon
if (c.showProcessing) {
$t.bind('filterStart.tsfilter filterEnd.tsfilter', function(e, v) {
var fc = (v) ? $t.find('.' + c.cssHeader).filter('[data-column]').filter(function(){
return v[$(this).data('column')] !== '';
}) : '';
ts.isProcessing($t[0], e.type === 'filterStart', v ? fc : '');
});
}
if (c.debug){
ts.benchmark("Applying Filter widget", time);
}
// add default values
$t.bind('tablesorter-initialized', function(){
ff = ts.getFilters(table);
for (i = 0; i < ff.length; i++) {
ff[i] = $ths.filter('[data-column="' + i + '"]:last').attr(wo.filter_defaultAttrib) || ff[i];
}
ts.setFilters(table, ff, true);
});
// filter widget initialized
$t.trigger('filterInit');
checkFilters();
}
},
remove: function(table, c, wo){
var k, $tb,
$t = c.$table,
b = c.$tbodies;
$t
.removeClass('hasFilters')
// add .tsfilter namespace to all BUT search
.unbind('addRows updateCell update updateComplete appendCache search filterStart filterEnd '.split(' ').join('.tsfilter '))
.find('.tablesorter-filter-row').remove();
for (k = 0; k < b.length; k++ ){
$tb = ts.processTbody(table, b.eq(k), true); // remove tbody
$tb.children().removeClass(wo.filter_filteredRow).show();
ts.processTbody(table, $tb, false); // restore tbody
}
if (wo.filterreset) { $(wo.filter_reset).unbind('click.tsfilter'); }
}
});
ts.getFilters = function(table) {
var c = table ? $(table)[0].config : {};
if (c && c.widgetOptions && !c.widgetOptions.filter_columnFilters) { return $(table).data('lastSearch'); }
return c && c.$filters ? c.$filters.find('.' + c.widgetOptions.filter_cssFilter).map(function(i, el) {
return $(el).val();
}).get() || [] : false;
};
ts.setFilters = function(table, filter, apply) {
var $t = $(table),
c = $t.length ? $t[0].config : {},
valid = c && c.$filters ? c.$filters.find('.' + c.widgetOptions.filter_cssFilter).each(function(i, el) {
$(el).val(filter[i] || '');
}).trigger('change.tsfilter') || false : false;
if (apply) { $t.trigger('search', [filter, false]); }
return !!valid;
};
// Widget: Sticky headers
// based on this awesome article:
// http://css-tricks.com/13465-persistent-headers/
// and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech
// **************************
ts.addWidget({
id: "stickyHeaders",
priority: 60,
options: {
stickyHeaders : 'tablesorter-stickyHeader',
stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element
stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists
stickyHeaders_addResizeEvent : true, // trigger "resize" event on headers
stickyHeaders_includeCaption : true // if false and a caption exist, it won't be included in the sticky header
},
format: function(table, c, wo){
if (c.$table.hasClass('hasStickyHeaders')) { return; }
var $t = c.$table,
$win = $(window),
header = $t.children('thead:first'),
hdrCells = header.children('tr:not(.sticky-false)').children(),
innr = '.tablesorter-header-inner',
tfoot = $t.find('tfoot'),
filterInputs = '.' + (wo.filter_cssFilter || 'tablesorter-filter'),
$stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '',
stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0,
$stickyTable = wo.$sticky = $t.clone()
.addClass('containsStickyHeaders')
.css({
position : 'fixed',
margin : 0,
top : stickyOffset,
visibility : 'hidden',
zIndex : 2
}),
stkyHdr = $stickyTable.children('thead:first').addClass(wo.stickyHeaders),
stkyCells,
laststate = '',
spacing = 0,
flag = false,
resizeHdr = function(){
stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0;
var bwsr = navigator.userAgent;
spacing = 0;
// yes, I dislike browser sniffing, but it really is needed here :(
// webkit automatically compensates for border spacing
if ($t.css('border-collapse') !== 'collapse' && !/(webkit|msie)/i.test(bwsr)) {
// Firefox & Opera use the border-spacing
// update border-spacing here because of demos that switch themes
spacing = parseInt(hdrCells.eq(0).css('border-left-width'), 10) * 2;
}
$stickyTable.css({
left : header.offset().left - $win.scrollLeft() - spacing,
width: $t.width()
});
stkyCells.filter(':visible').each(function(i){
var $h = hdrCells.filter(':visible').eq(i);
$(this)
.css({
width: $h.width() - spacing,
height: $h.height()
})
.find(innr).width( $h.find(innr).width() );
});
};
// fix clone ID, if it exists - fixes #271
if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; }
// clear out cloned table, except for sticky header
// include caption & filter row (fixes #126 & #249)
$stickyTable.find('thead:gt(0), tr.sticky-false, tbody, tfoot').remove();
if (!wo.stickyHeaders_includeCaption) {
$stickyTable.find('caption').remove();
}
// issue #172 - find td/th in sticky header
stkyCells = stkyHdr.children().children();
$stickyTable.css({ height:0, width:0, padding:0, margin:0, border:0 });
// remove resizable block
stkyCells.find('.tablesorter-resizer').remove();
// update sticky header class names to match real header after sorting
$t
.addClass('hasStickyHeaders')
.bind('sortEnd.tsSticky', function(){
hdrCells.filter(':visible').each(function(i){
var t = stkyCells.filter(':visible').eq(i);
t
.attr('class', $(this).attr('class'))
// remove processing icon
.removeClass(c.cssProcessing);
if (c.cssIcon){
t
.find('.' + c.cssIcon)
.attr('class', $(this).find('.' + c.cssIcon).attr('class'));
}
});
})
.bind('pagerComplete.tsSticky', function(){
resizeHdr();
});
// http://stackoverflow.com/questions/5312849/jquery-find-self;
hdrCells.find(c.selectorSort).add( c.$headers.filter(c.selectorSort) ).each(function(i){
var t = $(this),
// clicking on sticky will trigger sort
$cell = stkyHdr.children('tr.tablesorter-headerRow').children().eq(i).bind('mouseup', function(e){
t.trigger(e, true); // external mouseup flag (click timer is ignored)
});
// prevent sticky header text selection
if (c.cancelSelection) {
$cell
.attr('unselectable', 'on')
.bind('selectstart', false)
.css({
'user-select': 'none',
'MozUserSelect': 'none'
});
}
});
// add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned.
$t.after( $stickyTable );
// make it sticky!
$win.bind('scroll.tsSticky resize.tsSticky', function(e){
if (!$t.is(':visible')) { return; } // fixes #278
var pre = 'tablesorter-sticky-',
offset = $t.offset(),
cap = -(wo.stickyHeaders_includeCaption ? 0 : $t.find('caption').height()),
sTop = $win.scrollTop() + stickyOffset,
tableHt = $t.height() - ($stickyTable.height() + (tfoot.height() || 0)),
vis = (sTop > offset.top - cap) && (sTop < offset.top - cap + tableHt) ? 'visible' : 'hidden';
$stickyTable
.removeClass(pre + 'visible ' + pre + 'hidden')
.addClass(pre + vis)
.css({
// adjust when scrolling horizontally - fixes issue #143
left : header.offset().left - $win.scrollLeft() - spacing,
visibility : vis
});
if (vis !== laststate || e.type === 'resize'){
// make sure the column widths match
resizeHdr();
laststate = vis;
}
});
if (wo.stickyHeaders_addResizeEvent) {
ts.addHeaderResizeEvent(table);
}
// look for filter widget
$t.bind('filterEnd', function(){
if (flag) { return; }
stkyHdr.find('.tablesorter-filter-row').children().each(function(i){
$(this).find(filterInputs).val( c.$filters.find(filterInputs).eq(i).val() );
});
});
stkyCells.find(filterInputs).bind('keyup search change', function(e){
// ignore arrow and meta keys; allow backspace
if ((e.which < 32 && e.which !== 8) || (e.which >= 37 && e.which <=40)) { return; }
flag = true;
var $f = $(this), col = $f.attr('data-column');
c.$filters.find(filterInputs).eq(col)
.val( $f.val() )
.trigger('search');
setTimeout(function(){
flag = false;
}, wo.filter_searchDelay);
});
$t.trigger('stickyHeadersInit');
},
remove: function(table, c, wo){
c.$table
.removeClass('hasStickyHeaders')
.unbind('sortEnd.tsSticky pagerComplete.tsSticky')
.find('.' + wo.stickyHeaders).remove();
if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table
// don't unbind if any table on the page still has stickyheaders applied
if (!$('.hasStickyHeaders').length) {
$(window).unbind('scroll.tsSticky resize.tsSticky');
}
ts.addHeaderResizeEvent(table, false);
}
});
// Add Column resizing widget
// this widget saves the column widths if
// $.tablesorter.storage function is included
// **************************
ts.addWidget({
id: "resizable",
priority: 40,
options: {
resizable : true,
resizable_addLastColumn : false
},
format: function(table, c, wo){
if (c.$table.hasClass('hasResizable')) { return; }
c.$table.addClass('hasResizable');
var $t, t, i, j, s = {}, $c, $cols, w, tw,
$tbl = c.$table,
position = 0,
$target = null,
$next = null,
fullWidth = Math.abs($tbl.parent().width() - $tbl.width()) < 20,
stopResize = function(){
if (ts.storage && $target){
s[$target.index()] = $target.width();
s[$next.index()] = $next.width();
$target.width( s[$target.index()] );
$next.width( s[$next.index()] );
if (wo.resizable !== false){
ts.storage(table, 'tablesorter-resizable', s);
}
}
position = 0;
$target = $next = null;
$(window).trigger('resize'); // will update stickyHeaders, just in case
};
s = (ts.storage && wo.resizable !== false) ? ts.storage(table, 'tablesorter-resizable') : {};
// process only if table ID or url match
if (s){
for (j in s){
if (!isNaN(j) && j < c.$headers.length){
c.$headers.eq(j).width(s[j]); // set saved resizable widths
}
}
}
$t = $tbl.children('thead:first').children('tr');
// add resizable-false class name to headers (across rows as needed)
$t.children().each(function(){
t = $(this);
i = t.attr('data-column');
j = ts.getData( t, c.headers[i], 'resizable') === "false";
$t.children().filter('[data-column="' + i + '"]').toggleClass('resizable-false', j);
});
// add wrapper inside each cell to allow for positioning of the resizable target block
$t.each(function(){
$c = $(this).children(':not(.resizable-false)');
if (!$(this).find('.tablesorter-wrapper').length) {
// Firefox needs this inner div to position the resizer correctly
$c.wrapInner('<div class="tablesorter-wrapper" style="position:relative;height:100%;width:100%"></div>');
}
// don't include the last column of the row
if (!wo.resizable_addLastColumn) { $c = $c.slice(0,-1); }
$cols = $cols ? $cols.add($c) : $c;
});
$cols
.each(function(){
$t = $(this);
j = parseInt($t.css('padding-right'), 10) + 10; // 8 is 1/2 of the 16px wide resizer grip
t = '<div class="tablesorter-resizer" style="cursor:w-resize;position:absolute;z-index:1;right:-' + j +
'px;top:0;height:100%;width:20px;"></div>';
$t
.find('.tablesorter-wrapper')
.append(t);
})
.bind('mousemove.tsresize', function(e){
// ignore mousemove if no mousedown
if (position === 0 || !$target) { return; }
// resize columns
w = e.pageX - position;
tw = $target.width();
$target.width( tw + w );
if ($target.width() !== tw && fullWidth){
$next.width( $next.width() - w );
}
position = e.pageX;
})
.bind('mouseup.tsresize', function(){
stopResize();
})
.find('.tablesorter-resizer,.tablesorter-resizer-grip')
.bind('mousedown', function(e){
// save header cell and mouse position; closest() not supported by jQuery v1.2.6
$target = $(e.target).closest('th');
t = c.$headers.filter('[data-column="' + $target.attr('data-column') + '"]');
if (t.length > 1) { $target = $target.add(t); }
// if table is not as wide as it's parent, then resize the table
$next = e.shiftKey ? $target.parent().find('th:not(.resizable-false)').filter(':last') : $target.nextAll(':not(.resizable-false)').eq(0);
position = e.pageX;
});
$tbl.find('thead:first')
.bind('mouseup.tsresize mouseleave.tsresize', function(){
stopResize();
})
// right click to reset columns to default widths
.bind('contextmenu.tsresize', function(){
ts.resizableReset(table);
// $.isEmptyObject() needs jQuery 1.4+
var rtn = $.isEmptyObject ? $.isEmptyObject(s) : s === {}; // allow right click if already reset
s = {};
return rtn;
});
},
remove: function(table, c, wo){
c.$table
.removeClass('hasResizable')
.find('thead')
.unbind('mouseup.tsresize mouseleave.tsresize contextmenu.tsresize')
.find('tr').children()
.unbind('mousemove.tsresize mouseup.tsresize')
// don't remove "tablesorter-wrapper" as uitheme uses it too
.find('.tablesorter-resizer,.tablesorter-resizer-grip').remove();
ts.resizableReset(table);
}
});
ts.resizableReset = function(table){
table.config.$headers.filter(':not(.resizable-false)').css('width','');
if (ts.storage) { ts.storage(table, 'tablesorter-resizable', {}); }
};
// Save table sort widget
// this widget saves the last sort only if the
// saveSort widget option is true AND the
// $.tablesorter.storage function is included
// **************************
ts.addWidget({
id: 'saveSort',
priority: 20,
options: {
saveSort : true
},
init: function(table, thisWidget, c, wo){
// run widget format before all other widgets are applied to the table
thisWidget.format(table, c, wo, true);
},
format: function(table, c, wo, init){
var sl, time,
$t = c.$table,
ss = wo.saveSort !== false, // make saveSort active/inactive; default to true
sortList = { "sortList" : c.sortList };
if (c.debug){
time = new Date();
}
if ($t.hasClass('hasSaveSort')){
if (ss && table.hasInitialized && ts.storage){
ts.storage( table, 'tablesorter-savesort', sortList );
if (c.debug){
ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time);
}
}
} else {
// set table sort on initial run of the widget
$t.addClass('hasSaveSort');
sortList = '';
// get data
if (ts.storage){
sl = ts.storage( table, 'tablesorter-savesort' );
sortList = (sl && sl.hasOwnProperty('sortList') && $.isArray(sl.sortList)) ? sl.sortList : '';
if (c.debug){
ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time);
}
$t.bind('saveSortReset', function(e){
e.stopPropagation();
ts.storage( table, 'tablesorter-savesort', '' );
});
}
// init is true when widget init is run, this will run this widget before all other widgets have initialized
// this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice.
if (init && sortList && sortList.length > 0){
c.sortList = sortList;
} else if (table.hasInitialized && sortList && sortList.length > 0){
// update sort change
$t.trigger('sorton', [sortList]);
}
}
},
remove: function(table){
// clear storage
if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); }
}
});
})(jQuery);
/*! Filter widget formatter functions - updated 6/4/2013
* requires: tableSorter 2.7.7+ and jQuery 1.4.3+
* jQuery UI spinner, silder, range slider & datepicker (range)
* HTML5 number (spinner), range slider & color selector
*/
;(function(h){h.tablesorter=h.tablesorter||{};h.tablesorter.filterFormatter={uiSpinner:function(b,f,g){var a=h.extend({min:0,max:100,step:1,value:1,delayed:!0,addToggle:!0,disabled:!1,exactMatch:!0,compare:""},g);g=h('<input class="filter" type="hidden">').appendTo(b).bind("change.tsfilter",function(){d({value:this.value,delayed:!1})});var e=[],j=b.closest("table")[0].config,d=function(d){var c=!0,f,g=d&&d.value&&h.tablesorter.formatFloat((d.value+"").replace(/[><=]/g,""))||b.find(".spinner").val()|| a.value;a.addToggle&&(c=b.find(".toggle").is(":checked"));f=a.disabled||!c?"disable":"enable";b.find(".filter").val(c?(a.compare?a.compare:a.exactMatch?"=":"")+g:"").trigger("search",d&&"boolean"===typeof d.delayed?d.delayed:a.delayed).end().find(".spinner").spinner(f).val(g);e.length&&(e.find(".spinner").spinner(f).val(g),a.addToggle&&(e.find(".toggle")[0].checked=c))};a.oldcreate=a.create;a.oldspin=a.spin;a.create=function(b,c){d();"function"===typeof a.oldcreate&&a.oldcreate(b,c)};a.spin=function(b, c){d(c);"function"===typeof a.oldspin&&a.oldspin(b,c)};a.addToggle&&h('<div class="button"><input id="uispinnerbutton'+f+'" type="checkbox" class="toggle" /><label for="uispinnerbutton'+f+'"></label></div>').appendTo(b).find(".toggle").bind("change",function(){d()});b.closest("thead").find("th[data-column="+f+"]").addClass("filter-parsed");h('<input class="spinner spinner'+f+'" />').val(a.value).appendTo(b).spinner(a).bind("change keyup",function(){d()});j.$table.bind("stickyHeadersInit",function(){e= j.widgetOptions.$sticky.find(".tablesorter-filter-row").children().eq(f).empty();a.addToggle&&h('<div class="button"><input id="stickyuispinnerbutton'+f+'" type="checkbox" class="toggle" /><label for="stickyuispinnerbutton'+f+'"></label></div>').appendTo(e).find(".toggle").bind("change",function(){b.find(".toggle")[0].checked=this.checked;d()});h('<input class="spinner spinner'+f+'" />').val(a.value).appendTo(e).spinner(a).bind("change keyup",function(){b.find(".spinner").val(this.value);d()})}); j.$table.bind("filterReset",function(){a.addToggle&&(b.find(".toggle")[0].checked=!1);d()});d();return g},uiSlider:function(b,f,g){var a=h.extend({value:0,min:0,max:100,step:1,range:"min",delayed:!0,valueToHeader:!1,exactMatch:!0,compare:"",allText:"all"},g);g=h('<input class="filter" type="hidden">').appendTo(b).bind("change.tsfilter",function(){d({value:this.value})});var e=[],j=b.closest("table")[0].config,d=function(d){var c="undefined"!==typeof d?h.tablesorter.formatFloat((d.value+"").replace(/[><=]/g, ""))||a.min:a.value,g=a.compare+(a.compare?c:c===a.min?a.allText:c);a.valueToHeader?b.closest("thead").find("th[data-column="+f+"]").find(".curvalue").html(" ("+g+")"):b.find(".ui-slider-handle").addClass("value-popup").attr("data-value",g);b.find(".filter").val(a.compare?a.compare+c:c===a.min?"":(a.exactMatch?"=":"")+c).trigger("search",d&&"boolean"===typeof d.delayed?d.delayed:a.delayed).end().find(".slider").slider("value",c);e.length&&(e.find(".slider").slider("value",c),a.valueToHeader?e.closest("thead").find("th[data-column="+ f+"]").find(".curvalue").html(" ("+g+")"):e.find(".ui-slider-handle").addClass("value-popup").attr("data-value",g))};b.closest("thead").find("th[data-column="+f+"]").addClass("filter-parsed");a.valueToHeader&&b.closest("thead").find("th[data-column="+f+"]").find(".tablesorter-header-inner").append('<span class="curvalue" />');a.oldcreate=a.create;a.oldslide=a.slide;a.create=function(b,c){d();"function"===typeof a.oldcreate&&a.oldcreate(b,c)};a.slide=function(b,c){d(c);"function"===typeof a.oldslide&& a.oldslide(b,c)};h('<div class="slider slider'+f+'"/>').appendTo(b).slider(a);j.$table.bind("filterReset",function(){b.find(".slider").slider("value",a.value);d()});j.$table.bind("stickyHeadersInit",function(){e=j.widgetOptions.$sticky.find(".tablesorter-filter-row").children().eq(f).empty();h('<div class="slider slider'+f+'"/>').val(a.value).appendTo(e).slider(a).bind("change keyup",function(){b.find(".slider").val(this.value);d()})});return g},uiRange:function(b,f,g){var a=h.extend({values:[0,100], min:0,max:100,range:!0,delayed:!0,valueToHeader:!1},g);g=h('<input class="filter" type="hidden">').appendTo(b).bind("change.tsfilter",function(){var b=this.value.split(" - ");""===this.value&&(b=[a.min,a.max]);b&&b[1]&&d({values:b,delay:!1})});var e=[],j=b.closest("table")[0].config,d=function(d){var c=d&&d.values||a.values,g=c[0]+" - "+c[1],h=c[0]===a.min&&c[1]===a.max?"":g;a.valueToHeader?b.closest("thead").find("th[data-column="+f+"]").find(".currange").html(" ("+g+")"):b.find(".ui-slider-handle").addClass("value-popup").eq(0).attr("data-value", c[0]).end().eq(1).attr("data-value",c[1]);b.find(".filter").val(h).trigger("search",d&&"boolean"===typeof d.delayed?d.delayed:a.delayed).end().find(".range").slider("values",c);e.length&&(e.find(".range").slider("values",c),a.valueToHeader?e.closest("thead").find("th[data-column="+f+"]").find(".currange").html(" ("+g+")"):e.find(".ui-slider-handle").addClass("value-popup").eq(0).attr("data-value",c[0]).end().eq(1).attr("data-value",c[1]))};b.closest("thead").find("th[data-column="+f+"]").addClass("filter-parsed"); a.valueToHeader&&b.closest("thead").find("th[data-column="+f+"]").find(".tablesorter-header-inner").append('<span class="currange"/>');a.oldcreate=a.create;a.oldslide=a.slide;a.create=function(b,c){d();"function"===typeof a.oldcreate&&a.oldcreate(b,c)};a.slide=function(b,c){d(c);"function"===typeof a.oldslide&&a.oldslide(b,c)};h('<div class="range range'+f+'"/>').appendTo(b).slider(a);j.$table.bind("filterReset",function(){b.find(".range").slider("values",a.values);d()});j.$table.bind("stickyHeadersInit", function(){e=j.widgetOptions.$sticky.find(".tablesorter-filter-row").children().eq(f).empty();h('<div class="range range'+f+'"/>').val(a.value).appendTo(e).slider(a).bind("change keyup",function(){b.find(".range").val(this.value);d()})});return g},uiDateCompare:function(b,f,g){var a=h.extend({defaultDate:"",cellText:"",changeMonth:!0,changeYear:!0,numberOfMonths:1,compare:""},g);g=b.closest("thead").find("th[data-column="+f+"]");var e=h('<input class="dateCompare" type="hidden">').appendTo(b).bind("change.tsfilter", function(){var b=this.value;if(b)a.onClose(b)}),j,d=[],k=b.closest("table")[0].config;g.addClass("filter-parsed");j="<label>"+a.cellText+'</label><input type="text" class="date date'+f+'" placeholder="'+(g.data("placeholder")||g.attr("data-placeholder")||"")+'" />';h(j).appendTo(b);a.oldonClose=a.onClose;a.onClose=function(c,e){var f=(new Date(c+(a.compare.match("<")?" 23:59:59":""))).getTime()||"";b.find(".dateCompare").val(a.compare+f).trigger("search").end().find(".date").datepicker("setDate", c);d.length&&d.find(".date").datepicker("setDate",c);"function"===typeof a.oldonClose&&a.oldonClose(c,e)};b.find(".date").datepicker(a);a.filterDate&&b.find(".date").datepicker("setDate",a.filterDate);k.$table.bind("filterReset",function(){b.find(".date").val("").datepicker("option","currentText","");d.length&&d.find(".date").val("").datepicker("option","currentText","")});k.$table.bind("stickyHeadersInit",function(){d=k.widgetOptions.$sticky.find(".tablesorter-filter-row").children().eq(f).empty(); d.append(j).find(".date").datepicker(a)});return e.val(a.defaultDate?a.defaultDate:"")},uiDatepicker:function(b,f,g){var a=h.extend({from:"",to:"",textFrom:"from",textTo:"to",changeMonth:!0,changeYear:!0,numberOfMonths:1},g),e,j,d=[];g=h('<input class="dateRange" type="hidden">').appendTo(b).bind("change.tsfilter",function(){var c=this.value;if(c.match(" - "))c=c.split(" - "),b.find(".dateTo").val(c[1]),j(c[0]);else if(c.match(">="))j(c.replace(">=",""));else if(c.match("<="))a.onClose(c.replace("<=", ""))});var k=b.closest("table")[0].config;b.closest("thead").find("th[data-column="+f+"]").addClass("filter-parsed");e="<label>"+a.textFrom+'</label><input type="text" class="dateFrom" /><label>'+a.textTo+'</label><input type="text" class="dateTo" />';h(e).appendTo(b);a.oldonClose=a.onClose;a.defaultDate=a.from||a.defaultDate;j=a.onClose=function(c,e){var f=(new Date(c)).getTime()||"",g=(new Date(b.find(".dateTo").val()+" 23:59:59")).getTime()||"";b.find(".dateTo").datepicker("option","minDate",c).end().find(".dateFrom").val(c).end().find(".dateRange").val(f? g?f+" - "+g:">="+f:g?"<="+g:"").trigger("search");d.length&&d.find(".dateTo").datepicker("option","minDate",c).end().find(".dateFrom").val(c);"function"===typeof a.oldonClose&&a.oldonClose(c,e)};b.find(".dateFrom").datepicker(a);a.defaultDate=a.to||"+7d";a.onClose=function(c,e){var f=(new Date(b.find(".dateFrom").val())).getTime()||"",g=(new Date(c+" 23:59:59")).getTime()||"";b.find(".dateFrom").datepicker("option","maxDate",c).end().find(".dateTo").val(c).end().find(".dateRange").val(f?g?f+" - "+ g:">="+f:g?"<="+g:"").trigger("search");d.length&&d.find(".dateFrom").datepicker("option","maxDate",c).end().find(".dateTo").val(c);"function"===typeof a.oldonClose&&a.oldonClose(c,e)};b.find(".dateTo").datepicker(a);k.$table.bind("stickyHeadersInit",function(){d=k.widgetOptions.$sticky.find(".tablesorter-filter-row").children().eq(f).empty();d.append(e).find(".dateTo").datepicker(a);a.defaultDate=a.from||a.defaultDate||new Date;a.onClose=j;d.find(".dateFrom").datepicker(a)});b.closest("table").bind("filterReset", function(){b.find(".dateFrom, .dateTo").val("");d.length&&d.find(".dateFrom, .dateTo").val("")});e=a.from?a.to?a.from+" - "+a.to:">="+a.from:a.to?"<="+a.to:"";return g.val(e)},html5Number:function(b,f,g){var a,e=h.extend({value:0,min:0,max:100,step:1,delayed:!0,disabled:!1,addToggle:!0,exactMatch:!0,compare:"",skipTest:!1},g);g=h('<input type="number" style="visibility:hidden;" value="test">').appendTo(b);var j=e.skipTest||"number"===g.attr("type")&&"test"!==g.val(),d=[],k=b.closest("table")[0].config, c=function(a,c){var f=e.addToggle?b.find(".toggle").is(":checked"):!0;b.find("input[type=hidden]").val(!e.addToggle||f?(e.compare?e.compare:e.exactMatch?"=":"")+a:"").trigger("search",c?c:e.delayed).end().find(".number").val(a);b.find(".number").length&&(b.find(".number")[0].disabled=e.disabled||!f);d.length&&(d.find(".number").val(a)[0].disabled=e.disabled||!f,e.addToggle&&(d.find(".toggle")[0].checked=f))};g.remove();j&&(a=e.addToggle?'<div class="button"><input id="html5button'+f+'" type="checkbox" class="toggle" /><label for="html5button'+ f+'"></label></div>':"",a+='<input class="number" type="number" min="'+e.min+'" max="'+e.max+'" value="'+e.value+'" step="'+e.step+'" />',b.html(a+'<input type="hidden" />').find(".toggle, .number").bind("change",function(){c(b.find(".number").val())}).closest("thead").find("th[data-column="+f+"]").addClass("filter-parsed").closest("table").bind("filterReset",function(){e.addToggle&&(b.find(".toggle")[0].checked=!1,d.length&&(d.find(".toggle")[0].checked=!1));c(b.find(".number").val())}),b.find("input[type=hidden]").bind("change.tsfilter", function(){c(this.value)}),k.$table.bind("stickyHeadersInit",function(){d=k.widgetOptions.$sticky.find(".tablesorter-filter-row").children().eq(f).empty();d.html(a).find(".toggle, .number").bind("change",function(){c(d.find(".number").val())});c(b.find(".number").val())}),c(b.find(".number").val()));return j?b.find('input[type="hidden"]'):h('<input type="search">')},html5Range:function(b,f,g){var a=h.extend({value:0,min:0,max:100,step:1,delayed:!0,valueToHeader:!0,exactMatch:!0,compare:"",allText:"all", skipTest:!1},g);g=h('<input type="range" style="visibility:hidden;" value="test">').appendTo(b);var e=a.skipTest||"range"===g.attr("type")&&"test"!==g.val(),j=[],d=b.closest("table")[0].config,k=function(c,d){c=(c+"").replace(/[<>=]/g,"")||a.min;var e=" ("+(a.compare?a.compare+c:c==a.min?a.allText:c)+")";b.find("input[type=hidden]").val(a.compare?a.compare+c:c==a.min?"":(a.exactMatch?"=":"")+c).trigger("search",d?d:a.delayed).end().find(".range").val(c);b.closest("thead").find("th[data-column="+f+ "]").find(".curvalue").html(e);j.length&&(j.find(".range").val(c),j.closest("thead").find("th[data-column="+f+"]").find(".curvalue").html(e))};g.remove();e&&(b.html('<input type="hidden"><input class="range" type="range" min="'+a.min+'" max="'+a.max+'" value="'+a.value+'" />').closest("thead").find("th[data-column="+f+"]").addClass("filter-parsed").find(".tablesorter-header-inner").append('<span class="curvalue" />'),b.find(".range").bind("change",function(){k(this.value)}),b.find("input[type=hidden]").bind("change.tsfilter", function(){var b=this.value;b!==this.lastValue&&(this.value=this.lastValue=a.compare?a.compare+b:b==a.min?"":(a.exactMatch?"=":"")+b,k(b))}),d.$table.bind("stickyHeadersInit",function(){j=d.widgetOptions.$sticky.find(".tablesorter-filter-row").children().eq(f).empty();j.html('<input class="range" type="range" min="'+a.min+'" max="'+a.max+'" value="'+a.value+'" />').find(".range").bind("change",function(){k(j.find(".range").val())});k(b.find(".range").val())}),b.closest("table").bind("filterReset", function(){k(a.value)}),k(b.find(".range").val()));return e?b.find('input[type="hidden"]'):h('<input type="search">')},html5Color:function(b,f,g){var a,e=h.extend({value:"#000000",disabled:!1,addToggle:!0,exactMatch:!0,valueToHeader:!1,skipTest:!1},g);g=h('<input type="color" style="visibility:hidden;" value="test">').appendTo(b);var j=e.skipTest||"color"===g.attr("type")&&"test"!==g.val(),d=[],k=b.closest("table")[0].config,c=function(a){a=a||e.value;var c=!0,g=" ("+a+")";e.addToggle&&(c=b.find(".toggle").is(":checked")); b.find(".colorpicker").length&&(b.find(".colorpicker").val(a)[0].disabled=e.disabled||!c);b.find("input[type=hidden]").val(c?a+(e.exactMatch?"=":""):"").trigger("search");e.valueToHeader?b.closest("thead").find("th[data-column="+f+"]").find(".curcolor").html(g):b.find(".currentColor").html(g);d.length&&(d.find(".colorpicker").val(a)[0].disabled=e.disabled||!c,e.addToggle&&(d.find(".toggle")[0].checked=c),e.valueToHeader?d.closest("thead").find("th[data-column="+f+"]").find(".curcolor").html(g):d.find(".currentColor").html(g))}; g.remove();j&&(a='<div class="color-controls-wrapper">',a+=e.addToggle?'<div class="button"><input id="colorbutton'+f+'" type="checkbox" class="toggle" /><label for="colorbutton'+f+'"></label></div>':"",a+='<input type="hidden"><input class="colorpicker" type="color" />',a+=(e.valueToHeader?"":'<span class="currentColor">(#000000)</span>')+"</div>",b.html(a),e.valueToHeader&&b.closest("thead").find("th[data-column="+f+"]").find(".tablesorter-header-inner").append('<span class="curcolor" />'),b.find(".toggle, .colorpicker").bind("change", function(){c(b.find(".colorpicker").val())}),b.find("input[type=hidden]").bind("change.tsfilter",function(){c(this.value)}),b.closest("table").bind("filterReset",function(){b.find(".toggle")[0].checked=!1;c(b.find(".colorpicker").val())}),k.$table.bind("stickyHeadersInit",function(){d=k.widgetOptions.$sticky.find(".tablesorter-filter-row").children().eq(f);d.html(a).find(".toggle, .colorpicker").bind("change",function(){c(d.find(".colorpicker").val())});c(d.find(".colorpicker").val())}),c(e.value)); return j?b.find('input[type="hidden"]'):h('<input type="search">')}}})(jQuery);
/*! input & select parsers for jQuery 1.7+ & tablesorter 2.7.11+
* Demo: http://mottie.github.com/tablesorter/docs/example-widget-grouping.html
*/
/*jshint browser: true, jquery:true, unused:false */
;(function($){
"use strict";
var resort = true, // resort table after update
updateServer = function(event, $table, $input){
// do something here to update your server, if needed
// event = change event object
// $table = jQuery object of the table that was just updated
// $input = jQuery object of the input or select that was modified
};
// Custom parser for parsing input values
// updated dynamically using the "change" function below
$.tablesorter.addParser({
id: "inputs",
is: function(){
return false;
},
format: function(s, table, cell) {
return $(cell).find('input').val() || s;
},
type: "text"
});
// Custom parser for including checkbox status if using the grouping widget
// updated dynamically using the "change" function below
$.tablesorter.addParser({
id: "checkbox",
is: function(){
return false;
},
format: function(s, table, cell) {
// using plain language here because this is what is shown in the group headers
// change it as desired
var $c = $(cell).find('input');
return $c.length ? $c.is(':checked') ? 'checked' : 'unchecked' : s;
},
type: "text"
});
// Custom parser which returns the currently selected options
// updated dynamically using the "change" function below
$.tablesorter.addParser({
id: "select",
is: function(){
return false;
},
format: function(s, table, cell) {
return $(cell).find('select').val() || s;
},
type: "text"
});
// update select and all input types in the tablesorter cache when the change event fires.
// This method only works with jQuery 1.7+
// you can change it to use delegate (v1.4.3+) or live (v1.3+) as desired
// if this code interferes somehow, target the specific table $('#mytable'), instead of $('table')
$(window).load(function(){
// this flag prevents the updateCell event from being spammed
// it happens when you modify input text and hit enter
var alreadyUpdating = false;
$('#table').find('tbody').on('change', 'select, input', function(e){
if (!alreadyUpdating) {
var $tar = $(e.target),
$table = $tar.closest('table');
alreadyUpdating = true;
$table.trigger('updateCell', [ $tar.closest('td'), resort ]);
updateServer(e, $table, $tar);
setTimeout(function(){ alreadyUpdating = false; }, 10);
}
});
});
})(jQuery);
});