fae-rails 1.2.2 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);