@atlassian/aui 9.3.20 → 9.3.21

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