fae-rails 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ /*
2
+ * SimpleModal 1.4.4 - jQuery Plugin
3
+ * http://simplemodal.com/
4
+ * Copyright (c) 2013 Eric Martin
5
+ * Licensed under MIT and GPL
6
+ * Date: Sun, Jan 20 2013 15:58:56 -0800
7
+ */
8
+ (function(b){"function"===typeof define&&define.amd?define(["jquery"],b):b(jQuery)})(function(b){var j=[],n=b(document),k=navigator.userAgent.toLowerCase(),l=b(window),g=[],o=null,p=/msie/.test(k)&&!/opera/.test(k),q=/opera/.test(k),m,r;m=p&&/msie 6./.test(k)&&"object"!==typeof window.XMLHttpRequest;r=p&&/msie 7.0/.test(k);b.modal=function(a,h){return b.modal.impl.init(a,h)};b.modal.close=function(){b.modal.impl.close()};b.modal.focus=function(a){b.modal.impl.focus(a)};b.modal.setContainerDimensions=
9
+ function(){b.modal.impl.setContainerDimensions()};b.modal.setPosition=function(){b.modal.impl.setPosition()};b.modal.update=function(a,h){b.modal.impl.update(a,h)};b.fn.modal=function(a){return b.modal.impl.init(this,a)};b.modal.defaults={appendTo:"body",focus:!0,opacity:50,overlayId:"simplemodal-overlay",overlayCss:{},containerId:"simplemodal-container",containerCss:{},dataId:"simplemodal-data",dataCss:{},minHeight:null,minWidth:null,maxHeight:null,maxWidth:null,autoResize:!1,autoPosition:!0,zIndex:1E3,
10
+ close:!0,closeHTML:'<a class="modalCloseImg" title="Close"></a>',closeClass:"simplemodal-close",escClose:!0,overlayClose:!1,fixed:!0,position:null,persist:!1,modal:!0,onOpen:null,onShow:null,onClose:null};b.modal.impl={d:{},init:function(a,h){if(this.d.data)return!1;o=p&&!b.support.boxModel;this.o=b.extend({},b.modal.defaults,h);this.zIndex=this.o.zIndex;this.occb=!1;if("object"===typeof a){if(a=a instanceof b?a:b(a),this.d.placeholder=!1,0<a.parent().parent().size()&&(a.before(b("<span></span>").attr("id",
11
+ "simplemodal-placeholder").css({display:"none"})),this.d.placeholder=!0,this.display=a.css("display"),!this.o.persist))this.d.orig=a.clone(!0)}else if("string"===typeof a||"number"===typeof a)a=b("<div></div>").html(a);else return alert("SimpleModal Error: Unsupported data type: "+typeof a),this;this.create(a);this.open();b.isFunction(this.o.onShow)&&this.o.onShow.apply(this,[this.d]);return this},create:function(a){this.getDimensions();if(this.o.modal&&m)this.d.iframe=b('<iframe src="javascript:false;"></iframe>').css(b.extend(this.o.iframeCss,
12
+ {display:"none",opacity:0,position:"fixed",height:g[0],width:g[1],zIndex:this.o.zIndex,top:0,left:0})).appendTo(this.o.appendTo);this.d.overlay=b("<div></div>").attr("id",this.o.overlayId).addClass("simplemodal-overlay").css(b.extend(this.o.overlayCss,{display:"none",opacity:this.o.opacity/100,height:this.o.modal?j[0]:0,width:this.o.modal?j[1]:0,position:"fixed",left:0,top:0,zIndex:this.o.zIndex+1})).appendTo(this.o.appendTo);this.d.container=b("<div></div>").attr("id",this.o.containerId).addClass("simplemodal-container").css(b.extend({position:this.o.fixed?
13
+ "fixed":"absolute"},this.o.containerCss,{display:"none",zIndex:this.o.zIndex+2})).append(this.o.close&&this.o.closeHTML?b(this.o.closeHTML).addClass(this.o.closeClass):"").appendTo(this.o.appendTo);this.d.wrap=b("<div></div>").attr("tabIndex",-1).addClass("simplemodal-wrap").css({height:"100%",outline:0,width:"100%"}).appendTo(this.d.container);this.d.data=a.attr("id",a.attr("id")||this.o.dataId).addClass("simplemodal-data").css(b.extend(this.o.dataCss,{display:"none"})).appendTo("body");this.setContainerDimensions();
14
+ this.d.data.appendTo(this.d.wrap);(m||o)&&this.fixIE()},bindEvents:function(){var a=this;b("."+a.o.closeClass).bind("click.simplemodal",function(b){b.preventDefault();a.close()});a.o.modal&&a.o.close&&a.o.overlayClose&&a.d.overlay.bind("click.simplemodal",function(b){b.preventDefault();a.close()});n.bind("keydown.simplemodal",function(b){a.o.modal&&9===b.keyCode?a.watchTab(b):a.o.close&&a.o.escClose&&27===b.keyCode&&(b.preventDefault(),a.close())});l.bind("resize.simplemodal orientationchange.simplemodal",
15
+ function(){a.getDimensions();a.o.autoResize?a.setContainerDimensions():a.o.autoPosition&&a.setPosition();m||o?a.fixIE():a.o.modal&&(a.d.iframe&&a.d.iframe.css({height:g[0],width:g[1]}),a.d.overlay.css({height:j[0],width:j[1]}))})},unbindEvents:function(){b("."+this.o.closeClass).unbind("click.simplemodal");n.unbind("keydown.simplemodal");l.unbind(".simplemodal");this.d.overlay.unbind("click.simplemodal")},fixIE:function(){var a=this.o.position;b.each([this.d.iframe||null,!this.o.modal?null:this.d.overlay,
16
+ "fixed"===this.d.container.css("position")?this.d.container:null],function(b,e){if(e){var f=e[0].style;f.position="absolute";if(2>b)f.removeExpression("height"),f.removeExpression("width"),f.setExpression("height",'document.body.scrollHeight > document.body.clientHeight ? document.body.scrollHeight : document.body.clientHeight + "px"'),f.setExpression("width",'document.body.scrollWidth > document.body.clientWidth ? document.body.scrollWidth : document.body.clientWidth + "px"');else{var c,d;a&&a.constructor===
17
+ Array?(c=a[0]?"number"===typeof a[0]?a[0].toString():a[0].replace(/px/,""):e.css("top").replace(/px/,""),c=-1===c.indexOf("%")?c+' + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"':parseInt(c.replace(/%/,""))+' * ((document.documentElement.clientHeight || document.body.clientHeight) / 100) + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"',a[1]&&(d="number"===typeof a[1]?
18
+ a[1].toString():a[1].replace(/px/,""),d=-1===d.indexOf("%")?d+' + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"':parseInt(d.replace(/%/,""))+' * ((document.documentElement.clientWidth || document.body.clientWidth) / 100) + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"')):(c='(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (t = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"',
19
+ d='(document.documentElement.clientWidth || document.body.clientWidth) / 2 - (this.offsetWidth / 2) + (t = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft) + "px"');f.removeExpression("top");f.removeExpression("left");f.setExpression("top",c);f.setExpression("left",d)}}})},focus:function(a){var h=this,a=a&&-1!==b.inArray(a,["first","last"])?a:"first",e=b(":input:enabled:visible:"+a,h.d.wrap);setTimeout(function(){0<e.length?e.focus():h.d.wrap.focus()},
20
+ 10)},getDimensions:function(){var a="undefined"===typeof window.innerHeight?l.height():window.innerHeight;j=[n.height(),n.width()];g=[a,l.width()]},getVal:function(a,b){return a?"number"===typeof a?a:"auto"===a?0:0<a.indexOf("%")?parseInt(a.replace(/%/,""))/100*("h"===b?g[0]:g[1]):parseInt(a.replace(/px/,"")):null},update:function(a,b){if(!this.d.data)return!1;this.d.origHeight=this.getVal(a,"h");this.d.origWidth=this.getVal(b,"w");this.d.data.hide();a&&this.d.container.css("height",a);b&&this.d.container.css("width",
21
+ b);this.setContainerDimensions();this.d.data.show();this.o.focus&&this.focus();this.unbindEvents();this.bindEvents()},setContainerDimensions:function(){var a=m||r,b=this.d.origHeight?this.d.origHeight:q?this.d.container.height():this.getVal(a?this.d.container[0].currentStyle.height:this.d.container.css("height"),"h"),a=this.d.origWidth?this.d.origWidth:q?this.d.container.width():this.getVal(a?this.d.container[0].currentStyle.width:this.d.container.css("width"),"w"),e=this.d.data.outerHeight(!0),f=
22
+ this.d.data.outerWidth(!0);this.d.origHeight=this.d.origHeight||b;this.d.origWidth=this.d.origWidth||a;var c=this.o.maxHeight?this.getVal(this.o.maxHeight,"h"):null,d=this.o.maxWidth?this.getVal(this.o.maxWidth,"w"):null,c=c&&c<g[0]?c:g[0],d=d&&d<g[1]?d:g[1],i=this.o.minHeight?this.getVal(this.o.minHeight,"h"):"auto",b=b?this.o.autoResize&&b>c?c:b<i?i:b:e?e>c?c:this.o.minHeight&&"auto"!==i&&e<i?i:e:i,c=this.o.minWidth?this.getVal(this.o.minWidth,"w"):"auto",a=a?this.o.autoResize&&a>d?d:a<c?c:a:f?
23
+ f>d?d:this.o.minWidth&&"auto"!==c&&f<c?c:f:c;this.d.container.css({height:b,width:a});this.d.wrap.css({overflow:e>b||f>a?"auto":"visible"});this.o.autoPosition&&this.setPosition()},setPosition:function(){var a,b;a=g[0]/2-this.d.container.outerHeight(!0)/2;b=g[1]/2-this.d.container.outerWidth(!0)/2;var e="fixed"!==this.d.container.css("position")?l.scrollTop():0;this.o.position&&"[object Array]"===Object.prototype.toString.call(this.o.position)?(a=e+(this.o.position[0]||a),b=this.o.position[1]||b):
24
+ a=e+a;this.d.container.css({left:b,top:a})},watchTab:function(a){if(0<b(a.target).parents(".simplemodal-container").length){if(this.inputs=b(":input:enabled:visible:first, :input:enabled:visible:last",this.d.data[0]),!a.shiftKey&&a.target===this.inputs[this.inputs.length-1]||a.shiftKey&&a.target===this.inputs[0]||0===this.inputs.length)a.preventDefault(),this.focus(a.shiftKey?"last":"first")}else a.preventDefault(),this.focus()},open:function(){this.d.iframe&&this.d.iframe.show();b.isFunction(this.o.onOpen)?
25
+ this.o.onOpen.apply(this,[this.d]):(this.d.overlay.show(),this.d.container.show(),this.d.data.show());this.o.focus&&this.focus();this.bindEvents()},close:function(){if(!this.d.data)return!1;this.unbindEvents();if(b.isFunction(this.o.onClose)&&!this.occb)this.occb=!0,this.o.onClose.apply(this,[this.d]);else{if(this.d.placeholder){var a=b("#simplemodal-placeholder");this.o.persist?a.replaceWith(this.d.data.removeClass("simplemodal-data").css("display",this.display)):(this.d.data.hide().remove(),a.replaceWith(this.d.orig))}else this.d.data.hide().remove();
26
+ this.d.container.hide().remove();this.d.overlay.hide();this.d.iframe&&this.d.iframe.hide().remove();this.d.overlay.remove();this.d={}}}}});
@@ -0,0 +1,1901 @@
1
+ /**!
2
+ * TableSorter 2.17.8 - Client-side table sorting with ease!
3
+ * @requires jQuery v1.2.6+
4
+ *
5
+ * Copyright (c) 2007 Christian Bach
6
+ * Examples and docs at: http://tablesorter.com
7
+ * Dual licensed under the MIT and GPL licenses:
8
+ * http://www.opensource.org/licenses/mit-license.php
9
+ * http://www.gnu.org/licenses/gpl.html
10
+ *
11
+ * @type jQuery
12
+ * @name tablesorter
13
+ * @cat Plugins/Tablesorter
14
+ * @author Christian Bach/christian.bach@polyester.se
15
+ * @contributor Rob Garrison/https://github.com/Mottie/tablesorter
16
+ */
17
+ /*jshint browser:true, jquery:true, unused:false, expr: true */
18
+ /*global console:false, alert:false */
19
+ !(function($) {
20
+ "use strict";
21
+ $.extend({
22
+ /*jshint supernew:true */
23
+ tablesorter: new function() {
24
+
25
+ var ts = this;
26
+
27
+ ts.version = "2.17.8";
28
+
29
+ ts.parsers = [];
30
+ ts.widgets = [];
31
+ ts.defaults = {
32
+
33
+ // *** appearance
34
+ theme : 'default', // adds tablesorter-{theme} to the table for styling
35
+ widthFixed : false, // adds colgroup to fix widths of columns
36
+ showProcessing : false, // show an indeterminate timer icon in the header when the table is sorted or filtered.
37
+
38
+ headerTemplate : '{content}',// header layout template (HTML ok); {content} = innerHTML, {icon} = <i/> (class from cssIcon)
39
+ onRenderTemplate : null, // function(index, template){ return template; }, (template is a string)
40
+ onRenderHeader : null, // function(index){}, (nothing to return)
41
+
42
+ // *** functionality
43
+ cancelSelection : true, // prevent text selection in the header
44
+ tabIndex : true, // add tabindex to header for keyboard accessibility
45
+ dateFormat : 'mmddyyyy', // other options: "ddmmyyy" or "yyyymmdd"
46
+ sortMultiSortKey : 'shiftKey', // key used to select additional columns
47
+ sortResetKey : 'ctrlKey', // key used to remove sorting on a column
48
+ usNumberFormat : true, // false for German "1.234.567,89" or French "1 234 567,89"
49
+ delayInit : false, // if false, the parsed table contents will not update until the first sort
50
+ serverSideSorting: false, // if true, server-side sorting should be performed because client-side sorting will be disabled, but the ui and events will still be used.
51
+
52
+ // *** sort options
53
+ headers : {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc.
54
+ ignoreCase : true, // ignore case while sorting
55
+ sortForce : null, // column(s) first sorted; always applied
56
+ sortList : [], // Initial sort order; applied initially; updated when manually sorted
57
+ sortAppend : null, // column(s) sorted last; always applied
58
+ sortStable : false, // when sorting two rows with exactly the same content, the original sort order is maintained
59
+
60
+ sortInitialOrder : 'asc', // sort direction on first click
61
+ sortLocaleCompare: false, // replace equivalent character (accented characters)
62
+ sortReset : false, // third click on the header will reset column to default - unsorted
63
+ sortRestart : false, // restart sort to "sortInitialOrder" when clicking on previously unsorted columns
64
+
65
+ emptyTo : 'bottom', // sort empty cell to bottom, top, none, zero
66
+ stringTo : 'max', // sort strings in numerical column as max, min, top, bottom, zero
67
+ textExtraction : 'basic', // text extraction method/function - function(node, table, cellIndex){}
68
+ textAttribute : 'data-text',// data-attribute that contains alternate cell text (used in textExtraction function)
69
+ textSorter : null, // choose overall or specific column sorter function(a, b, direction, table, columnIndex) [alt: ts.sortText]
70
+ numberSorter : null, // choose overall numeric sorter function(a, b, direction, maxColumnValue)
71
+
72
+ // *** widget options
73
+ widgets: [], // method to add widgets, e.g. widgets: ['zebra']
74
+ widgetOptions : {
75
+ zebra : [ 'even', 'odd' ] // zebra widget alternating row class names
76
+ },
77
+ initWidgets : true, // apply widgets on tablesorter initialization
78
+
79
+ // *** callbacks
80
+ initialized : null, // function(table){},
81
+
82
+ // *** extra css class names
83
+ tableClass : '',
84
+ cssAsc : '',
85
+ cssDesc : '',
86
+ cssNone : '',
87
+ cssHeader : '',
88
+ cssHeaderRow : '',
89
+ cssProcessing : '', // processing icon applied to header during sort/filter
90
+
91
+ cssChildRow : 'tablesorter-childRow', // class name indiciating that a row is to be attached to the its parent
92
+ cssIcon : 'tablesorter-icon', // if this class exists, a <i> will be added to the header automatically
93
+ cssInfoBlock : 'tablesorter-infoOnly', // don't sort tbody with this class name (only one class name allowed here!)
94
+
95
+ // *** selectors
96
+ selectorHeaders : '> thead th, > thead td',
97
+ selectorSort : 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort
98
+ selectorRemove : '.remove-me',
99
+
100
+ // *** advanced
101
+ debug : false,
102
+
103
+ // *** Internal variables
104
+ headerList: [],
105
+ empties: {},
106
+ strings: {},
107
+ parsers: []
108
+
109
+ // deprecated; but retained for backwards compatibility
110
+ // widgetZebra: { css: ["even", "odd"] }
111
+
112
+ };
113
+
114
+ // internal css classes - these will ALWAYS be added to
115
+ // the table and MUST only contain one class name - fixes #381
116
+ ts.css = {
117
+ table : 'tablesorter',
118
+ cssHasChild: 'tablesorter-hasChildRow',
119
+ childRow : 'tablesorter-childRow',
120
+ header : 'tablesorter-header',
121
+ headerRow : 'tablesorter-headerRow',
122
+ headerIn : 'tablesorter-header-inner',
123
+ icon : 'tablesorter-icon',
124
+ info : 'tablesorter-infoOnly',
125
+ processing : 'tablesorter-processing',
126
+ sortAsc : 'tablesorter-headerAsc',
127
+ sortDesc : 'tablesorter-headerDesc',
128
+ sortNone : 'tablesorter-headerUnSorted'
129
+ };
130
+
131
+ // labels applied to sortable headers for accessibility (aria) support
132
+ ts.language = {
133
+ sortAsc : 'Ascending sort applied, ',
134
+ sortDesc : 'Descending sort applied, ',
135
+ sortNone : 'No sort applied, ',
136
+ nextAsc : 'activate to apply an ascending sort',
137
+ nextDesc : 'activate to apply a descending sort',
138
+ nextNone : 'activate to remove the sort'
139
+ };
140
+
141
+ /* debuging utils */
142
+ function log() {
143
+ var a = arguments[0],
144
+ s = arguments.length > 1 ? Array.prototype.slice.call(arguments) : a;
145
+ if (typeof console !== "undefined" && typeof console.log !== "undefined") {
146
+ console[ /error/i.test(a) ? 'error' : /warn/i.test(a) ? 'warn' : 'log' ](s);
147
+ } else {
148
+ alert(s);
149
+ }
150
+ }
151
+
152
+ function benchmark(s, d) {
153
+ log(s + " (" + (new Date().getTime() - d.getTime()) + "ms)");
154
+ }
155
+
156
+ ts.log = log;
157
+ ts.benchmark = benchmark;
158
+
159
+ // $.isEmptyObject from jQuery v1.4
160
+ function isEmptyObject(obj) {
161
+ /*jshint forin: false */
162
+ for (var name in obj) {
163
+ return false;
164
+ }
165
+ return true;
166
+ }
167
+
168
+ function getElementText(table, node, cellIndex) {
169
+ if (!node) { return ""; }
170
+ var te, c = table.config,
171
+ t = c.textExtraction || '',
172
+ text = "";
173
+ if (t === "basic") {
174
+ // check data-attribute first
175
+ text = $(node).attr(c.textAttribute) || node.textContent || node.innerText || $(node).text() || "";
176
+ } else {
177
+ if (typeof(t) === "function") {
178
+ text = t(node, table, cellIndex);
179
+ } else if (typeof (te = ts.getColumnData( table, t, cellIndex )) === 'function') {
180
+ text = te(node, table, cellIndex);
181
+ } else {
182
+ // previous "simple" method
183
+ text = node.textContent || node.innerText || $(node).text() || "";
184
+ }
185
+ }
186
+ return $.trim(text);
187
+ }
188
+
189
+ function detectParserForColumn(table, rows, rowIndex, cellIndex) {
190
+ var cur,
191
+ i = ts.parsers.length,
192
+ node = false,
193
+ nodeValue = '',
194
+ keepLooking = true;
195
+ while (nodeValue === '' && keepLooking) {
196
+ rowIndex++;
197
+ if (rows[rowIndex]) {
198
+ node = rows[rowIndex].cells[cellIndex];
199
+ nodeValue = getElementText(table, node, cellIndex);
200
+ if (table.config.debug) {
201
+ log('Checking if value was empty on row ' + rowIndex + ', column: ' + cellIndex + ': "' + nodeValue + '"');
202
+ }
203
+ } else {
204
+ keepLooking = false;
205
+ }
206
+ }
207
+ while (--i >= 0) {
208
+ cur = ts.parsers[i];
209
+ // ignore the default text parser because it will always be true
210
+ if (cur && cur.id !== 'text' && cur.is && cur.is(nodeValue, table, node)) {
211
+ return cur;
212
+ }
213
+ }
214
+ // nothing found, return the generic parser (text)
215
+ return ts.getParserById('text');
216
+ }
217
+
218
+ function buildParserCache(table) {
219
+ var c = table.config,
220
+ // update table bodies in case we start with an empty table
221
+ tb = c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')'),
222
+ rows, list, l, i, h, ch, np, p, e, time,
223
+ j = 0,
224
+ parsersDebug = "",
225
+ len = tb.length;
226
+ if ( len === 0) {
227
+ return c.debug ? log('Warning: *Empty table!* Not building a parser cache') : '';
228
+ } else if (c.debug) {
229
+ time = new Date();
230
+ log('Detecting parsers for each column');
231
+ }
232
+ list = {
233
+ extractors: [],
234
+ parsers: []
235
+ };
236
+ while (j < len) {
237
+ rows = tb[j].rows;
238
+ if (rows[j]) {
239
+ l = c.columns; // rows[j].cells.length;
240
+ for (i = 0; i < l; i++) {
241
+ h = c.$headers.filter('[data-column="' + i + '"]:last');
242
+ // get column indexed table cell
243
+ ch = ts.getColumnData( table, c.headers, i );
244
+ // get column parser/extractor
245
+ e = ts.getParserById( ts.getData(h, ch, 'extractor') );
246
+ p = ts.getParserById( ts.getData(h, ch, 'sorter') );
247
+ np = ts.getData(h, ch, 'parser') === 'false';
248
+ // empty cells behaviour - keeping emptyToBottom for backwards compatibility
249
+ c.empties[i] = ( ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' ) ).toLowerCase();
250
+ // text strings behaviour in numerical sorts
251
+ c.strings[i] = ( ts.getData(h, ch, 'string') || c.stringTo || 'max' ).toLowerCase();
252
+ if (np) {
253
+ p = ts.getParserById('no-parser');
254
+ }
255
+ if (!e) {
256
+ // For now, maybe detect someday
257
+ e = false;
258
+ }
259
+ if (!p) {
260
+ p = detectParserForColumn(table, rows, -1, i);
261
+ }
262
+ if (c.debug) {
263
+ parsersDebug += "column:" + i + "; extractor:" + e.id + "; parser:" + p.id + "; string:" + c.strings[i] + '; empty: ' + c.empties[i] + "\n";
264
+ }
265
+ list.parsers[i] = p;
266
+ list.extractors[i] = e;
267
+ }
268
+ }
269
+ j += (list.parsers.length) ? len : 1;
270
+ }
271
+ if (c.debug) {
272
+ log(parsersDebug ? parsersDebug : "No parsers detected");
273
+ benchmark("Completed detecting parsers", time);
274
+ }
275
+ c.parsers = list.parsers;
276
+ c.extractors = list.extractors;
277
+ }
278
+
279
+ /* utils */
280
+ function buildCache(table) {
281
+ var cc, t, tx, v, i, j, k, $row, rows, cols, cacheTime,
282
+ totalRows, rowData, colMax,
283
+ c = table.config,
284
+ $tb = c.$table.children('tbody'),
285
+ extractors = c.extractors,
286
+ parsers = c.parsers;
287
+ c.cache = {};
288
+ c.totalRows = 0;
289
+ // if no parsers found, return - it's an empty table.
290
+ if (!parsers) {
291
+ return c.debug ? log('Warning: *Empty table!* Not building a cache') : '';
292
+ }
293
+ if (c.debug) {
294
+ cacheTime = new Date();
295
+ }
296
+ // processing icon
297
+ if (c.showProcessing) {
298
+ ts.isProcessing(table, true);
299
+ }
300
+ for (k = 0; k < $tb.length; k++) {
301
+ colMax = []; // column max value per tbody
302
+ cc = c.cache[k] = {
303
+ normalized: [] // array of normalized row data; last entry contains "rowData" above
304
+ // colMax: # // added at the end
305
+ };
306
+
307
+ // ignore tbodies with class name from c.cssInfoBlock
308
+ if (!$tb.eq(k).hasClass(c.cssInfoBlock)) {
309
+ totalRows = ($tb[k] && $tb[k].rows.length) || 0;
310
+ for (i = 0; i < totalRows; ++i) {
311
+ rowData = {
312
+ // order: original row order #
313
+ // $row : jQuery Object[]
314
+ child: [] // child row text (filter widget)
315
+ };
316
+ /** Add the table data to main data array */
317
+ $row = $($tb[k].rows[i]);
318
+ rows = [ new Array(c.columns) ];
319
+ cols = [];
320
+ // if this is a child row, add it to the last row's children and continue to the next row
321
+ // ignore child row class, if it is the first row
322
+ if ($row.hasClass(c.cssChildRow) && i !== 0) {
323
+ t = cc.normalized.length - 1;
324
+ cc.normalized[t][c.columns].$row = cc.normalized[t][c.columns].$row.add($row);
325
+ // add "hasChild" class name to parent row
326
+ if (!$row.prev().hasClass(c.cssChildRow)) {
327
+ $row.prev().addClass(ts.css.cssHasChild);
328
+ }
329
+ // save child row content (un-parsed!)
330
+ rowData.child[t] = $.trim( $row[0].textContent || $row[0].innerText || $row.text() || "" );
331
+ // go to the next for loop
332
+ continue;
333
+ }
334
+ rowData.$row = $row;
335
+ rowData.order = i; // add original row position to rowCache
336
+ for (j = 0; j < c.columns; ++j) {
337
+ if (typeof parsers[j] === 'undefined') {
338
+ if (c.debug) {
339
+ log('No parser found for cell:', $row[0].cells[j], 'does it have a header?');
340
+ }
341
+ continue;
342
+ }
343
+ t = getElementText(table, $row[0].cells[j], j);
344
+ // do extract before parsing if there is one
345
+ if (typeof extractors[j].id === 'undefined') {
346
+ tx = t;
347
+ } else {
348
+ tx = extractors[j].format(t, table, $row[0].cells[j], j);
349
+ }
350
+ // allow parsing if the string is empty, previously parsing would change it to zero,
351
+ // in case the parser needs to extract data from the table cell attributes
352
+ v = parsers[j].id === 'no-parser' ? '' : parsers[j].format(tx, table, $row[0].cells[j], j);
353
+ cols.push( c.ignoreCase && typeof v === 'string' ? v.toLowerCase() : v );
354
+ if ((parsers[j].type || '').toLowerCase() === "numeric") {
355
+ // determine column max value (ignore sign)
356
+ colMax[j] = Math.max(Math.abs(v) || 0, colMax[j] || 0);
357
+ }
358
+ }
359
+ // ensure rowData is always in the same location (after the last column)
360
+ cols[c.columns] = rowData;
361
+ cc.normalized.push(cols);
362
+ }
363
+ cc.colMax = colMax;
364
+ // total up rows, not including child rows
365
+ c.totalRows += cc.normalized.length;
366
+ }
367
+ }
368
+ if (c.showProcessing) {
369
+ ts.isProcessing(table); // remove processing icon
370
+ }
371
+ if (c.debug) {
372
+ benchmark("Building cache for " + totalRows + " rows", cacheTime);
373
+ }
374
+ }
375
+
376
+ // init flag (true) used by pager plugin to prevent widget application
377
+ function appendToTable(table, init) {
378
+ var c = table.config,
379
+ wo = c.widgetOptions,
380
+ b = table.tBodies,
381
+ rows = [],
382
+ cc = c.cache,
383
+ n, totalRows, $bk, $tb,
384
+ i, k, appendTime;
385
+ // empty table - fixes #206/#346
386
+ if (isEmptyObject(cc)) {
387
+ // run pager appender in case the table was just emptied
388
+ return c.appender ? c.appender(table, rows) :
389
+ table.isUpdating ? c.$table.trigger("updateComplete", table) : ''; // Fixes #532
390
+ }
391
+ if (c.debug) {
392
+ appendTime = new Date();
393
+ }
394
+ for (k = 0; k < b.length; k++) {
395
+ $bk = $(b[k]);
396
+ if ($bk.length && !$bk.hasClass(c.cssInfoBlock)) {
397
+ // get tbody
398
+ $tb = ts.processTbody(table, $bk, true);
399
+ n = cc[k].normalized;
400
+ totalRows = n.length;
401
+ for (i = 0; i < totalRows; i++) {
402
+ rows.push(n[i][c.columns].$row);
403
+ // removeRows used by the pager plugin; don't render if using ajax - fixes #411
404
+ if (!c.appender || (c.pager && (!c.pager.removeRows || !wo.pager_removeRows) && !c.pager.ajax)) {
405
+ $tb.append(n[i][c.columns].$row);
406
+ }
407
+ }
408
+ // restore tbody
409
+ ts.processTbody(table, $tb, false);
410
+ }
411
+ }
412
+ if (c.appender) {
413
+ c.appender(table, rows);
414
+ }
415
+ if (c.debug) {
416
+ benchmark("Rebuilt table", appendTime);
417
+ }
418
+ // apply table widgets; but not before ajax completes
419
+ if (!init && !c.appender) { ts.applyWidget(table); }
420
+ if (table.isUpdating) {
421
+ c.$table.trigger("updateComplete", table);
422
+ }
423
+ }
424
+
425
+ function formatSortingOrder(v) {
426
+ // look for "d" in "desc" order; return true
427
+ return (/^d/i.test(v) || v === 1);
428
+ }
429
+
430
+ function buildHeaders(table) {
431
+ var ch, $t,
432
+ h, i, t, lock, time,
433
+ c = table.config;
434
+ c.headerList = [];
435
+ c.headerContent = [];
436
+ if (c.debug) {
437
+ time = new Date();
438
+ }
439
+ // children tr in tfoot - see issue #196 & #547
440
+ c.columns = ts.computeColumnIndex( c.$table.children('thead, tfoot').children('tr') );
441
+ // add icon if cssIcon option exists
442
+ i = c.cssIcon ? '<i class="' + ( c.cssIcon === ts.css.icon ? ts.css.icon : c.cssIcon + ' ' + ts.css.icon ) + '"></i>' : '';
443
+ // redefine c.$headers here in case of an updateAll that replaces or adds an entire header cell - see #683
444
+ c.$headers = $(table).find(c.selectorHeaders).each(function(index) {
445
+ $t = $(this);
446
+ // make sure to get header cell & not column indexed cell
447
+ ch = ts.getColumnData( table, c.headers, index, true );
448
+ // save original header content
449
+ c.headerContent[index] = $(this).html();
450
+ // if headerTemplate is empty, don't reformat the header cell
451
+ if ( c.headerTemplate !== '' ) {
452
+ // set up header template
453
+ t = c.headerTemplate.replace(/\{content\}/g, $(this).html()).replace(/\{icon\}/g, i);
454
+ if (c.onRenderTemplate) {
455
+ h = c.onRenderTemplate.apply($t, [index, t]);
456
+ if (h && typeof h === 'string') { t = h; } // only change t if something is returned
457
+ }
458
+ $(this).html('<div class="' + ts.css.headerIn + '">' + t + '</div>'); // faster than wrapInner
459
+ }
460
+ if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index]); }
461
+ this.column = parseInt( $(this).attr('data-column'), 10);
462
+ this.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2];
463
+ this.count = -1; // set to -1 because clicking on the header automatically adds one
464
+ this.lockedOrder = false;
465
+ lock = ts.getData($t, ch, 'lockedOrder') || false;
466
+ if (typeof lock !== 'undefined' && lock !== false) {
467
+ this.order = this.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0];
468
+ }
469
+ $t.addClass(ts.css.header + ' ' + c.cssHeader);
470
+ // add cell to headerList
471
+ c.headerList[index] = this;
472
+ // add to parent in case there are multiple rows
473
+ $t.parent().addClass(ts.css.headerRow + ' ' + c.cssHeaderRow).attr('role', 'row');
474
+ // allow keyboard cursor to focus on element
475
+ if (c.tabIndex) { $t.attr("tabindex", 0); }
476
+ }).attr({
477
+ scope: 'col',
478
+ role : 'columnheader'
479
+ });
480
+ // enable/disable sorting
481
+ updateHeader(table);
482
+ if (c.debug) {
483
+ benchmark("Built headers:", time);
484
+ log(c.$headers);
485
+ }
486
+ }
487
+
488
+ function commonUpdate(table, resort, callback) {
489
+ var c = table.config;
490
+ // remove rows/elements before update
491
+ c.$table.find(c.selectorRemove).remove();
492
+ // rebuild parsers
493
+ buildParserCache(table);
494
+ // rebuild the cache map
495
+ buildCache(table);
496
+ checkResort(c.$table, resort, callback);
497
+ }
498
+
499
+ function updateHeader(table) {
500
+ var s, $th, col,
501
+ c = table.config;
502
+ c.$headers.each(function(index, th){
503
+ $th = $(th);
504
+ col = ts.getColumnData( table, c.headers, index, true );
505
+ // add "sorter-false" class if "parser-false" is set
506
+ s = ts.getData( th, col, 'sorter' ) === 'false' || ts.getData( th, col, 'parser' ) === 'false';
507
+ th.sortDisabled = s;
508
+ $th[ s ? 'addClass' : 'removeClass' ]('sorter-false').attr('aria-disabled', '' + s);
509
+ // aria-controls - requires table ID
510
+ if (table.id) {
511
+ if (s) {
512
+ $th.removeAttr('aria-controls');
513
+ } else {
514
+ $th.attr('aria-controls', table.id);
515
+ }
516
+ }
517
+ });
518
+ }
519
+
520
+ function setHeadersCss(table) {
521
+ var f, i, j,
522
+ c = table.config,
523
+ list = c.sortList,
524
+ len = list.length,
525
+ none = ts.css.sortNone + ' ' + c.cssNone,
526
+ css = [ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc],
527
+ aria = ['ascending', 'descending'],
528
+ // find the footer
529
+ $t = $(table).find('tfoot tr').children().add(c.$extraHeaders).removeClass(css.join(' '));
530
+ // remove all header information
531
+ c.$headers
532
+ .removeClass(css.join(' '))
533
+ .addClass(none).attr('aria-sort', 'none');
534
+ for (i = 0; i < len; i++) {
535
+ // direction = 2 means reset!
536
+ if (list[i][1] !== 2) {
537
+ // multicolumn sorting updating - choose the :last in case there are nested columns
538
+ f = c.$headers.not('.sorter-false').filter('[data-column="' + list[i][0] + '"]' + (len === 1 ? ':last' : '') );
539
+ if (f.length) {
540
+ for (j = 0; j < f.length; j++) {
541
+ if (!f[j].sortDisabled) {
542
+ f.eq(j).removeClass(none).addClass(css[list[i][1]]).attr('aria-sort', aria[list[i][1]]);
543
+ }
544
+ }
545
+ // add sorted class to footer & extra headers, if they exist
546
+ if ($t.length) {
547
+ $t.filter('[data-column="' + list[i][0] + '"]').removeClass(none).addClass(css[list[i][1]]);
548
+ }
549
+ }
550
+ }
551
+ }
552
+ // add verbose aria labels
553
+ c.$headers.not('.sorter-false').each(function(){
554
+ var $this = $(this),
555
+ nextSort = this.order[(this.count + 1) % (c.sortReset ? 3 : 2)],
556
+ txt = $this.text() + ': ' +
557
+ ts.language[ $this.hasClass(ts.css.sortAsc) ? 'sortAsc' : $this.hasClass(ts.css.sortDesc) ? 'sortDesc' : 'sortNone' ] +
558
+ ts.language[ nextSort === 0 ? 'nextAsc' : nextSort === 1 ? 'nextDesc' : 'nextNone' ];
559
+ $this.attr('aria-label', txt );
560
+ });
561
+ }
562
+
563
+ // automatically add col group, and column sizes if set
564
+ function fixColumnWidth(table) {
565
+ var colgroup, overallWidth,
566
+ c = table.config;
567
+ if (c.widthFixed && c.$table.find('colgroup').length === 0) {
568
+ colgroup = $('<colgroup>');
569
+ overallWidth = $(table).width();
570
+ // only add col for visible columns - fixes #371
571
+ $(table.tBodies).not('.' + c.cssInfoBlock).find("tr:first").children(":visible").each(function() {
572
+ colgroup.append($('<col>').css('width', parseInt(($(this).width()/overallWidth)*1000, 10)/10 + '%'));
573
+ });
574
+ c.$table.prepend(colgroup);
575
+ }
576
+ }
577
+
578
+ function updateHeaderSortCount(table, list) {
579
+ var s, t, o, col, primary,
580
+ c = table.config,
581
+ sl = list || c.sortList;
582
+ c.sortList = [];
583
+ $.each(sl, function(i,v){
584
+ // ensure all sortList values are numeric - fixes #127
585
+ col = parseInt(v[0], 10);
586
+ // make sure header exists
587
+ o = c.$headers.filter('[data-column="' + col + '"]:last')[0];
588
+ if (o) { // prevents error if sorton array is wrong
589
+ // o.count = o.count + 1;
590
+ t = ('' + v[1]).match(/^(1|d|s|o|n)/);
591
+ t = t ? t[0] : '';
592
+ // 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext
593
+ switch(t) {
594
+ case '1': case 'd': // descending
595
+ t = 1;
596
+ break;
597
+ case 's': // same direction (as primary column)
598
+ // if primary sort is set to "s", make it ascending
599
+ t = primary || 0;
600
+ break;
601
+ case 'o':
602
+ s = o.order[(primary || 0) % (c.sortReset ? 3 : 2)];
603
+ // opposite of primary column; but resets if primary resets
604
+ t = s === 0 ? 1 : s === 1 ? 0 : 2;
605
+ break;
606
+ case 'n':
607
+ o.count = o.count + 1;
608
+ t = o.order[(o.count) % (c.sortReset ? 3 : 2)];
609
+ break;
610
+ default: // ascending
611
+ t = 0;
612
+ break;
613
+ }
614
+ primary = i === 0 ? t : primary;
615
+ s = [ col, parseInt(t, 10) || 0 ];
616
+ c.sortList.push(s);
617
+ t = $.inArray(s[1], o.order); // fixes issue #167
618
+ o.count = t >= 0 ? t : s[1] % (c.sortReset ? 3 : 2);
619
+ }
620
+ });
621
+ }
622
+
623
+ function getCachedSortType(parsers, i) {
624
+ return (parsers && parsers[i]) ? parsers[i].type || '' : '';
625
+ }
626
+
627
+ function initSort(table, cell, event){
628
+ if (table.isUpdating) {
629
+ // let any updates complete before initializing a sort
630
+ return setTimeout(function(){ initSort(table, cell, event); }, 50);
631
+ }
632
+ var arry, indx, col, order, s,
633
+ c = table.config,
634
+ key = !event[c.sortMultiSortKey],
635
+ $table = c.$table;
636
+ // Only call sortStart if sorting is enabled
637
+ $table.trigger("sortStart", table);
638
+ // get current column sort order
639
+ cell.count = event[c.sortResetKey] ? 2 : (cell.count + 1) % (c.sortReset ? 3 : 2);
640
+ // reset all sorts on non-current column - issue #30
641
+ if (c.sortRestart) {
642
+ indx = cell;
643
+ c.$headers.each(function() {
644
+ // only reset counts on columns that weren't just clicked on and if not included in a multisort
645
+ if (this !== indx && (key || !$(this).is('.' + ts.css.sortDesc + ',.' + ts.css.sortAsc))) {
646
+ this.count = -1;
647
+ }
648
+ });
649
+ }
650
+ // get current column index
651
+ indx = cell.column;
652
+ // user only wants to sort on one column
653
+ if (key) {
654
+ // flush the sort list
655
+ c.sortList = [];
656
+ if (c.sortForce !== null) {
657
+ arry = c.sortForce;
658
+ for (col = 0; col < arry.length; col++) {
659
+ if (arry[col][0] !== indx) {
660
+ c.sortList.push(arry[col]);
661
+ }
662
+ }
663
+ }
664
+ // add column to sort list
665
+ order = cell.order[cell.count];
666
+ if (order < 2) {
667
+ c.sortList.push([indx, order]);
668
+ // add other columns if header spans across multiple
669
+ if (cell.colSpan > 1) {
670
+ for (col = 1; col < cell.colSpan; col++) {
671
+ c.sortList.push([indx + col, order]);
672
+ }
673
+ }
674
+ }
675
+ // multi column sorting
676
+ } else {
677
+ // get rid of the sortAppend before adding more - fixes issue #115 & #523
678
+ if (c.sortAppend && c.sortList.length > 1) {
679
+ for (col = 0; col < c.sortAppend.length; col++) {
680
+ s = ts.isValueInArray(c.sortAppend[col][0], c.sortList);
681
+ if (s >= 0) {
682
+ c.sortList.splice(s,1);
683
+ }
684
+ }
685
+ }
686
+ // the user has clicked on an already sorted column
687
+ if (ts.isValueInArray(indx, c.sortList) >= 0) {
688
+ // reverse the sorting direction
689
+ for (col = 0; col < c.sortList.length; col++) {
690
+ s = c.sortList[col];
691
+ order = c.$headers.filter('[data-column="' + s[0] + '"]:last')[0];
692
+ if (s[0] === indx) {
693
+ // order.count seems to be incorrect when compared to cell.count
694
+ s[1] = order.order[cell.count];
695
+ if (s[1] === 2) {
696
+ c.sortList.splice(col,1);
697
+ order.count = -1;
698
+ }
699
+ }
700
+ }
701
+ } else {
702
+ // add column to sort list array
703
+ order = cell.order[cell.count];
704
+ if (order < 2) {
705
+ c.sortList.push([indx, order]);
706
+ // add other columns if header spans across multiple
707
+ if (cell.colSpan > 1) {
708
+ for (col = 1; col < cell.colSpan; col++) {
709
+ c.sortList.push([indx + col, order]);
710
+ }
711
+ }
712
+ }
713
+ }
714
+ }
715
+ if (c.sortAppend !== null) {
716
+ arry = c.sortAppend;
717
+ for (col = 0; col < arry.length; col++) {
718
+ if (arry[col][0] !== indx) {
719
+ c.sortList.push(arry[col]);
720
+ }
721
+ }
722
+ }
723
+ // sortBegin event triggered immediately before the sort
724
+ $table.trigger("sortBegin", table);
725
+ // setTimeout needed so the processing icon shows up
726
+ setTimeout(function(){
727
+ // set css for headers
728
+ setHeadersCss(table);
729
+ multisort(table);
730
+ appendToTable(table);
731
+ $table.trigger("sortEnd", table);
732
+ }, 1);
733
+ }
734
+
735
+ // sort multiple columns
736
+ function multisort(table) { /*jshint loopfunc:true */
737
+ var i, k, num, col, sortTime, colMax,
738
+ cache, order, sort, x, y,
739
+ dir = 0,
740
+ c = table.config,
741
+ cts = c.textSorter || '',
742
+ sortList = c.sortList,
743
+ l = sortList.length,
744
+ bl = table.tBodies.length;
745
+ if (c.serverSideSorting || isEmptyObject(c.cache)) { // empty table - fixes #206/#346
746
+ return;
747
+ }
748
+ if (c.debug) { sortTime = new Date(); }
749
+ for (k = 0; k < bl; k++) {
750
+ colMax = c.cache[k].colMax;
751
+ cache = c.cache[k].normalized;
752
+
753
+ cache.sort(function(a, b) {
754
+ // cache is undefined here in IE, so don't use it!
755
+ for (i = 0; i < l; i++) {
756
+ col = sortList[i][0];
757
+ order = sortList[i][1];
758
+ // sort direction, true = asc, false = desc
759
+ dir = order === 0;
760
+
761
+ if (c.sortStable && a[col] === b[col] && l === 1) {
762
+ return a[c.columns].order - b[c.columns].order;
763
+ }
764
+
765
+ // fallback to natural sort since it is more robust
766
+ num = /n/i.test(getCachedSortType(c.parsers, col));
767
+ if (num && c.strings[col]) {
768
+ // sort strings in numerical columns
769
+ if (typeof (c.string[c.strings[col]]) === 'boolean') {
770
+ num = (dir ? 1 : -1) * (c.string[c.strings[col]] ? -1 : 1);
771
+ } else {
772
+ num = (c.strings[col]) ? c.string[c.strings[col]] || 0 : 0;
773
+ }
774
+ // fall back to built-in numeric sort
775
+ // var sort = $.tablesorter["sort" + s](table, a[c], b[c], c, colMax[c], dir);
776
+ sort = c.numberSorter ? c.numberSorter(a[col], b[col], dir, colMax[col], table) :
777
+ ts[ 'sortNumeric' + (dir ? 'Asc' : 'Desc') ](a[col], b[col], num, colMax[col], col, table);
778
+ } else {
779
+ // set a & b depending on sort direction
780
+ x = dir ? a : b;
781
+ y = dir ? b : a;
782
+ // text sort function
783
+ if (typeof(cts) === 'function') {
784
+ // custom OVERALL text sorter
785
+ sort = cts(x[col], y[col], dir, col, table);
786
+ } else if (typeof(cts) === 'object' && cts.hasOwnProperty(col)) {
787
+ // custom text sorter for a SPECIFIC COLUMN
788
+ sort = cts[col](x[col], y[col], dir, col, table);
789
+ } else {
790
+ // fall back to natural sort
791
+ sort = ts[ 'sortNatural' + (dir ? 'Asc' : 'Desc') ](a[col], b[col], col, table, c);
792
+ }
793
+ }
794
+ if (sort) { return sort; }
795
+ }
796
+ return a[c.columns].order - b[c.columns].order;
797
+ });
798
+ }
799
+ if (c.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time", sortTime); }
800
+ }
801
+
802
+ function resortComplete($table, callback){
803
+ var table = $table[0];
804
+ if (table.isUpdating) {
805
+ $table.trigger('updateComplete', table);
806
+ }
807
+ if ($.isFunction(callback)) {
808
+ callback($table[0]);
809
+ }
810
+ }
811
+
812
+ function checkResort($table, flag, callback) {
813
+ var sl = $table[0].config.sortList;
814
+ // don't try to resort if the table is still processing
815
+ // this will catch spamming of the updateCell method
816
+ if (flag !== false && !$table[0].isProcessing && sl.length) {
817
+ $table.trigger("sorton", [sl, function(){
818
+ resortComplete($table, callback);
819
+ }, true]);
820
+ } else {
821
+ resortComplete($table, callback);
822
+ ts.applyWidget($table[0], false);
823
+ }
824
+ }
825
+
826
+ function bindMethods(table){
827
+ var c = table.config,
828
+ $table = c.$table;
829
+ // apply easy methods that trigger bound events
830
+ $table
831
+ .unbind('sortReset update updateRows updateCell updateAll addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave '.split(' ').join(c.namespace + ' '))
832
+ .bind("sortReset" + c.namespace, function(e, callback){
833
+ e.stopPropagation();
834
+ c.sortList = [];
835
+ setHeadersCss(table);
836
+ multisort(table);
837
+ appendToTable(table);
838
+ if ($.isFunction(callback)) {
839
+ callback(table);
840
+ }
841
+ })
842
+ .bind("updateAll" + c.namespace, function(e, resort, callback){
843
+ e.stopPropagation();
844
+ table.isUpdating = true;
845
+ ts.refreshWidgets(table, true, true);
846
+ ts.restoreHeaders(table);
847
+ buildHeaders(table);
848
+ ts.bindEvents(table, c.$headers, true);
849
+ bindMethods(table);
850
+ commonUpdate(table, resort, callback);
851
+ })
852
+ .bind("update" + c.namespace + " updateRows" + c.namespace, function(e, resort, callback) {
853
+ e.stopPropagation();
854
+ table.isUpdating = true;
855
+ // update sorting (if enabled/disabled)
856
+ updateHeader(table);
857
+ commonUpdate(table, resort, callback);
858
+ })
859
+ .bind("updateCell" + c.namespace, function(e, cell, resort, callback) {
860
+ e.stopPropagation();
861
+ table.isUpdating = true;
862
+ $table.find(c.selectorRemove).remove();
863
+ // get position from the dom
864
+ var v, t, row, icell,
865
+ $tb = $table.find('tbody'),
866
+ $cell = $(cell),
867
+ // update cache - format: function(s, table, cell, cellIndex)
868
+ // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
869
+ tbdy = $tb.index( $.fn.closest ? $cell.closest('tbody') : $cell.parents('tbody').filter(':first') ),
870
+ $row = $.fn.closest ? $cell.closest('tr') : $cell.parents('tr').filter(':first');
871
+ cell = $cell[0]; // in case cell is a jQuery object
872
+ // tbody may not exist if update is initialized while tbody is removed for processing
873
+ if ($tb.length && tbdy >= 0) {
874
+ row = $tb.eq(tbdy).find('tr').index( $row );
875
+ icell = $cell.index();
876
+ c.cache[tbdy].normalized[row][c.columns].$row = $row;
877
+ if (typeof c.extractors[icell].id === 'undefined') {
878
+ t = getElementText(table, cell, icell);
879
+ } else {
880
+ t = c.extractors[icell].format( getElementText(table, cell, icell), table, cell, icell );
881
+ }
882
+ v = c.parsers[icell].id === 'no-parser' ? '' :
883
+ c.parsers[icell].format( t, table, cell, icell );
884
+ c.cache[tbdy].normalized[row][icell] = c.ignoreCase && typeof v === 'string' ? v.toLowerCase() : v;
885
+ if ((c.parsers[icell].type || '').toLowerCase() === "numeric") {
886
+ // update column max value (ignore sign)
887
+ c.cache[tbdy].colMax[icell] = Math.max(Math.abs(v) || 0, c.cache[tbdy].colMax[icell] || 0);
888
+ }
889
+ checkResort($table, resort, callback);
890
+ }
891
+ })
892
+ .bind("addRows" + c.namespace, function(e, $row, resort, callback) {
893
+ e.stopPropagation();
894
+ table.isUpdating = true;
895
+ if (isEmptyObject(c.cache)) {
896
+ // empty table, do an update instead - fixes #450
897
+ updateHeader(table);
898
+ commonUpdate(table, resort, callback);
899
+ } else {
900
+ $row = $($row).attr('role', 'row'); // make sure we're using a jQuery object
901
+ var i, j, l, t, v, rowData, cells,
902
+ rows = $row.filter('tr').length,
903
+ tbdy = $table.find('tbody').index( $row.parents('tbody').filter(':first') );
904
+ // fixes adding rows to an empty table - see issue #179
905
+ if (!(c.parsers && c.parsers.length)) {
906
+ buildParserCache(table);
907
+ }
908
+ // add each row
909
+ for (i = 0; i < rows; i++) {
910
+ l = $row[i].cells.length;
911
+ cells = [];
912
+ rowData = {
913
+ child: [],
914
+ $row : $row.eq(i),
915
+ order: c.cache[tbdy].normalized.length
916
+ };
917
+ // add each cell
918
+ for (j = 0; j < l; j++) {
919
+ if (typeof c.extractors[j].id === 'undefined') {
920
+ t = getElementText(table, $row[i].cells[j], j);
921
+ } else {
922
+ t = c.extractors[j].format( getElementText(table, $row[i].cells[j], j), table, $row[i].cells[j], j );
923
+ }
924
+ v = c.parsers[j].id === 'no-parser' ? '' :
925
+ c.parsers[j].format( t, table, $row[i].cells[j], j );
926
+ cells[j] = c.ignoreCase && typeof v === 'string' ? v.toLowerCase() : v;
927
+ if ((c.parsers[j].type || '').toLowerCase() === "numeric") {
928
+ // update column max value (ignore sign)
929
+ c.cache[tbdy].colMax[j] = Math.max(Math.abs(cells[j]) || 0, c.cache[tbdy].colMax[j] || 0);
930
+ }
931
+ }
932
+ // add the row data to the end
933
+ cells.push(rowData);
934
+ // update cache
935
+ c.cache[tbdy].normalized.push(cells);
936
+ }
937
+ // resort using current settings
938
+ checkResort($table, resort, callback);
939
+ }
940
+ })
941
+ .bind("updateComplete" + c.namespace, function(){
942
+ table.isUpdating = false;
943
+ })
944
+ .bind("sorton" + c.namespace, function(e, list, callback, init) {
945
+ var c = table.config;
946
+ e.stopPropagation();
947
+ $table.trigger("sortStart", this);
948
+ // update header count index
949
+ updateHeaderSortCount(table, list);
950
+ // set css for headers
951
+ setHeadersCss(table);
952
+ // fixes #346
953
+ if (c.delayInit && isEmptyObject(c.cache)) { buildCache(table); }
954
+ $table.trigger("sortBegin", this);
955
+ // sort the table and append it to the dom
956
+ multisort(table);
957
+ appendToTable(table, init);
958
+ $table.trigger("sortEnd", this);
959
+ ts.applyWidget(table);
960
+ if ($.isFunction(callback)) {
961
+ callback(table);
962
+ }
963
+ })
964
+ .bind("appendCache" + c.namespace, function(e, callback, init) {
965
+ e.stopPropagation();
966
+ appendToTable(table, init);
967
+ if ($.isFunction(callback)) {
968
+ callback(table);
969
+ }
970
+ })
971
+ .bind("updateCache" + c.namespace, function(e, callback){
972
+ // rebuild parsers
973
+ if (!(c.parsers && c.parsers.length)) {
974
+ buildParserCache(table);
975
+ }
976
+ // rebuild the cache map
977
+ buildCache(table);
978
+ if ($.isFunction(callback)) {
979
+ callback(table);
980
+ }
981
+ })
982
+ .bind("applyWidgetId" + c.namespace, function(e, id) {
983
+ e.stopPropagation();
984
+ ts.getWidgetById(id).format(table, c, c.widgetOptions);
985
+ })
986
+ .bind("applyWidgets" + c.namespace, function(e, init) {
987
+ e.stopPropagation();
988
+ // apply widgets
989
+ ts.applyWidget(table, init);
990
+ })
991
+ .bind("refreshWidgets" + c.namespace, function(e, all, dontapply){
992
+ e.stopPropagation();
993
+ ts.refreshWidgets(table, all, dontapply);
994
+ })
995
+ .bind("destroy" + c.namespace, function(e, c, cb){
996
+ e.stopPropagation();
997
+ ts.destroy(table, c, cb);
998
+ })
999
+ .bind("resetToLoadState" + c.namespace, function(){
1000
+ // remove all widgets
1001
+ ts.refreshWidgets(table, true, true);
1002
+ // restore original settings; this clears out current settings, but does not clear
1003
+ // values saved to storage.
1004
+ c = $.extend(true, ts.defaults, c.originalSettings);
1005
+ table.hasInitialized = false;
1006
+ // setup the entire table again
1007
+ ts.setup( table, c );
1008
+ });
1009
+ }
1010
+
1011
+ /* public methods */
1012
+ ts.construct = function(settings) {
1013
+ return this.each(function() {
1014
+ var table = this,
1015
+ // merge & extend config options
1016
+ c = $.extend(true, {}, ts.defaults, settings);
1017
+ // save initial settings
1018
+ c.originalSettings = settings;
1019
+ // create a table from data (build table widget)
1020
+ if (!table.hasInitialized && ts.buildTable && this.tagName !== 'TABLE') {
1021
+ // return the table (in case the original target is the table's container)
1022
+ ts.buildTable(table, c);
1023
+ } else {
1024
+ ts.setup(table, c);
1025
+ }
1026
+ });
1027
+ };
1028
+
1029
+ ts.setup = function(table, c) {
1030
+ // if no thead or tbody, or tablesorter is already present, quit
1031
+ if (!table || !table.tHead || table.tBodies.length === 0 || table.hasInitialized === true) {
1032
+ return c.debug ? log('ERROR: stopping initialization! No table, thead, tbody or tablesorter has already been initialized') : '';
1033
+ }
1034
+
1035
+ var k = '',
1036
+ $table = $(table),
1037
+ m = $.metadata;
1038
+ // initialization flag
1039
+ table.hasInitialized = false;
1040
+ // table is being processed flag
1041
+ table.isProcessing = true;
1042
+ // make sure to store the config object
1043
+ table.config = c;
1044
+ // save the settings where they read
1045
+ $.data(table, "tablesorter", c);
1046
+ if (c.debug) { $.data( table, 'startoveralltimer', new Date()); }
1047
+
1048
+ // removing this in version 3 (only supports jQuery 1.7+)
1049
+ c.supportsDataObject = (function(version) {
1050
+ version[0] = parseInt(version[0], 10);
1051
+ return (version[0] > 1) || (version[0] === 1 && parseInt(version[1], 10) >= 4);
1052
+ })($.fn.jquery.split("."));
1053
+ // digit sort text location; keeping max+/- for backwards compatibility
1054
+ c.string = { 'max': 1, 'min': -1, 'emptymin': 1, 'emptymax': -1, 'zero': 0, 'none': 0, 'null': 0, 'top': true, 'bottom': false };
1055
+ // ensure case insensitivity
1056
+ c.emptyTo = c.emptyTo.toLowerCase();
1057
+ c.stringTo = c.stringTo.toLowerCase();
1058
+ // add table theme class only if there isn't already one there
1059
+ if (!/tablesorter\-/.test($table.attr('class'))) {
1060
+ k = (c.theme !== '' ? ' tablesorter-' + c.theme : '');
1061
+ }
1062
+ c.table = table;
1063
+ c.$table = $table
1064
+ .addClass(ts.css.table + ' ' + c.tableClass + k)
1065
+ .attr('role', 'grid');
1066
+ c.$headers = $table.find(c.selectorHeaders);
1067
+
1068
+ // give the table a unique id, which will be used in namespace binding
1069
+ if (!c.namespace) {
1070
+ c.namespace = '.tablesorter' + Math.random().toString(16).slice(2);
1071
+ } else {
1072
+ // make sure namespace starts with a period & doesn't have weird characters
1073
+ c.namespace = '.' + c.namespace.replace(/\W/g,'');
1074
+ }
1075
+
1076
+ c.$table.children().children('tr').attr('role', 'row');
1077
+ c.$tbodies = $table.children('tbody:not(.' + c.cssInfoBlock + ')').attr({
1078
+ 'aria-live' : 'polite',
1079
+ 'aria-relevant' : 'all'
1080
+ });
1081
+ if (c.$table.find('caption').length) {
1082
+ c.$table.attr('aria-labelledby', 'theCaption');
1083
+ }
1084
+ c.widgetInit = {}; // keep a list of initialized widgets
1085
+ // change textExtraction via data-attribute
1086
+ c.textExtraction = c.$table.attr('data-text-extraction') || c.textExtraction || 'basic';
1087
+ // build headers
1088
+ buildHeaders(table);
1089
+ // fixate columns if the users supplies the fixedWidth option
1090
+ // do this after theme has been applied
1091
+ fixColumnWidth(table);
1092
+ // try to auto detect column type, and store in tables config
1093
+ buildParserCache(table);
1094
+ // start total row count at zero
1095
+ c.totalRows = 0;
1096
+ // build the cache for the tbody cells
1097
+ // delayInit will delay building the cache until the user starts a sort
1098
+ if (!c.delayInit) { buildCache(table); }
1099
+ // bind all header events and methods
1100
+ ts.bindEvents(table, c.$headers, true);
1101
+ bindMethods(table);
1102
+ // get sort list from jQuery data or metadata
1103
+ // in jQuery < 1.4, an error occurs when calling $table.data()
1104
+ if (c.supportsDataObject && typeof $table.data().sortlist !== 'undefined') {
1105
+ c.sortList = $table.data().sortlist;
1106
+ } else if (m && ($table.metadata() && $table.metadata().sortlist)) {
1107
+ c.sortList = $table.metadata().sortlist;
1108
+ }
1109
+ // apply widget init code
1110
+ ts.applyWidget(table, true);
1111
+ // if user has supplied a sort list to constructor
1112
+ if (c.sortList.length > 0) {
1113
+ $table.trigger("sorton", [c.sortList, {}, !c.initWidgets, true]);
1114
+ } else {
1115
+ setHeadersCss(table);
1116
+ if (c.initWidgets) {
1117
+ // apply widget format
1118
+ ts.applyWidget(table, false);
1119
+ }
1120
+ }
1121
+
1122
+ // show processesing icon
1123
+ if (c.showProcessing) {
1124
+ $table
1125
+ .unbind('sortBegin' + c.namespace + ' sortEnd' + c.namespace)
1126
+ .bind('sortBegin' + c.namespace + ' sortEnd' + c.namespace, function(e) {
1127
+ clearTimeout(c.processTimer);
1128
+ ts.isProcessing(table);
1129
+ if (e.type === 'sortBegin') {
1130
+ c.processTimer = setTimeout(function(){
1131
+ ts.isProcessing(table, true);
1132
+ }, 500);
1133
+ }
1134
+ });
1135
+ }
1136
+
1137
+ // initialized
1138
+ table.hasInitialized = true;
1139
+ table.isProcessing = false;
1140
+ if (c.debug) {
1141
+ ts.benchmark("Overall initialization time", $.data( table, 'startoveralltimer'));
1142
+ }
1143
+ $table.trigger('tablesorter-initialized', table);
1144
+ if (typeof c.initialized === 'function') { c.initialized(table); }
1145
+ };
1146
+
1147
+ ts.getColumnData = function(table, obj, indx, getCell){
1148
+ if (typeof obj === 'undefined' || obj === null) { return; }
1149
+ table = $(table)[0];
1150
+ var result, $h, k,
1151
+ c = table.config;
1152
+ if (obj[indx]) {
1153
+ return getCell ? obj[indx] : obj[c.$headers.index( c.$headers.filter('[data-column="' + indx + '"]:last') )];
1154
+ }
1155
+ for (k in obj) {
1156
+ if (typeof k === 'string') {
1157
+ if (getCell) {
1158
+ // get header cell
1159
+ $h = c.$headers.eq(indx).filter(k);
1160
+ } else {
1161
+ // get column indexed cell
1162
+ $h = c.$headers.filter('[data-column="' + indx + '"]:last').filter(k);
1163
+ }
1164
+ if ($h.length) {
1165
+ return obj[k];
1166
+ }
1167
+ }
1168
+ }
1169
+ return result;
1170
+ };
1171
+
1172
+ // computeTableHeaderCellIndexes from:
1173
+ // http://www.javascripttoolbox.com/lib/table/examples.php
1174
+ // http://www.javascripttoolbox.com/temp/table_cellindex.html
1175
+ ts.computeColumnIndex = function(trs) {
1176
+ var matrix = [],
1177
+ lookup = {},
1178
+ cols = 0, // determine the number of columns
1179
+ i, j, k, l, $cell, cell, cells, rowIndex, cellId, rowSpan, colSpan, firstAvailCol, matrixrow;
1180
+ for (i = 0; i < trs.length; i++) {
1181
+ cells = trs[i].cells;
1182
+ for (j = 0; j < cells.length; j++) {
1183
+ cell = cells[j];
1184
+ $cell = $(cell);
1185
+ rowIndex = cell.parentNode.rowIndex;
1186
+ cellId = rowIndex + "-" + $cell.index();
1187
+ rowSpan = cell.rowSpan || 1;
1188
+ colSpan = cell.colSpan || 1;
1189
+ if (typeof(matrix[rowIndex]) === "undefined") {
1190
+ matrix[rowIndex] = [];
1191
+ }
1192
+ // Find first available column in the first row
1193
+ for (k = 0; k < matrix[rowIndex].length + 1; k++) {
1194
+ if (typeof(matrix[rowIndex][k]) === "undefined") {
1195
+ firstAvailCol = k;
1196
+ break;
1197
+ }
1198
+ }
1199
+ lookup[cellId] = firstAvailCol;
1200
+ cols = Math.max(firstAvailCol, cols);
1201
+ // add data-column
1202
+ $cell.attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex
1203
+ for (k = rowIndex; k < rowIndex + rowSpan; k++) {
1204
+ if (typeof(matrix[k]) === "undefined") {
1205
+ matrix[k] = [];
1206
+ }
1207
+ matrixrow = matrix[k];
1208
+ for (l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
1209
+ matrixrow[l] = "x";
1210
+ }
1211
+ }
1212
+ }
1213
+ }
1214
+ // may not be accurate if # header columns !== # tbody columns
1215
+ return cols + 1; // add one because it's a zero-based index
1216
+ };
1217
+
1218
+ // *** Process table ***
1219
+ // add processing indicator
1220
+ ts.isProcessing = function(table, toggle, $ths) {
1221
+ table = $(table);
1222
+ var c = table[0].config,
1223
+ // default to all headers
1224
+ $h = $ths || table.find('.' + ts.css.header);
1225
+ if (toggle) {
1226
+ // don't use sortList if custom $ths used
1227
+ if (typeof $ths !== 'undefined' && c.sortList.length > 0) {
1228
+ // get headers from the sortList
1229
+ $h = $h.filter(function(){
1230
+ // get data-column from attr to keep compatibility with jQuery 1.2.6
1231
+ return this.sortDisabled ? false : ts.isValueInArray( parseFloat($(this).attr('data-column')), c.sortList) >= 0;
1232
+ });
1233
+ }
1234
+ table.add($h).addClass(ts.css.processing + ' ' + c.cssProcessing);
1235
+ } else {
1236
+ table.add($h).removeClass(ts.css.processing + ' ' + c.cssProcessing);
1237
+ }
1238
+ };
1239
+
1240
+ // detach tbody but save the position
1241
+ // don't use tbody because there are portions that look for a tbody index (updateCell)
1242
+ ts.processTbody = function(table, $tb, getIt){
1243
+ table = $(table)[0];
1244
+ var holdr;
1245
+ if (getIt) {
1246
+ table.isProcessing = true;
1247
+ $tb.before('<span class="tablesorter-savemyplace"/>');
1248
+ holdr = ($.fn.detach) ? $tb.detach() : $tb.remove();
1249
+ return holdr;
1250
+ }
1251
+ holdr = $(table).find('span.tablesorter-savemyplace');
1252
+ $tb.insertAfter( holdr );
1253
+ holdr.remove();
1254
+ table.isProcessing = false;
1255
+ };
1256
+
1257
+ ts.clearTableBody = function(table) {
1258
+ $(table)[0].config.$tbodies.children().detach();
1259
+ };
1260
+
1261
+ ts.bindEvents = function(table, $headers, core){
1262
+ table = $(table)[0];
1263
+ var downTime,
1264
+ c = table.config;
1265
+ if (core !== true) {
1266
+ c.$extraHeaders = c.$extraHeaders ? c.$extraHeaders.add($headers) : $headers;
1267
+ }
1268
+ // apply event handling to headers and/or additional headers (stickyheaders, scroller, etc)
1269
+ $headers
1270
+ // http://stackoverflow.com/questions/5312849/jquery-find-self;
1271
+ .find(c.selectorSort).add( $headers.filter(c.selectorSort) )
1272
+ .unbind('mousedown mouseup sort keyup '.split(' ').join(c.namespace + ' '))
1273
+ .bind('mousedown mouseup sort keyup '.split(' ').join(c.namespace + ' '), function(e, external) {
1274
+ var cell, type = e.type;
1275
+ // only recognize left clicks or enter
1276
+ if ( ((e.which || e.button) !== 1 && !/sort|keyup/.test(type)) || (type === 'keyup' && e.which !== 13) ) {
1277
+ return;
1278
+ }
1279
+ // ignore long clicks (prevents resizable widget from initializing a sort)
1280
+ if (type === 'mouseup' && external !== true && (new Date().getTime() - downTime > 250)) { return; }
1281
+ // set timer on mousedown
1282
+ if (type === 'mousedown') {
1283
+ downTime = new Date().getTime();
1284
+ return /(input|select|button|textarea)/i.test(e.target.tagName) ? '' : !c.cancelSelection;
1285
+ }
1286
+ if (c.delayInit && isEmptyObject(c.cache)) { buildCache(table); }
1287
+ // jQuery v1.2.6 doesn't have closest()
1288
+ cell = $.fn.closest ? $(this).closest('th, td')[0] : /TH|TD/.test(this.tagName) ? this : $(this).parents('th, td')[0];
1289
+ // reference original table headers and find the same cell
1290
+ cell = c.$headers[ $headers.index( cell ) ];
1291
+ if (!cell.sortDisabled) {
1292
+ initSort(table, cell, e);
1293
+ }
1294
+ });
1295
+ if (c.cancelSelection) {
1296
+ // cancel selection
1297
+ $headers
1298
+ .attr('unselectable', 'on')
1299
+ .bind('selectstart', false)
1300
+ .css({
1301
+ 'user-select': 'none',
1302
+ 'MozUserSelect': 'none' // not needed for jQuery 1.8+
1303
+ });
1304
+ }
1305
+ };
1306
+
1307
+ // restore headers
1308
+ ts.restoreHeaders = function(table){
1309
+ var c = $(table)[0].config;
1310
+ // don't use c.$headers here in case header cells were swapped
1311
+ c.$table.find(c.selectorHeaders).each(function(i){
1312
+ // only restore header cells if it is wrapped
1313
+ // because this is also used by the updateAll method
1314
+ if ($(this).find('.' + ts.css.headerIn).length){
1315
+ $(this).html( c.headerContent[i] );
1316
+ }
1317
+ });
1318
+ };
1319
+
1320
+ ts.destroy = function(table, removeClasses, callback){
1321
+ table = $(table)[0];
1322
+ if (!table.hasInitialized) { return; }
1323
+ // remove all widgets
1324
+ ts.refreshWidgets(table, true, true);
1325
+ var $t = $(table), c = table.config,
1326
+ $h = $t.find('thead:first'),
1327
+ $r = $h.find('tr.' + ts.css.headerRow).removeClass(ts.css.headerRow + ' ' + c.cssHeaderRow),
1328
+ $f = $t.find('tfoot:first > tr').children('th, td');
1329
+ if (removeClasses === false && $.inArray('uitheme', c.widgets) >= 0) {
1330
+ // reapply uitheme classes, in case we want to maintain appearance
1331
+ $t.trigger('applyWidgetId', ['uitheme']);
1332
+ $t.trigger('applyWidgetId', ['zebra']);
1333
+ }
1334
+ // remove widget added rows, just in case
1335
+ $h.find('tr').not($r).remove();
1336
+ // disable tablesorter
1337
+ $t
1338
+ .removeData('tablesorter')
1339
+ .unbind('sortReset update updateAll updateRows updateCell addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd resetToLoadState '.split(' ').join(c.namespace + ' '));
1340
+ c.$headers.add($f)
1341
+ .removeClass( [ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone].join(' ') )
1342
+ .removeAttr('data-column')
1343
+ .removeAttr('aria-label')
1344
+ .attr('aria-disabled', 'true');
1345
+ $r.find(c.selectorSort).unbind('mousedown mouseup keypress '.split(' ').join(c.namespace + ' '));
1346
+ ts.restoreHeaders(table);
1347
+ $t.toggleClass(ts.css.table + ' ' + c.tableClass + ' tablesorter-' + c.theme, removeClasses === false);
1348
+ // clear flag in case the plugin is initialized again
1349
+ table.hasInitialized = false;
1350
+ delete table.config.cache;
1351
+ if (typeof callback === 'function') {
1352
+ callback(table);
1353
+ }
1354
+ };
1355
+
1356
+ // *** sort functions ***
1357
+ // regex used in natural sort
1358
+ ts.regex = {
1359
+ chunk : /(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi, // chunk/tokenize numbers & letters
1360
+ chunks: /(^\\0|\\0$)/, // replace chunks @ ends
1361
+ hex: /^0x[0-9a-f]+$/i // hex
1362
+ };
1363
+
1364
+ // Natural sort - https://github.com/overset/javascript-natural-sort (date sorting removed)
1365
+ // this function will only accept strings, or you'll see "TypeError: undefined is not a function"
1366
+ // I could add a = a.toString(); b = b.toString(); but it'll slow down the sort overall
1367
+ ts.sortNatural = function(a, b) {
1368
+ if (a === b) { return 0; }
1369
+ var xN, xD, yN, yD, xF, yF, i, mx,
1370
+ r = ts.regex;
1371
+ // first try and sort Hex codes
1372
+ if (r.hex.test(b)) {
1373
+ xD = parseInt(a.match(r.hex), 16);
1374
+ yD = parseInt(b.match(r.hex), 16);
1375
+ if ( xD < yD ) { return -1; }
1376
+ if ( xD > yD ) { return 1; }
1377
+ }
1378
+ // chunk/tokenize
1379
+ xN = a.replace(r.chunk, '\\0$1\\0').replace(r.chunks, '').split('\\0');
1380
+ yN = b.replace(r.chunk, '\\0$1\\0').replace(r.chunks, '').split('\\0');
1381
+ mx = Math.max(xN.length, yN.length);
1382
+ // natural sorting through split numeric strings and default strings
1383
+ for (i = 0; i < mx; i++) {
1384
+ // find floats not starting with '0', string or 0 if not defined
1385
+ xF = isNaN(xN[i]) ? xN[i] || 0 : parseFloat(xN[i]) || 0;
1386
+ yF = isNaN(yN[i]) ? yN[i] || 0 : parseFloat(yN[i]) || 0;
1387
+ // handle numeric vs string comparison - number < string - (Kyle Adams)
1388
+ if (isNaN(xF) !== isNaN(yF)) { return (isNaN(xF)) ? 1 : -1; }
1389
+ // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
1390
+ if (typeof xF !== typeof yF) {
1391
+ xF += '';
1392
+ yF += '';
1393
+ }
1394
+ if (xF < yF) { return -1; }
1395
+ if (xF > yF) { return 1; }
1396
+ }
1397
+ return 0;
1398
+ };
1399
+
1400
+ ts.sortNaturalAsc = function(a, b, col, table, c) {
1401
+ if (a === b) { return 0; }
1402
+ var e = c.string[ (c.empties[col] || c.emptyTo ) ];
1403
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
1404
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
1405
+ return ts.sortNatural(a, b);
1406
+ };
1407
+
1408
+ ts.sortNaturalDesc = function(a, b, col, table, c) {
1409
+ if (a === b) { return 0; }
1410
+ var e = c.string[ (c.empties[col] || c.emptyTo ) ];
1411
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
1412
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
1413
+ return ts.sortNatural(b, a);
1414
+ };
1415
+
1416
+ // basic alphabetical sort
1417
+ ts.sortText = function(a, b) {
1418
+ return a > b ? 1 : (a < b ? -1 : 0);
1419
+ };
1420
+
1421
+ // return text string value by adding up ascii value
1422
+ // so the text is somewhat sorted when using a digital sort
1423
+ // this is NOT an alphanumeric sort
1424
+ ts.getTextValue = function(a, num, mx) {
1425
+ if (mx) {
1426
+ // make sure the text value is greater than the max numerical value (mx)
1427
+ var i, l = a ? a.length : 0, n = mx + num;
1428
+ for (i = 0; i < l; i++) {
1429
+ n += a.charCodeAt(i);
1430
+ }
1431
+ return num * n;
1432
+ }
1433
+ return 0;
1434
+ };
1435
+
1436
+ ts.sortNumericAsc = function(a, b, num, mx, col, table) {
1437
+ if (a === b) { return 0; }
1438
+ var c = table.config,
1439
+ e = c.string[ (c.empties[col] || c.emptyTo ) ];
1440
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
1441
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
1442
+ if (isNaN(a)) { a = ts.getTextValue(a, num, mx); }
1443
+ if (isNaN(b)) { b = ts.getTextValue(b, num, mx); }
1444
+ return a - b;
1445
+ };
1446
+
1447
+ ts.sortNumericDesc = function(a, b, num, mx, col, table) {
1448
+ if (a === b) { return 0; }
1449
+ var c = table.config,
1450
+ e = c.string[ (c.empties[col] || c.emptyTo ) ];
1451
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
1452
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
1453
+ if (isNaN(a)) { a = ts.getTextValue(a, num, mx); }
1454
+ if (isNaN(b)) { b = ts.getTextValue(b, num, mx); }
1455
+ return b - a;
1456
+ };
1457
+
1458
+ ts.sortNumeric = function(a, b) {
1459
+ return a - b;
1460
+ };
1461
+
1462
+ // used when replacing accented characters during sorting
1463
+ ts.characterEquivalents = {
1464
+ "a" : "\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5", // áàâãäąå
1465
+ "A" : "\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5", // ÁÀÂÃÄĄÅ
1466
+ "c" : "\u00e7\u0107\u010d", // çćč
1467
+ "C" : "\u00c7\u0106\u010c", // ÇĆČ
1468
+ "e" : "\u00e9\u00e8\u00ea\u00eb\u011b\u0119", // éèêëěę
1469
+ "E" : "\u00c9\u00c8\u00ca\u00cb\u011a\u0118", // ÉÈÊËĚĘ
1470
+ "i" : "\u00ed\u00ec\u0130\u00ee\u00ef\u0131", // íìİîïı
1471
+ "I" : "\u00cd\u00cc\u0130\u00ce\u00cf", // ÍÌİÎÏ
1472
+ "o" : "\u00f3\u00f2\u00f4\u00f5\u00f6", // óòôõö
1473
+ "O" : "\u00d3\u00d2\u00d4\u00d5\u00d6", // ÓÒÔÕÖ
1474
+ "ss": "\u00df", // ß (s sharp)
1475
+ "SS": "\u1e9e", // ẞ (Capital sharp s)
1476
+ "u" : "\u00fa\u00f9\u00fb\u00fc\u016f", // úùûüů
1477
+ "U" : "\u00da\u00d9\u00db\u00dc\u016e" // ÚÙÛÜŮ
1478
+ };
1479
+ ts.replaceAccents = function(s) {
1480
+ var a, acc = '[', eq = ts.characterEquivalents;
1481
+ if (!ts.characterRegex) {
1482
+ ts.characterRegexArray = {};
1483
+ for (a in eq) {
1484
+ if (typeof a === 'string') {
1485
+ acc += eq[a];
1486
+ ts.characterRegexArray[a] = new RegExp('[' + eq[a] + ']', 'g');
1487
+ }
1488
+ }
1489
+ ts.characterRegex = new RegExp(acc + ']');
1490
+ }
1491
+ if (ts.characterRegex.test(s)) {
1492
+ for (a in eq) {
1493
+ if (typeof a === 'string') {
1494
+ s = s.replace( ts.characterRegexArray[a], a );
1495
+ }
1496
+ }
1497
+ }
1498
+ return s;
1499
+ };
1500
+
1501
+ // *** utilities ***
1502
+ ts.isValueInArray = function(column, arry) {
1503
+ var indx, len = arry.length;
1504
+ for (indx = 0; indx < len; indx++) {
1505
+ if (arry[indx][0] === column) {
1506
+ return indx;
1507
+ }
1508
+ }
1509
+ return -1;
1510
+ };
1511
+
1512
+ ts.addParser = function(parser) {
1513
+ var i, l = ts.parsers.length, a = true;
1514
+ for (i = 0; i < l; i++) {
1515
+ if (ts.parsers[i].id.toLowerCase() === parser.id.toLowerCase()) {
1516
+ a = false;
1517
+ }
1518
+ }
1519
+ if (a) {
1520
+ ts.parsers.push(parser);
1521
+ }
1522
+ };
1523
+
1524
+ ts.getParserById = function(name) {
1525
+ /*jshint eqeqeq:false */
1526
+ if (name == 'false') { return false; }
1527
+ var i, l = ts.parsers.length;
1528
+ for (i = 0; i < l; i++) {
1529
+ if (ts.parsers[i].id.toLowerCase() === (name.toString()).toLowerCase()) {
1530
+ return ts.parsers[i];
1531
+ }
1532
+ }
1533
+ return false;
1534
+ };
1535
+
1536
+ ts.addWidget = function(widget) {
1537
+ ts.widgets.push(widget);
1538
+ };
1539
+
1540
+ ts.hasWidget = function(table, name){
1541
+ table = $(table);
1542
+ return table.length && table[0].config && table[0].config.widgetInit[name] || false;
1543
+ };
1544
+
1545
+ ts.getWidgetById = function(name) {
1546
+ var i, w, l = ts.widgets.length;
1547
+ for (i = 0; i < l; i++) {
1548
+ w = ts.widgets[i];
1549
+ if (w && w.hasOwnProperty('id') && w.id.toLowerCase() === name.toLowerCase()) {
1550
+ return w;
1551
+ }
1552
+ }
1553
+ };
1554
+
1555
+ ts.applyWidget = function(table, init) {
1556
+ table = $(table)[0]; // in case this is called externally
1557
+ var c = table.config,
1558
+ wo = c.widgetOptions,
1559
+ widgets = [],
1560
+ time, w, wd;
1561
+ // prevent numerous consecutive widget applications
1562
+ if (init !== false && table.hasInitialized && (table.isApplyingWidgets || table.isUpdating)) { return; }
1563
+ if (c.debug) { time = new Date(); }
1564
+ if (c.widgets.length) {
1565
+ table.isApplyingWidgets = true;
1566
+ // ensure unique widget ids
1567
+ c.widgets = $.grep(c.widgets, function(v, k){
1568
+ return $.inArray(v, c.widgets) === k;
1569
+ });
1570
+ // build widget array & add priority as needed
1571
+ $.each(c.widgets || [], function(i,n){
1572
+ wd = ts.getWidgetById(n);
1573
+ if (wd && wd.id) {
1574
+ // set priority to 10 if not defined
1575
+ if (!wd.priority) { wd.priority = 10; }
1576
+ widgets[i] = wd;
1577
+ }
1578
+ });
1579
+ // sort widgets by priority
1580
+ widgets.sort(function(a, b){
1581
+ return a.priority < b.priority ? -1 : a.priority === b.priority ? 0 : 1;
1582
+ });
1583
+ // add/update selected widgets
1584
+ $.each(widgets, function(i,w){
1585
+ if (w) {
1586
+ if (init || !(c.widgetInit[w.id])) {
1587
+ // set init flag first to prevent calling init more than once (e.g. pager)
1588
+ c.widgetInit[w.id] = true;
1589
+ if (w.hasOwnProperty('options')) {
1590
+ wo = table.config.widgetOptions = $.extend( true, {}, w.options, wo );
1591
+ }
1592
+ if (w.hasOwnProperty('init')) {
1593
+ w.init(table, w, c, wo);
1594
+ }
1595
+ }
1596
+ if (!init && w.hasOwnProperty('format')) {
1597
+ w.format(table, c, wo, false);
1598
+ }
1599
+ }
1600
+ });
1601
+ }
1602
+ setTimeout(function(){
1603
+ table.isApplyingWidgets = false;
1604
+ }, 0);
1605
+ if (c.debug) {
1606
+ w = c.widgets.length;
1607
+ benchmark("Completed " + (init === true ? "initializing " : "applying ") + w + " widget" + (w !== 1 ? "s" : ""), time);
1608
+ }
1609
+ };
1610
+
1611
+ ts.refreshWidgets = function(table, doAll, dontapply) {
1612
+ table = $(table)[0]; // see issue #243
1613
+ var i, c = table.config,
1614
+ cw = c.widgets,
1615
+ w = ts.widgets, l = w.length;
1616
+ // remove previous widgets
1617
+ for (i = 0; i < l; i++){
1618
+ if ( w[i] && w[i].id && (doAll || $.inArray( w[i].id, cw ) < 0) ) {
1619
+ if (c.debug) { log( 'Refeshing widgets: Removing "' + w[i].id + '"' ); }
1620
+ // only remove widgets that have been initialized - fixes #442
1621
+ if (w[i].hasOwnProperty('remove') && c.widgetInit[w[i].id]) {
1622
+ w[i].remove(table, c, c.widgetOptions);
1623
+ c.widgetInit[w[i].id] = false;
1624
+ }
1625
+ }
1626
+ }
1627
+ if (dontapply !== true) {
1628
+ ts.applyWidget(table, doAll);
1629
+ }
1630
+ };
1631
+
1632
+ // get sorter, string, empty, etc options for each column from
1633
+ // jQuery data, metadata, header option or header class name ("sorter-false")
1634
+ // priority = jQuery data > meta > headers option > header class name
1635
+ ts.getData = function(h, ch, key) {
1636
+ var val = '', $h = $(h), m, cl;
1637
+ if (!$h.length) { return ''; }
1638
+ m = $.metadata ? $h.metadata() : false;
1639
+ cl = ' ' + ($h.attr('class') || '');
1640
+ if (typeof $h.data(key) !== 'undefined' || typeof $h.data(key.toLowerCase()) !== 'undefined'){
1641
+ // "data-lockedOrder" is assigned to "lockedorder"; but "data-locked-order" is assigned to "lockedOrder"
1642
+ // "data-sort-initial-order" is assigned to "sortInitialOrder"
1643
+ val += $h.data(key) || $h.data(key.toLowerCase());
1644
+ } else if (m && typeof m[key] !== 'undefined') {
1645
+ val += m[key];
1646
+ } else if (ch && typeof ch[key] !== 'undefined') {
1647
+ val += ch[key];
1648
+ } else if (cl !== ' ' && cl.match(' ' + key + '-')) {
1649
+ // include sorter class name "sorter-text", etc; now works with "sorter-my-custom-parser"
1650
+ val = cl.match( new RegExp('\\s' + key + '-([\\w-]+)') )[1] || '';
1651
+ }
1652
+ return $.trim(val);
1653
+ };
1654
+
1655
+ ts.formatFloat = function(s, table) {
1656
+ if (typeof s !== 'string' || s === '') { return s; }
1657
+ // allow using formatFloat without a table; defaults to US number format
1658
+ var i,
1659
+ t = table && table.config ? table.config.usNumberFormat !== false :
1660
+ typeof table !== "undefined" ? table : true;
1661
+ if (t) {
1662
+ // US Format - 1,234,567.89 -> 1234567.89
1663
+ s = s.replace(/,/g,'');
1664
+ } else {
1665
+ // German Format = 1.234.567,89 -> 1234567.89
1666
+ // French Format = 1 234 567,89 -> 1234567.89
1667
+ s = s.replace(/[\s|\.]/g,'').replace(/,/g,'.');
1668
+ }
1669
+ if(/^\s*\([.\d]+\)/.test(s)) {
1670
+ // make (#) into a negative number -> (10) = -10
1671
+ s = s.replace(/^\s*\(([.\d]+)\)/, '-$1');
1672
+ }
1673
+ i = parseFloat(s);
1674
+ // return the text instead of zero
1675
+ return isNaN(i) ? $.trim(s) : i;
1676
+ };
1677
+
1678
+ ts.isDigit = function(s) {
1679
+ // replace all unwanted chars and match
1680
+ return isNaN(s) ? (/^[\-+(]?\d+[)]?$/).test(s.toString().replace(/[,.'"\s]/g, '')) : true;
1681
+ };
1682
+
1683
+ }()
1684
+ });
1685
+
1686
+ // make shortcut
1687
+ var ts = $.tablesorter;
1688
+
1689
+ // extend plugin scope
1690
+ $.fn.extend({
1691
+ tablesorter: ts.construct
1692
+ });
1693
+
1694
+ // add default parsers
1695
+ ts.addParser({
1696
+ id: 'no-parser',
1697
+ is: function() {
1698
+ return false;
1699
+ },
1700
+ format: function() {
1701
+ return '';
1702
+ },
1703
+ type: 'text'
1704
+ });
1705
+
1706
+ ts.addParser({
1707
+ id: "text",
1708
+ is: function() {
1709
+ return true;
1710
+ },
1711
+ format: function(s, table) {
1712
+ var c = table.config;
1713
+ if (s) {
1714
+ s = $.trim( c.ignoreCase ? s.toLocaleLowerCase() : s );
1715
+ s = c.sortLocaleCompare ? ts.replaceAccents(s) : s;
1716
+ }
1717
+ return s;
1718
+ },
1719
+ type: "text"
1720
+ });
1721
+
1722
+ ts.addParser({
1723
+ id: "digit",
1724
+ is: function(s) {
1725
+ return ts.isDigit(s);
1726
+ },
1727
+ format: function(s, table) {
1728
+ var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ""), table);
1729
+ return s && typeof n === 'number' ? n : s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
1730
+ },
1731
+ type: "numeric"
1732
+ });
1733
+
1734
+ ts.addParser({
1735
+ id: "currency",
1736
+ is: function(s) {
1737
+ return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[+\-,. ]/g,'')); // £$€¤¥¢
1738
+ },
1739
+ format: function(s, table) {
1740
+ var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ""), table);
1741
+ return s && typeof n === 'number' ? n : s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
1742
+ },
1743
+ type: "numeric"
1744
+ });
1745
+
1746
+ ts.addParser({
1747
+ id: "ipAddress",
1748
+ is: function(s) {
1749
+ return (/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/).test(s);
1750
+ },
1751
+ format: function(s, table) {
1752
+ var i, a = s ? s.split(".") : '',
1753
+ r = "",
1754
+ l = a.length;
1755
+ for (i = 0; i < l; i++) {
1756
+ r += ("00" + a[i]).slice(-3);
1757
+ }
1758
+ return s ? ts.formatFloat(r, table) : s;
1759
+ },
1760
+ type: "numeric"
1761
+ });
1762
+
1763
+ ts.addParser({
1764
+ id: "url",
1765
+ is: function(s) {
1766
+ return (/^(https?|ftp|file):\/\//).test(s);
1767
+ },
1768
+ format: function(s) {
1769
+ return s ? $.trim(s.replace(/(https?|ftp|file):\/\//, '')) : s;
1770
+ },
1771
+ parsed : true, // filter widget flag
1772
+ type: "text"
1773
+ });
1774
+
1775
+ ts.addParser({
1776
+ id: "isoDate",
1777
+ is: function(s) {
1778
+ return (/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/).test(s);
1779
+ },
1780
+ format: function(s, table) {
1781
+ return s ? ts.formatFloat((s !== "") ? (new Date(s.replace(/-/g, "/")).getTime() || s) : "", table) : s;
1782
+ },
1783
+ type: "numeric"
1784
+ });
1785
+
1786
+ ts.addParser({
1787
+ id: "percent",
1788
+ is: function(s) {
1789
+ return (/(\d\s*?%|%\s*?\d)/).test(s) && s.length < 15;
1790
+ },
1791
+ format: function(s, table) {
1792
+ return s ? ts.formatFloat(s.replace(/%/g, ""), table) : s;
1793
+ },
1794
+ type: "numeric"
1795
+ });
1796
+
1797
+ ts.addParser({
1798
+ id: "usLongDate",
1799
+ is: function(s) {
1800
+ // two digit years are not allowed cross-browser
1801
+ // Jan 01, 2013 12:34:56 PM or 01 Jan 2013
1802
+ return (/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i).test(s) || (/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i).test(s);
1803
+ },
1804
+ format: function(s, table) {
1805
+ return s ? ts.formatFloat( (new Date(s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || s), table) : s;
1806
+ },
1807
+ type: "numeric"
1808
+ });
1809
+
1810
+ ts.addParser({
1811
+ id: "shortDate", // "mmddyyyy", "ddmmyyyy" or "yyyymmdd"
1812
+ is: function(s) {
1813
+ // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
1814
+ return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g," ").replace(/[\-.,]/g, "/"));
1815
+ },
1816
+ format: function(s, table, cell, cellIndex) {
1817
+ if (s) {
1818
+ var c = table.config,
1819
+ ci = c.$headers.filter('[data-column=' + cellIndex + ']:last'),
1820
+ format = ci.length && ci[0].dateFormat || ts.getData( ci, ts.getColumnData( table, c.headers, cellIndex ), 'dateFormat') || c.dateFormat;
1821
+ s = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/"); // escaped - because JSHint in Firefox was showing it as an error
1822
+ if (format === "mmddyyyy") {
1823
+ s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2");
1824
+ } else if (format === "ddmmyyyy") {
1825
+ s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$2/$1");
1826
+ } else if (format === "yyyymmdd") {
1827
+ s = s.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$1/$2/$3");
1828
+ }
1829
+ }
1830
+ return s ? ts.formatFloat( (new Date(s).getTime() || s), table) : s;
1831
+ },
1832
+ type: "numeric"
1833
+ });
1834
+
1835
+ ts.addParser({
1836
+ id: "time",
1837
+ is: function(s) {
1838
+ return (/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s);
1839
+ },
1840
+ format: function(s, table) {
1841
+ return s ? ts.formatFloat( (new Date("2000/01/01 " + s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || s), table) : s;
1842
+ },
1843
+ type: "numeric"
1844
+ });
1845
+
1846
+ ts.addParser({
1847
+ id: "metadata",
1848
+ is: function() {
1849
+ return false;
1850
+ },
1851
+ format: function(s, table, cell) {
1852
+ var c = table.config,
1853
+ p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
1854
+ return $(cell).metadata()[p];
1855
+ },
1856
+ type: "numeric"
1857
+ });
1858
+
1859
+ // add default widgets
1860
+ ts.addWidget({
1861
+ id: "zebra",
1862
+ priority: 90,
1863
+ format: function(table, c, wo) {
1864
+ var $tb, $tv, $tr, row, even, time, k,
1865
+ child = new RegExp(c.cssChildRow, 'i'),
1866
+ b = c.$tbodies;
1867
+ if (c.debug) {
1868
+ time = new Date();
1869
+ }
1870
+ for (k = 0; k < b.length; k++ ) {
1871
+ // loop through the visible rows
1872
+ row = 0;
1873
+ $tb = b.eq(k);
1874
+ $tv = $tb.children('tr:visible').not(c.selectorRemove);
1875
+ // revered back to using jQuery each - strangely it's the fastest method
1876
+ /*jshint loopfunc:true */
1877
+ $tv.each(function(){
1878
+ $tr = $(this);
1879
+ // style child rows the same way the parent row was styled
1880
+ if (!child.test(this.className)) { row++; }
1881
+ even = (row % 2 === 0);
1882
+ $tr.removeClass(wo.zebra[even ? 1 : 0]).addClass(wo.zebra[even ? 0 : 1]);
1883
+ });
1884
+ }
1885
+ if (c.debug) {
1886
+ ts.benchmark("Applying Zebra widget", time);
1887
+ }
1888
+ },
1889
+ remove: function(table, c, wo){
1890
+ var k, $tb,
1891
+ b = c.$tbodies,
1892
+ rmv = (wo.zebra || [ "even", "odd" ]).join(' ');
1893
+ for (k = 0; k < b.length; k++ ){
1894
+ $tb = $.tablesorter.processTbody(table, b.eq(k), true); // remove tbody
1895
+ $tb.children().removeClass(rmv);
1896
+ $.tablesorter.processTbody(table, $tb, false); // restore tbody
1897
+ }
1898
+ }
1899
+ });
1900
+
1901
+ })(jQuery);