Add JS files

This commit is contained in:
2025-05-12 15:45:17 +00:00
parent 7ddd15c4fa
commit 967007b0c7
3239 changed files with 1157078 additions and 0 deletions

View File

@@ -0,0 +1,191 @@
/* jQuery Quicksearch plugin
by riklomas https://github.com/riklomas/quicksearch
Modified to include childRows (for tablesorter)
See http://stackoverflow.com/q/20342203/145346 for
more details
*/
(function($, window, document, undefined) {
$.fn.quicksearch = function (target, opt) {
var timeout, cache, rowcache, jq_results, val = '', e = this, options = $.extend({
delay: 100,
selector: null,
stripeRows: null,
loader: null,
noResults: '',
childRow: 'tablesorter-childRow', // include child row with search results
matchedResultsCount: 0,
bind: 'keyup',
onBefore: function () {
return;
},
onAfter: function () {
return;
},
show: function () {
this.style.display = "";
},
hide: function () {
this.style.display = "none";
},
prepareQuery: function (val) {
return val.toLowerCase().split(' ');
},
testQuery: function (query, txt, _row) {
for (var i = 0; i < query.length; i += 1) {
if (txt.indexOf(query[i]) === -1) {
return false;
}
}
return true;
}
}, opt);
this.go = function () {
var i = 0,
numMatchedRows = 0,
noresults = true,
query = options.prepareQuery(val),
val_empty = (val.replace(' ', '').length === 0);
for (var i = 0, len = rowcache.length; i < len; i++) {
if (val_empty || options.testQuery(query, cache[i], rowcache[i]) ||
($(rowcache[i]).hasClass(options.childRow) && $(rowcache[i > 1 ? i - 1 : 0]).is(':visible'))) {
options.show.apply(rowcache[i]);
noresults = false;
numMatchedRows++;
} else {
options.hide.apply(rowcache[i]);
}
}
if (noresults) {
this.results(false);
} else {
this.results(true);
this.stripe();
}
this.matchedResultsCount = numMatchedRows;
this.loader(false);
options.onAfter();
return this;
};
/*
* External API so that users can perform search programatically.
* */
this.search = function (submittedVal) {
val = submittedVal;
e.trigger();
};
/*
* External API to get the number of matched results as seen in
* https://github.com/ruiz107/quicksearch/commit/f78dc440b42d95ce9caed1d087174dd4359982d6
* */
this.currentMatchedResults = function() {
return this.matchedResultsCount;
};
this.stripe = function () {
if (typeof options.stripeRows === "object" && options.stripeRows !== null)
{
var joined = options.stripeRows.join(' ');
var stripeRows_length = options.stripeRows.length;
jq_results.not(':hidden').each(function (i) {
$(this).removeClass(joined).addClass(options.stripeRows[i % stripeRows_length]);
});
}
return this;
};
this.strip_html = function (input) {
var output = input.replace(new RegExp('<[^<]+\>', 'g'), "");
output = $.trim(output.toLowerCase());
return output;
};
this.results = function (bool) {
if (typeof options.noResults === "string" && options.noResults !== "") {
if (bool) {
$(options.noResults).hide();
} else {
$(options.noResults).show();
}
}
return this;
};
this.loader = function (bool) {
if (typeof options.loader === "string" && options.loader !== "") {
(bool) ? $(options.loader).show() : $(options.loader).hide();
}
return this;
};
this.cache = function () {
jq_results = $(target);
if (typeof options.noResults === "string" && options.noResults !== "") {
jq_results = jq_results.not(options.noResults);
}
var t = (typeof options.selector === "string") ?
jq_results.find(options.selector) : $(target).not(options.noResults);
cache = t.map(function () {
return e.strip_html(this.innerHTML);
});
rowcache = jq_results.map(function () {
return this;
});
/*
* Modified fix for sync-ing "val".
* Original fix https://github.com/michaellwest/quicksearch/commit/4ace4008d079298a01f97f885ba8fa956a9703d1
* */
val = val || this.val() || "";
return this.go();
};
this.trigger = function () {
this.loader(true);
options.onBefore();
window.clearTimeout(timeout);
timeout = window.setTimeout(function () {
e.go();
}, options.delay);
return this;
};
this.cache();
this.results(true);
this.stripe();
this.loader(false);
return this.each(function () {
/*
* Changed from .bind to .on.
* */
$(this).on(options.bind, function () {
val = $(this).val();
e.trigger();
});
});
};
}(jQuery, this, document));

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,116 @@
/*
* Metadata - jQuery plugin for parsing metadata from elements
*
* Copyright (c) 2006 John Resig, Yehuda Katz, Jörn Zaefferer, Paul McLanahan
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
*/
/**
* Sets the type of metadata to use. Metadata is encoded in JSON, and each property
* in the JSON will become a property of the element itself.
*
* There are three supported types of metadata storage:
*
* attr: Inside an attribute. The name parameter indicates *which* attribute.
*
* class: Inside the class attribute, wrapped in curly braces: { }
*
* elem: Inside a child element (e.g. a script tag). The
* name parameter indicates *which* element.
*
* The metadata for an element is loaded the first time the element is accessed via jQuery.
*
* As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
* matched by expr, then redefine the metadata type and run another $(expr) for other elements.
*
* @name $.metadata.setType
*
* @example <p id="one" class="some_class {item_id: 1, item_label: 'Label'}">This is a p</p>
* @before $.metadata.setType("class")
* @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
* @desc Reads metadata from the class attribute
*
* @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
* @before $.metadata.setType("attr", "data")
* @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
* @desc Reads metadata from a "data" attribute
*
* @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
* @before $.metadata.setType("elem", "script")
* @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
* @desc Reads metadata from a nested script element
*
* @param String type The encoding type
* @param String name The name of the attribute to be used to get metadata (optional)
* @cat Plugins/Metadata
* @descr Sets the type of encoding to be used when loading metadata for the first time
* @type undefined
* @see metadata()
*/
(function($) {
$.extend({
metadata : {
defaults : {
type: 'class',
name: 'metadata',
cre: /(\{.*\})/,
single: 'metadata'
},
setType: function( type, name ){
this.defaults.type = type;
this.defaults.name = name;
},
get: function( elem, opts ){
var data, m, e, attr,
settings = $.extend({},this.defaults,opts);
// check for empty string in single property
if ( !settings.single.length ) { settings.single = 'metadata'; }
data = $.data(elem, settings.single);
// returned cached data if it already exists
if ( data ) { return data; }
data = "{}";
if ( settings.type === "class" ) {
m = settings.cre.exec( elem.className );
if ( m ) { data = m[1]; }
} else if ( settings.type === "elem" ) {
if( !elem.getElementsByTagName ) { return undefined; }
e = elem.getElementsByTagName(settings.name);
if ( e.length ) { data = $.trim(e[0].innerHTML); }
} else if ( elem.getAttribute !== undefined ) {
attr = elem.getAttribute( settings.name );
if ( attr ) { data = attr; }
}
if ( data.indexOf( '{' ) <0 ) { data = "{" + data + "}"; }
data = eval("(" + data + ")");
$.data( elem, settings.single, data );
return data;
}
}
});
/**
* Returns the metadata object for the first member of the jQuery object.
*
* @name metadata
* @descr Returns element's metadata object
* @param Object opts An object contianing settings to override the defaults
* @type jQuery
* @cat Plugins/Metadata
*/
$.fn.metadata = function( opts ){
return $.metadata.get( this[0], opts );
};
})(jQuery);

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,138 @@
/*! Filter widget select2 formatter function - updated 7/17/2014 (v2.17.5)
* requires: jQuery 1.7.2+, tableSorter 2.16+, filter widget 2.16+ and select2 v3.4.6+ plugin
*/
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery: false */
;(function($){
"use strict";
var ts = $.tablesorter || {};
ts.filterFormatter = ts.filterFormatter || {};
/************************\
Select2 Filter Formatter
\************************/
ts.filterFormatter.select2 = function($cell, indx, select2Def) {
var o = $.extend({
// select2 filter formatter options
cellText : '', // Text (wrapped in a label element)
match : true, // adds "filter-match" to header
value : '',
// include ANY select2 options below
multiple : true,
width : '100%'
}, select2Def ),
arry, data,
c = $cell.closest('table')[0].config,
wo = c.widgetOptions,
// Add a hidden input to hold the range values
$input = $('<input class="filter" type="hidden">')
.appendTo($cell)
// hidden filter update namespace trigger by filter widget
.bind('change' + c.namespace + 'filter', function(){
var val = this.value;
val = val.replace(/[/()$^]/g, '').split('|');
$cell.find('.select2').select2('val', val);
updateSelect2();
}),
$header = c.$headers.filter('[data-column="' + indx + '"]:last'),
onlyAvail = $header.hasClass(wo.filter_onlyAvail),
$shcell = [],
matchPrefix = o.match ? '' : '^',
matchSuffix = o.match ? '' : '$',
// this function updates the hidden input and adds the current values to the header cell text
updateSelect2 = function() {
var v = $cell.find('.select2').select2('val') || o.value || '';
$input
// add regex, so we filter exact numbers
.val( $.isArray(v) && v.length && v.join('') !== '' ? '/(' + matchPrefix + (v || []).join(matchSuffix + '|' + matchPrefix) + matchSuffix + ')/' : '' )
.trigger('search').end()
.find('.select2').select2('val', v);
// update sticky header cell
if ($shcell.length) {
$shcell
.find('.select2').select2('val', v);
}
},
// get options from table cell content or filter_selectSource (v2.16)
updateOptions = function(){
data = [];
arry = ts.filter.getOptionSource(c.$table[0], indx, onlyAvail) || [];
// build select2 data option
$.each(arry, function(i,v){
data.push({id: v, text: v});
});
o.data = data;
};
// get filter-match class from option
$header.toggleClass('filter-match', o.match);
if (o.cellText) {
$cell.prepend('<label>' + o.cellText + '</label>');
}
// don't add default in table options if either ajax or
// data options are already defined
if (!(o.ajax && !$.isEmptyObject(o.ajax)) && !o.data) {
updateOptions();
if (onlyAvail) {
c.$table.bind('filterEnd', function(){
updateOptions();
$cell.add($shcell).find('.select2').select2(o);
});
}
}
// add a select2 hidden input!
$('<input class="select2 select2-' + indx + '" type="hidden" />')
.val(o.value)
.appendTo($cell)
.select2(o)
.bind('change', function(){
updateSelect2();
});
// update select2 from filter hidden input, in case of saved filters
c.$table.bind('filterFomatterUpdate', function(){
// value = '/(^x$|^y$)/' => 'x,y'
var val = c.$table.data('lastSearch')[indx] || '';
val = val.replace(/[/()$^]/g, '').split('|');
$cell.find('.select2').select2('val', val);
updateSelect2();
ts.filter.formatterUpdated($cell, indx);
});
// has sticky headers?
c.$table.bind('stickyHeadersInit', function(){
$shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty();
// add a select2!
$('<input class="select2 select2-' + indx + '" type="hidden">')
.val(o.value)
.appendTo($shcell)
.select2(o)
.bind('change', function(){
$cell.find('.select2').select2('val', $shcell.find('.select2').select2('val') );
updateSelect2();
});
if (o.cellText) {
$shcell.prepend('<label>' + o.cellText + '</label>');
}
});
// on reset
c.$table.bind('filterReset', function(){
$cell.find('.select2').select2('val', o.value || '');
setTimeout(function(){
updateSelect2();
}, 0);
});
updateSelect2();
return $input;
};
})(jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,81 @@
/*!
* Extract out date parsers
*/
/*jshint jquery:true */
;(function($){
"use strict";
/*! extract US Long Date (ignore any other text)
* e.g. "Sue's Birthday! Jun 26, 2004 7:22 AM (8# 2oz)"
* demo: http://jsfiddle.net/abkNM/2293/
*/
$.tablesorter.addParser({
id: "extractUSLongDate",
is: function (s) {
// don't auto detect this parser
return false;
},
format: function (s, table) {
var date = s.match(/[A-Z]{3,10}\.?\s+\d{1,2},?\s+(?:\d{4})(?:\s+\d{1,2}:\d{2}(?::\d{2})?(?:\s+[AP]M)?)?/i);
return date ? $.tablesorter.formatFloat((new Date(date[0]).getTime() || ''), table) || s : s;
},
type: "numeric"
});
/*! extract MMDDYYYY (ignore any other text)
* demo: http://jsfiddle.net/Mottie/abkNM/2418/
*/
$.tablesorter.addParser({
id: "extractMMDDYYYY",
is: function (s) {
// don't auto detect this parser
return false;
},
format: function (s, table) {
var date = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/").match(/(\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4}(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?)/i);
return date ? $.tablesorter.formatFloat((new Date(date[0]).getTime() || ''), table) || s : s;
},
type: "numeric"
});
/*! extract DDMMYYYY (ignore any other text)
* demo: http://jsfiddle.net/Mottie/abkNM/2419/
*/
$.tablesorter.addParser({
id: "extractDDMMYYYY",
is: function (s) {
// don't auto detect this parser
return false;
},
format: function (s, table) {
var date = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/").match(/(\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4}(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?)/i);
if (date) {
date = date[0].replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$2/$1/$3");
return $.tablesorter.formatFloat((new Date(date).getTime() || ''), table) || s;
}
return s;
},
type: "numeric"
});
/*! extract YYYYMMDD (ignore any other text)
* demo: http://jsfiddle.net/Mottie/abkNM/2420/
*/
$.tablesorter.addParser({
id: "extractYYYYMMDD",
is: function (s) {
// don't auto detect this parser
return false;
},
format: function (s, table) {
var date = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/").match(/(\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2}(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?)/i);
if (date) {
date = date[0].replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$2/$3/$1");
return $.tablesorter.formatFloat((new Date(date).getTime() || ''), table) || s;
}
return s;
},
type: "numeric"
});
})(jQuery);

View File

@@ -0,0 +1,34 @@
/*! ISO-8601 date parser
* This parser will work with dates in ISO8601 format
* 2013-02-18T18:18:44+00:00
* Written by Sean Ellingham :https://github.com/seanellingham
* See https://github.com/Mottie/tablesorter/issues/247
*/
/*global jQuery: false */
;(function($){
"use strict";
var iso8601date = /^([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?$/;
$.tablesorter.addParser({
id : 'iso8601date',
is : function(s) {
return s.match(iso8601date);
},
format : function(s) {
var result = s.match(iso8601date);
if (result) {
var date = new Date(result[1], 0, 1);
if (result[3]) { date.setMonth(result[3] - 1); }
if (result[5]) { date.setDate(result[5]); }
if (result[7]) { date.setHours(result[7]); }
if (result[8]) { date.setMinutes(result[8]); }
if (result[10]) { date.setSeconds(result[10]); }
if (result[12]) { date.setMilliseconds(Number('0.' + result[12]) * 1000); }
return date;
}
return s;
},
type : 'numeric'
});
})(jQuery);

View File

@@ -0,0 +1,33 @@
/*! Month parser
* Demo: http://jsfiddle.net/Mottie/abkNM/477/
*/
/*jshint jquery:true */
;(function($){
"use strict";
var ts = $.tablesorter;
ts.dates = $.extend({}, ts.dates, {
// *** modify this array to change match the language ***
monthCased : [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
});
ts.dates.monthLower = ts.dates.monthCased.join(',').toLocaleLowerCase().split(',');
ts.addParser({
id: "month",
is: function(){
return false;
},
format: function(s, table) {
var j = -1, c = table.config,
n = c.ignoreCase ? s.toLocaleLowerCase() : s;
$.each(ts.dates[ 'month' + (c.ignoreCase ? 'Lower' : 'Cased') ], function(i,v){
if (j < 0 && n.match(v)) { j = i; }
});
// return s (original string) if there isn't a match
// (non-weekdays will sort separately and empty cells will sort as expected)
return j < 0 ? s : j;
},
type: "numeric"
});
})(jQuery);

View File

@@ -0,0 +1,74 @@
/*! Two digit year parser
* Demo: http://jsfiddle.net/Mottie/abkNM/427/
*/
/*jshint jquery:true */
;(function($){
"use strict";
var ts = $.tablesorter,
// Make the date be within +/- range of the 2 digit year
// so if the current year is 2020, and the 2 digit year is 80 (2080 - 2020 > 50), it becomes 1980
// if the 2 digit year is 50 (2050 - 2020 < 50), then it becomes 2050.
range = 50;
ts.dates = $.extend({}, ts.dates, {
regxxxxyy: /(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{2})/,
regyyxxxx: /(\d{2})[\/\s](\d{1,2})[\/\s](\d{1,2})/
});
ts.formatDate = function(s, regex, format, table){
var n = s
// replace separators
.replace(/\s+/g," ").replace(/[-.,]/g, "/")
// reformat xx/xx/xx to mm/dd/19yy;
.replace(regex, format),
d = new Date(n),
y = d.getFullYear(),
rng = table && table.config.dateRange || range,
now = new Date().getFullYear();
// if date > 50 years old (set range), add 100 years
// this will work when people start using "50" and mean "2050"
while (now - y > rng) {
y += 100;
}
return d.setFullYear(y) || s;
};
$.tablesorter.addParser({
id: "ddmmyy",
is: function() {
return false;
},
format: function(s, table) {
// reformat dd/mm/yy to mm/dd/19yy;
return ts.formatDate(s, ts.dates.regxxxxyy, "$2/$1/19$3", table);
},
type: "numeric"
});
$.tablesorter.addParser({
id: "mmddyy",
is: function() {
return false;
},
format: function(s, table) {
// reformat mm/dd/yy to mm/dd/19yy
return ts.formatDate(s, ts.dates.regxxxxyy, "$1/$2/19$3", table);
},
type: "numeric"
});
$.tablesorter.addParser({
id: "yymmdd",
is: function() {
return false;
},
format: function(s, table) {
// reformat yy/mm/dd to mm/dd/19yy
return ts.formatDate(s, ts.dates.regyyxxxx, "$2/$3/19$1", table);
},
type: "numeric"
});
})(jQuery);

View File

@@ -0,0 +1,33 @@
/*! Weekday parser
* Demo: http://jsfiddle.net/Mottie/abkNM/477/
*/
/*jshint jquery:true */
;(function($){
"use strict";
var ts = $.tablesorter;
ts.dates = $.extend({}, ts.dates, {
// *** modify this array to change match the language ***
weekdayCased : [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
});
ts.dates.weekdayLower = ts.dates.weekdayCased.join(',').toLocaleLowerCase().split(',');
ts.addParser({
id: "weekday",
is: function(){
return false;
},
format: function(s, table) {
var j = -1, c = table.config;
s = c.ignoreCase ? s.toLocaleLowerCase() : s;
$.each(ts.dates[ 'weekday' + (c.ignoreCase ? 'Lower' : 'Cased') ], function(i,v){
if (j < 0 && s.match(v)) { j = i; }
});
// return s (original string) if there isn't a match
// (non-weekdays will sort separately and empty cells will sort as expected)
return j < 0 ? s : j;
},
type: "numeric"
});
})(jQuery);

View File

@@ -0,0 +1,36 @@
/*!
* Extract dates using popular natural language date parsers
*/
/*jshint jquery:true */
;(function($){
"use strict";
/*! Sugar (http://sugarjs.com/dates#comparing_dates)
* demo: http://jsfiddle.net/Mottie/abkNM/551/
*/
$.tablesorter.addParser({
id: "sugar",
is: function() {
return false;
},
format: function(s) {
return Date.create ? Date.create(s).getTime() || s : new Date(s).getTime() || s;
},
type: "numeric"
});
/*! Datejs (http://www.datejs.com/)
* demo: http://jsfiddle.net/Mottie/abkNM/550/
*/
$.tablesorter.addParser({
id: "datejs",
is: function() {
return false;
},
format: function(s) {
return Date.parse && Date.parse(s) || s;
},
type: "numeric"
});
})(jQuery);

View File

@@ -0,0 +1,40 @@
/*! Duration parser
*/
/*jshint jquery:true, unused:false */
;(function($){
"use strict";
// If any number > 9999, then set table.config.durationLength = 5
// The below regex matches this duration example: 1y 23d 12h 44m 9s
$.tablesorter.addParser({
id: "duration",
is: function() {
return false;
},
format: function(s, table) {
var i, time,
c = table.config,
t = '',
duration = '',
len = c.durationLength || 4,
str = new Array(len + 1).join('0'),
labels = (c.durationLabels || '(?:years|year|y),(?:days|day|d),(?:hours|hour|h),(?:minutes|minute|min|m),(?:seconds|second|sec|s)').split(/\s*,\s*/),
llen = labels.length;
// build regex
if (!c.durationRegex) {
for (i = 0; i < llen; i++) {
t += '(?:(\\d+)\\s*' + labels[i] + '\\s*)?';
}
c.durationRegex = new RegExp(t, 'i');
}
// remove commas from value
time = ( c.usNumberFormat ? s.replace(/,/g, '') : s.replace( /(\d)(?:\.|\s*)(\d)/g, '$1$2') ).match(c.durationRegex);
for (i = 1; i < llen + 1; i++) {
duration += ( str + ( time[i] || 0 ) ).slice(-len);
}
return duration;
},
type: "text"
});
})(jQuery);

View File

@@ -0,0 +1,63 @@
/*! Distance parser
* This parser will parser numbers like 5'10" (5 foot 10 inches)
* and 31½ into sortable values.
* Demo: http://jsfiddle.net/Mottie/abkNM/154/
*/
/*global jQuery: false */
;(function($){
"use strict";
var ts = $.tablesorter;
ts.symbolRegex = /[\u215b\u215c\u215d\u215e\u00bc\u00bd\u00be]/g;
ts.processFractions = function(n, table) {
if (n) {
var t, p = 0;
n = $.trim(n.replace(/\"/,''));
// look for a space in the first part of the number: "10 3/4" and save the "10"
if (/\s/.test(n)) {
p = ts.formatFloat(n.split(' ')[0], table);
// remove stuff to the left of the space
n = $.trim(n.substring(n.indexOf(' '), n.length));
}
// look for a "/" to calculate fractions
if (/\//g.test(n)) {
t = n.split('/');
// turn 3/4 into .75; make sure we don't divide by zero
n = p + parseInt(t[0], 10) / parseInt(t[1] || 1, 10);
// look for fraction symbols
} else if (ts.symbolRegex.test(n)) {
n = p + n.replace(ts.symbolRegex, function(m){
return {
'\u215b' : '.125', // 1/8
'\u215c' : '.375', // 3/8
'\u215d' : '.625', // 5/8
'\u215e' : '.875', // 7/8
'\u00bc' : '.25', // 1/4
'\u00bd' : '.5', // 1/2
'\u00be' : '.75' // 3/4
}[m];
});
}
}
return n || 0;
};
$.tablesorter.addParser({
id: 'distance',
is: function() {
// return false so this parser is not auto detected
return false;
},
format: function(s, table) {
if (s === '') { return ''; }
// look for feet symbol = '
// very generic test to catch 1.1', 1 1/2' and 1½'
var d = (/^\s*\S*(\s+\S+)?\s*\'/.test(s)) ? s.split("'") : [0,s],
f = ts.processFractions(d[0], table), // feet
i = ts.processFractions(d[1], table); // inches
return (/[\'\"]/).test(s) ? parseFloat(f) + (parseFloat(i)/12 || 0) : parseFloat(f) + parseFloat(i);
},
type: 'numeric'
});
})(jQuery);

View File

@@ -0,0 +1,73 @@
/*! File Type parser
* When a file type extension is found, the equivalent name is
* prefixed into the parsed data, so sorting occurs in groups
*/
/*global jQuery: false */
;(function($){
"use strict";
// basic list from http://en.wikipedia.org/wiki/List_of_file_formats
// To add a custom equivalent, define:
// $.tablesorter.fileTypes.equivalents['xx'] = "A|B|C";
$.tablesorter.fileTypes = {
// divides filetype extensions in the equivalent list below
separator : '|',
equivalents : {
"3D Image" : "3dm|3ds|dwg|max|obj",
"Audio" : "aif|aac|ape|flac|la|m4a|mid|midi|mp2|mp3|ogg|ra|raw|rm|wav|wma",
"Compressed" : "7z|bin|cab|cbr|gz|gzip|iso|lha|lz|rar|tar|tgz|zip|zipx|zoo",
"Database" : "csv|dat|db|dbf|json|ldb|mdb|myd|pdb|sql|tsv|wdb|wmdb|xlr|xls|xlsx|xml",
"Development" : "asm|c|class|cls|cpp|cc|cs|cxx|cbp|cs|dba|fla|h|java|lua|pl|py|pyc|pyo|sh|sln|r|rb|vb",
"Document" : "doc|docx|odt|ott|pages|pdf|rtf|tex|wpd|wps|wrd|wri",
"Executable" : "apk|app|com|exe|gadget|lnk|msi",
"Fonts" : "eot|fnt|fon|otf|ttf|woff",
"Icons" : "ani|cur|icns|ico",
"Images" : "bmp|gif|jpg|jpeg|jpe|jp2|pic|png|psd|tga|tif|tiff|wmf|webp",
"Presentation" : "pps|ppt",
"Published" : "chp|epub|lit|pub|ppp|fm|mobi",
"Script" : "as|bat|cgi|cmd|jar|js|lua|scpt|scptd|sh|vbs|vb|wsf",
"Styles" : "css|less|sass",
"Text" : "info|log|md|markdown|nfo|tex|text|txt",
"Vectors" : "awg|ai|eps|cdr|ps|svg",
"Video" : "asf|avi|flv|m4v|mkv|mov|mp4|mpe|mpeg|mpg|ogg|rm|rv|swf|vob|wmv",
"Web" : "asp|aspx|cer|cfm|htm|html|php|url|xhtml"
}
};
$.tablesorter.addParser({
id: 'filetype',
is: function() {
return false;
},
format: function(s, table) {
var t,
c = table.config,
wo = c.widgetOptions,
i = s.lastIndexOf('.'),
sep = $.tablesorter.fileTypes.separator,
m = $.tablesorter.fileTypes.matching,
types = $.tablesorter.fileTypes.equivalents;
if (!m) {
// make a string to "quick" match the existing equivalents
var t = [];
$.each(types, function(i,v){
t.push(v);
});
m = $.tablesorter.fileTypes.matching = sep + t.join(sep) + sep;
}
if (i >= 0) {
t = sep + s.substring(i + 1, s.length) + sep;
if (m.indexOf(t) >= 0) {
for (i in types) {
if ((sep + types[i] + sep).indexOf(t) >= 0) {
return i + (wo.group_separator ? wo.group_separator : '-') + s;
}
}
}
}
return s;
},
type: 'text'
});
})(jQuery);

View File

@@ -0,0 +1,61 @@
/*! Title parser - updated 9/15/2014 (v2.17.8)
* This parser will remove "The", "A" and "An" from the beginning of a book
* or movie title, so it sorts by the second word or number
* Demo: http://jsfiddle.net/Mottie/abkNM/5/
*/
/*jshint browser: true, jquery:true, unused:false */
;(function($){
"use strict";
var ts = $.tablesorter;
// basic list from http://en.wikipedia.org/wiki/Article_%28grammar%29
ts.ignoreArticles = {
"en" : "the, a, an",
"de" : "der, die, das, des, dem, den, ein, eine, einer, eines, einem, einen",
"nl" : "de, het, de, een",
"es" : "el, la, lo, los, las, un, una, unos, unas",
"pt" : "o, a, os, as, um, uma, uns, umas",
"fr" : "le, la, l'_, les, un, une, des",
"it" : "il, lo, la, l'_, i, gli, le, un', uno, una, un",
"hu" : "a, az, egy"
};
// To add a custom parser, define:
// $.tablesorter.ignoreArticles['xx'] = "A, B, C";
// and then set the language id 'xx' in the headers option
// ignoreArticles : 'xx'
ts.addParser({
id: 'ignoreArticles',
is: function() {
return false;
},
format: function(s, table, cell, cellIndex) {
var art, ignore, lang,
c = table.config,
str = s || '';
if ( !(c.headers && c.headers[cellIndex] && c.headers[cellIndex].ignoreArticlesRegex) ) {
// initialize - save regex in c.headers[cellIndex].ignoreArticlesRegex
if (!c.headers) { c.headers = {}; }
if (!c.headers[cellIndex]) { c.headers[cellIndex] = {}; }
lang = ts.getData( c.$headers.eq(cellIndex), ts.getColumnData( table, c.headers, cellIndex ), 'ignoreArticles' );
art = (ts.ignoreArticles[lang] || "the, a, an" ) + "";
c.headers[cellIndex].ignoreArticlesRegex = new RegExp('^(' + $.trim( art.split(/\s*\,\s*/).join('\\s|') + "\\s" ).replace("_\\s","") + ')', 'i');
// exception regex stored in c.headers[cellIndex].ignoreArticlesRegex2
ignore = ts.getData( c.$headers.eq(cellIndex), ts.getColumnData( table, c.headers, cellIndex ), 'ignoreArticlesExcept' );
c.headers[cellIndex].ignoreArticlesRegex2 = ignore !== '' ? new RegExp('^(' + ignore.replace(/\s/g, "\\s") + ')', 'i') : '';
}
art = c.headers[cellIndex].ignoreArticlesRegex;
if (art.test(str)) {
ignore = c.headers[cellIndex].ignoreArticlesRegex2;
if ( !(ignore && ignore.test(str)) ) {
return str.replace(art, '');
}
}
return str;
},
type: 'text'
});
})(jQuery);

View File

@@ -0,0 +1,20 @@
/*! image alt attribute parser for jQuery 1.7+ & tablesorter 2.7.11+
* New 7/17/2014 (v2.17.5)
*/
/*jshint jquery:true, unused:false */
;(function($){
"use strict";
$.tablesorter.addParser({
id: "image",
is: function(){
return false;
},
format: function(s, table, cell) {
return $(cell).find('img').attr(table.config.imgAttr || 'alt') || s;
},
parsed : true, // filter widget flag
type: "text"
});
})(jQuery);

View File

@@ -0,0 +1,161 @@
/*! input & select parsers for jQuery 1.7+ & tablesorter 2.7.11+
* Updated 9/15/2014 (v2.17.8)
* 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;
},
parsed : true, // filter widget flag
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, cellIndex) {
var $c = $(cell),
$input = $c.find('input[type="checkbox"]'),
isChecked = $input.length ? $input[0].checked : '';
// adding class to row, indicating that a checkbox is checked; includes
// a column index in case more than one checkbox happens to be in a row
$c.closest('tr').toggleClass('checked-' + cellIndex, isChecked);
// returning plain language here because this is what is shown in the
// group headers - change it as desired
return $input.length ? isChecked ? 'checked' : 'unchecked' : s;
},
parsed : true, // filter widget flag
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;
},
parsed : true, // filter widget flag
type: "text"
});
// Select parser to get the selected text
$.tablesorter.addParser({
id: "select-text",
is: function(){
return false;
},
format: function(s, table, cell) {
var $s = $(cell).find('select');
return $s.length ? $s.find('option:selected').text() || '' : s;
},
parsed : true, // filter widget flag
type: "text"
});
// Custom parser for parsing textarea values
// updated dynamically using the "change" function below
$.tablesorter.addParser({
id: "textarea",
is: function(){
return false;
},
format: function(s, table, cell) {
return $(cell).find('textarea').val() || s;
},
parsed : true, // filter widget flag
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')
$(function(){
$('table').on('tablesorter-initialized', function(){
// this flag prevents the updateCell event from being spammed
// it happens when you modify input text and hit enter
var focused = false,
restoreValue = function(isTbody){
// focused = false; // uncomment this line to prevent auto-accepting changes
// make sure we restore original values
// isTbody is needed to prevent the select from closing in IE
// see https://connect.microsoft.com/IE/feedbackdetail/view/962618/
if (isTbody) {
$(':focus').blur();
}
return;
};
// bind to .tablesorter (default class name)
$(this).children('tbody')
.on('mouseleave', function(e){
restoreValue(e.target.tagName === 'TBODY');
})
.on('focus', 'select, input, textarea', function(){
focused = true;
$(this).data('ts-original-value', this.value);
})
.on('blur', 'input, textarea', function(){
// restore input value;
// "change" is triggered before "blur" so this doesn't replace the new update with the original
this.value = $(this).data('ts-original-value');
})
.on('change keyup', 'select, input, textarea', function(e){
if ( e.which === 27 ) {
// escape: restore original value
this.value = $(this).data('ts-original-value');
return;
}
// Update cell cache using... select: change, input: enter or textarea: alt + enter
if ( ( e.type === 'change' && focused ) ||
( e.type === 'keyup' && e.which === 13 && ( e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' && e.altKey ) ) ) {
var $tar = $(e.target),
$cell = $tar.closest('td'),
$table = $cell.closest('table'),
indx = $cell[0].cellIndex,
c = $table[0].config || false,
$hdr = c && c.$headers && c.$headers.eq(indx);
// abort if not a tablesorter table, or
// don't use updateCell if column is set to "sorter-false" and "filter-false", or column is set to "parser-false"
if ( !c || ( $hdr && $hdr.length && ( $hdr.hasClass('parser-false') || ( $hdr.hasClass('sorter-false') && $hdr.hasClass('filter-false') ) ) ) ) {
return restoreValue();
}
// ignore change event if nothing changed
if ($tar.val() !== $tar.data('ts-original-value')) {
$tar.data('ts-original-value', $tar.val());
$table.trigger('updateCell', [ $tar.closest('td'), resort, function(){
updateServer(e, $table, $tar);
setTimeout(function(){ focused = false; }, 10);
} ]);
}
}
});
});
});
})(jQuery);

View File

@@ -0,0 +1,76 @@
/*! IPv6 Address parser (WIP)
* IPv6 Address (ffff:0000:0000:0000:0000:0000:0000:0000)
* needs to support short versions like "::8" or "1:2::7:8"
* and "::00:192.168.10.184" (embedded IPv4 address)
* see http://www.intermapper.com/support/tools/IPV6-Validator.aspx
*/
/*global jQuery: false */
;(function($){
"use strict";
var ts = $.tablesorter;
$.extend( ts.regex, {}, {
ipv4Validate : /((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})/,
ipv4Extract : /([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/,
// simplified regex from http://www.intermapper.com/support/tools/IPV6-Validator.aspx
// (specifically from http://download.dartware.com/thirdparty/ipv6validator.js)
ipv6Validate : /^\s*((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/i
});
ts.addParser({
id: "ipv6Address",
is: function(s) {
return ts.regex.ipv6Validate.test(s);
},
format: function(address, table) {
// code modified from http://forrst.com/posts/JS_Expand_Abbreviated_IPv6_Addresses-1OR
var i, t, sides, groups, groupsPresent,
hex = table ? (typeof table === "boolean" ? table : table && table.config.ipv6HexFormat || false) : false,
fullAddress = '',
expandedAddress = '',
validGroupCount = 8,
validGroupSize = 4;
// remove any extra spaces
address = address.replace(/\s*/g, '');
// look for embedded ipv4
if (ts.regex.ipv4Validate.test(address)) {
groups = address.match(ts.regex.ipv4Extract);
t = '';
for (i = 1; i < groups.length; i++){
t += ('00' + (parseInt(groups[i], 10).toString(16)) ).slice(-2) + ( i === 2 ? ':' : '' );
}
address = address.replace( ts.regex.ipv4Extract, t );
}
if (address.indexOf("::") == -1) {
// All eight groups are present
fullAddress = address;
} else {
// Consecutive groups of zeroes have been collapsed with "::".
sides = address.split("::");
groupsPresent = 0;
for (i = 0; i < sides.length; i++) {
groupsPresent += sides[i].split(":").length;
}
fullAddress += sides[0] + ":";
for (i = 0; i < validGroupCount - groupsPresent; i++) {
fullAddress += "0000:";
}
fullAddress += sides[1];
}
groups = fullAddress.split(":");
for (i = 0; i < validGroupCount; i++) {
// it's fastest & easiest for tablesorter to sort decimal values (vs hex)
groups[i] = hex ? ('0000' + groups[i]).slice(-4) :
('00000' + (parseInt(groups[i], 16) || 0)).slice(-5);
expandedAddress += ( i != validGroupCount-1) ? groups[i] + ':' : groups[i];
}
return hex ? expandedAddress : expandedAddress.replace(/:/g, '');
},
// uses natural sort hex compare
type: "numeric"
});
})(jQuery);

View File

@@ -0,0 +1,77 @@
/*! Metric parser
* Demo: http://jsfiddle.net/Mottie/abkNM/382/
* Set the metric name in the header (defaults to "m|meter"), e.g.
* <th data-metric-name="b|byte">HDD Size</th>
* <th data-metric-name="m|meter">Distance</th>
*/
/*jshint jquery:true */
;(function($){
"use strict";
var prefixes = {
// "prefix" : [ base 10, base 2 ]
// skipping IEEE 1541 defined prefixes: kibibyte, mebibyte, etc, for now.
"Y|Yotta|yotta" : [ 1e24, Math.pow(1024, 8) ], // 1024^8
"Z|Zetta|zetta" : [ 1e21, Math.pow(1024, 7) ], // 1024^7
"E|Exa|exa" : [ 1e18, Math.pow(1024, 6) ], // 1024^6
"P|Peta|peta" : [ 1e15, Math.pow(1024, 5) ], // 1024^5
"T|Tera|tera" : [ 1e12, Math.pow(1024, 4) ], // 1024^4
"G|Giga|giga" : [ 1e9, Math.pow(1024, 3) ], // 1024^3
"M|Mega|mega" : [ 1e6, Math.pow(1024, 2) ], // 1024^2
"k|Kilo|kilo" : [ 1e3, 1024 ], // 1024
// prefixes below here are rarely, if ever, used in binary
"h|hecto" : [ 1e2, 1e2 ],
"da|deka" : [ 1e1, 1e1 ],
"d|deci" : [ 1e-1, 1e-1 ],
"c|centi" : [ 1e-2, 1e-2],
"m|milli" : [ 1e-3, 1e-3 ],
"µ|micro" : [ 1e-6, 1e-6 ],
"n|nano" : [ 1e-9, 1e-9 ],
"p|pico" : [ 1e-12, 1e-12 ],
"f|femto" : [ 1e-15, 1e-15 ],
"a|atto" : [ 1e-18, 1e-18 ],
"z|zepto" : [ 1e-21, 1e-21 ],
"y|yocto" : [ 1e-24, 1e-24 ]
},
// the \\d+ will not catch digits with spaces, commas or decimals; so use the value from n instead
RegLong = "(\\d+)(\\s+)?([Zz]etta|[Ee]xa|[Pp]eta|[Tt]era|[Gg]iga|[Mm]ega|kilo|hecto|deka|deci|centi|milli|micro|nano|pico|femto|atto|zepto|yocto)(",
RegAbbr = "(\\d+)(\\s+)?(Z|E|P|T|G|M|k|h|da|d|c|m|µ|n|p|f|a|z|y)(";
$.tablesorter.addParser({
id: 'metric',
is: function() {
return false;
},
format: function(s, table, cell, cellIndex) {
var v = 'm|meter',
b, t,
// process number here to get a numerical format (us or eu)
n = $.tablesorter.formatFloat(s.replace(/[^\w,. \-()]/g, ""), table),
$t = table.config.$headers.filter('[data-column="' + cellIndex + '"]'),
m = $t.data('metric');
if (!m) {
// stored values
t = ($t.attr('data-metric-name') || v).split('|');
m = [ t[1] || t[0].substring(1), t[0] ];
m[2] = new RegExp(RegLong + m[0] + "|" + m[1] + ")");
m[3] = new RegExp(RegAbbr + m[1] + ")");
$t.data('metric', m);
}
// find match to full name or abbreviation
t = s.match(m[2]) || s.match(m[3]);
if (t) {
for (v in prefixes) {
if (t[3].match(v)) {
// exception when using binary prefix
// change base for binary use
b = /^[b|bit|byte|o|octet]/.test(t[4]) ? 1 : 0;
return n * prefixes[v][b];
}
}
}
return n;
},
type: 'numeric'
});
})(jQuery);

View File

@@ -0,0 +1,117 @@
/*! Roman numeral parsers
* code modified from both:
* Steven Levithan @ http://blog.stevenlevithan.com/archives/javascript-roman-numeral-converter
* Jonathan Snook comment @ http://blog.stevenlevithan.com/archives/javascript-roman-numeral-converter#comment-16140
*/
/*jshint jquery:true, unused:false */
;(function($){
"use strict";
// allow lower case roman numerals, since lists use i, ii, iii, etc.
var validator = /^M*(?:D?C{0,3}|C[MD])(?:L?X{0,3}|X[CL])(?:V?I{0,3}|I[XV])$/i,
matcher = /\b([MCDLXVI]+\b)/gi,
lookup = { I:1, V:5, X:10, L:50, C:100, D:500, M:1000 };
$.tablesorter.addParser({
id: 'roman',
is: function(){
return false;
},
format: function(s) {
var val,
roman = s.toUpperCase().split(''),
num = 0;
// roman numerals not found!
if ( !(s && validator.test(s)) ) {
return s;
}
while (roman.length) {
val = lookup[roman.shift()];
num += val * (val < lookup[roman[0]] ? -1 : 1);
}
return num;
},
type: "numeric"
});
$.tablesorter.addParser({
id: 'roman-ignore',
is: function(){
return false;
},
format: function(s, table, cell, column) {
var val, orig,
c = table.config,
ignore = $.isArray(c.roman_ignore) ? c.roman_ignore[column] : 0,
// find roman numerals
roman = ( isNaN(ignore) ?
// ignore can be a regex or string
$.trim( s.replace(ignore, '') ) :
// or a number to ignore the last x letters...
$.trim( s.substring(0, s.length - ignore) )
).match(matcher),
v = validator.test(roman),
num = 0;
// roman numerals not found!
if ( !(v) ) {
return s;
}
// save roman numeral for replacement
orig = roman[0];
roman = orig.toUpperCase().split('');
while (roman.length) {
val = lookup[roman.shift()];
// ignore non-roman numerals
if (val) {
num += val * (val < lookup[roman[0]] ? -1 : 1);
}
}
return num ? s.replace(orig, num) : s;
},
type: "text"
});
$.tablesorter.addParser({
id: 'roman-extract',
is: function(){
return false;
},
format: function(s) {
var val,
// find roman numerals
roman = $.grep(s.split(/\b/), function(v, i){
return validator.test(v) ? v : '';
}).join('').match(matcher),
v = roman ? validator.test(roman) : 0,
num = 0;
// roman numerals not found!
if ( !(v) ) {
return s;
}
// save roman numeral for replacement
roman = roman[0].toUpperCase().split('');
while (roman.length) {
val = lookup[roman.shift()];
// ignore non-roman numerals
if (val) {
num += val * (val < lookup[roman[0]] ? -1 : 1);
}
}
return num ? num : s;
},
type: "numeric"
});
})(jQuery);

View File

@@ -0,0 +1,957 @@
/*! Widget: scroller - updated 4/18/2017 (v2.28.8) *//*
Copyright (C) 2011 T. Connell & Associates, Inc.
Dual-licensed under the MIT and GPL licenses
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Resizable scroller widget for the jQuery tablesorter plugin
Version 2.0 - modified by Rob Garrison 4/12/2013;
updated 3/5/2015 (v2.22.2) with lots of help from TheSin-
Requires jQuery v1.7+
Requires the tablesorter plugin, v2.8+, available at http://mottie.github.com/tablesorter/docs/
Usage:
$(function() {
$('table.tablesorter').tablesorter({
widgets: ['zebra', 'scroller'],
widgetOptions : {
scroller_height : 300, // height of scroll window
scroller_jumpToHeader : true, // header snap to browser top when scrolling the tbody
scroller_upAfterSort : true, // scroll tbody to top after sorting
scroller_fixedColumns : 0 // set number of fixed columns
}
});
});
Website: www.tconnell.com
*/
/*jshint browser:true, jquery:true, unused:false */
;( function( $, window ) {
'use strict';
var ts = $.tablesorter,
tscss = ts.css;
$.extend( ts.css, {
scrollerWrap : 'tablesorter-scroller',
scrollerHeader : 'tablesorter-scroller-header',
scrollerTable : 'tablesorter-scroller-table',
scrollerFooter : 'tablesorter-scroller-footer',
scrollerFixed : 'tablesorter-scroller-fixed',
scrollerFixedPanel : 'tablesorter-scroller-fixed-panel',
scrollerHasFix : 'tablesorter-scroller-has-fixed-columns',
scrollerHideColumn : 'tablesorter-scroller-hidden-column',
scrollerHideElement : 'tablesorter-scroller-hidden',
scrollerSpacerRow : 'tablesorter-scroller-spacer',
scrollerBarSpacer : 'tablesorter-scroller-bar-spacer',
scrollerAddedHeight : 'tablesorter-scroller-added-height',
scrollerHack : 'tablesorter-scroller-scrollbar-hack',
// class name on table cannot start with 'tablesorter-' or the
// suffix 'scroller-rtl' will match as a theme name
scrollerRtl : 'ts-scroller-rtl'
});
ts.addWidget({
id : 'scroller',
priority : 60, // run after the filter widget
options : {
scroller_height : 300,
// pop table header into view while scrolling up the page
scroller_jumpToHeader : true,
// scroll tbody to top after sorting
scroller_upAfterSort : true,
// set number of fixed columns
scroller_fixedColumns : 0,
// add hover highlighting to the fixed column (disable if it causes slowing)
scroller_rowHighlight : 'hover',
// add a fixed column overlay for styling
scroller_addFixedOverlay : false,
// In tablesorter v2.19.0 the scroll bar width is auto-detected
// add a value here to override the auto-detected setting
scroller_barWidth : null
},
format : function( table, c, wo ) {
if ( c.isScrolling ) {
ts.scroller.resize( c, wo );
} else {
// initialize here instead of in widget init to give the
// filter widget time to finish building the filter row
ts.scroller.setup( c, wo );
}
},
remove : function( table, c, wo ) {
ts.scroller.remove( c, wo );
}
});
/* Add window resizeEnd event (also used by columnSelector widget) */
ts.window_resize = function() {
if ( ts.timer_resize ) {
clearTimeout( ts.timer_resize );
}
ts.timer_resize = setTimeout( function() {
$( window ).trigger( 'resizeEnd' );
}, 250 );
};
// Add extra scroller css
$( function() {
var style = '<style>' +
/* overall wrapper & table section wrappers */
'.' + tscss.scrollerWrap + ' { position: relative; overflow: hidden; }' +
/* add border-box sizing to all scroller widget tables; see #135 */
'.' + tscss.scrollerWrap + ' * { box-sizing: border-box; }' +
'.' + tscss.scrollerHeader + ', .' + tscss.scrollerFooter + ' { position: relative; overflow: hidden; }' +
'.' + tscss.scrollerHeader + ' table.' + tscss.table + ' { margin-bottom: 0; }' +
/* always leave the scroll bar visible for tbody, or table overflows into the scrollbar
when height < max height (filtering) */
'.' + tscss.scrollerTable + ' { position: relative; overflow: auto; }' +
'.' + tscss.scrollerTable + ' table.' + tscss.table +
' { border-top: 0; margin-top: 0; margin-bottom: 0; overflow: hidden; max-width: initial; }' +
/* hide footer in original table */
'.' + tscss.scrollerTable + ' tfoot, .' + tscss.scrollerHideElement + ', .' + tscss.scrollerHideColumn +
' { display: none; }' +
/*** fixed column ***/
/* disable pointer-events on fixed column wrapper or the user can't interact with the horizontal scrollbar */
'.' + tscss.scrollerFixed + ', .' + tscss.scrollerFixed + ' .' + tscss.scrollerFixedPanel +
' { pointer-events: none; }' +
/* enable pointer-events for fixed column children; see #135 & #878 */
'.' + tscss.scrollerFixed + ' > div { pointer-events: all; }' +
'.' + tscss.scrollerWrap + ' .' + tscss.scrollerFixed + ' { position: absolute; top: 0; z-index: 1; left: 0 } ' +
'.' + tscss.scrollerWrap + ' .' + tscss.scrollerFixed + '.' + tscss.scrollerRtl + ' { left: auto; right: 0 } ' +
/* add horizontal scroll bar; set to 'auto', see #135 */
'.' + tscss.scrollerWrap + '.' + tscss.scrollerHasFix + ' > .' + tscss.scrollerTable + ' { overflow: auto; }' +
/* need to position the tbody & tfoot absolutely to hide the scrollbar & move the footer
below the horizontal scrollbar */
'.' + tscss.scrollerFixed + ' .' + tscss.scrollerFooter + ' { position: absolute; bottom: 0; }' +
/* hide fixed tbody scrollbar - see http://goo.gl/VsLe6n - set overflow to auto here for mousewheel scroll */
'.' + tscss.scrollerFixed + ' .' + tscss.scrollerTable +
' { position: relative; left: 0; overflow: auto; -ms-overflow-style: none; }' +
'.' + tscss.scrollerFixed + ' .' + tscss.scrollerTable + '::-webkit-scrollbar { display: none; }' +
/*** fixed column panel ***/
'.' + tscss.scrollerWrap + ' .' + tscss.scrollerFixedPanel +
' { position: absolute; top: 0; bottom: 0; z-index: 2; left: 0; right: 0; } ' +
'</style>';
$( 'head' ).append( style );
});
ts.scroller = {
// Ugh.. Firefox misbehaves, so it needs to be detected
isFirefox : navigator.userAgent.toLowerCase().indexOf( 'firefox' ) > -1,
// old IE needs a wrap to hide the fixed column scrollbar; http://stackoverflow.com/a/24408672/145346
isOldIE : document.all && !window.atob,
isIE : ( document.all && !window.atob ) || navigator.appVersion.indexOf( 'Trident/' ) > 0,
// http://stackoverflow.com/questions/7944460/detect-safari-browser - needed to position scrolling body
// when the table is set up in RTL direction
isSafari : navigator.userAgent.toLowerCase().indexOf( 'safari' ) > -1 &&
navigator.userAgent.toLowerCase().indexOf( 'chrome' ) === -1,
hasScrollBar : function( $target, checkWidth ) {
if ( checkWidth ) {
return $target.get(0).scrollWidth > $target.width();
} else {
return $target.get(0).scrollHeight > $target.height();
}
},
setWidth : function( $el, width ) {
$el.css({
'width' : width,
'min-width' : width,
'max-width' : width
});
},
// modified from http://davidwalsh.name/detect-scrollbar-width
getBarWidth : function() {
var $div = $( '<div>' ).css({
'position' : 'absolute',
'top' : '-9999px',
'left' : 0,
'width' : '100px',
'height' : '100px',
'overflow' : 'scroll',
'visibility' : 'hidden'
}).appendTo( 'body' ),
div = $div[0],
barWidth = div.offsetWidth - div.clientWidth;
$div.remove();
return barWidth;
},
setup : function( c, wo ) {
var tbHt, $hdr, $t, $hCells, $fCells, $tableWrap, events, tmp, detectedWidth,
$win = $( window ),
tsScroller = ts.scroller,
namespace = c.namespace + 'tsscroller',
$foot = $(),
// c.namespace contains a unique tablesorter ID, per table
id = c.namespace.slice( 1 ) + 'tsscroller',
$table = c.$table;
// force config.widthFixed option - this helps maintain proper alignment across cloned tables
c.widthFixed = true;
wo.scroller_calcWidths = [];
wo.scroller_saved = [ 0, 0 ];
wo.scroller_isBusy = true;
// set scrollbar width to one of the following (1) explicitly set scroller_barWidth option,
// (2) detected scrollbar width or (3) fallback of 15px
if ( wo.scroller_barWidth !== null ) {
wo.scroller_barSetWidth = wo.scroller_barWidth;
} else {
detectedWidth = tsScroller.getBarWidth();
wo.scroller_barSetWidth = detectedWidth !== null ? detectedWidth : 15;
}
tmp = $table.children( 'caption' );
$hdr = $( '<table class="' + $table.attr( 'class' ) + '" cellpadding=0 cellspacing=0>' +
( tmp.length ? tmp[ 0 ].outerHTML : '' ) +
$table.children( 'thead' )[ 0 ].outerHTML + '</table>' );
wo.scroller_$header = $hdr.addClass( c.namespace.slice( 1 ) + '_extra_table' );
$t = $table.children( 'tfoot' );
if ( $t.length ) {
$foot = $( '<table class="' + $table.attr( 'class' ) +
'" cellpadding=0 cellspacing=0 style="margin-top:0"></table>' )
.addClass( c.namespace.slice( 1 ) + '_extra_table' )
// maintain any bindings on the tfoot cells
.append( $t.clone( true ) )
.wrap( '<div class="' + tscss.scrollerFooter + '"/>' );
$fCells = $foot.children( 'tfoot' ).eq( 0 ).children( 'tr' ).children();
}
wo.scroller_$footer = $foot;
$table
.wrap( '<div id="' + id + '" class="' + tscss.scrollerWrap + '" />' )
.before( $hdr )
// shrink filter row but don't completely hide it because the inputs/selectors may distort the columns
.find( '.' + tscss.filterRow )
.addClass( tscss.filterRowHide );
wo.scroller_$container = $table.parent();
if ( $foot.length ) {
// $foot.parent() to include <div> wrapper
$table.after( $foot.parent() );
}
$hCells = $hdr
.wrap( '<div class="' + tscss.scrollerHeader + '" />' )
.find( '.' + tscss.header );
// if max-height is greater than 0 use max-height, so the height resizes dynamically while filtering
// else let the table not have a vertical scroll
$table.wrap( '<div class="' + tscss.scrollerTable +
( wo.scroller_height > 0 ? '" style="max-height:' + wo.scroller_height + 'px;">' : '">' ) );
$tableWrap = $table.parent();
// make scroller header sortable
ts.bindEvents( c.table, $hCells );
// look for filter widget
if ( $table.hasClass( 'hasFilters' ) ) {
ts.filter.bindSearch( $table, $hdr.find( '.' + tscss.filter ) );
}
$table
.children( 'thead, caption' )
.addClass( tscss.scrollerHideElement );
tbHt = $tableWrap.parent().height();
// The header will always jump into view if scrolling the table body
$tableWrap
.off( 'scroll' + namespace )
.on( 'scroll' + namespace, function() {
if ( wo.scroller_jumpToHeader ) {
var pos = $win.scrollTop() - $hdr.offset().top;
if ( $( this ).scrollTop() !== 0 && pos < tbHt && pos > 0 ) {
$win.scrollTop( $hdr.offset().top );
}
}
$hdr
.parent()
.add( $foot.parent() )
.scrollLeft( $( this ).scrollLeft() );
});
// resize/update events - filterEnd fires after "tablesorter-initialized" and "updateComplete"
events = ( ( ts.hasWidget( c.table, 'filter' ) ? 'filterEnd' : 'tablesorter-initialized updateComplete' ) +
' sortEnd pagerComplete columnUpdate ' ).split( ' ' ).join( namespace + ' ' );
$table
.off( namespace )
.on( 'sortEnd filterEnd'.split( ' ' ).join( namespace + ' ' ), function( event ) {
// Sorting, so scroll to top
if ( event.type === 'sortEnd' && wo.scroller_upAfterSort ) {
$tableWrap.animate({
scrollTop : 0
}, 'fast' );
} else if ( wo.scroller_fixedColumns ) {
setTimeout( function() {
// restore previous scroll position
$tableWrap
.scrollTop( wo.scroller_saved[1] )
.scrollLeft( wo.scroller_saved[0] );
tsScroller.updateFixed( c, wo );
}, 0 );
}
})
.on( 'setFixedColumnSize' + namespace, function( event, size ) {
var $wrap = wo.scroller_$container;
if ( typeof size !== 'undefined' && !isNaN( size ) ) {
wo.scroller_fixedColumns = parseInt( size, 10 );
}
// remove fixed columns
tsScroller.removeFixed( c, wo );
size = wo.scroller_fixedColumns;
if ( size > 0 && size < c.columns - 1 ) {
tsScroller.updateFixed( c, wo );
} else if ( $wrap.hasClass( tscss.scrollerHasFix ) ) {
$wrap.removeClass( tscss.scrollerHasFix );
// resize needed to make tables full width
tsScroller.resize( c, wo );
}
})
.on( events, function( event ) {
// Stop from running twice with pager
if ( ts.hasWidget( 'pager' ) && event.type === 'updateComplete' ) {
return;
}
if ( wo.scroller_fixedColumns > 0 ) {
tsScroller.updateFixed( c, wo );
}
// adjust column sizes after an update
tsScroller.resize( c, wo );
});
// Setup window.resizeEnd event
$win
.off( 'resize resizeEnd '.split( ' ' ).join( namespace + ' ' ) )
.on( 'resize' + namespace, ts.window_resize )
.on( 'resizeEnd' + namespace, function() {
// IE calls resize when you modify content, so we have to unbind the resize event
// so we don't end up with an infinite loop. we can rebind after we're done.
$win.off( 'resize' + namespace, ts.window_resize );
tsScroller.resize( c, wo );
$win.on( 'resize' + namespace, ts.window_resize );
$tableWrap.trigger( 'scroll' + namespace );
});
// initialization flag
c.isScrolling = true;
tsScroller.updateFixed( c, wo );
// updateAll called - need to give the browser time to adjust the layout
// before calculating fix column widths
if ( c.table.hasInitialized && c.isScrolling ) {
setTimeout(function(){
ts.scroller.resize( c, wo );
}, 50);
}
},
resize : function( c, wo ) {
if ( wo.scroller_isBusy ) { return; }
var index, borderWidth, setWidth, $headers, $this,
tsScroller = ts.scroller,
$container = wo.scroller_$container,
$table = c.$table,
$tableWrap = $table.parent(),
$hdr = wo.scroller_$header,
$foot = wo.scroller_$footer,
$win = $(window),
position = [ $win.scrollLeft(), $win.scrollTop() ],
id = c.namespace.slice( 1 ) + 'tsscroller',
// Hide other scrollers so we can resize
$div = $( 'div.' + tscss.scrollerWrap + '[id!="' + id + '"]' )
.addClass( tscss.scrollerHideElement ),
temp = 'padding:0;margin:0;border:0;height:0;max-height:0;min-height:0;',
row = '<tr class="' + tscss.scrollerSpacerRow + ' ' + c.selectorRemove.slice(1) +
'" style="' + temp + '">';
wo.scroller_calcWidths = [];
// Remove fixed so we get proper widths and heights
tsScroller.removeFixed( c, wo );
$container.find( '.' + tscss.scrollerSpacerRow ).remove();
// remove ts added colgroups
$container.find( '.' + ts.css.colgroup ).remove();
// show original table elements to get proper alignment
$table
.find( '.' + tscss.scrollerHideElement )
.removeClass( tscss.scrollerHideElement );
// include left & right border widths
borderWidth = parseInt( $table.css( 'border-left-width' ), 10 );
$headers = c.$headerIndexed;
for ( index = 0; index < c.columns; index++ ) {
$this = $headers[ index ];
// code from https://github.com/jmosbech/StickyTableHeaders
if ( $this.css( 'box-sizing' ) === 'border-box' ) {
setWidth = $this.outerWidth();
} else {
if ( $this.css( 'border-collapse' ) === 'collapse' ) {
if ( $this.length && window.getComputedStyle ) {
setWidth = parseFloat( window.getComputedStyle( $this[ 0 ], null ).width );
} else {
// ie8 only
setWidth = $this.outerWidth() - parseFloat( $this.css( 'padding-left' ) ) -
parseFloat( $this.css( 'padding-right' ) ) -
( parseFloat( $this.css( 'border-width' ) ) || 0 );
}
} else {
setWidth = $this.width();
}
}
row += '<td data-column="' + index + '" style="' + temp + 'width:' + setWidth +
'px;min-width:' + setWidth + 'px;max-width:' + setWidth + 'px"></td>';
// save current widths
wo.scroller_calcWidths[ index ] = setWidth;
}
row += '</tr>';
c.$tbodies.eq(0).append( row ); // tbody
$hdr.children( 'thead' ).append( row );
$foot.children( 'tfoot' ).append( row );
// include colgroup or alignment is off
ts.fixColumnWidth( c.table );
row = c.$table.children( 'colgroup' )[0].outerHTML;
$hdr.append( row );
$foot.append( row );
temp = $tableWrap.parent().innerWidth() -
( tsScroller.hasScrollBar( $tableWrap ) ? wo.scroller_barSetWidth : 0 );
$tableWrap.width( temp );
temp = ( tsScroller.hasScrollBar( $tableWrap ) ? wo.scroller_barSetWidth : 0 ) + borderWidth;
setWidth = $tableWrap.innerWidth() - temp;
$hdr
.parent()
.add( $foot.parent() )
.width( setWidth );
$tableWrap
.width( setWidth + temp );
// hide original table thead
$table.children( 'thead, caption' ).addClass( tscss.scrollerHideElement );
// update fixed column sizes
tsScroller.updateFixed( c, wo );
$div.removeClass( tscss.scrollerHideElement );
// restore scrollTop - fixes #926
$tableWrap.scrollTop( wo.scroller_saved[1] );
wo.scroller_$container
.find( '.' + tscss.scrollerFixed )
.find( '.' + tscss.scrollerTable )
.scrollTop( wo.scroller_saved[1] );
$win.scrollLeft( position[0] );
$win.scrollTop( position[1] );
// update resizable widget handles
setTimeout( function() {
c.$table.triggerHandler( 'resizableUpdate' );
c.$table.triggerHandler( 'scrollerComplete' );
}, 100 );
},
// Add fixed (frozen) columns (Do not call directly, use updateFixed)
setupFixed : function( c, wo ) {
var index, index2, $el, len, temp, $fixedColumn, $fixedTbody,
$table = c.$table,
$wrapper = wo.scroller_$container,
fixedColumns = wo.scroller_fixedColumns;
$fixedColumn = $wrapper
.addClass( tscss.scrollerHasFix )
.clone()
.addClass( tscss.scrollerFixed )
.removeClass( tscss.scrollerWrap )
.attr( 'id', '' );
$fixedColumn.find('caption').html('&nbsp;');
if ( wo.scroller_addFixedOverlay ) {
$fixedColumn.append( '<div class="' + tscss.scrollerFixedPanel + '">' );
}
$fixedTbody = $fixedColumn.find( '.' + tscss.scrollerTable );
$fixedTbody
.children( 'table' )
.addClass( c.namespace.slice( 1 ) + '_extra_table' )
.attr( 'id', '' )
.children( 'thead, tfoot' )
.remove();
wo.scroller_$fixedColumns = $fixedColumn;
// RTL support (fixes column on right)
if ( $table.hasClass( tscss.scrollerRtl ) ) {
$fixedColumn.addClass( tscss.scrollerRtl );
}
$el = $fixedColumn.find( 'tr' );
len = $el.length;
for ( index = 0; index < len; index++ ) {
$el.eq( index ).children( ':gt(' + ( fixedColumns - 1 ) + ')' ).remove();
}
$fixedColumn
.addClass( tscss.scrollerHideElement )
.prependTo( $wrapper );
// look for filter widget
if ( c.$table.hasClass( 'hasFilters' ) ) {
// make sure fixed column filters aren't disabled
$el = $fixedColumn
.find( '.' + tscss.filter )
.not( '.' + tscss.filterDisabled )
.prop( 'disabled', false );
ts.filter.bindSearch( $table, $fixedColumn.find( '.' + tscss.filter ) );
// disable/enable filters behind fixed column
$el = $wrapper
.children( '.' + tscss.scrollerHeader )
.find( '.' + tscss.filter );
len = $el.length;
for ( index = 0; index < len; index++ ) {
// previously disabled filter; don't mess with it! filterDisabled class added by filter widget
if ( !$el.eq( index ).hasClass( tscss.filterDisabled || 'disabled' ) ) {
// disable filters behind fixed column; don't disable visible filters
$el.eq( index ).prop( 'disabled', index < fixedColumns );
}
}
}
// disable/enable tab indexes behind fixed column
c.$table
.add( '.' + tscss.scrollerFooter + ' table' )
.children( 'thead' )
.children( 'tr.' + tscss.headerRow )
.children()
.attr( 'tabindex', -1 );
$el = wo.scroller_$header
.add( $fixedColumn.find( '.' + tscss.scrollerTable + ' table' ) )
.children( 'thead' )
.children( 'tr.' + tscss.headerRow );
len = $el.length;
for ( index = 0; index < len; index++ ) {
temp = $el.eq( index ).children();
for ( index2 = 0; index2 < temp.length; index2++ ) {
temp.eq( index2 ).attr( 'tabindex', index2 < fixedColumns ? -1 : 0 );
}
}
ts.bindEvents( c.table, $fixedColumn.find( '.' + tscss.header ) );
ts.scroller.bindFixedColumnEvents( c, wo );
/*** Scrollbar hack! Since we can't hide the scrollbar with css ***/
if ( ts.scroller.isFirefox || ts.scroller.isOldIE ) {
$fixedTbody.wrap( '<div class="' + tscss.scrollerHack + '" style="overflow:hidden;">' );
}
},
// https://remysharp.com/2010/07/21/throttling-function-calls
throttle : function(fn, threshhold, scope) {
threshhold = threshhold || 50;
var last, deferTimer;
return function() {
var context = scope || this,
now = +(new Date()),
args = arguments;
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(function() {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};
},
bindFixedColumnEvents : function( c, wo ) {
// update thead & tbody in fixed column
var tsScroller = ts.scroller,
namespace = c.namespace + 'tsscrollerFixed',
events = 'scroll' + namespace,
$fixedTbody = wo.scroller_$fixedColumns.find( '.' + tscss.scrollerTable ),
fixedScroll = true,
tableScroll = true;
c.$table
.parent()
// *** SCROLL *** scroll fixed column along with main
.off( events )
.on( events, tsScroller.throttle(function() {
// using flags to prevent firing the scroll event excessively leading to slow scrolling in Firefox
if ( !wo.scroller_isBusy && fixedScroll ) {
tableScroll = false;
var $this = $( this );
$fixedTbody[0].scrollTop = wo.scroller_saved[1] = $this.scrollTop();
wo.scroller_saved[0] = $this.scrollLeft();
setTimeout( function() {
tableScroll = true;
}, 20 );
}
}));
// scroll main along with fixed column
$fixedTbody
.off( events )
.on( events, tsScroller.throttle(function() {
// using flags to prevent firing the scroll event excessively leading to slow scrolling in Firefox
if ( !wo.scroller_isBusy && tableScroll ) {
fixedScroll = false;
c.$table.parent()[0].scrollTop = wo.scroller_saved[1] = $( this ).scrollTop();
setTimeout( function() {
fixedScroll = true;
}, 20 );
}
}))
.scroll();
// *** ROW HIGHLIGHT ***
if ( wo.scroller_rowHighlight !== '' ) {
events = 'mouseover mouseleave '.split( ' ' ).join( namespace + ' ' );
// can't use c.$tbodies because it doesn't include info-only tbodies
c.$table
.off( events, 'tbody > tr' )
.on( events, 'tbody > tr', function( event ) {
var indx = c.$table.children( 'tbody' ).children( 'tr' ).index( this );
$fixedTbody
.children( 'table' )
.children( 'tbody' )
.children( 'tr' )
.eq( indx )
.add( this )
.toggleClass( wo.scroller_rowHighlight, event.type === 'mouseover' );
});
$fixedTbody
.find( 'table' )
.off( events, 'tbody > tr' )
.on( events, 'tbody > tr', function( event ) {
var $fixed = $fixedTbody.children( 'table' ).children( 'tbody' ).children( 'tr' ),
indx = $fixed.index( this );
c.$table
.children( 'tbody' )
.children( 'tr' )
.eq( indx )
.add( this )
.toggleClass( wo.scroller_rowHighlight, event.type === 'mouseover' );
});
}
},
adjustWidth : function( c, wo, totalWidth, adj, dir ) {
var $wrapper = wo.scroller_$container;
// RTL support (fixes column on right)
$wrapper
.children( '.' + tscss.scrollerTable )
.css( dir ? 'right' : 'left', totalWidth );
$wrapper
.children( '.' + tscss.scrollerHeader + ', .' + tscss.scrollerFooter )
// Safari needs a scrollbar width of extra adjusment to align the fixed & scrolling columns
.css( dir ? 'right' : 'left', totalWidth + ( dir && ts.scroller.isSafari ? adj : 0 ) );
},
updateFixed : function( c, wo ) {
var temp, adj,
$wrapper = wo.scroller_$container,
$hdr = wo.scroller_$header,
$foot = wo.scroller_$footer,
$table = c.$table,
$tableWrap = $table.parent(),
scrollBarWidth = wo.scroller_barSetWidth,
dir = $table.hasClass( tscss.scrollerRtl );
if ( wo.scroller_fixedColumns === 0 ) {
wo.scroller_isBusy = false;
ts.scroller.removeFixed( c, wo );
temp = $wrapper.width();
$tableWrap.width( temp );
adj = ts.scroller.hasScrollBar( $tableWrap ) ? scrollBarWidth : 0;
$hdr
.parent()
.add( $foot.parent() )
.width( temp - adj );
return;
}
if ( !c.isScrolling ) {
return;
}
wo.scroller_isBusy = true;
// Make sure the wo.scroller_$fixedColumns container exists, if not build it
if ( !$wrapper.find( '.' + tscss.scrollerFixed ).length ) {
ts.scroller.setupFixed( c, wo );
}
// scroller_fixedColumns
var index, tbodyIndex, rowIndex, $tbody, $adjCol, $fb, totalRows,
// source cells for measurement
$mainTbodies = wo.scroller_$container
.children( '.' + tscss.scrollerTable )
.children( 'table' )
.children( 'tbody' ),
// variable gets redefined
$rows = wo.scroller_$header
.children( 'thead' )
.children( '.' + tscss.headerRow ),
// hide fixed column during resize, or we get a FOUC
$fixedColumn = wo.scroller_$fixedColumns
.addClass( tscss.scrollerHideElement ),
// target cells
$fixedTbodiesTable = $fixedColumn
.find( '.' + tscss.scrollerTable )
.children( 'table' ),
$fixedTbodies = $fixedTbodiesTable
.children( 'tbody' ),
// variables
tsScroller = ts.scroller,
fixedColumns = wo.scroller_fixedColumns,
// get dimensions
$temp = $table.find( 'tbody td' ),
borderRightWidth = parseInt( $temp.css( 'border-right-width' ), 10 ) || 1,
borderSpacing = parseInt( ( $temp.css( 'border-spacing' ) || '' ).split( /\s/ )[ 0 ], 10 ) / 2 || 0,
totalWidth = parseInt( $table.css( 'padding-left' ), 10 ) +
parseInt( $table.css( 'padding-right' ), 10 ) -
borderRightWidth,
widths = wo.scroller_calcWidths;
ts.scroller.removeFixed( c, wo, false );
// calculate fixed column width
for ( index = 0; index < fixedColumns; index++ ) {
totalWidth += widths[ index ] + borderSpacing;
}
// set fixed column width
totalWidth = totalWidth + borderRightWidth * 2 - borderSpacing;
tsScroller.setWidth( $fixedColumn.add( $fixedColumn.children() ), totalWidth );
tsScroller.setWidth( $fixedColumn.children().children( 'table' ), totalWidth );
// update fixed column tbody content, set cell widths on hidden row
for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
$tbody = $mainTbodies.eq( tbodyIndex );
if ( $tbody.length ) {
// get tbody
$rows = $tbody.children();
totalRows = $rows.length;
$fb = ts.processTbody( $fixedTbodiesTable, $fixedTbodies.eq( tbodyIndex ), true );
$fb.empty();
// update tbody cells after sort/filtering
for ( rowIndex = 0; rowIndex < totalRows; rowIndex++ ) {
$adjCol = $( $rows[ rowIndex ].outerHTML );
$adjCol
.children( 'td, th' )
.slice( fixedColumns )
.remove();
$fb.append( $adjCol );
}
// restore tbody
ts.processTbody( $fixedTbodiesTable, $fb, false );
}
}
adj = ts.scroller.hasScrollBar( $tableWrap ) ? scrollBarWidth : 0;
/*** scrollbar HACK! Since we can't hide the scrollbar with css ***/
if ( tsScroller.isFirefox || tsScroller.isOldIE ) {
$fixedTbodiesTable
.css( 'width', totalWidth )
.parent()
.css( 'width', totalWidth + adj );
}
$fixedColumn.removeClass( tscss.scrollerHideElement );
for ( index = 0; index < fixedColumns; index++ ) {
temp = ':nth-child(' + ( index + 1 ) + ')';
$wrapper
.children( 'div' )
.children( 'table' )
.find( 'th' + temp + ', td' + temp + ', col' + temp )
.addClass( tscss.scrollerHideColumn );
}
totalWidth = totalWidth - borderRightWidth;
temp = $tableWrap.parent().innerWidth() - totalWidth;
$tableWrap.width( temp );
// RTL support (fixes column on right)
$wrapper
.children( '.' + tscss.scrollerTable )
.css( dir ? 'right' : 'left', totalWidth );
$wrapper
.children( '.' + tscss.scrollerHeader + ', .' + tscss.scrollerFooter )
// Safari needs a scrollbar width of extra adjusment to align the fixed & scrolling columns
.css( dir ? 'right' : 'left', totalWidth + ( dir && ts.scroller.isSafari ? adj : 0 ) );
$hdr
.parent()
.add( $foot.parent() )
.width( temp - adj );
// fix gap under the tbody for the horizontal scrollbar
temp = ts.scroller.hasScrollBar( $tableWrap, true );
adj = temp ? scrollBarWidth : 0;
if ( !$fixedColumn.find( '.' + tscss.scrollerBarSpacer ).length && temp ) {
$temp = $( '<div class="' + tscss.scrollerBarSpacer + '">' )
.css( 'height', adj + 'px' );
$fixedColumn.find( '.' + tscss.scrollerTable ).append( $temp );
} else if ( !temp ) {
$fixedColumn.find( '.' + tscss.scrollerBarSpacer ).remove();
}
ts.scroller.updateRowHeight( c, wo );
// set fixed column height (changes with filtering)
$fixedColumn.height( $wrapper.height() );
$fixedColumn.removeClass( tscss.scrollerHideElement );
// adjust caption height, see #1202
$fixedColumn.find('caption').height( wo.scroller_$header.find( 'caption' ).height() );
wo.scroller_isBusy = false;
},
fixHeight : function( $rows, $fixedRows ) {
var index, heightRow, heightFixed, $r, $f,
addedHt = tscss.scrollerAddedHeight,
len = $rows.length;
for ( index = 0; index < len; index++ ) {
$r = $rows.eq( index );
$f = $fixedRows.eq( index );
heightRow = $r.height();
heightFixed = $f.height();
if ( heightRow > heightFixed ) {
$f.addClass( addedHt ).height( heightRow );
} else if ( heightRow < heightFixed ) {
$r.addClass( addedHt ).height( heightFixed );
}
}
},
updateRowHeight : function( c, wo ) {
var $rows, $fixed,
$fixedColumns = wo.scroller_$fixedColumns;
wo.scroller_$container
.find( '.' + tscss.scrollerAddedHeight )
.removeClass( tscss.scrollerAddedHeight )
.height( '' );
$rows = wo.scroller_$header
.children( 'thead' )
.children( 'tr' );
$fixed = $fixedColumns
.children( '.' + tscss.scrollerHeader )
.children( 'table' )
.children( 'thead' )
.children( 'tr' );
ts.scroller.fixHeight( $rows, $fixed );
$rows = wo.scroller_$footer
.children( 'tfoot' )
.children( 'tr' );
$fixed = $fixedColumns
.children( '.' + tscss.scrollerFooter )
.children( 'table' )
.children( 'tfoot' )
.children( 'tr' );
ts.scroller.fixHeight( $rows, $fixed );
if ( ts.scroller.isFirefox || ts.scroller.isOldIE ) {
// Firefox/Old IE scrollbar hack (wraps table to hide the scrollbar)
$fixedColumns = $fixedColumns.find( '.' + tscss.scrollerHack );
}
$rows = c.$table
.children( 'tbody' )
.children( 'tr' );
$fixed = $fixedColumns
.children( '.' + tscss.scrollerTable )
.children( 'table' )
.children( 'tbody' )
.children( 'tr' );
ts.scroller.fixHeight( $rows, $fixed );
},
removeFixed : function( c, wo, removeIt ) {
var $table = c.$table,
$wrapper = wo.scroller_$container,
dir = $table.hasClass( tscss.scrollerRtl );
// remove fixed columns
if ( removeIt || typeof removeIt === 'undefined' ) {
$wrapper.find( '.' + tscss.scrollerFixed ).remove();
}
$wrapper
.find( '.' + tscss.scrollerHideColumn )
.removeClass( tscss.scrollerHideColumn );
// RTL support ( fixes column on right )
$wrapper
.children( ':not(.' + tscss.scrollerFixed + ')' )
.css( dir ? 'right' : 'left', 0 );
},
remove : function( c, wo ) {
var $wrap = wo.scroller_$container,
namespace = c.namespace + 'tsscroller';
c.$table.off( namespace );
$( window ).off( namespace );
if ( $wrap ) {
c.$table
.insertBefore( $wrap )
.find( 'thead' )
.removeClass( tscss.scrollerHideElement )
.children( 'tr.' + tscss.headerRow )
.children()
.attr( 'tabindex', 0 )
.end()
.find( '.' + tscss.filterRow )
.removeClass( tscss.scrollerHideElement + ' ' + tscss.filterRowHide );
c.$table
.find( '.' + tscss.filter )
.not( '.' + tscss.filterDisabled )
.prop( 'disabled', false );
$wrap.remove();
c.isScrolling = false;
}
}
};
})( jQuery, window );

View File

@@ -0,0 +1,145 @@
/*! tablesorter Align Character widget - updated 3/12/2014 (core v2.15.8)
* Requires tablesorter v2.8+ and jQuery 1.7+
* by Rob Garrison
*/
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery: false */
;(function($){
"use strict";
var ts = $.tablesorter;
ts.alignChar = {
init : function(table, c, wo) {
c.$headers.filter('[' + wo.alignChar_charAttrib + ']').each(function(){
var $this = $(this),
vars = {
column : this.column,
align : $this.attr(wo.alignChar_charAttrib),
alignIndex : parseInt( $this.attr(wo.alignChar_indexAttrib) || 0, 10),
adjust : parseFloat($this.attr(wo.alignChar_adjustAttrib)) || 0,
};
vars.regex = new RegExp('\\' + vars.align, 'g');
if (typeof vars.align !== 'undefined') {
wo.alignChar_savedVars[this.column] = vars;
ts.alignChar.setup(table, c, wo, vars);
}
});
},
setup: function(table, c, wo, v){
// do nothing for empty tables
if ($.isEmptyObject(c.cache)) { return; }
var tbodyIndex, rowIndex, start, end, last, index, rows, val, count,
len, wLeft, wRight, alignChar, $row,
left = [],
right = [];
for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++){
rows = c.cache[tbodyIndex];
len = rows.normalized.length;
for (rowIndex = 0; rowIndex < len; rowIndex++) {
// set up to work with modified cache v2.16.0+
$row = rows.row ? rows.row[rowIndex] : rows.normalized[rowIndex][c.columns].$row;
val = $row.find('td').eq(v.column).text().replace(/[ ]/g, "\u00a0");
// count how many "align" characters are in the string
count = (val.match( v.regex ) || []).length;
// set alignment @ alignIndex (one-based index)
if (count > 0 && v.alignIndex > 0) {
end = Math.min(v.alignIndex, count);
start = 0;
index = 0;
last = 0;
// find index of nth align character based on alignIndex (data-align-index)
while (start++ < end) {
last = val.indexOf(v.align, last + 1);
index = last < 0 ? index : last;
}
} else {
index = val.indexOf(v.align);
}
if ( index >= 0 ) {
left.push( val.substring(0, index) || '' );
right.push( val.substring(index, val.length) || '' );
} else {
// no align character found!
// put val in right or left based on the align index
left.push( (count >= 1 && v.alignIndex >= count) ? '' : val || '' );
right.push( (count >= 1 && v.alignIndex >= count) ? val || '' : '' );
}
}
}
// find widest segments
wLeft = ($.extend([], left)).sort(function(a,b){ return b.length - a.length; })[0];
wRight = ($.extend([], right)).sort(function(a,b){ return b.length - a.length; })[0];
// calculate percentage widths
v.width = v.width || ( Math.floor(wLeft.length / (wLeft.length + wRight.length) * 100) + v.adjust );
wLeft = 'min-width:' + v.width + '%';
wRight = 'min-width:' + (100 - v.width) + '%';
for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++){
rows = c.cache[tbodyIndex];
len = rows.normalized.length;
for (rowIndex = 0; rowIndex < len; rowIndex++) {
alignChar = $(wo.alignChar_wrap).length ? $(wo.alignChar_wrap).html(v.align)[0].outerHTML : v.align;
$row = rows.row ? rows.row[rowIndex] : rows.normalized[rowIndex][c.columns].$row;
$row.find('td').eq(v.column).html(
'<span class="ts-align-wrap"><span class="ts-align-left" style="' + wLeft + '">' + left[rowIndex] + '</span>' +
'<span class="ts-align-right" style="' + wRight + '">' + alignChar +
right[rowIndex].slice(v.align.length) + '</span></span>'
);
}
}
wo.alignChar_initialized = true;
},
remove: function(table, c, column){
if ($.isEmptyObject(c.cache)) { return; }
var tbodyIndex, rowIndex, len, rows, $row, $cell;
for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++){
rows = c.cache[tbodyIndex];
len = rows.normalized.length;
for (rowIndex = 0; rowIndex < len; rowIndex++) {
$row = rows.row ? rows.row[rowIndex] : rows.normalized[rowIndex][c.columns].$row;
$cell = $row.find('td').eq(column);
$cell.html( $cell.text().replace(/\s/g, ' ') );
}
}
}
};
ts.addWidget({
id: 'alignChar',
priority: 100,
options: {
alignChar_wrap : '',
alignChar_charAttrib : 'data-align-char',
alignChar_indexAttrib : 'data-align-index',
alignChar_adjustAttrib : 'data-align-adjust' // percentage width adjustments
},
init: function(table, thisWidget, c, wo){
wo.alignChar_initialized = false;
wo.alignChar_savedVars = [];
ts.alignChar.init(table, c, wo);
c.$table.on('pagerEnd refreshAlign', function(){
c.$headers.filter('[' + wo.alignChar_charAttrib + ']').each(function(){
ts.alignChar.remove(table, c, this.column);
});
ts.alignChar.init(table, c, wo);
});
},
format : function(table, c, wo){
// reinitialize in case table is empty when first initialized
if (!wo.alignChar_initialized) {
c.$table.trigger('refreshAlign');
}
},
remove : function(table, c, wo){
c.$headers.filter('[' + wo.alignChar_charAttrib + ']').each(function(){
ts.alignChar.remove(table, c, this.column);
});
wo.alignChar_initialized = false;
}
});
})(jQuery);

View File

@@ -0,0 +1,453 @@
/*! Build Table widget for tableSorter v2.16.0 (4/23/2014)
* by Rob Garrison
*/
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery: false */
;(function($){
"use strict";
var ts = $.tablesorter = $.tablesorter || {},
// build a table from data (requires existing <table> tag)
// data.header contains an array of header titles
// data.rows contains an array of rows which contains an array of cells
bt = ts.buildTable = function(tar, c){
// add table if one doesn't exist
var $tbl = tar.tagName === 'TABLE' ? $(tar) : $('<table>').appendTo(tar),
table = $tbl[0],
wo = c.widgetOptions = $.extend( true, {}, bt.defaults, c.widgetOptions ),
p = wo.build_processing,
typ = wo.build_type,
d = wo.build_source || c.data,
// determine type: html, json, array, csv, object
runType = function(d){
var t = $.type(d),
jq = d instanceof jQuery;
// run any processing if set
if ( typeof p === 'function' ) { d = p(d, wo); }
// store processed data in table.config.data
c.data = d;
// String (html or unprocessed json) or jQuery object
if ( jq || t === 'string' ) {
// look for </tr> closing tag, then we have an HTML string
if ( jq || /<\s*\/tr\s*>/.test(d) ) {
return bt.html( table, d, wo );
}
try {
d = $.parseJSON(d);
if (d) {
// valid JSON!
return bt.object( table, d, wo );
}
} catch(ignore) {}
// fall through in case it's a csv string
}
// Array
if (t === 'array' || t === 'string' || typ === 'array' || typ === 'csv') {
// build table using an array (csv & array combined script)
return bt.csv( table, d, wo );
}
// if we got here, it's an object, or nothing
return bt.object( table, d, wo );
};
// store config
table.config = c;
// even if wo.build_type is undefined, we can try to figure out the type
if ( !ts.buildTable.hasOwnProperty(typ) && typ !== '' ) {
if (c.debug) { ts.log('aborting build table widget, incorrect build type'); }
return false;
}
if ( d instanceof jQuery ) {
// get data from within a jQuery object (csv)
runType( $.trim( d.html() ) );
} else if ( d && ( d.hasOwnProperty('url') || typ === 'json' ) ) {
// load data via ajax
$.ajax( wo.build_source )
.done(function(data) {
runType(data);
})
.fail(function( jqXHR, textStatus, errorThrown) {
if (c.debug) { ts.log('aborting build table widget, failed ajax load'); }
$tbl.html('<tr><td class="error">' + jqXHR.status + ' ' + textStatus + '</td></tr>');
});
} else {
runType(d);
}
};
bt.defaults = {
// *** build widget core ***
build_type : '', // array, csv, object, json, html
build_source : '', // array, object, jQuery Object or ajaxObject { url: '', dataType: 'json' },
build_processing : null, // function that returns a useable build_type (e.g. string to array)
build_complete : 'tablesorter-build-complete', // triggered event when build completes
// *** CSV & Array ***
build_headers : {
rows : 1, // Number of header rows from the csv
classes : [], // Header classes to apply to cells
text : [], // Header cell text
widths : [] // set header cell widths (set in colgroup)
},
build_footers : {
rows : 1, // Number of header rows from the csv
classes : [], // Footer classes to apply to cells
text : [] // Footer cell text
},
build_numbers : {
addColumn : false, // include row numbering column?
sortable : false // make column sortable?
},
// *** CSV only options ***
build_csvStartLine : 0, // line within the csv to start adding to table
build_csvSeparator : ",", // csv separator
// *** build object options ***
build_objectRowKey : 'rows', // object key containing table rows
build_objectCellKey : 'cells', // object key containing table cells (within the rows object)
build_objectHeaderKey : 'headers', // object key containing table headers
build_objectFooterKey : 'footers' // object key containing table footers
};
bt.build = {
colgroup : function(widths) {
var t = '';
// add colgroup if widths set
if (widths && widths.length) {
t += '<colgroup>';
$.each(widths, function(i, w){
t += '<col' + ( w ? ' style="width:' + w + '"' : '' ) + '>';
});
t += '</colgroup>';
}
return t;
},
// d = cell data; typ = 'th' or 'td'; first = save widths from first header row only
cell : function(d, wo, typ, col, first){
var j, $td,
$col = first ? $('<col>') : '',
cls = wo.build_headers.classes,
cw = wo.build_headers.widths;
// d is just an array
if (/string|number/.test(typeof d)) {
// add classes from options, but not text
$td = $('<' + typ + (cls && cls[col] ? ' class="' + cls[col] + '"' : '') + '>' + d + '</' + typ + '>');
// get widths from options (only from first row)
if (first && cw && cw[col]) {
$col.width(cw[col] || '');
}
} else {
// assume we have an object
$td = $('<' + typ + '>');
for (j in d) {
if (d.hasOwnProperty(j)){
if (j === 'text' || j === 'html') {
$td[j]( d[j] );
} else if (first && j === 'width') {
// set column width, but only from first row
$col.width(d[j] || '');
} else {
$td.attr(j, d[j]);
}
}
}
}
return [ $td, $col ];
},
// h1 = header text from data
header : function(h1, wo){
var h2 = wo.build_headers.text,
cls = wo.build_headers.classes,
t = '<tr>' + (wo.build_numbers.addColumn ? '<th' + (wo.build_numbers.sortable ? '' :
' class="sorter-false"') + '>' + wo.build_numbers.addColumn + '</th>' : '');
$.each(h1, function(i, h) {
if (/<\s*\/t(d|h)\s*>/.test(h)) {
t += h;
} else {
t += '<th' + (cls && cls[i] ? ' class="' + cls[i] + '"' : '') + '>' +
(h2 && h2[i] ? h2[i] : h) + '</th>';
}
});
return t + '</tr>';
},
rows : function(items, txt, c, wo, num, ftr){
var h = (ftr ? 'th' : 'td'),
t = '<tr>' + (wo.build_numbers.addColumn ? '<' + h + '>' + (ftr ? '' : num) + '</' + h + '>' : '');
$.each(items, function(i, item) {
// test if HTML is already included; look for closing </td>
if (/<\s*\/t(d|h)\s*>/.test(item)) {
t += item;
} else {
t += '<' + (ftr ? h + (c && c[i] ? ' class="' + c[i] + '"' : '') : h) + '>' +
(ftr && txt && txt.length && txt[i] ? txt[i] : item) + '</' + h + '>';
}
});
return t + '</tr>';
}
};
bt.buildComplete = function(table, wo){
$(table).trigger(wo.build_complete);
ts.setup(table, table.config);
};
/* ==== Array example ====
[
[ "header1", "header2", ... "headerN" ],
[ "row1cell1", "row1cell2", ... "row1cellN" ],
[ "row2cell1", "row2cell2", ... "row2cellN" ],
...
[ "rowNcell1", "rowNcell2", ... "rowNcellN" ]
]
*/
bt.array = function(table, data, wo) {
return bt.csv(table, data, wo);
};
/* ==== CSV example ====
ID, Name, Age, Date
A42b, Parker, 28, "Jul 6, 2006 8:14 AM"
A255, Hood, 33, "Dec 10, 2002 5:14 AM"
A33, Kent, 18, "Jan 12, 2003 11:14 AM"
A1, Franklin, 45, "Jan 18, 2001 9:12 AM"
A102, Evans, 22, "Jan 18, 2007 9:12 AM"
A42a, Everet, 22, "Jan 18, 2007 9:12 AM"
ID, Name, Age, Date
*/
// Adapted & modified from csvToTable.js by Steve Sobel
// MIT license: https://code.google.com/p/jquerycsvtotable/
bt.csv = function(table, data, wo) {
var c, h,
csv = wo.build_type === 'csv' || typeof data === 'string',
$t = $(table),
lines = csv ? data.replace('\r','').split('\n') : data,
len = lines.length,
printedLines = 0,
infooter = false,
r = wo.build_headers.rows + (csv ? wo.build_csvStartLine : 0),
f = wo.build_footers.rows,
headerCount = 0,
error = '',
items,
tableHTML = bt.build.colgroup( wo.build_headers.widths ) + '<thead>';
$.each(lines, function(n, line) {
if ( n >= len - f ) { infooter = true; }
// build header
if ( (csv ? n >= wo.build_csvStartLine : true) && ( n < r ) ) {
h = csv ? bt.splitCSV( line, wo.build_csvSeparator ) : line;
headerCount = h.length;
tableHTML += bt.build.header(h, wo);
} else if ( n >= r ) {
// build tbody & tfoot rows
if (n === r) {
tableHTML += '</thead><tbody>';
}
items = csv ? bt.splitCSV( line, wo.build_csvSeparator ) : line;
if (infooter && f > 0) {
tableHTML += (n === len - f ? '</tbody><tfoot>' : '') +
(n === len ? '</tfoot>' : '');
}
if (items.length > 1) {
printedLines++;
if ( items.length !== headerCount ) {
error += 'error on line ' + n + ': Item count (' + items.length +
') does not match header count (' + headerCount + ') \n';
}
c = infooter ? wo.build_footers.classes : '';
tableHTML += bt.build.rows(items, wo.build_footers.text, c, wo, printedLines, infooter);
}
}
});
tableHTML += (f > 0 ? '' : '</tbody>');
if (error) {
$t.html(error);
} else {
$t.html(tableHTML);
bt.buildComplete(table, wo);
}
};
// CSV Parser by Brian Huisman (http://www.greywyvern.com/?post=258)
bt.splitCSV = function(str, sep) {
var x, tl,
thisCSV = $.trim(str).split(sep = sep || ",");
for ( x = thisCSV.length - 1; x >= 0; x-- ) {
if ( thisCSV[x].replace(/\"\s+$/, '"').charAt(thisCSV[x].length - 1) === '"' ) {
if ( (tl = thisCSV[x].replace(/^\s+\"/, '"')).length > 1 && tl.charAt(0) === '"' ) {
thisCSV[x] = thisCSV[x].replace(/^\s*"|"\s*$/g, '').replace(/""/g, '"');
} else if (x) {
thisCSV.splice(x - 1, 2, [thisCSV[x - 1], thisCSV[x]].join(sep));
} else {
thisCSV = thisCSV.shift().split(sep).concat(thisCSV);
}
} else {
thisCSV[x].replace(/""/g, '"');
}
}
return thisCSV;
};
// data may be a jQuery object after processing
bt.html = function(table, data, wo) {
var $t = $(table);
if ( data instanceof jQuery ) {
$t.empty().append(data);
} else {
$t.html(data);
}
bt.buildComplete(table, wo);
};
/* ==== Object example ====
data : {
headers : [
[
{ text: 'First Name', class: 'fname', width: '20%' }, // row 1 cell 1
'Last Name',
{ text: 'Age', class: 'age', 'data-sorter' : false },
'Total',
{ text: 'Discount', class : 'sorter-false' },
{ text: 'Date', class : 'date' } // row 1 cell 6
]
],
footers : 'clone', // clone headers or assign array like headers
rows : [
// TBODY 1
[ 'Peter', 'Parker', 28, '$9.99', '20%', 'Jul 6, 2006 8:14 AM' ], // row 1
[ 'John', 'Hood', 33, '$19.99', '25%', 'Dec 10, 2002 5:14 AM' ], // row 2
[ 'Clark', 'Kent', 18, '$15.89', '44%', 'Jan 12, 2003 11:14 AM' ], // row 3
// TBODY 2
{ newTbody: true, class: 'tablesorter-infoOnly' },
{ cells : [ { text: 'Info Row', colSpan: 6 } ] }, // row 4
// TBODY 3
{ newTbody: true },
[ 'Bruce', 'Evans', 22, '$13.19', '11%', 'Jan 18, 2007 9:12 AM' ], // row 5
[ 'Brice', 'Almighty', 45, '$153.19', '44%', 'Jan 18, 2001 9:12 AM' ], // row 6
{ class: 'specialRow', // row 7
cells: [
{ text: 'Fred', class: 'fname' },
{ text: 'Smith', class: 'lname' },
{ text: 18, class: 'age', 'data-info': 'fake ID!, he is really 16' },
{ text: '$22.44', class: 'total' },
{ text: '8%', class: 'discount' },
{ text: 'Aug 20, 2012 10:15 AM', class: 'date' }
],
'data-info' : 'This row likes turtles'
}
]
}
*/
bt.object = function(table, data, wo) {
// "rows"
var j, l, t, $c, $t, $tb, $tr,
c = table.config,
kh = wo.build_objectHeaderKey,
kr = wo.build_objectRowKey,
h = data.hasOwnProperty(kh) && !$.isEmptyObject(data.kh) ? data.kh : data.hasOwnProperty('headers') ? data.headers : false,
r = data.hasOwnProperty(kr) && !$.isEmptyObject(data.kr) ? data.kr : data.hasOwnProperty('rows') ? data.rows : false;
if (!h || !r || h.length === 0 || r.length === 0) {
if (c.debug) { ts.log('aborting build table widget, missing data for object build'); }
return false;
}
$c = $('<colgroup>');
$t = $('<table><thead/></table>');
// Build thead
// h = [ ['headerRow1Cell1', 'headerRow1Cell2', ... 'headerRow1CellN' ], ['headerRow2Cell1', ... ] ]
// or h = [ [ { text: 'firstCell', class: 'fc', width: '20%' }, ..., { text: 'last Cell' } ], [ /* second row */ ] ]
$.each(h, function(i, d){
$tr = $('<tr>').appendTo( $t.find('thead') );
l = d.length; // header row
for ( j = 0; j < l; j++ ) {
// cell(cellData, widgetOptions, 'th', first row)
t = bt.build.cell(d[j], wo, 'th', j, i === 0);
if (t[0] && t[0].length) { t[0].appendTo( $tr ); } // add cell
if (i === 0 && t[1]) { t[1].appendTo( $c ); } // add col to colgroup
}
});
if ($c.find('col[style]').length) {
// add colgroup if it contains col elements
$t.prepend( $c );
}
$tb = $('<tbody>');
// Build tbody
$.each(r, function(i, d){
var j;
t = $.type(d) === 'object';
// add new tbody
if (t && d.newTbody) {
$tb = $('<tbody>').appendTo( $t );
for (j in d) {
if (d.hasOwnProperty(j) && j !== 'newTbody'){
$tb.attr(j, d[j]);
}
}
} else {
if (i === 0) {
// add tbody, if the first item in the object isn't a call for a new tbody
$tb.appendTo( $t );
}
$tr = $('<tr>').appendTo( $tb );
if (t) {
// row defined by object
for (j in d) {
if (d.hasOwnProperty(j) && j !== wo.build_objectCellKey){
$tr.attr(j, d[j]);
}
}
if (d.hasOwnProperty(wo.build_objectCellKey)) {
// cells contains each cell info
d = d.cells;
}
}
l = d.length;
for ( j = 0; j < l; j++ ) {
// cell(cellData, widgetOptions, 'td')
$c = bt.build.cell(d[j], wo, 'td', j);
if ($c[0] && $c[0].length) { $c[0].appendTo( $tr ); } // add cell
}
}
});
// add footer
if (data.hasOwnProperty(wo.build_objectFooterKey)) {
t = data[wo.build_objectFooterKey];
if (t === 'clone') {
$c = $t.find('thead').html();
$t.append('<tfoot>' + $c + '</tfoot>');
} else {
$c = $('<tfoot>').appendTo( $t );
$.each(t, function(i, d) {
$tr = $('<tr>').appendTo( $c );
l = d.length; // footer cells
for ( j = 0; j < l; j++ ) {
// cell(cellData, widgetOptions, 'th')
$tb = bt.build.cell(d[j], wo, 'th', j);
if ($tb[0] && $tb[0].length) { $tb[0].appendTo( $tr ); } // add cell
}
});
}
}
$(table).html( $t.html() );
bt.buildComplete(table, wo);
};
bt.ajax = bt.json = function(table, data, wo) {
return bt.object(table, data, wo);
};
})(jQuery);

View File

@@ -0,0 +1,317 @@
/* Column Selector/Responsive table widget (beta) for TableSorter 5/22/2014 (v2.17.0)
* Requires tablesorter v2.8+ and jQuery 1.7+
* by Justin Hallett & Rob Garrison
*/
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery: false */
;(function($){
"use strict";
var ts = $.tablesorter,
namespace = '.tscolsel',
tsColSel = ts.columnSelector = {
queryAll : '@media only all { [columns] { display: none; } }',
queryBreak : '@media all and (min-width: [size]) { [columns] { display: table-cell; } }',
init: function(table, c, wo) {
var $t, colSel;
// abort if no input is contained within the layout
$t = $(wo.columnSelector_layout);
if (!$t.find('input').add( $t.filter('input') ).length) {
if (c.debug) {
ts.log('*** ERROR: Column Selector aborting, no input found in the layout! ***');
}
return;
}
// unique table class name
c.tableId = 'tablesorter' + new Date().getTime();
c.$table.addClass( c.tableId );
// build column selector/state array
colSel = c.selector = { $container : $(wo.columnSelector_container || '<div>') };
colSel.$style = $('<style></style>').prop('disabled', true).appendTo('head');
colSel.$breakpoints = $('<style></style>').prop('disabled', true).appendTo('head');
colSel.isInitializing = true;
tsColSel.setupSelector(table, c, wo);
if (wo.columnSelector_mediaquery) {
tsColSel.setupBreakpoints(c, wo);
}
colSel.isInitializing = false;
if (colSel.$container.length) {
tsColSel.updateCols(c, wo);
}
c.$table
.off('refreshColumnSelector' + namespace)
.on('refreshColumnSelector' + namespace, function(){
// make sure we're using current config settings
var c = this.config;
tsColSel.updateBreakpoints(c, c.widgetOptions);
tsColSel.updateCols(c, c.widgetOptions);
});
},
setupSelector: function(table, c, wo) {
var name,
colSel = c.selector,
$container = colSel.$container,
useStorage = wo.columnSelector_saveColumns && ts.storage,
// get stored column states
saved = useStorage ? ts.storage( table, 'tablesorter-columnSelector' ) : [],
state = useStorage ? ts.storage( table, 'tablesorter-columnSelector-auto') : {};
// initial states
colSel.auto = $.isEmptyObject(state) || $.type(state.auto) !== "boolean" ? wo.columnSelector_mediaqueryState : state.auto;
colSel.states = [];
colSel.$column = [];
colSel.$wrapper = [];
colSel.$checkbox = [];
// populate the selector container
c.$table.children('thead').find('tr:first th', table).each(function() {
var $this = $(this),
// if no data-priority is assigned, default to 1, but don't remove it from the selector list
priority = $this.attr(wo.columnSelector_priority) || 1,
colId = $this.attr('data-column'),
state = ts.getData(this, c.headers[colId], 'columnSelector');
// if this column not hidable at all
// include getData check (includes "columnSelector-false" class, data attribute, etc)
if ( isNaN(priority) && priority.length > 0 || state === 'disable' ||
( wo.columnSelector_columns[colId] && wo.columnSelector_columns[colId] === 'disable') ) {
return true; // goto next
}
// set default state; storage takes priority
colSel.states[colId] = saved && typeof(saved[colId]) !== 'undefined' ?
saved[colId] : typeof(wo.columnSelector_columns[colId]) !== 'undefined' ?
wo.columnSelector_columns[colId] : (state === 'true' || !(state === 'false'));
colSel.$column[colId] = $(this);
// set default col title
name = $this.attr(wo.columnSelector_name) || $this.text();
if ($container.length) {
colSel.$wrapper[colId] = $(wo.columnSelector_layout.replace(/\{name\}/g, name)).appendTo($container);
colSel.$checkbox[colId] = colSel.$wrapper[colId]
// input may not be wrapped within the layout template
.find('input').add( colSel.$wrapper[colId].filter('input') )
.attr('data-column', colId)
.prop('checked', colSel.states[colId])
.on('change', function(){
colSel.states[colId] = this.checked;
tsColSel.updateCols(c, wo);
}).change();
}
});
},
setupBreakpoints: function(c, wo){
var colSel = c.selector;
// add responsive breakpoints
if (wo.columnSelector_mediaquery) {
// used by window resize function
colSel.lastIndex = -1;
wo.columnSelector_breakpoints.sort();
tsColSel.updateBreakpoints(c, wo);
c.$table
.off('updateAll' + namespace)
.on('updateAll' + namespace, function(){
tsColSel.updateBreakpoints(c, wo);
tsColSel.updateCols(c, wo);
});
}
if (colSel.$container.length) {
// Add media queries toggle
if (wo.columnSelector_mediaquery) {
colSel.$auto = $( wo.columnSelector_layout.replace(/\{name\}/g, wo.columnSelector_mediaqueryName) ).prependTo(colSel.$container);
colSel.$auto
// needed in case the input in the layout is not wrapped
.find('input').add( colSel.$auto.filter('input') )
.attr('data-column', 'auto')
.prop('checked', colSel.auto)
.on('change', function(){
colSel.auto = this.checked;
$.each( colSel.$checkbox, function(i, $cb){
if ($cb) {
$cb[0].disabled = colSel.auto;
colSel.$wrapper[i].toggleClass('disabled', colSel.auto);
}
});
if (wo.columnSelector_mediaquery) {
tsColSel.updateBreakpoints(c, wo);
}
tsColSel.updateCols(c, wo);
// copy the column selector to a popup/tooltip
if (c.selector.$popup) {
c.selector.$popup.find('.tablesorter-column-selector')
.html( colSel.$container.html() )
.find('input').each(function(){
var indx = $(this).attr('data-column');
$(this).prop( 'checked', indx === 'auto' ? colSel.auto : colSel.states[indx] );
});
}
if (wo.columnSelector_saveColumns && ts.storage) {
ts.storage( c.$table[0], 'tablesorter-columnSelector-auto', { auto : colSel.auto } );
}
}).change();
}
// Add a bind on update to re-run col setup
c.$table.off('update' + namespace).on('update' + namespace, function() {
tsColSel.updateCols(c, wo);
});
}
},
updateBreakpoints: function(c, wo) {
var priority, column, breaks,
colSel = c.selector,
prefix = '.' + c.tableId,
mediaAll = [],
breakpts = '';
if (wo.columnSelector_mediaquery && !colSel.auto) {
colSel.$breakpoints.prop('disabled', true);
colSel.$style.prop('disabled', false);
return;
}
// only 6 breakpoints (same as jQuery Mobile)
for (priority = 0; priority < 6; priority++){
/*jshint loopfunc:true */
breaks = [];
c.$headers.filter('[' + wo.columnSelector_priority + '=' + (priority + 1) + ']').each(function(){
column = parseInt($(this).attr('data-column'), 10) + 1;
breaks.push(prefix + ' tr th:nth-child(' + column + ')');
breaks.push(prefix + ' tr td:nth-child(' + column + ')');
});
if (breaks.length) {
mediaAll = mediaAll.concat( breaks );
breakpts += tsColSel.queryBreak
.replace(/\[size\]/g, wo.columnSelector_breakpoints[priority])
.replace(/\[columns\]/g, breaks.join(','));
}
}
if (colSel.$style) {
colSel.$style.prop('disabled', true);
}
colSel.$breakpoints
.prop('disabled', false)
.html( tsColSel.queryAll.replace(/\[columns\]/g, mediaAll.join(',')) + breakpts );
},
updateCols: function(c, wo) {
if (wo.columnSelector_mediaquery && c.selector.auto || c.selector.isInitializing) {
return;
}
var column,
colSel = c.selector,
styles = [],
prefix = '.' + c.tableId;
colSel.$container.find('input[data-column]').filter('[data-column!="auto"]').each(function(){
if (!this.checked) {
column = parseInt( $(this).attr('data-column'), 10 ) + 1;
styles.push(prefix + ' tr th:nth-child(' + column + ')');
styles.push(prefix + ' tr td:nth-child(' + column + ')');
}
});
if (wo.columnSelector_mediaquery){
colSel.$breakpoints.prop('disabled', true);
}
if (colSel.$style) {
colSel.$style.prop('disabled', false).html( styles.length ? styles.join(',') + ' { display: none; }' : '' );
}
if (wo.columnSelector_saveColumns && ts.storage) {
ts.storage( c.$table[0], 'tablesorter-columnSelector', colSel.states );
}
},
attachTo : function(table, elm) {
table = $(table)[0];
var colSel, wo, indx,
c = table.config,
$popup = $(elm);
if ($popup.length && c) {
if (!$popup.find('.tablesorter-column-selector').length) {
// add a wrapper to add the selector into, in case the popup has other content
$popup.append('<span class="tablesorter-column-selector"></span>');
}
colSel = c.selector;
wo = c.widgetOptions;
$popup.find('.tablesorter-column-selector')
.html( colSel.$container.html() )
.find('input').each(function(){
var indx = $(this).attr('data-column');
$(this).prop( 'checked', indx === 'auto' ? colSel.auto : colSel.states[indx] );
});
colSel.$popup = $popup.on('change', 'input', function(){
// data input
indx = $(this).attr('data-column');
// update original popup
colSel.$container.find('input[data-column="' + indx + '"]')
.prop('checked', this.checked)
.trigger('change');
});
}
}
};
ts.addWidget({
id: "columnSelector",
priority: 10,
options: {
// target the column selector markup
columnSelector_container : null,
// column status, true = display, false = hide
// disable = do not display on list
columnSelector_columns : {},
// remember selected columns
columnSelector_saveColumns: true,
// container layout
columnSelector_layout : '<label><input type="checkbox">{name}</label>',
// data attribute containing column name to use in the selector container
columnSelector_name : 'data-selector-name',
/* Responsive Media Query settings */
// enable/disable mediaquery breakpoints
columnSelector_mediaquery: true,
// toggle checkbox name
columnSelector_mediaqueryName: 'Auto: ',
// breakpoints checkbox initial setting
columnSelector_mediaqueryState: true,
// responsive table hides columns with priority 1-6 at these breakpoints
// see http://view.jquerymobile.com/1.3.2/dist/demos/widgets/table-column-toggle/#Applyingapresetbreakpoint
// *** set to false to disable ***
columnSelector_breakpoints : [ '20em', '30em', '40em', '50em', '60em', '70em' ],
// data attribute containing column priority
// duplicates how jQuery mobile uses priorities:
// http://view.jquerymobile.com/1.3.2/dist/demos/widgets/table-column-toggle/
columnSelector_priority : 'data-priority'
},
init: function(table, thisWidget, c, wo) {
tsColSel.init(table, c, wo);
},
remove: function(table, c){
var csel = c.selector;
csel.$container.empty();
if (csel.$popup) { csel.$popup.empty(); }
csel.$style.remove();
csel.$breakpoints.remove();
c.$table.off('updateAll' + namespace + ' update' + namespace);
}
});
})(jQuery);

View File

@@ -0,0 +1,70 @@
/*! tablesorter CSS Sticky Headers widget - updated 5/5/2014 (v2.16.4)
* Requires a modern browser, tablesorter v2.8+
*/
/*jshint jquery:true, unused:false */
;(function($){
"use strict";
$.tablesorter.addWidget({
id: "cssStickyHeaders",
priority: 10,
options: {
cssStickyHeaders_offset : 0,
cssStickyHeaders_addCaption : false,
cssStickyHeaders_attachTo : null,
cssStickyHeaders_filteredToTop : true,
cssStickyHeaders_zIndex : 10
},
init : function(table, thisWidget, c, wo) {
var $attach = $(wo.cssStickyHeaders_attachTo),
namespace = '.cssstickyheader',
$thead = c.$table.children('thead'),
$caption = c.$table.find('caption'),
$win = $attach.length ? $attach : $(window);
$win.bind('scroll resize '.split(' ').join(namespace + ' '), function() {
var top = $attach.length ? $attach.offset().top : $win.scrollTop(),
// add caption height; include table padding top & border-spacing or text may be above the fold (jQuery UI themes)
// border-spacing needed in Firefox, but not webkit... not sure if I should account for that
captionTop = wo.cssStickyHeaders_addCaption ? $caption.outerHeight(true) +
(parseInt(c.$table.css('padding-top'), 10) || 0) + (parseInt(c.$table.css('border-spacing'), 10) || 0) : 0,
bottom = c.$table.height() - $thead.height() - (c.$table.find('tfoot').height() || 0) - captionTop,
deltaY = top - $thead.offset().top + (parseInt(c.$table.css('border-top-width'), 10) || 0) +
(wo.cssStickyHeaders_offset || 0) + captionTop,
finalY = (deltaY > 0 && deltaY <= bottom ? deltaY : 0),
// IE can only transform header cells - fixes #447 thanks to @gakreol!
$cells = $thead.children().children();
if (wo.cssStickyHeaders_addCaption) {
$cells = $cells.add($caption);
}
$cells.css({
"position" : "relative",
"z-index" : wo.cssStickyHeaders_zIndex,
"transform" : finalY === 0 ? "" : "translate(0px," + finalY + "px)",
"-ms-transform" : finalY === 0 ? "" : "translate(0px," + finalY + "px)",
"-webkit-transform" : finalY === 0 ? "" : "translate(0px," + finalY + "px)"
});
});
c.$table.bind('filterEnd', function() {
if (wo.cssStickyHeaders_filteredToTop) {
// scroll top of table into view
window.scrollTo(0, c.$table.position().top);
}
});
},
remove: function(table, c, wo){
var namespace = '.cssstickyheader';
$(window).unbind('scroll resize '.split(' ').join(namespace + ' '));
c.$table
.unbind('update updateAll '.split(' ').join(namespace + ' '))
.children('thead, caption').css({
"position" : "",
"z-index" : "",
"transform" : "",
"-ms-transform" : "",
"-webkit-transform" : ""
});
}
});
})(jQuery);

View File

@@ -0,0 +1,200 @@
/*! tablesorter Editable Content widget - updated 9/15/2014 (core v2.17.8)
* Requires tablesorter v2.8+ and jQuery 1.7+
* by Rob Garrison
*/
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery: false */
;(function($){
"use strict";
$.tablesorter.addWidget({
id: 'editable',
options : {
editable_columns : [],
editable_enterToAccept : true,
editable_autoAccept : true,
editable_autoResort : false,
editable_wrapContent : '<div>', // wrap the cell content... makes this widget work in IE, and with autocomplete
editable_trimContent : true, // trim content inside of contenteditable (remove tabs & carriage returns)
editable_validate : null, // function(text, originalText){ return text; }
editable_focused : null, // function(text, columnIndex, $element) {}
editable_blur : null, // function(text, columnIndex, $element) { }
editable_selectAll : false, // true/false or function(text, columnIndex, $element) { return true; }
editable_noEdit : 'no-edit',
editable_editComplete : 'editComplete'
},
init: function(table, thisWidget, c, wo){
if ( !wo.editable_columns.length ) { return; }
var indx, tmp, $t,
cols = [],
editComplete = function($cell, refocus){
$cell
.removeClass('tseditable-last-edited-cell')
.trigger( wo.editable_editComplete, [c] );
// restore focus last cell after updating
if (refocus) {
setTimeout(function(){
$cell.focus();
}, 50);
}
},
selectAll = function(cell){
setTimeout(function(){
// select all text in contenteditable
// see http://stackoverflow.com/a/6150060/145346
var range = document.createRange();
range.selectNodeContents(cell);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}, 100);
};
if ( $.type(wo.editable_columns) === "string" && wo.editable_columns.indexOf('-') >= 0 ) {
// editable_columns can contain a range string (i.e. "2-4" )
tmp = wo.editable_columns.split('-');
indx = parseInt(tmp[0],10) || 0;
tmp = parseInt(tmp[1],10) || (c.columns - 1);
if ( tmp > c.columns ) { tmp = c.columns - 1; }
for ( ; indx <= tmp; indx++ ) {
cols.push('td:nth-child(' + (indx + 1) + ')');
}
} else if ( $.isArray(wo.editable_columns) ) {
$.each(wo.editable_columns, function(i, col){
if ( col < c.columns ) {
cols.push('td:nth-child(' + (col + 1) + ')');
}
});
}
tmp = $('<div>').wrapInner(wo.editable_wrapContent).children().length || $.isFunction(wo.editable_wrapContent);
// IE does not allow making TR/TH/TD cells directly editable (issue #404)
// so add a div or span inside ( it's faster than using wrapInner() )
c.$tbodies.find( cols.join(',') ).not( '.' + wo.editable_noEdit ).each(function(){
// test for children, if they exist, then make the children editable
$t = $(this);
if (tmp && $t.children().length === 0) {
$t.wrapInner( wo.editable_wrapContent );
}
if ($t.children().length) {
// make all children content editable
$t.children().not('.' + wo.editable_noEdit).each(function(){
var $this = $(this);
if (wo.editable_trimContent) {
$this.text(function(i, txt){
return $.trim(txt);
});
}
$this.prop( 'contenteditable', true );
});
} else {
if (wo.editable_trimContent) {
$t.text(function(i, txt){
return $.trim(txt);
});
}
$t.prop( 'contenteditable', true );
}
});
c.$tbodies
.on('mouseleave.tseditable', function(){
if ( c.$table.data('contentFocused') ) {
// change to "true" instead of element to allow focusout to process
c.$table.data( 'contentFocused', true );
$(':focus').trigger('focusout');
}
})
.on('focus.tseditable', '[contenteditable]', function(e){
clearTimeout( $(this).data('timer') );
c.$table.data( 'contentFocused', e.target );
var $this = $(this),
selAll = wo.editable_selectAll,
column = $this.closest('td').index(),
txt = $.trim( $this.text() );
if (wo.editable_enterToAccept) {
// prevent enter from adding into the content
$this.on('keydown.tseditable', function(e){
if ( e.which === 13 ) {
e.preventDefault();
}
});
}
$this.data({ before : txt, original: txt });
if (typeof wo.editable_focused === 'function') {
wo.editable_focused( txt, column, $this );
}
if (selAll) {
if (typeof selAll === 'function') {
if ( selAll( txt, column, $this ) ) {
selectAll($this[0]);
}
} else {
selectAll($this[0]);
}
}
})
.on('blur focusout keydown '.split(' ').join('.tseditable '), '[contenteditable]', function(e){
if ( !c.$table.data('contentFocused') ) { return; }
var t, validate,
valid = false,
$this = $(e.target),
txt = $.trim( $this.text() ),
column = $this.closest('td').index();
if ( e.which === 27 ) {
// user cancelled
$this.html( $.trim( $this.data('original') ) ).trigger('blur.tseditable');
c.$table.data( 'contentFocused', false );
return false;
}
// accept on enter (if set), alt-enter (always) or if autoAccept is set and element is blurred or unfocused
t = e.which === 13 && ( wo.editable_enterToAccept || e.altKey ) || wo.editable_autoAccept && e.type !== 'keydown';
// change if new or user hits enter (if option set)
if ( t && $this.data('before') !== txt ) {
validate = wo.editable_validate;
valid = txt;
if (typeof(validate) === "function") {
valid = validate( txt, $this.data('original'), column, $this );
} else if (typeof (validate = $.tablesorter.getColumnData( table, validate, column )) === 'function') {
valid = validate( txt, $this.data('original'), column, $this );
}
if ( t && valid !== false ) {
c.$table.find('.tseditable-last-edited-cell').removeClass('tseditable-last-edited-cell');
$this
.addClass('tseditable-last-edited-cell')
.html( $.trim( valid ) )
.data('before', valid)
.data('original', valid)
.trigger('change');
c.$table.trigger('updateCell', [ $this.closest('td'), false, function(){
if (wo.editable_autoResort) {
setTimeout(function(){
c.$table.trigger("sorton", [ c.sortList, function(){
editComplete(c.$table.find('.tseditable-last-edited-cell'), true);
}, true ]);
}, 10);
} else {
editComplete(c.$table.find('.tseditable-last-edited-cell'));
}
} ]);
return false;
}
} else if ( !valid && e.type !== 'keydown' ) {
clearTimeout( $this.data('timer') );
$this.data('timer', setTimeout(function(){
if ($.isFunction(wo.editable_blur)) {
wo.editable_blur( $.trim( $this.text() ), column, $this );
}
}, 100));
// restore original content on blur
$this.html( $.trim( $this.data('original') ) );
}
});
}
});
})(jQuery);

View File

@@ -0,0 +1,249 @@
/*! tablesorter Grouping widget - updated 3/7/2014 (core v2.15.6)
* Requires tablesorter v2.8+ and jQuery 1.7+
* by Rob Garrison
*/
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery: false */
;(function($){
"use strict";
var ts = $.tablesorter;
ts.grouping = {
types : {
number : function(c, $column, txt, num, group){
var value, word;
if (num > 1 && txt !== '') {
if ($column.hasClass(ts.css.sortAsc)) {
value = Math.floor(parseFloat(txt)/num) * num;
return value > parseFloat(group || 0) ? value : parseFloat(group || 0);
} else {
value = Math.ceil(parseFloat(txt)/num) * num;
return value < parseFloat(group || num) - value ? parseFloat(group || num) - value : value;
}
} else {
word = (txt + '').match(/\d+/g);
return word && word.length >= num ? word[num - 1] : txt || '';
}
},
separator : function(c, $column, txt, num){
var word = (txt + '').split(c.widgetOptions.group_separator);
return $.trim(word && num > 0 && word.length >= num ? word[(num || 1) - 1] : '');
},
word : function(c, $column, txt, num){
var word = (txt + ' ').match(/\w+/g);
return word && word.length >= num ? word[num - 1] : txt || '';
},
letter : function(c, $column, txt, num){
return txt ? (txt + ' ').substring(0, num) : '';
},
date : function(c, $column, txt, part, group){
var wo = c.widgetOptions,
time = new Date(txt || ''),
hours = time.getHours();
return part === 'year' ? time.getFullYear() :
part === 'month' ? wo.group_months[time.getMonth()] :
part === 'day' ? wo.group_months[time.getMonth()] + ' ' + time.getDate() :
part === 'week' ? wo.group_week[time.getDay()] :
part === 'time' ? ('00' + (hours > 12 ? hours - 12 : hours === 0 ? hours + 12 : hours)).slice(-2) + ':' +
('00' + time.getMinutes()).slice(-2) + ' ' + ('00' + wo.group_time[hours >= 12 ? 1 : 0]).slice(-2) :
wo.group_dateString(time);
}
},
update : function(table, c, wo){
if ($.isEmptyObject(c.cache)) { return; }
var rowIndex, tbodyIndex, currentGroup, $rows, groupClass, grouping, time, cache, saveName, direction,
lang = wo.grouping_language,
group = '',
savedGroup = false,
column = c.sortList[0] ? c.sortList[0][0] : -1;
c.$table
.find('tr.group-hidden').removeClass('group-hidden').end()
.find('tr.group-header').remove();
if (wo.group_collapsible) {
// clear pager saved spacer height (in case the rows are collapsed)
c.$table.data('pagerSavedHeight', 0);
}
if (column >= 0 && !c.$headers.filter('[data-column="' + column + '"]:last').hasClass('group-false')) {
if (c.debug){ time = new Date(); }
wo.group_currentGroup = ''; // save current groups
wo.group_currentGroups = {};
// group class finds "group-{word/separator/letter/number/date/false}-{optional:#/year/month/day/week/time}"
groupClass = (c.$headers.filter('[data-column="' + column + '"]:last').attr('class') || '').match(/(group-\w+(-\w+)?)/g);
// grouping = [ 'group', '{word/separator/letter/number/date/false}', '{#/year/month/day/week/time}' ]
grouping = groupClass ? groupClass[0].split('-') : ['group','letter',1]; // default to letter 1
// save current grouping
if (wo.group_collapsible && wo.group_saveGroups && ts.storage) {
wo.group_currentGroups = ts.storage( table, 'tablesorter-groups' ) || {};
// include direction when grouping numbers > 1 (reversed direction shows different range values)
direction = (grouping[1] === 'number' && grouping[2] > 1) ? 'dir' + c.sortList[0][1] : '';
// combine column, sort direction & grouping as save key
saveName = wo.group_currentGroup = '' + column + direction + grouping.join('');
if (!wo.group_currentGroups[saveName]) {
wo.group_currentGroups[saveName] = [];
} else {
savedGroup = true;
}
}
for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++) {
cache = c.cache[tbodyIndex].normalized;
group = ''; // clear grouping across tbodies
$rows = c.$tbodies.eq(tbodyIndex).children('tr').not('.' + c.cssChildRow);
for (rowIndex = 0; rowIndex < $rows.length; rowIndex++) {
if ( $rows.eq(rowIndex).is(':visible') ) {
// fixes #438
if (ts.grouping.types[grouping[1]]) {
currentGroup = cache[rowIndex] ?
ts.grouping.types[grouping[1]]( c, c.$headers.filter('[data-column="' + column + '"]:last'), cache[rowIndex][column], /date/.test(groupClass) ?
grouping[2] : parseInt(grouping[2] || 1, 10) || 1, group, lang ) : currentGroup;
if (group !== currentGroup) {
group = currentGroup;
// show range if number > 1
if (grouping[1] === 'number' && grouping[2] > 1 && currentGroup !== '') {
currentGroup += ' - ' + (parseInt(currentGroup, 10) +
((parseInt(grouping[2],10) - 1) * (c.$headers.filter('[data-column="' + column + '"]:last').hasClass(ts.css.sortAsc) ? 1 : -1)));
}
if ($.isFunction(wo.group_formatter)) {
currentGroup = wo.group_formatter((currentGroup || '').toString(), column, table, c, wo) || currentGroup;
}
$rows.eq(rowIndex).before('<tr class="group-header ' + c.selectorRemove.slice(1) +
'" unselectable="on"><td colspan="' +
c.columns + '">' + (wo.group_collapsible ? '<i/>' : '') + '<span class="group-name">' +
currentGroup + '</span><span class="group-count"></span></td></tr>');
if (wo.group_saveGroups && !savedGroup && wo.group_collapsed && wo.group_collapsible) {
// all groups start collapsed
wo.group_currentGroups[wo.group_currentGroup].push(currentGroup);
}
}
}
}
}
}
c.$table.find('tr.group-header')
.bind('selectstart', false)
.each(function(){
var isHidden, $label, name,
$row = $(this),
$rows = $row.nextUntil('tr.group-header').filter(':visible');
if (wo.group_count || $.isFunction(wo.group_callback)) {
$label = $row.find('.group-count');
if ($label.length) {
if (wo.group_count) {
$label.html( wo.group_count.replace(/\{num\}/g, $rows.length) );
}
if ($.isFunction(wo.group_callback)) {
wo.group_callback($row.find('td'), $rows, column, table);
}
}
}
if (wo.group_saveGroups && wo.group_currentGroups[wo.group_currentGroup].length) {
name = $row.find('.group-name').text().toLowerCase();
isHidden = $.inArray( name, wo.group_currentGroups[wo.group_currentGroup] ) > -1;
$row.toggleClass('collapsed', isHidden);
$rows.toggleClass('group-hidden', isHidden);
} else if (wo.group_collapsed && wo.group_collapsible) {
$row.addClass('collapsed');
$rows.addClass('group-hidden');
}
});
c.$table.trigger(wo.group_complete);
if (c.debug) {
$.tablesorter.benchmark("Applying groups widget: ", time);
}
}
},
bindEvents : function(table, c, wo){
if (wo.group_collapsible) {
wo.group_currentGroups = [];
// .on() requires jQuery 1.7+
c.$table.on('click toggleGroup', 'tr.group-header', function(event){
event.stopPropagation();
var isCollapsed, $groups, indx,
$this = $(this),
name = $this.find('.group-name').text().toLowerCase();
// use shift-click to toggle ALL groups
if (event.type === 'click' && event.shiftKey) {
$this.siblings('.group-header').trigger('toggleGroup');
}
$this.toggleClass('collapsed');
// nextUntil requires jQuery 1.4+
$this.nextUntil('tr.group-header').toggleClass('group-hidden', $this.hasClass('collapsed') );
// save collapsed groups
if (wo.group_saveGroups && ts.storage) {
$groups = c.$table.find('.group-header');
isCollapsed = $this.hasClass('collapsed');
if (!wo.group_currentGroups[wo.group_currentGroup]) {
wo.group_currentGroups[wo.group_currentGroup] = [];
}
if (isCollapsed && wo.group_currentGroup) {
wo.group_currentGroups[wo.group_currentGroup].push( name );
} else if (wo.group_currentGroup) {
indx = $.inArray( name, wo.group_currentGroups[wo.group_currentGroup] );
if (indx > -1) {
wo.group_currentGroups[wo.group_currentGroup].splice( indx, 1 );
}
}
ts.storage( table, 'tablesorter-groups', wo.group_currentGroups );
}
});
}
$(wo.group_saveReset).on('click', function(){
ts.grouping.clearSavedGroups(table);
});
c.$table.on('pagerChange.tsgrouping', function(){
ts.grouping.update(table, c, wo);
});
},
clearSavedGroups: function(table){
if (table && ts.storage) {
ts.storage(table, 'tablesorter-groups', '');
ts.grouping.update(table, table.config, table.config.widgetOptions);
}
}
};
ts.addWidget({
id: 'group',
priority: 100,
options: {
group_collapsible : true, // make the group header clickable and collapse the rows below it.
group_collapsed : false, // start with all groups collapsed
group_saveGroups : true, // remember collapsed groups
group_saveReset : null, // element to clear saved collapsed groups
group_count : ' ({num})', // if not false, the "{num}" string is replaced with the number of rows in the group
group_separator : '-', // group name separator; used when group-separator-# class is used.
group_formatter : null, // function(txt, column, table, c, wo) { return txt; }
group_callback : null, // function($cell, $rows, column, table){}, callback allowing modification of the group header labels
group_complete : 'groupingComplete', // event triggered on the table when the grouping widget has finished work
// change these default date names based on your language preferences
group_months : [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ],
group_week : [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ],
group_time : [ 'AM', 'PM' ],
// this function is used when "group-date" is set to create the date string
// you can just return date, date.toLocaleString(), date.toLocaleDateString() or d.toLocaleTimeString()
// reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#Conversion_getter
group_dateString : function(date) { return date.toLocaleString(); }
},
init: function(table, thisWidget, c, wo){
ts.grouping.bindEvents(table, c, wo);
},
format: function(table, c, wo) {
ts.grouping.update(table, c, wo);
},
remove : function(table, c, wo){
c.$table
.off('click', 'tr.group-header')
.off('pagerChange.tsgrouping')
.find('.group-hidden').removeClass('group-hidden').end()
.find('tr.group-header').remove();
}
});
})(jQuery);

View File

@@ -0,0 +1,91 @@
/*! tablesorter headerTitles widget - updated 3/5/2014 (core v2.15.6)
* Requires tablesorter v2.8+ and jQuery 1.7+
* by Rob Garrison
*/
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery: false */
;(function($){
"use strict";
var ts = $.tablesorter;
ts.addWidget({
id: 'headerTitles',
options: {
// use aria-label text
// e.g. "First Name: Ascending sort applied, activate to apply a descending sort"
headerTitle_useAria : false,
// add tooltip class
headerTitle_tooltip : '',
// custom titles [ ascending, descending, unsorted ]
headerTitle_cur_text : [ ' sort: A - Z', ' sort: Z - A', 'ly unsorted' ],
headerTitle_cur_numeric : [ ' sort: 0 - 9', ' sort: 9 - 0', 'ly unsorted' ],
headerTitle_nxt_text : [ ' sort: A - Z', ' sort: Z - A', 'remove sort' ],
headerTitle_nxt_numeric : [ ' sort: 0 - 9', ' sort: 9 - 0', 'remove sort' ],
// title display; {prefix} adds above prefix
// {type} adds the current sort order from above (text or numeric)
// {next} adds the next sort direction using the sort order above
headerTitle_output_sorted : 'current{current}; activate to {next}',
headerTitle_output_unsorted : 'current{current}; activate to {next} ',
headerTitle_output_nosort : 'No sort available',
// use this type to override the parser detection result
// e.g. use for numerically parsed columns (e.g. dates), but you
// want the user to see a text sort, e.g. [ 'text', 'numeric' ]
headerTitle_type : [],
// manipulate the title as desired
headerTitle_callback : null // function($cell, txt) { return txt; }
},
init: function(table, thisWidget, c, wo){
// force refresh
c.$table.on('refreshHeaderTitle', function(){
thisWidget.format(table, c, wo);
});
// add tooltip class
if ($.isArray(wo.headerTitle_tooltip)) {
c.$headers.each(function(){
$(this).addClass( wo.headerTitle_tooltip[this.column] || '' );
});
} else if (wo.headerTitle_tooltip !== '') {
c.$headers.addClass( wo.headerTitle_tooltip );
}
},
format: function (table, c, wo) {
var txt;
c.$headers.each(function(){
var t = this,
$this = $(this),
sortType = wo.headerTitle_type[t.column] || c.parsers[ t.column ].type || 'text',
sortDirection = $this.hasClass(ts.css.sortAsc) ? 0 : $this.hasClass(ts.css.sortDesc) ? 1 : 2,
sortNext = t.order[(t.count + 1) % (c.sortReset ? 3 : 2)];
if (wo.headerTitle_useAria) {
txt = $this.hasClass('sorter-false') ? wo.headerTitle_output_nosort : $this.attr('aria-label') || '';
} else {
txt = (wo.headerTitle_prefix || '') + // now deprecated
($this.hasClass('sorter-false') ? wo.headerTitle_output_nosort :
ts.isValueInArray( t.column, c.sortList ) >= 0 ? wo.headerTitle_output_sorted : wo.headerTitle_output_unsorted);
txt = txt.replace(/\{(current|next|name)\}/gi, function(m){
return {
'{name}' : $this.text(),
'{current}' : wo[ 'headerTitle_cur_' + sortType ][ sortDirection ] || '',
'{next}' : wo[ 'headerTitle_nxt_' + sortType ][ sortNext ] || ''
}[m.toLowerCase()];
});
}
$this.attr('title', $.isFunction(wo.headerTitle_callback) ? wo.headerTitle_callback($this, txt) : txt);
});
},
remove: function (table, c, wo) {
c.$headers.attr('title', '');
c.$table.off('refreshHeaderTitle');
// remove tooltip class
if ($.isArray(wo.headerTitle_tooltip)) {
c.$headers.each(function(){
$(this).removeClass( wo.headerTitle_tooltip[this.column] || '' );
});
} else if (wo.headerTitle_tooltip !== '') {
c.$headers.removeClass( wo.headerTitle_tooltip );
}
}
});
})(jQuery);

View File

@@ -0,0 +1,413 @@
/*! tablesorter math widget - beta - updated 5/28/2014 (v2.17.1)
* Requires tablesorter v2.16+ and jQuery 1.7+
* by Rob Garrison
*/
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery: false */
;(function($){
"use strict";
var ts = $.tablesorter,
math = {
// get all of the row numerical values in an arry
getRow : function(table, wo, $el, dataAttrib) {
var $t, txt,
c = table.config,
arry = [],
$row = $el.closest('tr'),
$cells = $row.children();
if (!$row.hasClass(wo.filter_filteredRow || 'filtered')) {
if (wo.math_ignore.length) {
$cells = $cells.not('[' + dataAttrib + '=ignore]').not('[data-column=' + wo.math_ignore.join('],[data-column=') + ']');
}
arry = $cells.not($el).map(function(){
$t = $(this);
txt = $t.attr(c.textAttribute);
if (typeof txt === "undefined") {
txt = this.textContent || $t.text();
}
txt = ts.formatFloat(txt.replace(/[^\w,. \-()]/g, ""), table);
return isNaN(txt) ? 0 : txt;
}).get();
}
return arry;
},
// get all of the column numerical values in an arry
getColumn : function(table, wo, $el, type, dataAttrib){
var i, txt, $t, len, mathAbove,
arry = [],
c = table.config,
filtered = wo.filter_filteredRow || 'filtered',
cIndex = parseInt( $el.attr('data-column'), 10 ),
$rows = c.$table.children('tbody').children(),
$row = $el.closest('tr');
// make sure tfoot rows are AFTER the tbody rows
// $rows.add( c.$table.children('tfoot').children() );
if (type === 'above') {
len = $rows.index($row);
i = len;
while (i >= 0) {
$t = $rows.eq(i).children().filter('[data-column=' + cIndex + ']');
mathAbove = $t.filter('[' + dataAttrib + '^=above]').length;
// ignore filtered rows & rows with data-math="ignore" (and starting row)
if ( ( !$rows.eq(i).hasClass(filtered) && $rows.eq(i).not('[' + dataAttrib + '=ignore]').length && i !== len ) || mathAbove && i !== len ) {
// stop calculating "above", when encountering another "above"
if (mathAbove) {
i = 0;
} else if ($t.length) {
txt = $t.attr(c.textAttribute);
if (typeof txt === "undefined") {
txt = $t[0].textContent || $t.text();
}
txt = ts.formatFloat(txt.replace(/[^\w,. \-()]/g, ""), table);
arry.push(isNaN(txt) ? 0 : txt);
}
}
i--;
}
} else {
$rows.each(function(){
$t = $(this).children().filter('[data-column=' + cIndex + ']');
if (!$(this).hasClass(filtered) && $t.not('[' + dataAttrib + '^=above],[' + dataAttrib + '^=col]').length && !$t.is($el)) {
txt = $t.attr(c.textAttribute);
if (typeof txt === "undefined") {
txt = ($t[0] ? $t[0].textContent : '') || $t.text();
}
txt = ts.formatFloat(txt.replace(/[^\w,. \-()]/g, ""), table);
arry.push(isNaN(txt) ? 0 : txt);
}
});
}
return arry;
},
// get all of the column numerical values in an arry
getAll : function(table, wo, dataAttrib){
var txt, $t, col,
arry = [],
c = table.config,
filtered = wo.filter_filteredRow || 'filtered',
$rows = c.$table.children('tbody').children();
$rows.each(function(){
if (!$(this).hasClass(filtered)) {
$(this).children().each(function(){
$t = $(this);
col = parseInt( $t.attr('data-column'), 10);
if (!$t.filter('[' + dataAttrib + ']').length && $.inArray(col, wo.math_ignore) < 0) {
txt = $t.attr(c.textAttribute);
if (typeof txt === "undefined") {
txt = ($t[0] ? $t[0].textContent : '') || $t.text();
}
txt = ts.formatFloat(txt.replace(/[^\w,. \-()]/g, ""), table);
arry.push(isNaN(txt) ? 0 : txt);
}
});
}
});
return arry;
},
recalculate : function(table, c, wo, init){
if (c && (!wo.math_isUpdating || init)) {
// add data-column attributes to all table cells
if (init) {
ts.computeColumnIndex( c.$table.children('tbody').children() );
}
// data-attribute name (defaults to data-math)
var dataAttrib = 'data-' + (wo.math_data || 'math'),
// all non-info tbody cells
$mathCells = c.$tbodies.find('[' + dataAttrib + ']');
math.mathType( table, wo, $mathCells, wo.math_priority, dataAttrib );
// only info tbody cells
$mathCells = c.$table.find('.' + c.cssInfoBlock + ', tfoot').find('[' + dataAttrib + ']');
math.mathType( table, wo, $mathCells, wo.math_priority, dataAttrib );
// find the "all" total
math.mathType( table, wo, c.$table.find('[' + dataAttrib + '^=all]'), ['all'], dataAttrib );
wo.math_isUpdating = true;
c.$table.trigger('update');
}
},
mathType : function(table, wo, $cells, priority, dataAttrib) {
if ($cells.length) {
var formula, t, $t, arry, getAll,
eq = ts.equations;
if (priority[0] === 'all') {
// no need to get all cells more than once
getAll = math.getAll(table, wo, dataAttrib);
}
$.each( priority, function(i, type) {
$cells.filter('[' + dataAttrib + '^=' + type + ']').each(function(){
$t = $(this);
formula = ($t.attr(dataAttrib) || '').replace(type + '-', '');
arry = (type === "row") ? math.getRow(table, wo, $t, dataAttrib) :
(type === "all") ? getAll : math.getColumn(table, wo, $t, type, dataAttrib);
if (eq[formula]) {
t = eq[formula](arry);
if (table.config.debug && console && console.log) {
console.log($t.attr(dataAttrib), arry, '=', t);
}
math.output( $t, wo, t, arry );
}
});
});
}
},
output : function($cell, wo, value, arry) {
// get mask from cell data-attribute: data-math-mask="#,##0.00"
var result = ts.formatMask( $cell.attr('data-' + wo.math_data + '-mask') || wo.math_mask, value, wo.math_wrapPrefix, wo.math_wrapSuffix );
if ($.isFunction(wo.math_complete)) {
result = wo.math_complete($cell, wo, result, value, arry);
}
if (result !== false) {
$cell.html(result);
}
}
};
// Modified from https://code.google.com/p/javascript-number-formatter/
/**
* @preserve IntegraXor Web SCADA - JavaScript Number Formatter
* http:// www.integraxor.com/
* author: KPL, KHL
* (c)2011 ecava
* Dual licensed under the MIT or GPL Version 2 licenses.
*/
ts.formatMask = function(m, v, tmpPrefix, tmpSuffix){
if ( !m || isNaN(+v) ) {
return v; // return as it is.
}
var isNegative, result, decimal, group, posLeadZero, posTrailZero, posSeparator, part, szSep,
integer, str, offset, i, l, len, start, tmp, end, inv,
prefix = '',
suffix = '';
// find prefix/suffix
len = m.length;
start = m.search( /[0-9\-\+#]/ );
tmp = start > 0 ? m.substring(0, start) : '';
prefix = tmp;
if ( start > 0 && tmpPrefix ) {
if ( /\{content\}/.test(tmpPrefix || '') ) {
prefix = (tmpPrefix || '').replace(/\{content\}/g, tmp || '');
} else {
prefix = (tmpPrefix || '') + tmp;
}
}
// reverse string: not an ideal method if there are surrogate pairs
inv = m.split('').reverse().join('');
end = inv.search( /[0-9\-\+#]/ );
i = len - end;
i += (m.substring( i, i + 1 ) === '.') ? 1 : 0;
tmp = end > 0 ? m.substring( i, len) : '';
suffix = tmp;
if (tmp !== '' && tmpSuffix) {
if ( /\{content\}/.test(tmpSuffix || '') ) {
suffix = (tmpSuffix || '').replace(/\{content\}/g, tmp || '');
} else {
suffix = tmp + (tmpSuffix || '');
}
}
m = m.substring(start, i);
// convert any string to number according to formation sign.
v = m.charAt(0) == '-' ? -v : +v;
isNegative = v < 0 ? v = -v : 0; // process only abs(), and turn on flag.
// search for separator for grp & decimal, anything not digit, not +/- sign, not #.
result = m.match( /[^\d\-\+#]/g );
decimal = ( result && result[result.length-1] ) || '.'; // treat the right most symbol as decimal
group = ( result && result[1] && result[0] ) || ','; // treat the left most symbol as group separator
// split the decimal for the format string if any.
m = m.split( decimal );
// Fix the decimal first, toFixed will auto fill trailing zero.
v = v.toFixed( m[1] && m[1].length );
v = +(v) + ''; // convert number to string to trim off *all* trailing decimal zero(es)
// fill back any trailing zero according to format
posTrailZero = m[1] && m[1].lastIndexOf('0'); // look for last zero in format
part = v.split('.');
// integer will get !part[1]
if ( !part[1] || ( part[1] && part[1].length <= posTrailZero ) ) {
v = (+v).toFixed( posTrailZero + 1 );
}
szSep = m[0].split( group ); // look for separator
m[0] = szSep.join(''); // join back without separator for counting the pos of any leading 0.
posLeadZero = m[0] && m[0].indexOf('0');
if ( posLeadZero > -1 ) {
while ( part[0].length < ( m[0].length - posLeadZero ) ) {
part[0] = '0' + part[0];
}
} else if ( +part[0] === 0 ) {
part[0] = '';
}
v = v.split('.');
v[0] = part[0];
// process the first group separator from decimal (.) only, the rest ignore.
// get the length of the last slice of split result.
posSeparator = ( szSep[1] && szSep[ szSep.length - 1 ].length );
if ( posSeparator ) {
integer = v[0];
str = '';
offset = integer.length % posSeparator;
l = integer.length;
for ( i = 0; i < l; i++ ) {
str += integer.charAt(i); // ie6 only support charAt for sz.
// -posSeparator so that won't trail separator on full length
/*jshint -W018 */
if ( !( ( i - offset + 1 ) % posSeparator ) && i < l - posSeparator ) {
str += group;
}
}
v[0] = str;
}
v[1] = ( m[1] && v[1] ) ? decimal + v[1] : "";
// put back any negation, combine integer and fraction, and add back prefix & suffix
return prefix + ( ( isNegative ? '-' : '' ) + v[0] + v[1] ) + suffix;
};
ts.equations = {
count : function(arry) {
return arry.length;
},
sum : function(arry) {
var total = 0;
$.each( arry, function(i) {
total += arry[i];
});
return total;
},
mean : function(arry) {
var total = ts.equations.sum( arry );
return total / arry.length;
},
median : function(arry) {
// https://gist.github.com/caseyjustus/1166258
arry.sort( function(a,b){ return a - b; } );
var half = Math.floor( arry.length / 2 );
return (arry.length % 2) ? arry[half] : ( arry[half - 1] + arry[half] ) / 2.0;
},
mode : function(arry) {
// http://stackoverflow.com/a/3451640/145346
if ( arry.length === 0 ) { return 'none'; }
var i, el,
modeMap = {},
maxCount = 1,
modes = [arry[0]];
for (i = 0; i < arry.length; i++) {
el = arry[i];
modeMap[el] = modeMap[el] ? modeMap[el] + 1 : 1;
if ( modeMap[el] > maxCount ) {
modes = [el];
maxCount = modeMap[el];
} else if (modeMap[el] === maxCount) {
modes.push(el);
maxCount = modeMap[el];
}
}
// returns arry of modes if there is a tie
return modes.sort( function(a,b){ return a - b; } );
},
max : function(arry) {
return Math.max.apply( Math, arry );
},
min : function(arry) {
return Math.min.apply( Math, arry );
},
range: function(arry) {
var v = arry.sort(function(a,b){ return a - b; });
return v[ arry.length - 1 ] - v[0];
},
// common variance equation
// (not accessible via data-attribute setting)
variance: function(arry, population) {
var avg = ts.equations.mean( arry ),
v = 0,
i = arry.length;
while (i--) {
v += Math.pow( ( arry[i] - avg ), 2 );
}
v /= ( arry.length - (population ? 0 : 1) );
return v;
},
// variance (population)
varp : function(arry) {
return ts.equations.variance(arry, true);
},
// variance (sample)
vars : function(arry) {
return ts.equations.variance(arry);
},
// standard deviation (sample)
stdevs : function(arry) {
var vars = ts.equations.variance(arry);
return Math.sqrt( vars );
},
// standard deviation (population)
stdevp : function(arry){
var varp = ts.equations.variance(arry, true);
return Math.sqrt( varp );
}
};
// add new widget called repeatHeaders
// ************************************
ts.addWidget({
id: "math",
priority: 100,
options: {
math_data : 'math',
// column index to ignore
math_ignore : [],
// mask info: https://code.google.com/p/javascript-number-formatter/
math_mask : '#,##0.00',
// complete executed after each fucntion
math_complete : null, // function($cell, wo, result, value, arry){ return result; },
// order of calculation; "all" is last
math_priority : [ 'row', 'above', 'col' ],
// template for or just prepend the mask prefix & suffix with this HTML
// e.g. '<span class="red">{content}</span>'
math_prefix : '',
math_suffix : ''
},
init : function(table, thisWidget, c, wo){
c.$table
.bind('tablesorter-initialized update updateRows addRows updateCell filterReset filterEnd '.split(' ').join('.tsmath '), function(e){
var init = e.type === 'tablesorter-initialized';
if (!wo.math_isUpdating || init) {
math.recalculate( table, c, wo, init );
}
})
.bind('updateComplete.tsmath', function(){
setTimeout(function(){
wo.math_isUpdating = false;
}, 500);
});
wo.math_isUpdating = false;
},
// this remove function is called when using the refreshWidgets method or when destroying the tablesorter plugin
// this function only applies to tablesorter v2.4+
remove: function(table, c, wo){
$(table)
.unbind('tablesorter-initialized update updateRows addRows updateCell filterReset filterEnd '.split(' ').join('.tsmath '))
.find('[data-' + wo.math_data + ']').empty();
}
});
})(jQuery);

View File

@@ -0,0 +1,316 @@
/* Output widget (beta) for TableSorter 7/17/2014 (v2.17.5)
* Requires tablesorter v2.8+ and jQuery 1.7+
* Modified from:
* HTML Table to CSV: http://www.kunalbabre.com/projects/table2CSV.php (License unknown?)
* Download-File-JS: https://github.com/PixelsCommander/Download-File-JS (http://www.apache.org/licenses/LICENSE-2.0)
*/
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery: false */
;(function($){
"use strict";
var ts = $.tablesorter,
output = ts.output = {
event : 'outputTable',
// wrap line breaks & tabs in quotes
regexQuote : /([\n\t]|<[^<]+>)/, // test
regexBR : /(<br([\s\/])?>|\n)/g, // replace
regexIMG : /<img[^>]+alt\s*=\s*['"]([^'"]+)['"][^>]*>/i, // match
regexHTML : /<[^<]+>/g, // replace
replaceCR : '\\n',
replaceTab : '\\t',
popupTitle : 'Output',
popupStyle : 'width:100%;height:100%;', // for textarea
message : 'Your device does not support downloading. Please try again in desktop browser.',
init : function(c) {
c.$table
.off(output.event)
.on(output.event, function(){
// explicitly use table.config.widgetOptions because we want
// the most up-to-date values; not the "wo" from initialization
output.process(c, c.widgetOptions);
});
},
processRow: function(c, $rows, isHeader, isJSON) {
var $this, row, col, rowlen, collen, txt,
wo = c.widgetOptions,
tmpRow = [],
dupe = wo.output_duplicateSpans,
addSpanIndex = isHeader && isJSON && wo.output_headerRows && $.isFunction(wo.output_callbackJSON),
cellIndex = 0;
$rows.each(function(rowIndex) {
if (!tmpRow[rowIndex]) { tmpRow[rowIndex] = []; }
cellIndex = 0;
$(this).children().each(function(){
$this = $(this);
// process rowspans
if ($this.filter('[rowspan]').length) {
rowlen = parseInt( $this.attr('rowspan'), 10) - 1;
txt = output.formatData( wo, $this.attr(wo.output_dataAttrib) || $this.html(), isHeader );
for (row = 1; row <= rowlen; row++) {
if (!tmpRow[rowIndex + row]) { tmpRow[rowIndex + row] = []; }
tmpRow[rowIndex + row][cellIndex] = isHeader ? txt : dupe ? txt : '';
}
}
// process colspans
if ($this.filter('[colspan]').length) {
collen = parseInt( $this.attr('colspan'), 10) - 1;
txt = output.formatData( wo, $this.attr(wo.output_dataAttrib) || $this.html(), isHeader );
for (col = 1; col <= collen; col++) {
// if we're processing the header & making JSON, the header names need to be unique
if ($this.filter('[rowspan]').length) {
rowlen = parseInt( $this.attr('rowspan'), 10);
for (row = 0; row < rowlen; row++) {
if (!tmpRow[rowIndex + row]) { tmpRow[rowIndex + row] = []; }
tmpRow[rowIndex + row][cellIndex + col] = addSpanIndex ?
wo.output_callbackJSON($this, txt, cellIndex + col) || txt + '(' + (cellIndex + col) + ')' : isHeader ? txt : dupe ? txt : '';
}
} else {
tmpRow[rowIndex][cellIndex + col] = addSpanIndex ?
wo.output_callbackJSON($this, txt, cellIndex + col) || txt + '(' + (cellIndex + col) + ')' : isHeader ? txt : dupe ? txt : '';
}
}
}
// don't include hidden columns
if ( $this.css('display') !== 'none' ) {
// skip column if already defined
while (typeof tmpRow[rowIndex][cellIndex] !== 'undefined') { cellIndex++; }
tmpRow[rowIndex][cellIndex] = tmpRow[rowIndex][cellIndex] ||
output.formatData( wo, $this.attr(wo.output_dataAttrib) || $this.html(), isHeader );
cellIndex++;
}
});
});
return tmpRow;
},
ignoreColumns : function(wo, data) {
// ignore columns -> remove data from built array (because we've already processed any rowspan/colspan)
$.each( data, function(indx, val){
data[indx] = $.grep(val, function(v, cellIndx){
return $.inArray(cellIndx, wo.output_ignoreColumns) < 0;
});
});
return data;
},
process : function(c, wo) {
var mydata, $this, $rows, headers, csvData, len,
hasStringify = window.JSON && JSON.hasOwnProperty('stringify'),
indx = 0,
tmpData = (wo.output_separator || ',').toLowerCase(),
outputJSON = tmpData === 'json',
outputArray = tmpData === 'array',
separator = outputJSON || outputArray ? ',' : wo.output_separator,
$el = c.$table;
// regex to look for the set separator or HTML
wo.output_regex = new RegExp('(' + (/\\/.test(separator) ? '\\' : '' ) + separator + ')' );
// get header cells
$this = $el.find('thead tr:visible').not('.' + (ts.css.filterRow || 'tablesorter-filter-row') );
headers = output.processRow(c, $this, true, outputJSON);
// all tbody rows
$rows = $el.children('tbody').children('tr');
// get (f)iltered, (v)isible or all rows (look for the first letter only)
$rows = /f/.test(wo.output_saveRows) ? $rows.not('.' + (wo.filter_filteredRow || 'filtered') ) :
/v/.test(wo.output_saveRows) ? $rows.filter(':visible') : $rows;
// process to array of arrays
csvData = output.processRow(c, $rows);
len = headers.length;
if (wo.output_ignoreColumns.length) {
headers = output.ignoreColumns(wo, headers);
csvData = output.ignoreColumns(wo, csvData);
}
if (outputJSON) {
tmpData = [];
$.each( csvData, function(indx, val){
// multiple header rows & output_headerRows = true, pick the last row...
tmpData.push( output.row2Hash( headers[ (len > 1 && wo.output_headerRows) ? indx % len : len - 1], val ) );
});
// requires JSON stringify; if it doesn't exist, the output will show [object Object],... in the output window
mydata = hasStringify ? JSON.stringify(tmpData) : tmpData;
} else {
tmpData = output.row2CSV(wo, wo.output_headerRows ? headers : [ headers[ (len > 1 && wo.output_headerRows) ? indx % len : len - 1] ], outputArray)
.concat( output.row2CSV(wo, csvData, outputArray) );
// stringify the array; if stringify doesn't exist the array will be flattened
mydata = outputArray && hasStringify ? JSON.stringify(tmpData) : tmpData.join('\n');
}
// callback; if true returned, continue processing
if ($.isFunction(wo.output_callback) && !wo.output_callback(c, mydata)) { return; }
if ( /p/.test( (wo.output_delivery || '').toLowerCase() ) ) {
output.popup(mydata, wo.output_popupStyle, outputJSON || outputArray);
} else {
output.download(wo, mydata);
}
}, // end process
row2CSV : function(wo, tmpRow, outputArray) {
var tmp, rowIndex,
csvData = [],
rowLen = tmpRow.length;
for (rowIndex = 0; rowIndex < rowLen; rowIndex++) {
// remove any blank rows
tmp = tmpRow[rowIndex].join('').replace(/\"/g,'');
if (tmpRow[rowIndex].length > 0 && tmp !== '') {
csvData[csvData.length] = outputArray ? tmpRow[rowIndex] : tmpRow[rowIndex].join(wo.output_separator);
}
}
return csvData;
},
row2Hash : function(keys, values) {
var json = {};
$.each(values, function(indx, val) {
if ( indx < keys.length ) {
json[ keys[indx] ] = val;
}
});
return json;
},
formatData : function(wo, input, isHeader) {
var txt,
quotes = (wo.output_separator || ',').toLowerCase(),
separator = quotes === 'json' || quotes === 'array',
// replace " with “ if undefined
result = input.replace(/\"/g, wo.output_replaceQuote || '\u201c');
// replace line breaks with \\n & tabs with \\t
result = result.replace(output.regexBR, output.replaceCR).replace(/\t/g, output.replaceTab);
// extract img alt text
txt = result.match(output.regexIMG);
if (!wo.output_includeHTML && txt !== null) {
result = txt[1];
}
// replace/remove html
result = wo.output_includeHTML && !isHeader ? result : result.replace(output.regexHTML, '');
result = wo.output_trimSpaces || isHeader ? $.trim(result) : result;
// JSON & array outputs don't need quotes
quotes = separator ? false : wo.output_wrapQuotes || wo.output_regex.test(result) || output.regexQuote.test(result);
return quotes ? '"' + result + '"' : result;
},
popup : function(data, style, wrap) {
var generator = window.open('', output.popupTitle, style);
generator.document.write(
'<html><head><title>' + output.popupTitle + '</title></head><body>' +
'<textarea wrap="' + (wrap ? 'on' : 'off') + '" style="' + output.popupStyle + '">' + data + '\n</textarea>' +
'</body></html>'
);
generator.document.close();
generator.focus();
// select all text and focus within the textarea in the popup
// $(generator.document).find('textarea').select().focus();
return true;
},
// modified from https://github.com/PixelsCommander/Download-File-JS
// & http://html5-demos.appspot.com/static/a.download.html
download : function (wo, data){
var e, blob, gotBlob,
nav = window.navigator,
link = document.createElement('a');
// iOS devices do not support downloading. We have to inform user about this.
if (/(iP)/g.test(nav.userAgent)) {
alert(output.message);
return false;
}
// test for blob support
try {
gotBlob = !!new Blob();
} catch (err) {
gotBlob = false;
}
// Use HTML5 Blob if browser supports it
if ( gotBlob ) {
window.URL = window.webkitURL || window.URL;
blob = new Blob([data], {type: wo.output_encoding});
if (nav.msSaveBlob) {
// IE 10+
nav.msSaveBlob(blob, wo.output_saveFileName);
} else {
// all other browsers
link.href = window.URL.createObjectURL(blob);
link.download = wo.output_saveFileName;
// Dispatching click event; using $(link).trigger() won't work
if (document.createEvent) {
e = document.createEvent('MouseEvents');
// event.initMouseEvent(type, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget);
e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
link.dispatchEvent(e);
}
}
return false;
}
// fallback to force file download (whether supported by server).
// not sure if this actually works in IE9 and older...
window.open( wo.output_encoding + encodeURIComponent(data) + '?download' , '_self');
return true;
},
remove : function(c) {
c.$table.off(output.event);
}
};
ts.addWidget({
id: "output",
options: {
output_separator : ',', // set to "json", "array" or any separator
output_ignoreColumns : [], // columns to ignore [0, 1,... ] (zero-based index)
output_dataAttrib : 'data-name', // header attrib containing modified header name
output_headerRows : false, // if true, include multiple header rows (JSON only)
output_delivery : 'popup', // popup, download
output_saveRows : 'filtered', // all, visible or filtered
output_duplicateSpans: true, // duplicate output data in tbody colspan/rowspan
output_replaceQuote : '\u201c;', // left double quote
output_includeHTML : false,
output_trimSpaces : true,
output_wrapQuotes : false,
output_popupStyle : 'width=500,height=300',
output_saveFileName : 'mytable.csv',
// callback executed when processing completes
// return true to continue download/output
// return false to stop delivery & do something else with the data
output_callback : function(config, data){ return true; },
// JSON callback executed when a colspan is encountered in the header
output_callbackJSON : function($cell, txt, cellIndex) { return txt + '(' + (cellIndex) + ')'; },
// the need to modify this for Excel no longer exists
output_encoding : 'data:application/octet-stream;charset=utf8,'
},
init: function(table, thisWidget, c) {
output.init(c);
},
remove: function(table, c){
output.remove(c);
}
});
})(jQuery);

View File

@@ -0,0 +1,975 @@
/* Pager widget for TableSorter 9/15/2014 (v2.17.8) */
/*jshint browser:true, jquery:true, unused:false */
;(function($){
"use strict";
var tsp,
ts = $.tablesorter;
ts.addWidget({
id: "pager",
priority: 55, // load pager after filter widget
options : {
// output default: '{page}/{totalPages}'
// possible variables: {page}, {totalPages}, {filteredPages}, {startRow}, {endRow}, {filteredRows} and {totalRows}
pager_output: '{startRow} to {endRow} of {totalRows} rows', // '{page}/{totalPages}'
// apply disabled classname to the pager arrows when the rows at either extreme is visible
pager_updateArrows: true,
// starting page of the pager (zero based index)
pager_startPage: 0,
// reset pager after filtering; set to desired page #
// set to false to not change page at filter start
pager_pageReset: 0,
// Number of visible rows
pager_size: 10,
// Save pager page & size if the storage script is loaded (requires $.tablesorter.storage in jquery.tablesorter.widgets.js)
pager_savePages: true,
//defines custom storage key
pager_storageKey: 'tablesorter-pager',
// 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
pager_fixedHeight: false,
// count child rows towards the set page size? (set true if it is a visible table row within the pager)
// if true, child row(s) may not appear to be attached to its parent row, may be split across pages or
// may distort the table if rowspan or cellspans are included.
pager_countChildRows: 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.
pager_removeRows: false, // removing rows in larger tables speeds up the sort
// use this format: "http://mydatabase.com?page={page}&size={size}&{sortList:col}&{filterList:fcol}"
// where {page} is replaced by the page number, {size} is replaced by the number of records to show,
// {sortList:col} adds the sortList to the url into a "col" array, and {filterList:fcol} adds
// the filterList to the url into an "fcol" array.
// So a sortList = [[2,0],[3,0]] becomes "&col[2]=0&col[3]=0" in the url
// and a filterList = [[2,Blue],[3,13]] becomes "&fcol[2]=Blue&fcol[3]=13" in the url
pager_ajaxUrl: null,
// modify the url after all processing has been applied
pager_customAjaxUrl: function(table, url) { return url; },
// modify the $.ajax object to allow complete control over your ajax requests
pager_ajaxObject: {
dataType: 'json'
},
// set this to false if you want to block ajax loading on init
pager_processAjaxOnInit: true,
// process ajax so that the following information is returned:
// [ total_rows (number), rows (array of arrays), headers (array; optional) ]
// example:
// [
// 100, // total rows
// [
// [ "row1cell1", "row1cell2", ... "row1cellN" ],
// [ "row2cell1", "row2cell2", ... "row2cellN" ],
// ...
// [ "rowNcell1", "rowNcell2", ... "rowNcellN" ]
// ],
// [ "header1", "header2", ... "headerN" ] // optional
// ]
pager_ajaxProcessing: function(ajax){ return [ 0, [], null ]; },
// css class names of pager arrows
pager_css: {
container : 'tablesorter-pager',
errorRow : 'tablesorter-errorRow', // error information row (don't include period at beginning)
disabled : 'disabled' // class added to arrows @ extremes (i.e. prev/first arrows "disabled" on first page)
},
// jQuery selectors
pager_selectors: {
container : '.pager', // target the pager markup
first : '.first', // go to first page arrow
prev : '.prev', // previous page arrow
next : '.next', // next page arrow
last : '.last', // go to last page arrow
gotoPage : '.gotoPage', // go to page selector - select dropdown that sets the current page
pageDisplay : '.pagedisplay', // location of where the "output" is displayed
pageSize : '.pagesize' // page size selector - select dropdown that sets the "size" option
}
},
init: function(table){
tsp.init(table);
},
// only update to complete sorter initialization
format: function(table, c){
if (!(c.pager && c.pager.initialized)){
return tsp.initComplete(table, c);
}
tsp.moveToPage(table, c.pager, false);
},
remove: function(table, c){
tsp.destroyPager(table, c);
}
});
/* pager widget functions */
tsp = ts.pager = {
init: function(table) {
// check if tablesorter has initialized
if (table.hasInitialized && table.config.pager.initialized) { return; }
var t,
c = table.config,
wo = c.widgetOptions,
s = wo.pager_selectors,
// save pager variables
p = c.pager = $.extend({
totalPages: 0,
filteredRows: 0,
filteredPages: 0,
currentFilters: [],
page: wo.pager_startPage,
startRow: 0,
endRow: 0,
ajaxCounter: 0,
$size: null,
last: {}
}, c.pager);
// pager initializes multiple times before table has completed initialization
if (p.isInitializing) { return; }
p.isInitializing = true;
if (c.debug) {
ts.log('Pager initializing');
}
p.size = $.data(table, 'pagerLastSize') || wo.pager_size;
// added in case the pager is reinitialized after being destroyed.
p.$container = $(s.container).addClass(wo.pager_css.container).show();
// goto selector
p.$goto = p.$container.find(s.gotoPage); // goto is a reserved word #657
// page size selector
p.$size = p.$container.find(s.pageSize);
p.totalRows = c.$tbodies.eq(0).children('tr').not( c.widgetOptions.pager_countChildRows ? '' : '.' + c.cssChildRow ).length;
p.oldAjaxSuccess = p.oldAjaxSuccess || wo.pager_ajaxObject.success;
c.appender = tsp.appender;
if (ts.filter && $.inArray('filter', c.widgets) >= 0) {
// get any default filter settings (data-value attribute) fixes #388
p.currentFilters = c.$table.data('lastSearch') || ts.filter.setDefaults(table, c, wo) || [];
// set, but don't apply current filters
ts.setFilters(table, p.currentFilters, false);
}
if (wo.pager_savePages && ts.storage) {
t = ts.storage(table, wo.pager_storageKey) || {}; // fixes #387
p.page = isNaN(t.page) ? p.page : t.page;
p.size = ( isNaN(t.size) ? p.size : t.size ) || 10;
$.data(table, 'pagerLastSize', p.size);
}
// skipped rows
p.regexRows = new RegExp('(' + (wo.filter_filteredRow || 'filtered') + '|' + c.selectorRemove.replace(/^(\w+\.)/g,'') + '|' + c.cssChildRow + ')');
// clear initialized flag
p.initialized = false;
// before initialization event
c.$table.trigger('pagerBeforeInitialized', c);
tsp.enablePager(table, c, false);
if ( typeof(wo.pager_ajaxUrl) === 'string' ) {
// ajax pager; interact with database
p.ajax = true;
// When filtering with ajax, allow only custom filtering function, disable default filtering since it will be done server side.
wo.filter_serversideFiltering = true;
c.serverSideSorting = true;
tsp.moveToPage(table, p);
} else {
p.ajax = false;
// Regular pager; all rows stored in memory
c.$table.trigger("appendCache", [{}, true]);
tsp.hideRowsSetup(table, c);
}
},
initComplete: function(table, c){
var p = c.pager;
tsp.changeHeight(table, c);
tsp.bindEvents(table, c);
tsp.setPageSize(table, 0, c); // page size 0 is ignored
// pager initialized
p.initialized = true;
p.isInitializing = false;
c.$table.trigger('pagerInitialized', c);
tsp.updatePageDisplay(table, c);
},
bindEvents: function(table, c){
var ctrls, fxn,
p = c.pager,
wo = c.widgetOptions,
s = wo.pager_selectors;
c.$table
.unbind('filterStart filterEnd sortEnd disable enable destroy updateComplete pageSize '.split(' ').join('.pager '))
.bind('filterStart.pager', function(e, filters) {
p.currentFilters = filters;
// don't change page is filters are the same (pager updating, etc)
if (wo.pager_pageReset !== false && (c.lastCombinedFilter || '') !== (filters || []).join('')) {
p.page = wo.pager_pageReset; // fixes #456 & #565
}
})
// update pager after filter widget completes
.bind('filterEnd.pager sortEnd.pager', function() {
if (p.initialized) {
if (c.delayInit && c.rowsCopy && c.rowsCopy.length === 0) {
// make sure we have a copy of all table rows once the cache has been built
tsp.updateCache(table);
}
// update page display first, so we update p.filteredPages
tsp.updatePageDisplay(table, c, false);
// tsp.moveToPage(table, p, false); <-- called when applyWidgets is triggered
c.pager.last.page = -1;
c.$table.trigger('applyWidgets');
tsp.fixHeight(table, c);
}
})
.bind('disable.pager', function(e){
e.stopPropagation();
tsp.showAllRows(table, c);
})
.on('enable.pager', function(e){
e.stopPropagation();
tsp.enablePager(table, c, true);
})
.on('destroy.pager', function(e){
e.stopPropagation();
tsp.destroyPager(table, c);
})
.on('updateComplete.pager', function(e, table, triggered){
e.stopPropagation();
// table can be unintentionally undefined in tablesorter v2.17.7 and earlier
if (!table || triggered) { return; }
tsp.fixHeight(table, c);
var $rows = c.$tbodies.eq(0).children('tr').not(c.selectorRemove);
p.totalRows = $rows.length - ( c.widgetOptions.pager_countChildRows ? 0 : $rows.filter('.' + c.cssChildRow).length );
p.totalPages = Math.ceil( p.totalRows / p.size );
if ($rows.length && c.rowsCopy && c.rowsCopy.length === 0) {
// make a copy of all table rows once the cache has been built
tsp.updateCache(table);
}
tsp.updatePageDisplay(table, c);
tsp.hideRows(table, c);
// make sure widgets are applied - fixes #450
c.$table.trigger('applyWidgets');
})
.on('pageSize.pager', function(e,v){
e.stopPropagation();
tsp.setPageSize(table, parseInt(v, 10) || 10, c);
tsp.hideRows(table, c);
tsp.updatePageDisplay(table, c, false);
if (p.$size.length) { p.$size.val(p.size); } // twice?
})
.on('pageSet.pager', function(e,v){
e.stopPropagation();
p.page = (parseInt(v, 10) || 1) - 1;
if (p.$goto.length) { p.$goto.val(c.size); } // twice?
tsp.moveToPage(table, p, true);
tsp.updatePageDisplay(table, c, false);
});
// clicked controls
ctrls = [ s.first, s.prev, s.next, s.last ];
fxn = [ 'moveToFirstPage', 'moveToPrevPage', 'moveToNextPage', 'moveToLastPage' ];
p.$container.find(ctrls.join(','))
.attr("tabindex", 0)
.unbind('click.pager')
.bind('click.pager', function(e){
e.stopPropagation();
var i,
$c = $(this),
l = ctrls.length;
if ( !$c.hasClass(wo.pager_css.disabled) ) {
for (i = 0; i < l; i++) {
if ($c.is(ctrls[i])) {
tsp[fxn[i]](table, p);
break;
}
}
}
});
if ( p.$goto.length ) {
p.$goto
.unbind('change')
.bind('change', function(){
p.page = $(this).val() - 1;
tsp.moveToPage(table, p, true);
tsp.updatePageDisplay(table, c, false);
});
}
if ( p.$size.length ) {
// setting an option as selected appears to cause issues with initial page size
p.$size.find('option').removeAttr('selected');
p.$size
.unbind('change.pager')
.bind('change.pager', function() {
p.$size.val( $(this).val() ); // in case there are more than one pagers
if ( !$(this).hasClass(wo.pager_css.disabled) ) {
tsp.setPageSize(table, parseInt( $(this).val(), 10 ), c);
tsp.changeHeight(table, c);
}
return false;
});
}
},
// hide arrows at extremes
pagerArrows: function(c, disable) {
var p = c.pager,
dis = !!disable,
first = dis || p.page === 0,
tp = Math.min( p.totalPages, p.filteredPages ),
last = dis || p.page === tp - 1 || tp === 0,
wo = c.widgetOptions,
s = wo.pager_selectors;
if ( wo.pager_updateArrows ) {
p.$container.find(s.first + ',' + s.prev).toggleClass(wo.pager_css.disabled, first).attr('aria-disabled', first);
p.$container.find(s.next + ',' + s.last).toggleClass(wo.pager_css.disabled, last).attr('aria-disabled', last);
}
},
updatePageDisplay: function(table, c, completed) {
var i, pg, s, $out, regex,
wo = c.widgetOptions,
p = c.pager,
f = c.$table.hasClass('hasFilters'),
t = [],
sz = p.size || 10; // don't allow dividing by zero
t = [ wo && wo.filter_filteredRow || 'filtered', c.selectorRemove.replace(/^(\w+\.)/g,'') ];
if (wo.pager_countChildRows) { t.push(c.cssChildRow); }
regex = new RegExp( '(' + t.join('|') + ')' );
p.$size.add(p.$goto).removeClass(wo.pager_css.disabled).removeAttr('disabled').attr('aria-disabled', 'false');
if (f && !wo.pager_ajaxUrl) {
if ($.isEmptyObject(c.cache)) {
// delayInit: true so nothing is in the cache
p.filteredRows = p.totalRows = c.$tbodies.eq(0).children('tr').not( c.widgetOptions.pager_countChildRows ? '' : '.' + c.cssChildRow ).length;
} else {
p.filteredRows = 0;
$.each(c.cache[0].normalized, function(i, el) {
p.filteredRows += p.regexRows.test(el[c.columns].$row[0].className) ? 0 : 1;
});
}
} else if (!f) {
p.filteredRows = p.totalRows;
}
p.totalPages = Math.ceil( p.totalRows / sz ); // needed for "pageSize" method
c.totalRows = p.totalRows;
c.filteredRows = p.filteredRows;
p.filteredPages = Math.ceil( p.filteredRows / sz ) || 0;
if ( Math.min( p.totalPages, p.filteredPages ) >= 0 ) {
t = (p.size * p.page > p.filteredRows);
p.startRow = (t) ? 1 : (p.filteredRows === 0 ? 0 : p.size * p.page + 1);
p.page = (t) ? 0 : p.page;
p.endRow = Math.min( p.filteredRows, p.totalRows, p.size * ( p.page + 1 ) );
$out = p.$container.find(wo.pager_selectors.pageDisplay);
// form the output string (can now get a new output string from the server)
s = ( p.ajaxData && p.ajaxData.output ? p.ajaxData.output || wo.pager_output : wo.pager_output )
// {page} = one-based index; {page+#} = zero based index +/- value
.replace(/\{page([\-+]\d+)?\}/gi, function(m,n){
return p.totalPages ? p.page + (n ? parseInt(n, 10) : 1) : 0;
})
// {totalPages}, {extra}, {extra:0} (array) or {extra : key} (object)
.replace(/\{\w+(\s*:\s*\w+)?\}/gi, function(m){
var len, indx,
str = m.replace(/[{}\s]/g,''),
extra = str.split(':'),
data = p.ajaxData,
// return zero for default page/row numbers
deflt = /(rows?|pages?)$/i.test(str) ? 0 : '';
if (/(startRow|page)/.test(extra[0]) && extra[1] === 'input') {
len = ('' + (extra[0] === 'page' ? p.totalPages : p.totalRows)).length;
indx = extra[0] === 'page' ? p.page + 1 : p.startRow;
return '<input type="text" class="ts-' + extra[0] + '" style="max-width:' + len + 'em" value="' + indx + '"/>';
}
return extra.length > 1 && data && data[extra[0]] ? data[extra[0]][extra[1]] : p[str] || (data ? data[str] : deflt) || deflt;
});
if ($out.length) {
$out[ ($out[0].tagName === 'INPUT') ? 'val' : 'html' ](s);
if ( p.$goto.length ) {
t = '';
pg = Math.min( p.totalPages, p.filteredPages );
for ( i = 1; i <= pg; i++ ) {
t += '<option>' + i + '</option>';
}
p.$goto[0].innerHTML = t;
p.$goto[0].value = p.page + 1;
}
// rebind startRow/page inputs
$out.find('.ts-startRow, .ts-page').unbind('change').bind('change', function(){
var v = $(this).val(),
pg = $(this).hasClass('ts-startRow') ? Math.floor( v/p.size ) + 1 : v;
c.$table.trigger('pageSet.pager', [ pg ]);
});
}
}
tsp.pagerArrows(c);
if (p.initialized && completed !== false) {
c.$table.trigger('pagerComplete', c);
// save pager info to storage
if (wo.pager_savePages && ts.storage) {
ts.storage(table, wo.pager_storageKey, {
page : p.page,
size : p.size
});
}
}
},
fixHeight: function(table, c) {
var d, h,
p = c.pager,
wo = c.widgetOptions,
$b = c.$tbodies.eq(0);
if (wo.pager_fixedHeight) {
$b.find('tr.pagerSavedHeightSpacer').remove();
h = $.data(table, 'pagerSavedHeight');
if (h) {
d = h - $b.height();
if ( d > 5 && $.data(table, 'pagerLastSize') === p.size && $b.children('tr:visible').length < p.size ) {
$b.append('<tr class="pagerSavedHeightSpacer ' + wo.pager_selectors.remove.replace(/^(\w+\.)/g,'') + '" style="height:' + d + 'px;"></tr>');
}
}
}
},
changeHeight: function(table, c) {
var $b = c.$tbodies.eq(0);
$b.find('tr.pagerSavedHeightSpacer').remove();
$.data(table, 'pagerSavedHeight', $b.height());
tsp.fixHeight(table, c);
$.data(table, 'pagerLastSize', c.pager.size);
},
hideRows: function(table, c){
if (!c.widgetOptions.pager_ajaxUrl) {
var i,
lastIndex = 0,
p = c.pager,
wo = c.widgetOptions,
rows = c.$tbodies.eq(0).children('tr'),
l = rows.length,
s = ( p.page * p.size ),
e = s + p.size,
f = wo && wo.filter_filteredRow || 'filtered',
j = 0; // size counter
for ( i = 0; i < l; i++ ){
if ( !rows[i].className.match(f) ) {
if (j === s && rows[i].className.match(c.cssChildRow)) {
// hide child rows @ start of pager (if already visible)
rows[i].style.display = 'none';
} else {
rows[i].style.display = ( j >= s && j < e ) ? '' : 'none';
// don't count child rows
j += rows[i].className.match(c.cssChildRow + '|' + c.selectorRemove.replace(/^(\w+\.)/g,'')) && !wo.pager_countChildRows ? 0 : 1;
if ( j === e && rows[i].style.display !== 'none' && rows[i].className.match(ts.css.cssHasChild) ) {
lastIndex = i;
}
}
}
}
// add any attached child rows to last row of pager. Fixes part of issue #396
if ( lastIndex > 0 && rows[lastIndex].className.match(ts.css.cssHasChild) ) {
while ( ++lastIndex < l && rows[lastIndex].className.match(c.cssChildRow) ) {
rows[lastIndex].style.display = '';
}
}
}
},
hideRowsSetup: function(table, c){
var p = c.pager;
p.size = parseInt( p.$size.val(), 10 ) || p.size;
$.data(table, 'pagerLastSize', p.size);
tsp.pagerArrows(c);
if ( !c.widgetOptions.pager_removeRows ) {
tsp.hideRows(table, c);
c.$table.on('sortEnd.pager filterEnd.pager', function(){
tsp.hideRows(table, c);
});
}
},
renderAjax: function(data, table, c, xhr, exception){
var p = c.pager,
wo = c.widgetOptions;
// process data
if ( $.isFunction(wo.pager_ajaxProcessing) ) {
// ajaxProcessing result: [ total, rows, headers ]
var i, j, t, hsh, $f, $sh, th, d, l, rr_count,
$t = c.$table,
tds = '',
result = wo.pager_ajaxProcessing(data, table, xhr) || [ 0, [] ],
hl = $t.find('thead th').length;
// Clean up any previous error.
ts.showError(table);
if ( exception ) {
if (c.debug) {
ts.log('Ajax Error', xhr, exception);
}
ts.showError(table, exception.message + ' (' + xhr.status + ')');
c.$tbodies.eq(0).children('tr').detach();
p.totalRows = 0;
} else {
// process ajax object
if (!$.isArray(result)) {
p.ajaxData = result;
c.totalRows = p.totalRows = result.total;
c.filteredRows = p.filteredRows = typeof result.filteredRows !== 'undefined' ? result.filteredRows : result.total;
th = result.headers;
d = result.rows;
} else {
// allow [ total, rows, headers ] or [ rows, total, headers ]
t = isNaN(result[0]) && !isNaN(result[1]);
// ensure a zero returned row count doesn't fail the logical ||
rr_count = result[t ? 1 : 0];
p.totalRows = isNaN(rr_count) ? p.totalRows || 0 : rr_count;
// can't set filtered rows when returning an array
c.totalRows = c.filteredRows = p.filteredRows = p.totalRows;
d = p.totalRows === 0 ? [""] : result[t ? 0 : 1] || []; // row data
th = result[2]; // headers
}
l = d && d.length;
if (d instanceof jQuery) {
if (wo.pager_processAjaxOnInit) {
// append jQuery object
c.$tbodies.eq(0).children('tr').detach();
c.$tbodies.eq(0).append(d);
}
} else if (l) {
// build table from array
for ( i = 0; i < l; i++ ) {
tds += '<tr>';
for ( j = 0; j < d[i].length; j++ ) {
// build tbody cells; watch for data containing HTML markup - see #434
tds += /^\s*<td/.test(d[i][j]) ? $.trim(d[i][j]) : '<td>' + d[i][j] + '</td>';
}
tds += '</tr>';
}
// add rows to first tbody
if (wo.pager_processAjaxOnInit) {
c.$tbodies.eq(0).html( tds );
}
}
wo.pager_processAjaxOnInit = true;
// only add new header text if the length matches
if ( th && th.length === hl ) {
hsh = $t.hasClass('hasStickyHeaders');
$sh = hsh ? wo.$sticky.children('thead:first').children('tr').children() : '';
$f = $t.find('tfoot tr:first').children();
// don't change td headers (may contain pager)
c.$headers.filter('th').each(function(j){
var $t = $(this), icn;
// add new test within the first span it finds, or just in the header
if ( $t.find('.' + ts.css.icon).length ) {
icn = $t.find('.' + ts.css.icon).clone(true);
$t.find('.tablesorter-header-inner').html( th[j] ).append(icn);
if ( hsh && $sh.length ) {
icn = $sh.eq(j).find('.' + ts.css.icon).clone(true);
$sh.eq(j).find('.tablesorter-header-inner').html( th[j] ).append(icn);
}
} else {
$t.find('.tablesorter-header-inner').html( th[j] );
if (hsh && $sh.length) {
$sh.eq(j).find('.tablesorter-header-inner').html( th[j] );
}
}
$f.eq(j).html( th[j] );
});
}
}
if (c.showProcessing) {
ts.isProcessing(table); // remove loading icon
}
// make sure last pager settings are saved, prevents multiple server side calls with
// the same parameters
p.totalPages = Math.ceil( p.totalRows / ( p.size || 10 ) );
p.last.totalRows = p.totalRows;
p.last.currentFilters = p.currentFilters;
p.last.sortList = (c.sortList || []).join(',');
tsp.updatePageDisplay(table, c);
tsp.fixHeight(table, c);
$t.trigger('updateCache', [function(){
if (p.initialized) {
// apply widgets after table has rendered & after a delay to prevent
// multiple applyWidget blocking code from blocking this trigger
setTimeout(function(){
$t
.trigger('applyWidgets')
.trigger('pagerChange', p);
}, 0);
}
}]);
}
if (!p.initialized) {
c.$table.trigger('applyWidgets');
}
},
getAjax: function(table, c){
var counter,
url = tsp.getAjaxUrl(table, c),
$doc = $(document),
wo = c.widgetOptions,
p = c.pager;
if ( url !== '' ) {
if (c.showProcessing) {
ts.isProcessing(table, true); // show loading icon
}
$doc.on('ajaxError.pager', function(e, xhr, settings, exception) {
tsp.renderAjax(null, table, c, xhr, exception);
$doc.unbind('ajaxError.pager');
});
counter = ++p.ajaxCounter;
wo.pager_ajaxObject.url = url; // from the ajaxUrl option and modified by customAjaxUrl
wo.pager_ajaxObject.success = function(data, status, jqxhr) {
// Refuse to process old ajax commands that were overwritten by new ones - see #443
if (counter < p.ajaxCounter){
return;
}
tsp.renderAjax(data, table, c, jqxhr);
$doc.unbind('ajaxError.pager');
if (typeof p.oldAjaxSuccess === 'function') {
p.oldAjaxSuccess(data);
}
};
if (c.debug) {
ts.log('ajax initialized', wo.pager_ajaxObject);
}
$.ajax(wo.pager_ajaxObject);
}
},
getAjaxUrl: function(table, c) {
var p = c.pager,
wo = c.widgetOptions,
url = (wo.pager_ajaxUrl) ? wo.pager_ajaxUrl
// allow using "{page+1}" in the url string to switch to a non-zero based index
.replace(/\{page([\-+]\d+)?\}/, function(s,n){ return p.page + (n ? parseInt(n, 10) : 0); })
.replace(/\{size\}/g, p.size) : '',
sl = c.sortList,
fl = p.currentFilters || $(table).data('lastSearch') || [],
sortCol = url.match(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/),
filterCol = url.match(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/),
arry = [];
if (sortCol) {
sortCol = sortCol[1];
$.each(sl, function(i,v){
arry.push(sortCol + '[' + v[0] + ']=' + v[1]);
});
// if the arry is empty, just add the col parameter... "&{sortList:col}" becomes "&col"
url = url.replace(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : sortCol );
arry = [];
}
if (filterCol) {
filterCol = filterCol[1];
$.each(fl, function(i,v){
if (v) {
arry.push(filterCol + '[' + i + ']=' + encodeURIComponent(v));
}
});
// if the arry is empty, just add the fcol parameter... "&{filterList:fcol}" becomes "&fcol"
url = url.replace(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : filterCol );
p.currentFilters = fl;
}
if ( $.isFunction(wo.pager_customAjaxUrl) ) {
url = wo.pager_customAjaxUrl(table, url);
}
if (c.debug) {
ts.log('Pager ajax url: ' + url);
}
return url;
},
renderTable: function(table, rows) {
var $tb, index, count, added,
c = table.config,
p = c.pager,
wo = c.widgetOptions,
f = c.$table.hasClass('hasFilters'),
l = rows && rows.length || 0, // rows may be undefined
s = ( p.page * p.size ),
e = p.size;
if ( l < 1 ) {
if (c.debug) {
ts.log('Pager: no rows for pager to render');
}
// empty table, abort!
return;
}
if ( p.page >= p.totalPages ) {
// lets not render the table more than once
return tsp.moveToLastPage(table, p);
}
p.isDisabled = false; // needed because sorting will change the page and re-enable the pager
if (p.initialized) { c.$table.trigger('pagerChange', c); }
if ( !wo.pager_removeRows ) {
tsp.hideRows(table, c);
} else {
ts.clearTableBody(table);
$tb = ts.processTbody(table, c.$tbodies.eq(0), true);
// not filtered, start from the calculated starting point (s)
// if filtered, start from zero
index = f ? 0 : s;
count = f ? 0 : s;
added = 0;
while (added < e && index < rows.length) {
if (!f || !/filtered/.test(rows[index][0].className)){
count++;
if (count > s && added <= e) {
added++;
$tb.append(rows[index]);
}
}
index++;
}
ts.processTbody(table, $tb, false);
}
tsp.updatePageDisplay(table, c);
if ( !p.isDisabled ) { tsp.fixHeight(table, c); }
wo.pager_startPage = p.page;
wo.pager_size = p.size;
if (table.isUpdating) {
c.$table.trigger('updateComplete', [ table, true ]);
}
},
showAllRows: function(table, c){
var p = c.pager,
wo = c.widgetOptions;
if ( p.ajax ) {
tsp.pagerArrows(c, true);
} else {
p.isDisabled = true;
$.data(table, 'pagerLastPage', p.page);
$.data(table, 'pagerLastSize', p.size);
p.page = 0;
p.size = p.totalRows;
p.totalPages = 1;
c.$table
.addClass('pagerDisabled')
.removeAttr('aria-describedby')
.find('tr.pagerSavedHeightSpacer').remove();
tsp.renderTable(table, c.rowsCopy);
c.$table.trigger('applyWidgets');
if (c.debug) {
ts.log('pager disabled');
}
}
// disable size selector
p.$size.add(p.$goto).each(function(){
$(this).attr('aria-disabled', 'true').addClass(wo.pager_css.disabled)[0].disabled = true;
});
},
// updateCache if delayInit: true
// this is normally done by "appendToTable" function in the tablesorter core AFTER a sort
updateCache: function(table) {
var c = table.config,
p = c.pager;
c.$table.trigger('updateCache', [ function(){
if ( !$.isEmptyObject(table.config.cache) ) {
var i,
rows = [],
n = table.config.cache[0].normalized;
p.totalRows = n.length;
for (i = 0; i < p.totalRows; i++) {
rows.push(n[i][c.columns].$row);
}
c.rowsCopy = rows;
tsp.moveToPage(table, p, true);
// clear out last search to force an update
p.last.currentFilters = [' '];
}
} ]);
},
moveToPage: function(table, p, pageMoved) {
if ( p.isDisabled ) { return; }
if ( pageMoved !== false && p.initialized && $.isEmptyObject(table.config.cache)) {
return tsp.updateCache(table);
}
var c = table.config,
l = p.last,
pg = Math.min( p.totalPages, p.filteredPages );
if ( p.page < 0 ) { p.page = 0; }
if ( p.page > ( pg - 1 ) && pg !== 0 ) { p.page = pg - 1; }
// fixes issue where one current filter is [] and the other is ['','',''],
// making the next if comparison think the filters as different. Fixes #202.
l.currentFilters = (l.currentFilters || []).join('') === '' ? [] : l.currentFilters;
p.currentFilters = (p.currentFilters || []).join('') === '' ? [] : p.currentFilters;
// don't allow rendering multiple times on the same page/size/totalRows/filters/sorts
if ( l.page === p.page && l.size === p.size && l.totalRows === p.totalRows &&
(l.currentFilters || []).join(',') === (p.currentFilters || []).join(',') &&
l.sortList === (c.sortList || []).join(',') ) {
return;
}
if (c.debug) {
ts.log('Pager changing to page ' + p.page);
}
p.last = {
page : p.page,
size : p.size,
// fixes #408; modify sortList otherwise it auto-updates
sortList : (c.sortList || []).join(','),
totalRows : p.totalRows,
currentFilters : p.currentFilters || []
};
if (p.ajax) {
tsp.getAjax(table, c);
} else if (!p.ajax) {
tsp.renderTable(table, c.rowsCopy);
}
$.data(table, 'pagerLastPage', p.page);
if (p.initialized && pageMoved !== false) {
c.$table
.trigger('pageMoved', c)
.trigger('applyWidgets');
if (!p.ajax && table.isUpdating) {
c.$table.trigger('updateComplete', [ table, true ]);
}
}
},
setPageSize: function(table, size, c) {
var p = c.pager;
p.size = size || p.size || 10;
p.$size.val(p.size);
$.data(table, 'pagerLastPage', p.page);
$.data(table, 'pagerLastSize', p.size);
p.totalPages = Math.ceil( p.totalRows / p.size );
p.filteredPages = Math.ceil( p.filteredRows / p.size );
tsp.moveToPage(table, p, true);
},
moveToFirstPage: function(table, p) {
p.page = 0;
tsp.moveToPage(table, p, true);
},
moveToLastPage: function(table, p) {
p.page = ( Math.min( p.totalPages, p.filteredPages ) - 1 );
tsp.moveToPage(table, p, true);
},
moveToNextPage: function(table, p) {
p.page++;
if ( p.page >= ( Math.min( p.totalPages, p.filteredPages ) - 1 ) ) {
p.page = ( Math.min( p.totalPages, p.filteredPages ) - 1 );
}
tsp.moveToPage(table, p, true);
},
moveToPrevPage: function(table, p) {
p.page--;
if ( p.page <= 0 ) {
p.page = 0;
}
tsp.moveToPage(table, p, true);
},
destroyPager: function(table, c){
var p = c.pager;
tsp.showAllRows(table, c);
p.$container.hide(); // hide pager
c.appender = null; // remove pager appender function
p.initialized = false;
delete table.config.rowsCopy;
c.$table.unbind('destroy.pager sortEnd.pager filterEnd.pager enable.pager disable.pager');
if (ts.storage) {
ts.storage(table, c.widgetOptions.pager_storageKey, '');
}
},
enablePager: function(table, c, triggered){
var info, p = c.pager;
p.isDisabled = false;
p.page = $.data(table, 'pagerLastPage') || p.page || 0;
p.size = $.data(table, 'pagerLastSize') || parseInt(p.$size.find('option[selected]').val(), 10) || p.size || 10;
p.$size.val(p.size); // set page size
p.totalPages = Math.ceil( Math.min( p.totalRows, p.filteredRows ) / p.size );
c.$table.removeClass('pagerDisabled');
// if table id exists, include page display with aria info
if ( table.id ) {
info = table.id + '_pager_info';
p.$container.find(c.widgetOptions.pager_selectors.pageDisplay).attr('id', info);
c.$table.attr('aria-describedby', info);
}
if ( triggered ) {
c.$table.trigger('updateRows');
tsp.setPageSize(table, p.size, c);
tsp.hideRowsSetup(table, c);
tsp.fixHeight(table, c);
if (c.debug) {
ts.log('pager enabled');
}
}
},
appender: function(table, rows) {
var c = table.config,
wo = c.widgetOptions,
p = c.pager;
if ( !p.ajax ) {
c.rowsCopy = rows;
p.totalRows = c.widgetOptions.pager_countChildRows ? c.$tbodies.eq(0).children('tr').length : rows.length;
p.size = $.data(table, 'pagerLastSize') || p.size || wo.pager_size || 10;
p.totalPages = Math.ceil( p.totalRows / p.size );
tsp.moveToPage(table, p);
// update display here in case all rows are removed
tsp.updatePageDisplay(table, c, false);
} else {
tsp.moveToPage(table, p, true);
}
}
};
// see #486
ts.showError = function(table, message){
$(table).each(function(){
var $row,
c = this.config,
errorRow = c.pager && c.pager.cssErrorRow || c.widgetOptions.pager_css && c.widgetOptions.pager_css.errorRow || 'tablesorter-errorRow';
if (c) {
if (typeof message === 'undefined') {
c.$table.find('thead').find(c.selectorRemove).remove();
} else {
$row = ( /tr\>/.test(message) ? $(message) : $('<tr><td colspan="' + c.columns + '">' + message + '</td></tr>') )
.click(function(){
$(this).remove();
})
// add error row to thead instead of tbody, or clicking on the header will result in a parser error
.appendTo( c.$table.find('thead:first') )
.addClass( errorRow + ' ' + c.selectorRemove.replace(/^(\w+\.)/g,'') )
.attr({
role : 'alert',
'aria-live' : 'assertive'
});
}
}
});
};
})(jQuery);

View File

@@ -0,0 +1,123 @@
/* Print widget (beta) for TableSorter 6/18/2014 (v2.17.2)
* Requires tablesorter v2.8+ and jQuery 1.2.6+
*/
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery: false */
;(function($){
"use strict";
var ts = $.tablesorter,
printTable = ts.printTable = {
event : 'printTable',
basicStyle : 'table, tr, td, th { border : solid 1px black; border-collapse : collapse; } td, th { padding: 2px; }',
init : function(c) {
c.$table
.unbind(printTable.event)
.bind(printTable.event, function(){
// explicitly use table.config.widgetOptions because we want
// the most up-to-date values; not the "wo" from initialization
printTable.process(c, c.widgetOptions);
});
},
process : function(c, wo) {
var $this,
$table = $('<div/>').append(c.$table.clone()),
printStyle = printTable.basicStyle + 'table { width: 100% }' +
// hide filter row
'.tablesorter-filter-row { display: none }' +
// hide sort arrows
'.tablesorter-header { background-image: none !important; }';
// replace content with data-attribute content
$table.find('[' + wo.print_dataAttrib + ']').each(function(){
$this = $(this);
$this.text( $this.attr(wo.print_dataAttrib) );
});
// === rows ===
// Assume "visible" means rows hidden by the pager (rows set to "display:none")
// or hidden by a class name which is added to the wo.print_extraCSS definition
if (/a/i.test(wo.print_rows)) {
// force show of all rows
printStyle += 'tbody tr { display: table-row !important; }';
} else if (/f/i.test(wo.print_rows)) {
// add definition to show all non-filtered rows (cells hidden by the pager)
printStyle += 'tbody tr:not(.' + (wo.filter_filteredRow || 'filtered') + ') { display: table-row !important; }';
}
// === columns ===
// columnSelector -> c.selector.$style
// Assume "visible" means hidden columns have a "display:none" style, or a class name
// add the definition to the wo.print_extraCSS option
if (/s/i.test(wo.print_columns) && c.selector && c.widgets.indexOf('columnSelector') >= 0) {
// show selected (visible) columns; make a copy of the columnSelector widget css (not media queries)
printStyle += c.selector.auto ? '' : c.selector.$style.text();
} else if (/a/i.test(wo.print_columns)) {
// force show all cells
printStyle += 'td, th { display: table-cell !important; }';
}
printStyle += wo.print_extraCSS;
// callback function
if ( $.isFunction(wo.print_callback) ) {
wo.print_callback( c, $table, printStyle );
} else {
printTable.printOutput(c, $table.html(), printStyle);
}
}, // end process
printOutput : function(c, data, style) {
var wo = c.widgetOptions,
generator = window.open('', wo.print_title, 'width=500,height=300'),
t = wo.print_title || c.$table.find('caption').text() || c.$table[0].id || document.title || 'table';
generator.document.write(
'<html><head><title>' + t + '</title>' +
( wo.print_styleSheet ? '<link rel="stylesheet" href="' + wo.print_styleSheet + '">' : '' ) +
'<style>' + style + '</style>' +
'</head><body>' + data + '</body></html>'
);
generator.document.close();
generator.print();
generator.close();
return true;
},
remove : function(c) {
c.$table.off(printTable.event);
}
};
ts.addWidget({
id: 'print',
options: {
print_title : '', // this option > caption > table id > "table"
print_dataAttrib : 'data-name', // header attrib containing modified header name
print_rows : 'filtered', // (a)ll, (v)isible or (f)iltered
print_columns : 'selected', // (a)ll or (s)elected (if columnSelector widget is added)
print_extraCSS : '', // add any extra css definitions for the popup window here
print_styleSheet : '', // add the url of your print stylesheet
// callback executed when processing completes
// to continue printing, use the following function:
// function( config, $table, printStyle ) {
// // do something to the table or printStyle string
// $.tablesorter.printTable.printOutput( config, $table.html(), printStyle );
// }
print_callback : null
},
init: function(table, thisWidget, c) {
printTable.init(c);
},
remove: function(table, c){
printTable.remove(c);
}
});
})(jQuery);

View File

@@ -0,0 +1,179 @@
/* table reflow widget (beta) for TableSorter 3/22/2014 (v2.16.0)
* Requires tablesorter v2.8+ and jQuery 1.7+
* Also, this widget requires the following default css (modify as desired)
/ * REQUIRED CSS: change your reflow breakpoint here (35em below) * /
@media ( max-width: 35em ) {
.ui-table-reflow td,
.ui-table-reflow th {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
float: right;
/ * if not using the stickyHeaders widget (not the css3 version)
* the "!important" flag, and "height: auto" can be removed * /
width: 100% !important;
height: auto !important;
}
/ * reflow widget * /
.ui-table-reflow tbody td[data-title]:before {
color: #469;
font-size: .9em;
content: attr(data-title);
float: left;
width: 50%;
white-space: pre-wrap;
text-align: bottom;
display: inline-block;
}
/ * reflow2 widget * /
table.ui-table-reflow .ui-table-cell-label.ui-table-cell-label-top {
display: block;
padding: .4em 0;
margin: .4em 0;
text-transform: uppercase;
font-size: .9em;
font-weight: 400;
}
table.ui-table-reflow .ui-table-cell-label {
padding: .4em;
min-width: 30%;
display: inline-block;
margin: -.4em 1em -.4em -.4em;
}
}
.ui-table-reflow .ui-table-cell-label {
display: none;
}
*/
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery: false */
;(function($){
"use strict";
var ts = $.tablesorter,
tablereflow = {
// simple reflow
// add data-attribute to each cell which shows when media query is active
// this widget DOES NOT WORK on a table with multiple thead rows
init : function(table, c, wo) {
var $this,
title = wo.reflow_dataAttrib,
header = wo.reflow_headerAttrib,
headers = [];
c.$table
.addClass(wo.reflow_className)
.off('refresh.tsreflow updateComplete.tsreflow2')
// emulate jQuery Mobile refresh
// https://api.jquerymobile.com/table-reflow/#method-refresh
.on('refresh.tsreflow updateComplete.tsreflow2', function(){
tablereflow.init(table, c, wo);
});
c.$headers.each(function(){
$this = $(this);
headers.push( $this.attr(header) || $this.text() );
});
c.$tbodies.children().each(function(){
$(this).children().each(function(i){
$(this).attr(title, headers[i]);
});
});
},
init2: function(table, c, wo) {
var $this, $tbody, i, $hdr, txt, len,
cols = c.columns,
header = wo.reflow2_headerAttrib,
headers = [];
c.$table
.addClass(wo.reflow2_className)
.off('refresh.tsreflow2 updateComplete.tsreflow2')
// emulate jQuery Mobile refresh
// https://api.jquerymobile.com/table-reflow/#method-refresh
.on('refresh.tsreflow2 updateComplete.tsreflow2', function(){
tablereflow.init2(table, c, wo);
});
// add <b> to every table cell with thead cell contents
for (i = 0; i < cols; i++) {
$hdr = c.$headers.filter('[data-column="' + i + '"]');
if ($hdr.length > 1) {
txt = [];
$hdr.each(function(){
$this = $(this);
if (!$this.hasClass(wo.reflow2_classIgnore)) {
txt.push( $this.attr(header) || $this.text() );
}
});
} else {
txt = [ $hdr.attr(header) || $hdr.text() ];
}
headers.push( txt );
}
// include "remove-me" class so these additional elements are removed before updating
txt = '<b class="' + c.selectorRemove.slice(1) + ' ' + wo.reflow2_labelClass;
c.$tbodies.children().each(function(){
$tbody = ts.processTbody(table, $(this), true);
$tbody.children().each(function(j){
$this = $(this);
len = headers[j].length
i = len - 1;
while (i >= 0) {
$this.prepend(txt + (i === 0 && len > 1 ? ' ' + wo.reflow2_labelTop : '') + '">' + headers[j][i] + '</b>');
i--;
}
});
ts.processTbody(table, $tbody, false);
});
},
remove : function(table, c, wo) {
c.$table.removeClass(wo.reflow_className);
},
remove2 : function(table, c, wo) {
c.$table.removeClass(wo.reflow2_className);
}
};
ts.addWidget({
id: "reflow",
options: {
// class name added to make it responsive (class name within media query)
reflow_className : 'ui-table-reflow',
// header attribute containing modified header name
reflow_headerAttrib : 'data-name',
// data attribute added to each tbody cell
reflow_dataAttrib : 'data-title'
},
init: function(table, thisWidget, c, wo) {
tablereflow.init(table, c, wo);
},
remove: function(table, c, wo){
tablereflow.remove(table, c, wo);
}
});
ts.addWidget({
id: "reflow2",
options: {
// class name added to make it responsive (class name within media query)
reflow2_className : 'ui-table-reflow',
// ignore header cell content with this class name
reflow2_classIgnore : 'ui-table-reflow-ignore',
// header attribute containing modified header name
reflow2_headerAttrib : 'data-name',
// class name applied to thead labels
reflow2_labelClass : 'ui-table-cell-label',
// class name applied to first row thead label
reflow2_labelTop : 'ui-table-cell-label-top'
},
init: function(table, thisWidget, c, wo) {
tablereflow.init2(table, c, wo);
},
remove: function(table, c, wo){
tablereflow.remove2(table, c, wo);
}
});
})(jQuery);

View File

@@ -0,0 +1,50 @@
/*! tablesorter repeatHeaders widget - updated 4/12/2013
* Requires tablesorter v2.8+ and jQuery 1.7+
* Original by Christian Bach from the example-widgets.html demo
*/
/*global jQuery: false */
;(function($){
"use strict";
$.tablesorter.addWidget({
id: "repeatHeaders",
priority: 10,
options: {
rowsToSkip : 4
},
// format is called on init and when a sorting has finished
format: function(table, c, wo) {
var h = '', i, $tr, l, skip;
// cache and collect all TH headers
if (!wo.repeatHeaders) {
h = '<tr class="repeated-header remove-me">';
$.each(c.headerContent, function(i,t) {
h += '<th>' + t + '</th>';
});
// "remove-me" class was added in case the table needs to be updated, the "remove-me" rows will be
// removed prior to the update to prevent including the rows in the update - see "selectorRemove" option
wo.repeatHeaders = h + '</tr>';
}
// number of rows to skip
skip = wo && wo.rowsToSkip || 4;
// remove appended headers by classname
c.$table.find("tr.repeated-header").remove();
$tr = c.$tbodies.find('tr');
l = $tr.length;
// loop all tr elements and insert a copy of the "headers"
for (i = skip; i < l; i += skip) {
// insert a copy of the table head every X rows
$tr.eq(i).before(wo.repeatHeaders);
}
},
// this remove function is called when using the refreshWidgets method or when destroying the tablesorter plugin
// this function only applies to tablesorter v2.4+
remove: function(table, c){
c.$table.find("tr.repeated-header").remove();
}
});
})(jQuery);

View File

@@ -0,0 +1,218 @@
/*!
Copyright (C) 2011 T. Connell & Associates, Inc.
Dual-licensed under the MIT and GPL licenses
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Resizable scroller widget for the jQuery tablesorter plugin
Version 2.0 - modified by Rob Garrison 4/12/2013; updated 6/28/2014 (v2.17.3)
Requires jQuery v1.7+
Requires the tablesorter plugin, v2.8+, available at http://mottie.github.com/tablesorter/docs/
Usage:
$(function() {
$('table.tablesorter').tablesorter({
widgets: ['zebra', 'scroller'],
widgetOptions : {
scroller_height : 300, // height of scroll window
scroller_barWidth : 18, // scroll bar width
scroller_jumpToHeader : true, // header snap to browser top when scrolling the tbody
scroller_idPrefix : 's_' // cloned thead id prefix (random number added to end)
}
});
});
Website: www.tconnell.com
*/
/*jshint browser:true, jquery:true, unused:false */
;(function($){
"use strict";
$.fn.hasScrollBar = function(){
return this.get(0).scrollHeight > this.height();
};
var ts = $.tablesorter;
ts.window_resize = function(){
if (this.resize_timer) {
clearTimeout(this.resize_timer);
}
this.resize_timer = setTimeout(function(){
$(this).trigger('resizeEnd');
}, 250);
};
// Add extra scroller css
$(function(){
var s = '<style>' +
'.tablesorter-scroller-reset { width: auto !important; } ' +
'.tablesorter-scroller { text-align: left; overflow: hidden; }' +
'.tablesorter-scroller-header { overflow: hidden; }' +
'.tablesorter-scroller-header table.tablesorter { margin-bottom: 0; }' +
'.tablesorter-scroller-table { overflow-y: scroll; }' +
'.tablesorter-scroller-table table.tablesorter { margin-top: 0; overflow: scroll; } ' +
'.tablesorter-scroller-table .tablesorter-filter-row, .tablesorter-scroller-table tfoot { display: none; }' +
'.tablesorter-scroller-table table.tablesorter thead tr.tablesorter-headerRow * {' +
'line-height:0;height:0;border:none;background-image:none;padding-top:0;padding-bottom:0;margin-top:0;margin-bottom:0;overflow:hidden;' +
'}</style>';
$(s).appendTo('body');
});
ts.addWidget({
id: 'scroller',
priority: 60, // run after the filter widget
options: {
scroller_height : 300,
scroller_barWidth : 18,
scroller_jumpToHeader: true,
scroller_upAfterSort: true,
scroller_idPrefix : 's_'
},
init: function(table, thisWidget, c, wo){
var $win = $(window);
// Setup window.resizeEnd event
$win
.bind('resize', ts.window_resize)
.bind('resizeEnd', function() {
// init is run before format, so scroller_resizeWidth
// won't be defined within the "c" or "wo" parameters
if (typeof table.config.widgetOptions.scroller_resizeWidth === 'function') {
// IE calls resize when you modify content, so we have to unbind the resize event
// so we don't end up with an infinite loop. we can rebind after we're done.
$win.unbind('resize', ts.window_resize);
table.config.widgetOptions.scroller_resizeWidth();
$win.bind('resize', ts.window_resize);
}
});
},
format: function(table, c, wo) {
var h, $hdr, id, t, resize, $cells,
$win = $(window),
$tbl = c.$table;
if (!c.isScrolling) {
h = wo.scroller_height || 300;
t = $tbl.find('tbody').height();
if (t !== 0 && h > t) { h = t + 10; } // Table is less than h px
id = wo.scroller_id = wo.scroller_idPrefix + Math.floor(Math.random() * 1001);
$hdr = $('<table class="' + $tbl.attr('class') + '" cellpadding=0 cellspacing=0><thead>' + $tbl.find('thead:first').html() + '</thead></table>');
$tbl
.wrap('<div id="' + id + '" class="tablesorter-scroller" />')
.before($hdr)
.find('.tablesorter-filter-row').addClass('hideme');
$cells = $hdr
.wrap('<div class="tablesorter-scroller-header" style="width:' + $tbl.width() + ';" />')
.find('.' + ts.css.header);
$tbl.wrap('<div class="tablesorter-scroller-table" style="height:' + h + 'px;width:' + $tbl.width() + ';" />');
// make scroller header sortable
ts.bindEvents(table, $cells);
// look for filter widget
if ($tbl.hasClass('hasFilters')) {
ts.filter.bindSearch( $tbl, $hdr.find('.' + ts.css.filter) );
}
resize = function(){
var d, b, $h, $th, w,
// Hide other scrollers so we can resize
$div = $('div.scroller[id != "' + id + '"]').hide();
$tbl.find('thead').show();
// Reset sizes so parent can resize.
$tbl
.addClass('tablesorter-scroller-reset')
.find('thead').find('.tablesorter-header-inner').addClass('tablesorter-scroller-reset');
d = $tbl.parent();
d.addClass('tablesorter-scroller-reset');
d.parent().trigger('resize');
// Shrink a bit to accommodate scrollbar
d.width( d.parent().innerWidth() - ( d.parent().hasScrollBar() ? wo.scroller_barWidth : 0 ) );
w = d.innerWidth() - ( d.hasScrollBar() ? wo.scroller_barWidth : 0 );
$tbl.width( w );
$hdr.width( w );
$hdr.parent().width( w );
$tbl.closest('.tablesorter-scroller').find('.tablesorter-scroller-reset').removeClass('tablesorter-scroller-reset');
// include left & right border widths
b = parseInt( $tbl.css('border-left-width'), 10 ) + parseInt( $tbl.css('border-right-width'), 10 );
$h = $hdr.find('thead').children().children();
// adjust cloned header to match original table width - includes wrappers, headers, and header inner div
$tbl.find('thead').children().children().each(function(i, c){
$th = $(c).find('.tablesorter-header-inner');
if ($th.length) {
// I have no idea why this is in here anymore LOL
w = parseInt( $th.css('min-width').replace('auto', '0').replace(/(px|em)/, ''), 10 );
if ( $th.width() < w ) {
$th.width(w);
} else {
w = $th.width();
}
$h.eq(i)
.find('.tablesorter-header-inner').width(w - b)
// set inner width first
.parent()
.width( $th.parent().width() - b );
}
});
$div.show();
};
// Expose to external calls
wo.scroller_resizeWidth = resize;
resize();
$tbl.find('thead').css('visibility', 'hidden');
c.isScrolling = true;
t = $tbl.parent().parent().height();
// The header will always jump into view if scrolling the table body
$tbl.parent().bind('scroll', function(){
if (wo.scroller_jumpToHeader) {
var pos = $win.scrollTop() - $hdr.offset().top;
if ($(this).scrollTop() !== 0 && pos < t && pos > 0) {
$win.scrollTop( $hdr.offset().top );
}
}
$hdr.parent().scrollLeft( $(this).scrollLeft() );
});
}
// Sorting, so scroll to top
if (wo.scroller_upAfterSort) {
$tbl.parent().animate({ scrollTop: 0 }, 'fast');
}
},
remove : function(table, c, wo){
var $table = c.$table;
$table.closest('.tablesorter-scroller').find('.tablesorter-scroller-header').remove();
$table
.unwrap()
.find('.tablesorter-filter-row').removeClass('hideme').end()
.find('thead').show().css('visibility', 'visible');
c.isScrolling = false;
}
});
})(jQuery);

View File

@@ -0,0 +1,124 @@
/*
* StaticRow widget for jQuery TableSorter 2.0
* Version 1.2 - modified by Rob Garrison (6/28/2014 for tablesorter v2.16.1-beta+)
* Requires:
* jQuery v1.4+
* tablesorter plugin, v2.8+, available at http://mottie.github.com/tablesorter/docs/
*
* Copyright (c) 2011 Nils Luxton
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
*/
/*jshint browser:true, jquery:true, unused:false */
/*global jQuery: false */
;(function($){
"use strict";
var ts = $.tablesorter,
// events triggered on the table that update this widget
events = 'staticRowsRefresh updateComplete '.split(' ').join('.tsstaticrows '),
// add/refresh row indexes
addIndexes = function(table){
var $tr, wo, v, indx, rows,
c = table.config;
// "Index" the static rows, saving their current (starting) position in the
// table inside a data() param on the <tr> element itself for later use.
if (c) {
wo = c.widgetOptions;
c.$tbodies.each(function(){
$tr = $(this).children();
rows = $tr.length;
$tr.filter(wo.staticRow_class).each(function() {
$tr = $(this);
indx = $tr.data(wo.staticRow_index);
if (typeof indx !== "undefined") {
v = parseFloat(indx);
// percentage of total rows
indx = (/%/.test(indx)) ? Math.round(v/100 * rows) : v;
} else {
indx = $tr.index();
}
// row indexing starts over within each tbody
$tr.data( wo.staticRow_data, indx );
});
});
}
};
ts.addWidget({
// Give the new Widget an ID to be used in the tablesorter() call, as follows:
// $('#myElement').tablesorter({ widgets: ['zebra', 'staticRow'] });
id: 'staticRow',
options: {
staticRow_class : '.static',
staticRow_data : 'static-index',
staticRow_index : 'row-index'
},
init: function(table, thisWidget, c, wo){
addIndexes(table);
// refresh static rows after updates
c.$table
.unbind(events)
.bind(events, function(){
addIndexes(table);
c.$table.trigger('applyWidgets');
});
},
format: function(table, c, wo) {
// Loop thru static rows, moving them to their original "indexed" position,
// & repeat until no more re-shuffling is needed
var targetIndex, $thisRow, indx, numRows, $tbody, hasShuffled, $rows, max;
c.$tbodies.each(function(){
$tbody = $.tablesorter.processTbody(table, $(this), true); // remove tbody
hasShuffled = true;
indx = 0;
$rows = $tbody.children(wo.staticRow_class);
numRows = $tbody.children('tr').length - 1;
max = $rows.length;
// don't allow the while loop to cycle more times than the set number of static rows
while (hasShuffled && indx < max) {
hasShuffled = false;
/*jshint loopfunc:true */
$rows.each(function() {
targetIndex = $(this).data(wo.staticRow_data);
// allow setting target index >> num rows to always make a row last
targetIndex = targetIndex >= numRows ? numRows : targetIndex < 0 ? 0 : targetIndex;
if (targetIndex !== $(this).index()) {
hasShuffled = true;
$thisRow = $(this).detach();
if (targetIndex >= numRows) {
// Are we trying to be the last row?
$thisRow.appendTo( $tbody );
} else if (targetIndex === 0) {
// Are we trying to be the first row?
$thisRow.prependTo( $tbody );
} else {
// No, we want to be somewhere in the middle!
$thisRow.insertBefore( $tbody.find('tr:eq(' + targetIndex + ')') );
}
}
});
indx++;
}
$.tablesorter.processTbody(table, $tbody, false); // restore tbody
});
c.$table.trigger('staticRowsComplete', table);
},
remove : function(table, c, wo){
c.$table.unbind(events);
}
});
})(jQuery);