jquery-tablesorter 1.16.5 → 1.17.0

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/jquery-tablesorter/version.rb +1 -1
  4. data/vendor/assets/javascripts/jquery-tablesorter/addons/pager/jquery.tablesorter.pager.js +38 -27
  5. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.combined.js +1104 -839
  6. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.js +167 -123
  7. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.widgets.js +938 -717
  8. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date.js +5 -5
  9. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-globalize.js +46 -0
  10. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-input-select.js +96 -72
  11. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-named-numbers.js +6 -5
  12. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-network.js +26 -17
  13. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-columns.js +1 -1
  14. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-editable.js +95 -42
  15. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-filter.js +921 -700
  16. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-grouping.js +5 -3
  17. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-math.js +22 -20
  18. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-output.js +7 -5
  19. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-pager.js +40 -29
  20. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-resizable.js +6 -6
  21. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-saveSort.js +1 -1
  22. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-scroller.js +53 -31
  23. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-stickyHeaders.js +1 -1
  24. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-storage.js +1 -1
  25. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-uitheme.js +1 -1
  26. data/vendor/assets/stylesheets/jquery-tablesorter/theme.black-ice.css +2 -1
  27. data/vendor/assets/stylesheets/jquery-tablesorter/theme.blue.css +2 -1
  28. data/vendor/assets/stylesheets/jquery-tablesorter/theme.bootstrap.css +2 -1
  29. data/vendor/assets/stylesheets/jquery-tablesorter/theme.bootstrap_2.css +8 -6
  30. data/vendor/assets/stylesheets/jquery-tablesorter/theme.dark.css +2 -1
  31. data/vendor/assets/stylesheets/jquery-tablesorter/theme.default.css +1 -1
  32. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a506f228e740d405af7dbf96ff2dddcc8a17bfbd
4
- data.tar.gz: 2a3dd948476f1f9b9667faf560b19509d2c931dd
3
+ metadata.gz: cf240445d0e97fd4ef728159289bc7db7b0865de
4
+ data.tar.gz: e7b1e3511ffd03b9e1da5c5877fa1428270bcac1
5
5
  SHA512:
6
- metadata.gz: 23fbf1cbbc8a6940618ce4f4b0883794977dcee6d59446c327be55901734d5ba59125827c54ca5909aa2b9b8f0dd3379cf046148d650a082b02856ea62bb3527
7
- data.tar.gz: 02166e4ebd4556d1336378ec2dfc707b8b1cd6bfd3e672e63060011f920b8622ddd938324d2c2d4ff8e17b7aafbf2b4075bcae9ec58cbb9cbc3f93cbbda5f7f3
6
+ metadata.gz: f26a5aaae4e5683e0e748a65516926a4bf24391c1bb81577aafd07a7f6fff6c7c41cb08d9ce3eb00885bd10365844b56fed5079db6e5c5c12edb0fa4c625dbf3
7
+ data.tar.gz: 67ef7bbd510a4d350516b1bf8f231bacab71006b0e248aedfb183dcb0a257115132f7ca6cc093203aa9c7bc75c105af5f86c2cfd19f2582c2b6cd75ebb23ca15
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Simple integration of jquery-tablesorter into the asset pipeline.
6
6
 
7
- Current tablesorter version: 2.21.5 (4/8/2015), [documentation]
7
+ Current tablesorter version: 2.22.0 (5/17/2015), [documentation]
8
8
 
9
9
  Any issue associated with the js/css files, please report to [Mottie's fork].
10
10
 
@@ -26,7 +26,7 @@ Or install it yourself as:
26
26
 
27
27
  Rails 3.2 and higher (tested up to 4.2)
28
28
 
29
- Tested with ruby 1.9.3 - 2.2.1
29
+ Tested with ruby 1.9.3 - 2.2.2
30
30
 
31
31
  ## Usage
32
32
 
@@ -1,3 +1,3 @@
1
1
  module JqueryTablesorter
2
- VERSION = '1.16.5'
2
+ VERSION = '1.17.0'
3
3
  end
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * tablesorter (FORK) pager plugin
3
- * updated 3/26/2015 (v2.21.3)
3
+ * updated 5/17/2015 (v2.22.0)
4
4
  */
5
5
  /*jshint browser:true, jquery:true, unused:false */
6
6
  ;(function($) {
@@ -377,12 +377,12 @@
377
377
  // process data
378
378
  if ( typeof(p.ajaxProcessing) === "function" ) {
379
379
  // ajaxProcessing result: [ total, rows, headers ]
380
- var i, j, hsh, $f, $sh, t, th, d, l, rr_count,
380
+ var i, j, t, hsh, $f, $sh, $headers, $h, icon, th, d, l, rr_count, len,
381
381
  c = table.config,
382
- $t = c.$table,
382
+ $table = c.$table,
383
383
  tds = '',
384
384
  result = p.ajaxProcessing(data, table, xhr) || [ 0, [] ],
385
- hl = $t.find('thead th').length;
385
+ hl = $table.find('thead th').length;
386
386
 
387
387
  // Clean up any previous error.
388
388
  ts.showError(table);
@@ -445,28 +445,30 @@
445
445
  p.processAjaxOnInit = true;
446
446
  // only add new header text if the length matches
447
447
  if ( th && th.length === hl ) {
448
- hsh = $t.hasClass('hasStickyHeaders');
448
+ hsh = $table.hasClass('hasStickyHeaders');
449
449
  $sh = hsh ? c.widgetOptions.$sticky.children('thead:first').children('tr').children() : '';
450
- $f = $t.find('tfoot tr:first').children();
450
+ $f = $table.find('tfoot tr:first').children();
451
451
  // don't change td headers (may contain pager)
452
- c.$headers.filter('th').each(function(j){
453
- var $t = $(this), icn;
452
+ $headers = c.$headers.filter( 'th ' );
453
+ len = $headers.length;
454
+ for ( j = 0; j < len; j++ ) {
455
+ $h = $headers.eq( j );
454
456
  // add new test within the first span it finds, or just in the header
455
- if ( $t.find('.' + ts.css.icon).length ) {
456
- icn = $t.find('.' + ts.css.icon).clone(true);
457
- $t.find('.tablesorter-header-inner').html( th[j] ).append(icn);
457
+ if ( $h.find('.' + ts.css.icon).length ) {
458
+ icon = $h.find('.' + ts.css.icon).clone(true);
459
+ $h.find('.tablesorter-header-inner').html( th[j] ).append(icon);
458
460
  if ( hsh && $sh.length ) {
459
- icn = $sh.eq(j).find('.' + ts.css.icon).clone(true);
460
- $sh.eq(j).find('.tablesorter-header-inner').html( th[j] ).append(icn);
461
+ icon = $sh.eq(j).find('.' + ts.css.icon).clone(true);
462
+ $sh.eq(j).find('.tablesorter-header-inner').html( th[j] ).append(icon);
461
463
  }
462
464
  } else {
463
- $t.find('.tablesorter-header-inner').html( th[j] );
465
+ $h.find('.tablesorter-header-inner').html( th[j] );
464
466
  if (hsh && $sh.length) {
465
467
  $sh.eq(j).find('.tablesorter-header-inner').html( th[j] );
466
468
  }
467
469
  }
468
470
  $f.eq(j).html( th[j] );
469
- });
471
+ }
470
472
  }
471
473
  }
472
474
  if (c.showProcessing) {
@@ -479,7 +481,7 @@
479
481
  p.last.currentFilters = p.currentFilters;
480
482
  p.last.sortList = (c.sortList || []).join(',');
481
483
  updatePageDisplay(table, p, false);
482
- $t.trigger('updateCache', [function(){
484
+ $table.trigger('updateCache', [function(){
483
485
  if (p.initialized) {
484
486
  // apply widgets after table has rendered & after a delay to prevent
485
487
  // multiple applyWidget blocking code from blocking this trigger
@@ -487,7 +489,7 @@
487
489
  if (c.debug) {
488
490
  ts.log('Pager: Triggering pagerChange');
489
491
  }
490
- $t
492
+ $table
491
493
  .trigger('applyWidgets')
492
494
  .trigger('pagerChange', p);
493
495
  updatePageDisplay(table, p, true);
@@ -649,6 +651,7 @@
649
651
  },
650
652
 
651
653
  showAllRows = function(table, p){
654
+ var index, $controls, len;
652
655
  if ( p.ajax ) {
653
656
  pagerArrows(p, true);
654
657
  } else {
@@ -669,9 +672,15 @@
669
672
  }
670
673
  }
671
674
  // disable size selector
672
- p.$size.add(p.$goto).add(p.$container.find('.ts-startRow, .ts-page')).each(function(){
673
- $(this).attr('aria-disabled', 'true').addClass(p.cssDisabled)[0].disabled = true;
674
- });
675
+ $controls = p.$size
676
+ .add( p.$goto )
677
+ .add( p.$container.find( '.ts-startRow, .ts-page' ) );
678
+ len = $controls.length;
679
+ for ( index = 0; index < len; index++ ) {
680
+ $controls.eq( index )
681
+ .attr( 'aria-disabled', 'true' )
682
+ .addClass( p.cssDisabled )[0].disabled = true;
683
+ }
675
684
  },
676
685
 
677
686
  // updateCache if delayInit: true
@@ -1043,13 +1052,15 @@
1043
1052
  }() });
1044
1053
 
1045
1054
  // see #486
1046
- ts.showError = function(table, message){
1047
- $(table).each(function(){
1048
- var $row,
1049
- c = this.config,
1055
+ ts.showError = function(table, message) {
1056
+ var index, $row, c, errorRow,
1057
+ $table = $( table ),
1058
+ len = $table.length;
1059
+ for ( index = 0; index < len; index++ ) {
1060
+ c = $table[ index ].config;
1061
+ if ( c ) {
1050
1062
  errorRow = c.pager && c.pager.cssErrorRow || c.widgetOptions.pager_css && c.widgetOptions.pager_css.errorRow || 'tablesorter-errorRow';
1051
- if (c) {
1052
- if (typeof message === 'undefined') {
1063
+ if ( typeof message === 'undefined' ) {
1053
1064
  c.$table.find('thead').find(c.selectorRemove).remove();
1054
1065
  } else {
1055
1066
  $row = ( /tr\>/.test(message) ? $(message) : $('<tr><td colspan="' + c.columns + '">' + message + '</td></tr>') )
@@ -1065,7 +1076,7 @@
1065
1076
  });
1066
1077
  }
1067
1078
  }
1068
- });
1079
+ }
1069
1080
  };
1070
1081
 
1071
1082
  // extend plugin scope
@@ -4,7 +4,7 @@
4
4
  ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▀▀ ▀▀▀▀██
5
5
  █████▀ ▀████▀ ██ ██ ▀████▀ ██ ██ ██ ██ ▀████▀ █████▀ ██ ██ █████▀
6
6
  */
7
- /*! tablesorter (FORK) - updated 04-08-2015 (v2.21.5)*/
7
+ /*! tablesorter (FORK) - updated 05-17-2015 (v2.22.0)*/
8
8
  /* Includes widgets ( storage,uitheme,columns,filter,stickyHeaders,resizable,saveSort ) */
9
9
  (function(factory) {
10
10
  if (typeof define === 'function' && define.amd) {
@@ -16,7 +16,7 @@
16
16
  }
17
17
  }(function($) {
18
18
 
19
- /*! TableSorter (FORK) v2.21.5 *//*
19
+ /*! TableSorter (FORK) v2.22.0 *//*
20
20
  * Client-side table sorting with ease!
21
21
  * @requires jQuery v1.2.6+
22
22
  *
@@ -44,7 +44,7 @@
44
44
 
45
45
  var ts = this;
46
46
 
47
- ts.version = '2.21.5';
47
+ ts.version = '2.22.0';
48
48
 
49
49
  ts.parsers = [];
50
50
  ts.widgets = [];
@@ -119,6 +119,11 @@
119
119
  cssNoSort : 'tablesorter-noSort', // class name added to element inside header; clicking on it won't cause a sort
120
120
  cssIgnoreRow : 'tablesorter-ignoreRow', // header row to ignore; cells within this row will not be added to c.$headers
121
121
 
122
+ // *** events
123
+ pointerClick : 'click',
124
+ pointerDown : 'mousedown',
125
+ pointerUp : 'mouseup',
126
+
122
127
  // *** selectors
123
128
  selectorHeaders : '> thead th, > thead td',
124
129
  selectorSort : 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort
@@ -203,7 +208,11 @@
203
208
  $node = node.jquery ? node : $(node);
204
209
  if (typeof(t) === 'string') {
205
210
  // check data-attribute first when set to 'basic'; don't use node.innerText - it's really slow!
206
- return $.trim( ( t === 'basic' ? $node.attr(c.textAttribute) || node.textContent : node.textContent ) || $node.text() || '' );
211
+ // http://www.kellegous.com/j/2013/02/27/innertext-vs-textcontent/
212
+ return $.trim(
213
+ ( t === 'basic' ? $node.attr(c.textAttribute) || node.textContent : node.textContent ) ||
214
+ $node.text()
215
+ );
207
216
  } else {
208
217
  if (typeof(t) === 'function') {
209
218
  return $.trim( t($node[0], c.table, cellIndex) );
@@ -212,7 +221,7 @@
212
221
  }
213
222
  }
214
223
  // fallback
215
- return $.trim( $node[0].textContent || $node.text() || '' );
224
+ return $.trim( $node[0].textContent || $node.text() );
216
225
  };
217
226
 
218
227
  function detectParserForColumn(table, rows, rowIndex, cellIndex) {
@@ -246,6 +255,32 @@
246
255
  return ts.getParserById('text');
247
256
  }
248
257
 
258
+ // centralized function to extract/parse cell contents
259
+ function getParsedText( c, cell, colIndex, txt ) {
260
+ if ( typeof txt === 'undefined' ) {
261
+ txt = ts.getElementText( c, cell, colIndex );
262
+ }
263
+ // if no parser, make sure to return the txt
264
+ var val = '' + txt,
265
+ parser = c.parsers[ colIndex ],
266
+ extractor = c.extractors[ colIndex ];
267
+ if ( parser ) {
268
+ // do extract before parsing, if there is one
269
+ if ( extractor && typeof extractor.format === 'function' ) {
270
+ txt = extractor.format( txt, c.table, cell, colIndex );
271
+ }
272
+ // allow parsing if the string is empty, previously parsing would change it to zero,
273
+ // in case the parser needs to extract data from the table cell attributes
274
+ val = parser.id === 'no-parser' ? '' :
275
+ // make sure txt is a string (extractor may have converted it)
276
+ parser.format( '' + txt, c.table, cell, colIndex );
277
+ if ( c.ignoreCase && typeof val === 'string' ) {
278
+ val = val.toLowerCase();
279
+ }
280
+ }
281
+ return val;
282
+ }
283
+
249
284
  function buildParserCache(table) {
250
285
  var c = table.config,
251
286
  // update table bodies in case we start with an empty table
@@ -309,11 +344,10 @@
309
344
 
310
345
  /* utils */
311
346
  function buildCache(table) {
312
- var cc, t, tx, v, i, j, k, $row, cols, cacheTime,
313
- totalRows, rowData, colMax,
347
+ var cc, t, v, i, j, k, $row, cols, cacheTime,
348
+ totalRows, rowData, prevRowData, colMax,
314
349
  c = table.config,
315
350
  $tb = c.$tbodies,
316
- extractors = c.extractors,
317
351
  parsers = c.parsers;
318
352
  c.cache = {};
319
353
  c.totalRows = 0;
@@ -344,62 +378,61 @@
344
378
  raw: [] // original row text
345
379
  };
346
380
  /** Add the table data to main data array */
347
- $row = $($tb[k].rows[i]);
381
+ $row = $( $tb[ k ].rows[ i ] );
348
382
  cols = [];
349
383
  // if this is a child row, add it to the last row's children and continue to the next row
350
384
  // ignore child row class, if it is the first row
351
- if ($row.hasClass(c.cssChildRow) && i !== 0) {
385
+ if ( $row.hasClass( c.cssChildRow ) && i !== 0 ) {
352
386
  t = cc.normalized.length - 1;
353
- cc.normalized[t][c.columns].$row = cc.normalized[t][c.columns].$row.add($row);
387
+ prevRowData = cc.normalized[ t ][ c.columns ];
388
+ prevRowData.$row = prevRowData.$row.add( $row );
354
389
  // add 'hasChild' class name to parent row
355
- if (!$row.prev().hasClass(c.cssChildRow)) {
356
- $row.prev().addClass(ts.css.cssHasChild);
390
+ if ( !$row.prev().hasClass( c.cssChildRow ) ) {
391
+ $row.prev().addClass( ts.css.cssHasChild );
357
392
  }
358
393
  // save child row content (un-parsed!)
359
- rowData.child[t] = $.trim( $row[0].textContent || $row.text() || '' );
394
+ v = $row.children( 'th, td' );
395
+ t = prevRowData.child.length;
396
+ prevRowData.child[ t ] = [];
397
+ // child row content does not account for colspans/rowspans; so indexing may be off
398
+ for ( j = 0; j < c.columns; j++ ) {
399
+ prevRowData.child[ t ][ j ] = getParsedText( c, v[ j ], j );
400
+ }
360
401
  // go to the next for loop
361
402
  continue;
362
403
  }
363
404
  rowData.$row = $row;
364
405
  rowData.order = i; // add original row position to rowCache
365
- for (j = 0; j < c.columns; ++j) {
366
- if (typeof parsers[j] === 'undefined') {
367
- if (c.debug) {
368
- log('No parser found for cell:', $row[0].cells[j], 'does it have a header?');
406
+ for ( j = 0; j < c.columns; ++j ) {
407
+ if (typeof parsers[ j ] === 'undefined') {
408
+ if ( c.debug ) {
409
+ log( 'No parser found for cell:', $row[ 0 ].cells[ j ], 'does it have a header?' );
369
410
  }
370
411
  continue;
371
412
  }
372
- t = ts.getElementText(c, $row[0].cells[j], j);
373
- rowData.raw.push(t); // save original row text
374
- // do extract before parsing if there is one
375
- if (typeof extractors[j].id === 'undefined') {
376
- tx = t;
377
- } else {
378
- tx = extractors[j].format(t, table, $row[0].cells[j], j);
379
- }
380
- // allow parsing if the string is empty, previously parsing would change it to zero,
381
- // in case the parser needs to extract data from the table cell attributes
382
- v = parsers[j].id === 'no-parser' ? '' : parsers[j].format(tx, table, $row[0].cells[j], j);
383
- cols.push( c.ignoreCase && typeof v === 'string' ? v.toLowerCase() : v );
384
- if ((parsers[j].type || '').toLowerCase() === 'numeric') {
413
+ t = ts.getElementText( c, $row[ 0 ].cells[j], j );
414
+ rowData.raw.push( t ); // save original row text
415
+ v = getParsedText( c, $row[ 0 ].cells[ j ], j, t );
416
+ cols.push( v );
417
+ if ( ( parsers[ j ].type || '' ).toLowerCase() === 'numeric' ) {
385
418
  // determine column max value (ignore sign)
386
- colMax[j] = Math.max(Math.abs(v) || 0, colMax[j] || 0);
419
+ colMax[ j ] = Math.max( Math.abs( v ) || 0, colMax[ j ] || 0 );
387
420
  }
388
421
  }
389
422
  // ensure rowData is always in the same location (after the last column)
390
- cols[c.columns] = rowData;
391
- cc.normalized.push(cols);
423
+ cols[ c.columns ] = rowData;
424
+ cc.normalized.push( cols );
392
425
  }
393
426
  cc.colMax = colMax;
394
427
  // total up rows, not including child rows
395
428
  c.totalRows += cc.normalized.length;
396
429
 
397
430
  }
398
- if (c.showProcessing) {
399
- ts.isProcessing(table); // remove processing icon
431
+ if ( c.showProcessing ) {
432
+ ts.isProcessing( table ); // remove processing icon
400
433
  }
401
- if (c.debug) {
402
- benchmark('Building cache for ' + totalRows + ' rows', cacheTime);
434
+ if ( c.debug ) {
435
+ benchmark( 'Building cache for ' + totalRows + ' rows', cacheTime );
403
436
  }
404
437
  }
405
438
 
@@ -539,14 +572,15 @@
539
572
  }
540
573
 
541
574
  function updateHeader(table) {
542
- var s, $th, col,
543
- c = table.config;
544
- c.$headers.each(function(index, th){
545
- $th = $(th);
575
+ var index, s, $th, col,
576
+ c = table.config,
577
+ len = c.$headers.length;
578
+ for ( index = 0; index < len; index++ ) {
579
+ $th = c.$headers.eq( index );
546
580
  col = ts.getColumnData( table, c.headers, index, true );
547
581
  // add 'sorter-false' class if 'parser-false' is set
548
- s = ts.getData( th, col, 'sorter' ) === 'false' || ts.getData( th, col, 'parser' ) === 'false';
549
- th.sortDisabled = s;
582
+ s = ts.getData( $th, col, 'sorter' ) === 'false' || ts.getData( $th, col, 'parser' ) === 'false';
583
+ $th[0].sortDisabled = s;
550
584
  $th[ s ? 'addClass' : 'removeClass' ]('sorter-false').attr('aria-disabled', '' + s);
551
585
  // aria-controls - requires table ID
552
586
  if (table.id) {
@@ -556,11 +590,11 @@
556
590
  $th.attr('aria-controls', table.id);
557
591
  }
558
592
  }
559
- });
593
+ }
560
594
  }
561
595
 
562
596
  function setHeadersCss(table) {
563
- var f, i, j,
597
+ var f, h, i, j, $headers, $h, nextSort, txt,
564
598
  c = table.config,
565
599
  list = c.sortList,
566
600
  len = list.length,
@@ -604,14 +638,19 @@
604
638
  }
605
639
  }
606
640
  // add verbose aria labels
607
- c.$headers.not('.sorter-false').each(function(){
608
- var $this = $(this),
609
- nextSort = this.order[(this.count + 1) % (c.sortReset ? 3 : 2)],
610
- txt = $.trim( $this.text() ) + ': ' +
611
- ts.language[ $this.hasClass(ts.css.sortAsc) ? 'sortAsc' : $this.hasClass(ts.css.sortDesc) ? 'sortDesc' : 'sortNone' ] +
641
+ len = c.$headers.length;
642
+ $headers = c.$headers.not('.sorter-false');
643
+ for ( i = 0; i < len; i++ ) {
644
+ $h = $headers.eq( i );
645
+ if ( $h.length ) {
646
+ h = $headers[ i ];
647
+ nextSort = h.order[ ( h.count + 1 ) % ( c.sortReset ? 3 : 2 ) ],
648
+ txt = $.trim( $h.text() ) + ': ' +
649
+ ts.language[ $h.hasClass( ts.css.sortAsc ) ? 'sortAsc' : $h.hasClass( ts.css.sortDesc ) ? 'sortDesc' : 'sortNone' ] +
612
650
  ts.language[ nextSort === 0 ? 'nextAsc' : nextSort === 1 ? 'nextDesc' : 'nextNone' ];
613
- $this.attr('aria-label', txt );
614
- });
651
+ $h.attr( 'aria-label', txt );
652
+ }
653
+ }
615
654
  }
616
655
 
617
656
  function updateHeaderSortCount( table, list ) {
@@ -624,9 +663,10 @@
624
663
  val = sortList[indx];
625
664
  // ensure all sortList values are numeric - fixes #127
626
665
  col = parseInt(val[0], 10);
627
- // make sure header exists
628
- header = c.$headerIndexed[col][0];
629
- if (header) { // prevents error if sorton array is wrong
666
+ // prevents error if sorton array is wrong
667
+ if ( col < c.columns && c.$headerIndexed[col] ) {
668
+ // make sure header exists
669
+ header = c.$headerIndexed[col][0];
630
670
  // o.count = o.count + 1;
631
671
  dir = ('' + val[1]).match(/^(1|d|s|o|n)/);
632
672
  dir = dir ? dir[0] : '';
@@ -670,10 +710,11 @@
670
710
  // let any updates complete before initializing a sort
671
711
  return setTimeout(function(){ initSort(table, cell, event); }, 50);
672
712
  }
673
- var arry, indx, col, order, s,
713
+ var arry, indx, i, col, order, s, $header,
674
714
  c = table.config,
675
715
  key = !event[c.sortMultiSortKey],
676
- $table = c.$table;
716
+ $table = c.$table,
717
+ len = c.$headers.length;
677
718
  // Only call sortStart if sorting is enabled
678
719
  $table.trigger('sortStart', table);
679
720
  // get current column sort order
@@ -681,12 +722,13 @@
681
722
  // reset all sorts on non-current column - issue #30
682
723
  if (c.sortRestart) {
683
724
  indx = cell;
684
- c.$headers.each(function() {
725
+ for ( i = 0; i < len; i++ ) {
726
+ $header = c.$headers.eq( i );
685
727
  // only reset counts on columns that weren't just clicked on and if not included in a multisort
686
- if (this !== indx && (key || !$(this).is('.' + ts.css.sortDesc + ',.' + ts.css.sortAsc))) {
687
- this.count = -1;
728
+ if ( $header[0] !== indx && ( key || !$header.is('.' + ts.css.sortDesc + ',.' + ts.css.sortAsc) ) ) {
729
+ $header[0].count = -1;
688
730
  }
689
- });
731
+ }
690
732
  }
691
733
  // get current column index
692
734
  indx = parseInt( $(cell).attr('data-column'), 10 );
@@ -912,35 +954,31 @@
912
954
  table.isUpdating = true;
913
955
  $table.find(c.selectorRemove).remove();
914
956
  // get position from the dom
915
- var v, t, row, icell,
957
+ var t, row, icell, cache,
916
958
  $tb = c.$tbodies,
917
959
  $cell = $(cell),
918
960
  // update cache - format: function(s, table, cell, cellIndex)
919
961
  // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
920
962
  tbdy = $tb.index( $.fn.closest ? $cell.closest('tbody') : $cell.parents('tbody').filter(':first') ),
963
+ tbcache = c.cache[ tbdy ],
921
964
  $row = $.fn.closest ? $cell.closest('tr') : $cell.parents('tr').filter(':first');
922
965
  cell = $cell[0]; // in case cell is a jQuery object
923
966
  // tbody may not exist if update is initialized while tbody is removed for processing
924
967
  if ($tb.length && tbdy >= 0) {
925
- row = $tb.eq(tbdy).find('tr').index( $row );
968
+ row = $tb.eq( tbdy ).find( 'tr' ).index( $row );
969
+ cache = tbcache.normalized[ row ];
926
970
  icell = $cell.index();
927
- c.cache[tbdy].normalized[row][c.columns].$row = $row;
928
- if (typeof c.extractors[icell].id === 'undefined') {
929
- t = ts.getElementText(c, cell, icell);
930
- } else {
931
- t = c.extractors[icell].format( ts.getElementText(c, cell, icell), table, cell, icell );
932
- }
933
- v = c.parsers[icell].id === 'no-parser' ? '' :
934
- c.parsers[icell].format( t, table, cell, icell );
935
- c.cache[tbdy].normalized[row][icell] = c.ignoreCase && typeof v === 'string' ? v.toLowerCase() : v;
936
- if ((c.parsers[icell].type || '').toLowerCase() === 'numeric') {
971
+ t = getParsedText( c, cell, icell );
972
+ cache[ icell ] = t;
973
+ cache[ c.columns ].$row = $row;
974
+ if ( (c.parsers[icell].type || '').toLowerCase() === 'numeric' ) {
937
975
  // update column max value (ignore sign)
938
- c.cache[tbdy].colMax[icell] = Math.max(Math.abs(v) || 0, c.cache[tbdy].colMax[icell] || 0);
976
+ tbcache.colMax[icell] = Math.max(Math.abs(t) || 0, tbcache.colMax[icell] || 0);
939
977
  }
940
- v = resort !== 'undefined' ? resort : c.resort;
941
- if (v !== false) {
978
+ t = resort !== 'undefined' ? resort : c.resort;
979
+ if (t !== false) {
942
980
  // widgets will be reapplied
943
- checkResort(c, v, callback);
981
+ checkResort(c, t, callback);
944
982
  } else {
945
983
  // don't reapply widgets is resort is false, just in case it causes
946
984
  // problems with element focus
@@ -960,7 +998,7 @@
960
998
  commonUpdate(table, resort, callback);
961
999
  } else {
962
1000
  $row = $($row).attr('role', 'row'); // make sure we're using a jQuery object
963
- var i, j, l, t, v, rowData, cells,
1001
+ var i, j, l, rowData, cells,
964
1002
  rows = $row.filter('tr').length,
965
1003
  tbdy = c.$tbodies.index( $row.parents('tbody').filter(':first') );
966
1004
  // fixes adding rows to an empty table - see issue #179
@@ -978,14 +1016,7 @@
978
1016
  };
979
1017
  // add each cell
980
1018
  for (j = 0; j < l; j++) {
981
- if (typeof c.extractors[j].id === 'undefined') {
982
- t = ts.getElementText(c, $row[i].cells[j], j);
983
- } else {
984
- t = c.extractors[j].format( ts.getElementText(c, $row[i].cells[j], j), table, $row[i].cells[j], j );
985
- }
986
- v = c.parsers[j].id === 'no-parser' ? '' :
987
- c.parsers[j].format( t, table, $row[i].cells[j], j );
988
- cells[j] = c.ignoreCase && typeof v === 'string' ? v.toLowerCase() : v;
1019
+ cells[j] = getParsedText( c, $row[i].cells[j], j );
989
1020
  if ((c.parsers[j].type || '').toLowerCase() === 'numeric') {
990
1021
  // update column max value (ignore sign)
991
1022
  c.cache[tbdy].colMax[j] = Math.max(Math.abs(cells[j]) || 0, c.cache[tbdy].colMax[j] || 0);
@@ -1213,7 +1244,7 @@
1213
1244
  // automatically add a colgroup with col elements set to a percentage width
1214
1245
  ts.fixColumnWidth = function(table) {
1215
1246
  table = $(table)[0];
1216
- var overallWidth, percent,
1247
+ var overallWidth, percent, $tbodies, len, index,
1217
1248
  c = table.config,
1218
1249
  colgroup = c.$table.children('colgroup');
1219
1250
  // remove plugin-added colgroup, in case we need to refresh the widths
@@ -1224,10 +1255,12 @@
1224
1255
  colgroup = $('<colgroup class="' + ts.css.colgroup + '">');
1225
1256
  overallWidth = c.$table.width();
1226
1257
  // only add col for visible columns - fixes #371
1227
- c.$tbodies.find('tr:first').children(':visible').each(function() {
1228
- percent = parseInt( ( $(this).width() / overallWidth ) * 1000, 10 ) / 10 + '%';
1258
+ $tbodies = c.$tbodies.find('tr:first').children(':visible'); //.each(function()
1259
+ len = $tbodies.length;
1260
+ for ( index = 0; index < len; index++ ) {
1261
+ percent = parseInt( ( $tbodies.eq( index ).width() / overallWidth ) * 1000, 10 ) / 10 + '%';
1229
1262
  colgroup.append( $('<col>').css('width', percent) );
1230
- });
1263
+ }
1231
1264
  c.$table.prepend(colgroup);
1232
1265
  }
1233
1266
  };
@@ -1262,9 +1295,10 @@
1262
1295
  // http://www.javascripttoolbox.com/lib/table/examples.php
1263
1296
  // http://www.javascripttoolbox.com/temp/table_cellindex.html
1264
1297
  ts.computeColumnIndex = function(trs) {
1265
- var matrix = [],
1266
- lookup = {},
1267
- i, j, k, l, $cell, cell, cells, rowIndex, cellId, rowSpan, colSpan, firstAvailCol, matrixrow;
1298
+ var i, j, k, l, $cell, cell, cells, rowIndex, cellId, rowSpan, colSpan, firstAvailCol,
1299
+ matrix = [],
1300
+ matrixrow = [],
1301
+ lookup = {};
1268
1302
  for (i = 0; i < trs.length; i++) {
1269
1303
  cells = trs[i].cells;
1270
1304
  for (j = 0; j < cells.length; j++) {
@@ -1344,7 +1378,7 @@
1344
1378
  $(table)[0].config.$tbodies.children().detach();
1345
1379
  };
1346
1380
 
1347
- ts.bindEvents = function(table, $headers, core){
1381
+ ts.bindEvents = function(table, $headers, core) {
1348
1382
  table = $(table)[0];
1349
1383
  var t, downTarget = null,
1350
1384
  c = table.config;
@@ -1355,28 +1389,35 @@
1355
1389
  $(t).addClass( c.namespace.slice(1) + '_extra_table' );
1356
1390
  }
1357
1391
  }
1392
+ t = ( c.pointerDown + ' ' + c.pointerUp + ' ' + c.pointerClick + ' sort keyup ' )
1393
+ .replace(/\s+/g, ' ')
1394
+ .split(' ')
1395
+ .join(c.namespace + ' ');
1358
1396
  // apply event handling to headers and/or additional headers (stickyheaders, scroller, etc)
1359
1397
  $headers
1360
1398
  // http://stackoverflow.com/questions/5312849/jquery-find-self;
1361
1399
  .find(c.selectorSort).add( $headers.filter(c.selectorSort) )
1362
- .unbind( ('mousedown mouseup click sort keyup '.split(' ').join(c.namespace + ' ')).replace(/\s+/g, ' ') )
1363
- .bind( 'mousedown mouseup click sort keyup '.split(' ').join(c.namespace + ' '), function(e, external) {
1400
+ .unbind(t)
1401
+ .bind(t, function(e, external) {
1364
1402
  var cell,
1365
1403
  $target = $(e.target),
1366
- type = e.type;
1404
+ // wrap event type in spaces, so the match doesn't trigger on inner words
1405
+ type = ' ' + e.type + ' ';
1367
1406
  // only recognize left clicks
1368
- if ( ( ( e.which || e.button ) !== 1 && !/sort|keyup|click/.test(type) ) ||
1407
+ if ( ( ( e.which || e.button ) !== 1 && !type.match( ' ' + c.pointerClick + ' | sort | keyup ' ) ) ||
1369
1408
  // allow pressing enter
1370
- ( type === 'keyup' && e.which !== 13 ) ||
1409
+ ( type === ' keyup ' && e.which !== 13 ) ||
1371
1410
  // allow triggering a click event (e.which is undefined) & ignore physical clicks
1372
- ( type === 'click' && typeof e.which !== 'undefined' ) ) {
1411
+ ( type.match(' ' + c.pointerClick + ' ') && typeof e.which !== 'undefined' ) ) {
1373
1412
  return;
1374
1413
  }
1375
1414
  // ignore mouseup if mousedown wasn't on the same target
1376
- if ( type === 'mouseup' && downTarget !== e.target && external !== true ) { return; }
1415
+ if ( type.match(' ' + c.pointerUp + ' ') && downTarget !== e.target && external !== true ) { return; }
1377
1416
  // set timer on mousedown
1378
- if ( type === 'mousedown' ) {
1417
+ if ( type.match(' ' + c.pointerDown + ' ') ) {
1379
1418
  downTarget = e.target;
1419
+ // needed or jQuery v1.2.6 throws an error
1420
+ e.preventDefault();
1380
1421
  return;
1381
1422
  }
1382
1423
  downTarget = null;
@@ -1411,17 +1452,20 @@
1411
1452
 
1412
1453
  // restore headers
1413
1454
  ts.restoreHeaders = function(table){
1414
- var $cell,
1415
- c = $(table)[0].config;
1455
+ var index, $cell,
1456
+ c = $(table)[0].config,
1457
+ $headers = c.$table.find( c.selectorHeaders ),
1458
+ len = $headers.length;
1416
1459
  // don't use c.$headers here in case header cells were swapped
1417
- c.$table.find(c.selectorHeaders).each(function(i){
1418
- $cell = $(this);
1460
+ for ( index = 0; index < len; index++ ) {
1461
+ // c.$table.find(c.selectorHeaders).each(function(i){
1462
+ $cell = $headers.eq( index );
1419
1463
  // only restore header cells if it is wrapped
1420
1464
  // because this is also used by the updateAll method
1421
- if ($cell.find('.' + ts.css.headerIn).length){
1422
- $cell.html( c.headerContent[i] );
1465
+ if ( $cell.find( '.' + ts.css.headerIn ).length ) {
1466
+ $cell.html( c.headerContent[ index ] );
1423
1467
  }
1424
- });
1468
+ }
1425
1469
  };
1426
1470
 
1427
1471
  ts.destroy = function(table, removeClasses, callback){
@@ -1581,8 +1625,8 @@
1581
1625
  'E' : '\u00c9\u00c8\u00ca\u00cb\u011a\u0118', // ÉÈÊËĚĘ
1582
1626
  'i' : '\u00ed\u00ec\u0130\u00ee\u00ef\u0131', // íìİîïı
1583
1627
  'I' : '\u00cd\u00cc\u0130\u00ce\u00cf', // ÍÌİÎÏ
1584
- 'o' : '\u00f3\u00f2\u00f4\u00f5\u00f6', // óòôõö
1585
- 'O' : '\u00d3\u00d2\u00d4\u00d5\u00d6', // ÓÒÔÕÖ
1628
+ 'o' : '\u00f3\u00f2\u00f4\u00f5\u00f6\u014d', // óòôõöō
1629
+ 'O' : '\u00d3\u00d2\u00d4\u00d5\u00d6\u014c', // ÓÒÔÕÖŌ
1586
1630
  'ss': '\u00df', // ß (s sharp)
1587
1631
  'SS': '\u1e9e', // ẞ (Capital sharp s)
1588
1632
  'u' : '\u00fa\u00f9\u00fb\u00fc\u016f', // úùûüů
@@ -1922,7 +1966,7 @@
1922
1966
 
1923
1967
  ts.isDigit = function(s) {
1924
1968
  // replace all unwanted chars and match
1925
- return isNaN(s) ? (/^[\-+(]?\d+[)]?$/).test(s.toString().replace(/[,.'"\s]/g, '')) : true;
1969
+ return isNaN(s) ? (/^[\-+(]?\d+[)]?$/).test(s.toString().replace(/[,.'"\s]/g, '')) : s !== '';
1926
1970
  };
1927
1971
 
1928
1972
  }()
@@ -2108,7 +2152,7 @@
2108
2152
  id: 'zebra',
2109
2153
  priority: 90,
2110
2154
  format: function(table, c, wo) {
2111
- var $tb, $tv, $tr, row, even, time, k,
2155
+ var $tv, $tr, row, even, time, k, i, len,
2112
2156
  child = new RegExp(c.cssChildRow, 'i'),
2113
2157
  b = c.$tbodies.add( $( c.namespace + '_extra_table' ).children( 'tbody' ) );
2114
2158
  if (c.debug) {
@@ -2117,17 +2161,17 @@
2117
2161
  for (k = 0; k < b.length; k++ ) {
2118
2162
  // loop through the visible rows
2119
2163
  row = 0;
2120
- $tb = b.eq(k);
2121
- $tv = $tb.children('tr:visible').not(c.selectorRemove);
2122
- // revered back to using jQuery each - strangely it's the fastest method
2123
- /*jshint loopfunc:true */
2124
- $tv.each(function(){
2125
- $tr = $(this);
2164
+ $tv = b.eq( k ).children( 'tr:visible' ).not( c.selectorRemove );
2165
+ len = $tv.length;
2166
+ for ( i = 0; i < len; i++ ) {
2167
+ $tr = $tv.eq( i );
2126
2168
  // style child rows the same way the parent row was styled
2127
- if (!child.test(this.className)) { row++; }
2128
- even = (row % 2 === 0);
2129
- $tr.removeClass(wo.zebra[even ? 1 : 0]).addClass(wo.zebra[even ? 0 : 1]);
2130
- });
2169
+ if ( !child.test( $tr[0].className ) ) { row++; }
2170
+ even = ( row % 2 === 0 );
2171
+ $tr
2172
+ .removeClass( wo.zebra[ even ? 1 : 0 ] )
2173
+ .addClass( wo.zebra[ even ? 0 : 1 ] );
2174
+ }
2131
2175
  }
2132
2176
  },
2133
2177
  remove: function(table, c, wo, refreshing){
@@ -2149,7 +2193,7 @@
2149
2193
  ;(function ($, window, document) {
2150
2194
  'use strict';
2151
2195
 
2152
- var ts = $.tablesorter = $.tablesorter || {};
2196
+ var ts = $.tablesorter || {};
2153
2197
  // *** Store data in local storage, with a cookie fallback ***
2154
2198
  /* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json)
2155
2199
  if you need it, then include https://github.com/douglascrockford/JSON-js
@@ -2238,7 +2282,7 @@ ts.storage = function(table, key, value, options) {
2238
2282
  /*! Widget: uitheme - updated 3/26/2015 (v2.21.3) */
2239
2283
  ;(function ($) {
2240
2284
  'use strict';
2241
- var ts = $.tablesorter = $.tablesorter || {};
2285
+ var ts = $.tablesorter || {};
2242
2286
 
2243
2287
  ts.themes = {
2244
2288
  'bootstrap' : {
@@ -2424,7 +2468,7 @@ ts.addWidget({
2424
2468
  /*! Widget: columns */
2425
2469
  ;(function ($) {
2426
2470
  'use strict';
2427
- var ts = $.tablesorter = $.tablesorter || {};
2471
+ var ts = $.tablesorter || {};
2428
2472
 
2429
2473
  ts.addWidget({
2430
2474
  id: "columns",
@@ -2500,16 +2544,16 @@ ts.addWidget({
2500
2544
 
2501
2545
  })(jQuery);
2502
2546
 
2503
- /*! Widget: filter - updated 3/26/2015 (v2.21.3) *//*
2547
+ /*! Widget: filter - updated 5/17/2015 (v2.22.0) *//*
2504
2548
  * Requires tablesorter v2.8+ and jQuery 1.7+
2505
2549
  * by Rob Garrison
2506
2550
  */
2507
- ;(function ($) {
2551
+ ;( function ( $ ) {
2508
2552
  'use strict';
2509
- var ts = $.tablesorter = $.tablesorter || {},
2553
+ var ts = $.tablesorter || {},
2510
2554
  tscss = ts.css;
2511
2555
 
2512
- $.extend(tscss, {
2556
+ $.extend( tscss, {
2513
2557
  filterRow : 'tablesorter-filter-row',
2514
2558
  filter : 'tablesorter-filter',
2515
2559
  filterDisabled : 'disabled',
@@ -2517,26 +2561,27 @@ $.extend(tscss, {
2517
2561
  });
2518
2562
 
2519
2563
  ts.addWidget({
2520
- id: "filter",
2564
+ id: 'filter',
2521
2565
  priority: 50,
2522
2566
  options : {
2523
2567
  filter_childRows : false, // if true, filter includes child row content in the search
2568
+ filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped
2524
2569
  filter_columnFilters : true, // if true, a filter will be added to the top of each table column
2525
- filter_columnAnyMatch: true, // if true, allows using "#:{query}" in AnyMatch searches (column:query)
2526
- filter_cellFilter : '', // css class name added to the filter cell (string or array)
2527
- filter_cssFilter : '', // css class name added to the filter row & each input in the row (tablesorter-filter is ALWAYS added)
2528
- filter_defaultFilter : {}, // add a default column filter type "~{query}" to make fuzzy searches default; "{q1} AND {q2}" to make all searches use a logical AND.
2570
+ filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query )
2571
+ filter_cellFilter : '', // css class name added to the filter cell ( string or array )
2572
+ filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added )
2573
+ filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND.
2529
2574
  filter_excludeFilter : {}, // filters to exclude, per column
2530
- filter_external : '', // jQuery selector string (or jQuery object) of external filters
2575
+ filter_external : '', // jQuery selector string ( or jQuery object ) of external filters
2531
2576
  filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin
2532
2577
  filter_formatter : null, // add custom filter elements to the filter row
2533
2578
  filter_functions : null, // add custom filter functions using this option
2534
2579
  filter_hideEmpty : true, // hide filter row when table is empty
2535
2580
  filter_hideFilters : false, // collapse filter row when mouse leaves the area
2536
2581
  filter_ignoreCase : true, // if true, make all searches case-insensitive
2537
- filter_liveSearch : true, // if true, search column content while the user types (with a delay)
2538
- filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available (visible) options within the drop down
2539
- filter_placeholder : { search : '', select : '' }, // default placeholder text (overridden by any header "data-placeholder" setting)
2582
+ filter_liveSearch : true, // if true, search column content while the user types ( with a delay )
2583
+ filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down
2584
+ filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting )
2540
2585
  filter_reset : null, // jQuery selector string of an element used to reset the filters
2541
2586
  filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters
2542
2587
  filter_searchDelay : 300, // typing delay in milliseconds before starting a search
@@ -2548,37 +2593,38 @@ ts.addWidget({
2548
2593
  filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value
2549
2594
  filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text
2550
2595
  },
2551
- format: function(table, c, wo) {
2552
- if (!c.$table.hasClass('hasFilters')) {
2553
- ts.filter.init(table, c, wo);
2596
+ format: function( table, c, wo ) {
2597
+ if ( !c.$table.hasClass( 'hasFilters' ) ) {
2598
+ ts.filter.init( table, c, wo );
2554
2599
  }
2555
2600
  },
2556
- remove: function(table, c, wo, refreshing) {
2601
+ remove: function( table, c, wo, refreshing ) {
2557
2602
  var tbodyIndex, $tbody,
2558
2603
  $table = c.$table,
2559
2604
  $tbodies = c.$tbodies,
2560
- events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '.split(' ').join(c.namespace + 'filter ');
2605
+ events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '
2606
+ .split( ' ' ).join( c.namespace + 'filter ' );
2561
2607
  $table
2562
- .removeClass('hasFilters')
2608
+ .removeClass( 'hasFilters' )
2563
2609
  // add .tsfilter namespace to all BUT search
2564
- .unbind( events.replace(/\s+/g, ' ') )
2610
+ .unbind( events.replace( /\s+/g, ' ' ) )
2565
2611
  // remove the filter row even if refreshing, because the column might have been moved
2566
- .find('.' + tscss.filterRow).remove();
2567
- if (refreshing) { return; }
2568
- for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
2569
- $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody
2570
- $tbody.children().removeClass(wo.filter_filteredRow).show();
2571
- ts.processTbody(table, $tbody, false); // restore tbody
2612
+ .find( '.' + tscss.filterRow ).remove();
2613
+ if ( refreshing ) { return; }
2614
+ for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
2615
+ $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody
2616
+ $tbody.children().removeClass( wo.filter_filteredRow ).show();
2617
+ ts.processTbody( table, $tbody, false ); // restore tbody
2572
2618
  }
2573
- if (wo.filter_reset) {
2574
- $(document).undelegate(wo.filter_reset, 'click.tsfilter');
2619
+ if ( wo.filter_reset ) {
2620
+ $( document ).undelegate( wo.filter_reset, 'click.tsfilter' );
2575
2621
  }
2576
2622
  }
2577
2623
  });
2578
2624
 
2579
2625
  ts.filter = {
2580
2626
 
2581
- // regex used in filter "check" functions - not for general use and not documented
2627
+ // regex used in filter 'check' functions - not for general use and not documented
2582
2628
  regex: {
2583
2629
  regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex
2584
2630
  child : /tablesorter-childRow/, // child row class name; this gets updated in the script
@@ -2591,22 +2637,33 @@ ts.filter = {
2591
2637
  },
2592
2638
  // function( c, data ) { }
2593
2639
  // c = table.config
2594
- // data.filter = array of filter input values;
2595
- // data.iFilter = same array, except lowercase (if wo.filter_ignoreCase is true)
2596
- // data.exact = table cell text (or parsed data if column parser enabled)
2597
- // data.iExact = same as data.exact, except lowercase (if wo.filter_ignoreCase is true)
2598
- // data.cache = table cell text from cache, so it has been parsed (& in all lower case if config.ignoreCase is true)
2599
- // data.index = column index; table = table element (DOM)
2600
- // data.parsed = array (by column) of boolean values (from filter_useParsedData or "filter-parsed" class)
2640
+ // data.$row = jQuery object of the row currently being processed
2641
+ // data.$cells = jQuery object of all cells within the current row
2642
+ // data.filters = array of filters for all columns ( some may be undefined )
2643
+ // data.filter = filter for the current column
2644
+ // data.iFilter = same as data.filter, except lowercase ( if wo.filter_ignoreCase is true )
2645
+ // data.exact = table cell text ( or parsed data if column parser enabled )
2646
+ // data.iExact = same as data.exact, except lowercase ( if wo.filter_ignoreCase is true )
2647
+ // data.cache = table cell text from cache, so it has been parsed ( & in all lower case if c.ignoreCase is true )
2648
+ // data.cacheArray = An array of parsed content from each table cell in the row being processed
2649
+ // data.index = column index; table = table element ( DOM )
2650
+ // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
2601
2651
  types: {
2602
2652
  // Look for regex
2603
2653
  regex: function( c, data ) {
2604
- if ( ts.filter.regex.regex.test(data.iFilter) ) {
2654
+ if ( ts.filter.regex.regex.test( data.filter ) ) {
2605
2655
  var matches,
2606
- regex = ts.filter.regex.regex.exec(data.iFilter);
2656
+ // cache regex per column for optimal speed
2657
+ regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ),
2658
+ isRegex = regex instanceof RegExp;
2607
2659
  try {
2608
- matches = new RegExp(regex[1], regex[2]).test( data.iExact );
2609
- } catch (error) {
2660
+ if ( !isRegex ) {
2661
+ // force case insensitive search if ignoreCase option set?
2662
+ // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; }
2663
+ data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] );
2664
+ }
2665
+ matches = regex.test( data.exact );
2666
+ } catch ( error ) {
2610
2667
  matches = false;
2611
2668
  }
2612
2669
  return matches;
@@ -2615,46 +2672,56 @@ ts.filter = {
2615
2672
  },
2616
2673
  // Look for operators >, >=, < or <=
2617
2674
  operators: function( c, data ) {
2618
- if ( /^[<>]=?/.test(data.iFilter) ) {
2619
- var cachedValue, result,
2675
+ // ignore empty strings... because '' < 10 is true
2676
+ if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) {
2677
+ var cachedValue, result, txt,
2620
2678
  table = c.table,
2621
2679
  index = data.index,
2622
2680
  parsed = data.parsed[index],
2623
- query = ts.formatFloat( data.iFilter.replace(ts.filter.regex.operators, ''), table ),
2681
+ query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ),
2624
2682
  parser = c.parsers[index],
2625
2683
  savedSearch = query;
2626
- // parse filter value in case we're comparing numbers (dates)
2627
- if (parsed || parser.type === 'numeric') {
2628
- result = ts.filter.parseFilter(c, $.trim('' + data.iFilter.replace(ts.filter.regex.operators, '')), index, parsed, true);
2629
- query = ( typeof result === "number" && result !== '' && !isNaN(result) ) ? result : query;
2684
+ // parse filter value in case we're comparing numbers ( dates )
2685
+ if ( parsed || parser.type === 'numeric' ) {
2686
+ txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) );
2687
+ result = ts.filter.parseFilter( c, txt, index, true );
2688
+ query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query;
2630
2689
  }
2631
-
2632
2690
  // iExact may be numeric - see issue #149;
2633
- // check if cached is defined, because sometimes j goes out of range? (numeric columns)
2634
- cachedValue = ( parsed || parser.type === 'numeric' ) && !isNaN(query) && typeof data.cache !== 'undefined' ? data.cache :
2635
- isNaN(data.iExact) ? ts.formatFloat( data.iExact.replace(ts.filter.regex.nondigit, ''), table) :
2636
- ts.formatFloat( data.iExact, table );
2637
-
2638
- if ( />/.test(data.iFilter) ) { result = />=/.test(data.iFilter) ? cachedValue >= query : cachedValue > query; }
2639
- if ( /</.test(data.iFilter) ) { result = /<=/.test(data.iFilter) ? cachedValue <= query : cachedValue < query; }
2691
+ // check if cached is defined, because sometimes j goes out of range? ( numeric columns )
2692
+ if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) &&
2693
+ typeof data.cache !== 'undefined' ) {
2694
+ cachedValue = data.cache;
2695
+ } else {
2696
+ txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
2697
+ cachedValue = ts.formatFloat( txt, table );
2698
+ }
2699
+ if ( />/.test( data.iFilter ) ) {
2700
+ result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query;
2701
+ } else if ( /</.test( data.iFilter ) ) {
2702
+ result = /<=/.test( data.iFilter ) ? cachedValue <= query : cachedValue < query;
2703
+ }
2640
2704
  // keep showing all rows if nothing follows the operator
2641
- if ( !result && savedSearch === '' ) { result = true; }
2705
+ if ( !result && savedSearch === '' ) {
2706
+ result = true;
2707
+ }
2642
2708
  return result;
2643
2709
  }
2644
2710
  return null;
2645
2711
  },
2646
2712
  // Look for a not match
2647
2713
  notMatch: function( c, data ) {
2648
- if ( /^\!/.test(data.iFilter) ) {
2714
+ if ( /^\!/.test( data.iFilter ) ) {
2649
2715
  var indx,
2650
- filter = ts.filter.parseFilter(c, data.iFilter.replace('!', ''), data.index, data.parsed[data.index]) || '';
2651
- if (ts.filter.regex.exact.test(filter)) {
2716
+ txt = data.iFilter.replace( '!', '' ),
2717
+ filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
2718
+ if ( ts.filter.regex.exact.test( filter ) ) {
2652
2719
  // look for exact not matches - see #628
2653
- filter = filter.replace(ts.filter.regex.exact, '');
2654
- return filter === '' ? true : $.trim(filter) !== data.iExact;
2720
+ filter = filter.replace( ts.filter.regex.exact, '' );
2721
+ return filter === '' ? true : $.trim( filter ) !== data.iExact;
2655
2722
  } else {
2656
- indx = data.iExact.search( $.trim(filter) );
2657
- return filter === '' ? true : !(c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 0);
2723
+ indx = data.iExact.search( $.trim( filter ) );
2724
+ return filter === '' ? true : !( c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 0 );
2658
2725
  }
2659
2726
  }
2660
2727
  return null;
@@ -2662,84 +2729,101 @@ ts.filter = {
2662
2729
  // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
2663
2730
  exact: function( c, data ) {
2664
2731
  /*jshint eqeqeq:false */
2665
- if (ts.filter.regex.exact.test(data.iFilter)) {
2666
- var filter = ts.filter.parseFilter(c, data.iFilter.replace(ts.filter.regex.exact, ''), data.index, data.parsed[data.index]) || '';
2667
- return data.anyMatch ? $.inArray(filter, data.rowArray) >= 0 : filter == data.iExact;
2732
+ if ( ts.filter.regex.exact.test( data.iFilter ) ) {
2733
+ var txt = data.iFilter.replace( ts.filter.regex.exact, '' ),
2734
+ filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
2735
+ return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact;
2668
2736
  }
2669
2737
  return null;
2670
2738
  },
2671
- // Look for an AND or && operator (logical and)
2739
+ // Look for an AND or && operator ( logical and )
2672
2740
  and : function( c, data ) {
2673
- if ( ts.filter.regex.andTest.test(data.filter) ) {
2741
+ if ( ts.filter.regex.andTest.test( data.filter ) ) {
2674
2742
  var index = data.index,
2675
2743
  parsed = data.parsed[index],
2676
2744
  query = data.iFilter.split( ts.filter.regex.andSplit ),
2677
- result = data.iExact.search( $.trim( ts.filter.parseFilter(c, query[0], index, parsed) ) ) >= 0,
2745
+ result = data.iExact.search( $.trim( ts.filter.parseFilter( c, query[0], index, parsed ) ) ) >= 0,
2678
2746
  indx = query.length - 1;
2679
- while (result && indx) {
2680
- result = result && data.iExact.search( $.trim( ts.filter.parseFilter(c, query[indx], index, parsed) ) ) >= 0;
2747
+ while ( result && indx ) {
2748
+ result = result &&
2749
+ data.iExact.search( $.trim( ts.filter.parseFilter( c, query[indx], index, parsed ) ) ) >= 0;
2681
2750
  indx--;
2682
2751
  }
2683
2752
  return result;
2684
2753
  }
2685
2754
  return null;
2686
2755
  },
2687
- // Look for a range (using " to " or " - ") - see issue #166; thanks matzhu!
2756
+ // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
2688
2757
  range : function( c, data ) {
2689
- if ( ts.filter.regex.toTest.test(data.iFilter) ) {
2690
- var result, tmp,
2758
+ if ( ts.filter.regex.toTest.test( data.iFilter ) ) {
2759
+ var result, tmp, range1, range2,
2691
2760
  table = c.table,
2692
2761
  index = data.index,
2693
2762
  parsed = data.parsed[index],
2694
2763
  // make sure the dash is for a range and not indicating a negative number
2695
- query = data.iFilter.split( ts.filter.regex.toSplit ),
2696
- range1 = ts.formatFloat( ts.filter.parseFilter(c, query[0].replace(ts.filter.regex.nondigit, '') || '', index, parsed), table ),
2697
- range2 = ts.formatFloat( ts.filter.parseFilter(c, query[1].replace(ts.filter.regex.nondigit, '') || '', index, parsed), table );
2698
- // parse filter value in case we're comparing numbers (dates)
2699
- if (parsed || c.parsers[index].type === 'numeric') {
2700
- result = c.parsers[index].format('' + query[0], table, c.$headers.eq(index), index);
2701
- range1 = (result !== '' && !isNaN(result)) ? result : range1;
2702
- result = c.parsers[index].format('' + query[1], table, c.$headers.eq(index), index);
2703
- range2 = (result !== '' && !isNaN(result)) ? result : range2;
2704
- }
2705
- result = ( parsed || c.parsers[index].type === 'numeric' ) && !isNaN(range1) && !isNaN(range2) ? data.cache :
2706
- isNaN(data.iExact) ? ts.formatFloat( data.iExact.replace(ts.filter.regex.nondigit, ''), table) :
2707
- ts.formatFloat( data.iExact, table );
2708
- if (range1 > range2) { tmp = range1; range1 = range2; range2 = tmp; } // swap
2709
- return (result >= range1 && result <= range2) || (range1 === '' || range2 === '');
2764
+ query = data.iFilter.split( ts.filter.regex.toSplit );
2765
+
2766
+ tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || '';
2767
+ range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
2768
+ tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || '';
2769
+ range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
2770
+ // parse filter value in case we're comparing numbers ( dates )
2771
+ if ( parsed || c.parsers[index].type === 'numeric' ) {
2772
+ result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index );
2773
+ range1 = ( result !== '' && !isNaN( result ) ) ? result : range1;
2774
+ result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index );
2775
+ range2 = ( result !== '' && !isNaN( result ) ) ? result : range2;
2776
+ }
2777
+ if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) {
2778
+ result = data.cache;
2779
+ } else {
2780
+ tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
2781
+ result = ts.formatFloat( tmp, table );
2782
+ }
2783
+ if ( range1 > range2 ) {
2784
+ tmp = range1; range1 = range2; range2 = tmp; // swap
2785
+ }
2786
+ return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' );
2710
2787
  }
2711
2788
  return null;
2712
2789
  },
2713
2790
  // Look for wild card: ? = single, * = multiple, or | = logical OR
2714
2791
  wild : function( c, data ) {
2715
- if ( /[\?\*\|]/.test(data.iFilter) || ts.filter.regex.orReplace.test(data.filter) ) {
2792
+ if ( /[\?\*\|]/.test( data.iFilter ) || ts.filter.regex.orReplace.test( data.filter ) ) {
2716
2793
  var index = data.index,
2717
- parsed = data.parsed[index],
2718
- query = ts.filter.parseFilter(c, data.iFilter.replace(ts.filter.regex.orReplace, "|"), index, parsed) || '';
2719
- // look for an exact match with the "or" unless the "filter-match" class is found
2720
- if (!c.$headerIndexed[index].hasClass('filter-match') && /\|/.test(query)) {
2794
+ parsed = data.parsed[ index ],
2795
+ txt = data.iFilter.replace( ts.filter.regex.orReplace, '|' ),
2796
+ query = ts.filter.parseFilter( c, txt, index, parsed ) || '';
2797
+ // look for an exact match with the 'or' unless the 'filter-match' class is found
2798
+ if ( !c.$headerIndexed[ index ].hasClass( 'filter-match' ) && /\|/.test( query ) ) {
2721
2799
  // show all results while using filter match. Fixes #727
2722
- if (query[ query.length - 1 ] === '|') { query += '*'; }
2723
- query = data.anyMatch && $.isArray(data.rowArray) ? '(' + query + ')' : '^(' + query + ')$';
2800
+ if ( query[ query.length - 1 ] === '|' ) {
2801
+ query += '*';
2802
+ }
2803
+ query = data.anyMatch && $.isArray( data.rowArray ) ?
2804
+ '(' + query + ')' :
2805
+ '^(' + query + ')$';
2724
2806
  }
2725
2807
  // parsing the filter may not work properly when using wildcards =/
2726
- return new RegExp( query.replace(/\?/g, '\\S{1}').replace(/\*/g, '\\S*') ).test(data.iExact);
2808
+ return new RegExp( query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ) )
2809
+ .test( data.iExact );
2727
2810
  }
2728
2811
  return null;
2729
2812
  },
2730
- // fuzzy text search; modified from https://github.com/mattyork/fuzzy (MIT license)
2813
+ // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license )
2731
2814
  fuzzy: function( c, data ) {
2732
- if ( /^~/.test(data.iFilter) ) {
2815
+ if ( /^~/.test( data.iFilter ) ) {
2733
2816
  var indx,
2734
2817
  patternIndx = 0,
2735
2818
  len = data.iExact.length,
2736
- pattern = ts.filter.parseFilter(c, data.iFilter.slice(1), data.index, data.parsed[data.index]) || '';
2737
- for (indx = 0; indx < len; indx++) {
2738
- if (data.iExact[indx] === pattern[patternIndx]) {
2819
+ txt = data.iFilter.slice( 1 ),
2820
+ pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
2821
+ for ( indx = 0; indx < len; indx++ ) {
2822
+ if ( data.iExact[ indx ] === pattern[ patternIndx ] ) {
2739
2823
  patternIndx += 1;
2740
2824
  }
2741
2825
  }
2742
- if (patternIndx === pattern.length) {
2826
+ if ( patternIndx === pattern.length ) {
2743
2827
  return true;
2744
2828
  }
2745
2829
  return false;
@@ -2747,17 +2831,17 @@ ts.filter = {
2747
2831
  return null;
2748
2832
  }
2749
2833
  },
2750
- init: function(table, c, wo) {
2834
+ init: function( table, c, wo ) {
2751
2835
  // filter language options
2752
- ts.language = $.extend(true, {}, {
2836
+ ts.language = $.extend( true, {}, {
2753
2837
  to : 'to',
2754
2838
  or : 'or',
2755
2839
  and : 'and'
2756
- }, ts.language);
2840
+ }, ts.language );
2757
2841
 
2758
2842
  var options, string, txt, $header, column, filters, val, fxn, noSelect,
2759
2843
  regex = ts.filter.regex;
2760
- c.$table.addClass('hasFilters');
2844
+ c.$table.addClass( 'hasFilters' );
2761
2845
 
2762
2846
  // define timers so using clearTimeout won't cause an undefined error
2763
2847
  wo.searchTimer = null;
@@ -2767,512 +2851,566 @@ ts.filter = {
2767
2851
  wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]';
2768
2852
  wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]';
2769
2853
 
2770
- txt = '\\{' + ts.filter.regex.query + '\\}';
2854
+ val = '\\{' + ts.filter.regex.query + '\\}';
2771
2855
  $.extend( regex, {
2772
- child : new RegExp(c.cssChildRow),
2773
- filtered : new RegExp(wo.filter_filteredRow),
2774
- alreadyFiltered : new RegExp('(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i'),
2775
- toTest : new RegExp('\\s+(-|' + ts.language.to + ')\\s+', 'i'),
2776
- toSplit : new RegExp('(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi'),
2777
- andTest : new RegExp('\\s+(' + ts.language.and + '|&&)\\s+', 'i'),
2778
- andSplit : new RegExp('(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi'),
2779
- orReplace : new RegExp('\\s+(' + ts.language.or + ')\\s+', 'gi'),
2780
- iQuery : new RegExp(txt, 'i'),
2781
- igQuery : new RegExp(txt, 'ig')
2856
+ child : new RegExp( c.cssChildRow ),
2857
+ filtered : new RegExp( wo.filter_filteredRow ),
2858
+ alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ),
2859
+ toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ),
2860
+ toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ),
2861
+ andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ),
2862
+ andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ),
2863
+ orReplace : new RegExp( '\\s+(' + ts.language.or + ')\\s+', 'gi' ),
2864
+ iQuery : new RegExp( val, 'i' ),
2865
+ igQuery : new RegExp( val, 'ig' )
2782
2866
  });
2783
2867
 
2784
- // don't build filter row if columnFilters is false or all columns are set to "filter-false" - issue #156
2785
- if (wo.filter_columnFilters !== false && c.$headers.filter('.filter-false, .parser-false').length !== c.$headers.length) {
2868
+ // don't build filter row if columnFilters is false or all columns are set to 'filter-false'
2869
+ // see issue #156
2870
+ val = c.$headers.filter( '.filter-false, .parser-false' ).length;
2871
+ if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) {
2786
2872
  // build filter row
2787
- ts.filter.buildRow(table, c, wo);
2873
+ ts.filter.buildRow( table, c, wo );
2788
2874
  }
2789
2875
 
2790
- txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '.split(' ').join(c.namespace + 'filter ');
2791
- c.$table.bind( txt, function(event, filter) {
2792
- val = (wo.filter_hideEmpty && $.isEmptyObject(c.cache) && !(c.delayInit && event.type === 'appendCache'));
2793
- // hide filter row using the "filtered" class name
2794
- c.$table.find('.' + tscss.filterRow).toggleClass(wo.filter_filteredRow, val ); // fixes #450
2795
- if ( !/(search|filter)/.test(event.type) ) {
2876
+ txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '
2877
+ .split( ' ' ).join( c.namespace + 'filter ' );
2878
+ c.$table.bind( txt, function( event, filter ) {
2879
+ val = wo.filter_hideEmpty &&
2880
+ $.isEmptyObject( c.cache ) &&
2881
+ !( c.delayInit && event.type === 'appendCache' );
2882
+ // hide filter row using the 'filtered' class name
2883
+ c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450
2884
+ if ( !/(search|filter)/.test( event.type ) ) {
2796
2885
  event.stopPropagation();
2797
- ts.filter.buildDefault(table, true);
2886
+ ts.filter.buildDefault( table, true );
2798
2887
  }
2799
- if (event.type === 'filterReset') {
2800
- c.$table.find('.' + tscss.filter).add(wo.filter_$externalFilters).val('');
2801
- ts.filter.searching(table, []);
2802
- } else if (event.type === 'filterEnd') {
2803
- ts.filter.buildDefault(table, true);
2888
+ if ( event.type === 'filterReset' ) {
2889
+ c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' );
2890
+ ts.filter.searching( table, [] );
2891
+ } else if ( event.type === 'filterEnd' ) {
2892
+ ts.filter.buildDefault( table, true );
2804
2893
  } else {
2805
- // send false argument to force a new search; otherwise if the filter hasn't changed, it will return
2806
- filter = event.type === 'search' ? filter : event.type === 'updateComplete' ? c.$table.data('lastSearch') : '';
2807
- if (/(update|add)/.test(event.type) && event.type !== "updateComplete") {
2894
+ // send false argument to force a new search; otherwise if the filter hasn't changed,
2895
+ // it will return
2896
+ filter = event.type === 'search' ? filter :
2897
+ event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : '';
2898
+ if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) {
2808
2899
  // force a new search since content has changed
2809
2900
  c.lastCombinedFilter = null;
2810
2901
  c.lastSearch = [];
2811
2902
  }
2812
- // pass true (skipFirst) to prevent the tablesorter.setFilters function from skipping the first input
2813
- // ensures all inputs are updated when a search is triggered on the table $('table').trigger('search', [...]);
2814
- ts.filter.searching(table, filter, true);
2903
+ // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first
2904
+ // input ensures all inputs are updated when a search is triggered on the table
2905
+ // $( 'table' ).trigger( 'search', [...] );
2906
+ ts.filter.searching( table, filter, true );
2815
2907
  }
2816
2908
  return false;
2817
2909
  });
2818
2910
 
2819
2911
  // reset button/link
2820
- if (wo.filter_reset) {
2821
- if (wo.filter_reset instanceof $) {
2912
+ if ( wo.filter_reset ) {
2913
+ if ( wo.filter_reset instanceof $ ) {
2822
2914
  // reset contains a jQuery object, bind to it
2823
- wo.filter_reset.click(function(){
2824
- c.$table.trigger('filterReset');
2915
+ wo.filter_reset.click( function() {
2916
+ c.$table.trigger( 'filterReset' );
2825
2917
  });
2826
- } else if ($(wo.filter_reset).length) {
2918
+ } else if ( $( wo.filter_reset ).length ) {
2827
2919
  // reset is a jQuery selector, use event delegation
2828
- $(document)
2829
- .undelegate(wo.filter_reset, 'click.tsfilter')
2830
- .delegate(wo.filter_reset, 'click.tsfilter', function() {
2831
- // trigger a reset event, so other functions (filter_formatter) know when to reset
2832
- c.$table.trigger('filterReset');
2833
- });
2920
+ $( document )
2921
+ .undelegate( wo.filter_reset, 'click.tsfilter' )
2922
+ .delegate( wo.filter_reset, 'click.tsfilter', function() {
2923
+ // trigger a reset event, so other functions ( filter_formatter ) know when to reset
2924
+ c.$table.trigger( 'filterReset' );
2925
+ });
2834
2926
  }
2835
2927
  }
2836
- if (wo.filter_functions) {
2837
- for (column = 0; column < c.columns; column++) {
2928
+ if ( wo.filter_functions ) {
2929
+ for ( column = 0; column < c.columns; column++ ) {
2838
2930
  fxn = ts.getColumnData( table, wo.filter_functions, column );
2839
- if (fxn) {
2840
- // remove "filter-select" from header otherwise the options added here are replaced with all options
2841
- $header = c.$headerIndexed[column].removeClass('filter-select');
2842
- // don't build select if "filter-false" or "parser-false" set
2843
- noSelect = !($header.hasClass('filter-false') || $header.hasClass('parser-false'));
2931
+ if ( fxn ) {
2932
+ // remove 'filter-select' from header otherwise the options added here are replaced with
2933
+ // all options
2934
+ $header = c.$headerIndexed[ column ].removeClass( 'filter-select' );
2935
+ // don't build select if 'filter-false' or 'parser-false' set
2936
+ noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
2844
2937
  options = '';
2845
2938
  if ( fxn === true && noSelect ) {
2846
- ts.filter.buildSelect(table, column);
2939
+ ts.filter.buildSelect( table, column );
2847
2940
  } else if ( typeof fxn === 'object' && noSelect ) {
2848
2941
  // add custom drop down list
2849
- for (string in fxn) {
2850
- if (typeof string === 'string') {
2942
+ for ( string in fxn ) {
2943
+ if ( typeof string === 'string' ) {
2851
2944
  options += options === '' ?
2852
- '<option value="">' + ($header.data('placeholder') || $header.attr('data-placeholder') || wo.filter_placeholder.select || '') + '</option>' : '';
2945
+ '<option value="">' +
2946
+ ( $header.data( 'placeholder' ) ||
2947
+ $header.attr( 'data-placeholder' ) ||
2948
+ wo.filter_placeholder.select ||
2949
+ ''
2950
+ ) +
2951
+ '</option>' : '';
2853
2952
  val = string;
2854
2953
  txt = string;
2855
- if (string.indexOf(wo.filter_selectSourceSeparator) >= 0) {
2856
- val = string.split(wo.filter_selectSourceSeparator);
2954
+ if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
2955
+ val = string.split( wo.filter_selectSourceSeparator );
2857
2956
  txt = val[1];
2858
2957
  val = val[0];
2859
2958
  }
2860
- options += '<option ' + (txt === val ? '' : 'data-function-name="' + string + '" ') + 'value="' + val + '">' + txt + '</option>';
2959
+ options += '<option ' +
2960
+ ( txt === val ? '' : 'data-function-name="' + string + '" ' ) +
2961
+ 'value="' + val + '">' + txt + '</option>';
2861
2962
  }
2862
2963
  }
2863
- c.$table.find('thead').find('select.' + tscss.filter + '[data-column="' + column + '"]').append(options);
2964
+ c.$table
2965
+ .find( 'thead' )
2966
+ .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' )
2967
+ .append( options );
2864
2968
  txt = wo.filter_selectSource;
2865
- fxn = $.isFunction(txt) ? true : ts.getColumnData( table, txt, column );
2866
- if (fxn) {
2969
+ fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column );
2970
+ if ( fxn ) {
2867
2971
  // updating so the extra options are appended
2868
- ts.filter.buildSelect(c.table, column, '', true, $header.hasClass(wo.filter_onlyAvail));
2972
+ ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) );
2869
2973
  }
2870
2974
  }
2871
2975
  }
2872
2976
  }
2873
2977
  }
2874
- // not really updating, but if the column has both the "filter-select" class & filter_functions set to true,
2875
- // it would append the same options twice.
2876
- ts.filter.buildDefault(table, true);
2978
+ // not really updating, but if the column has both the 'filter-select' class &
2979
+ // filter_functions set to true, it would append the same options twice.
2980
+ ts.filter.buildDefault( table, true );
2877
2981
 
2878
- ts.filter.bindSearch( table, c.$table.find('.' + tscss.filter), true );
2879
- if (wo.filter_external) {
2982
+ ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true );
2983
+ if ( wo.filter_external ) {
2880
2984
  ts.filter.bindSearch( table, wo.filter_external );
2881
2985
  }
2882
2986
 
2883
- if (wo.filter_hideFilters) {
2884
- ts.filter.hideFilters(table, c);
2987
+ if ( wo.filter_hideFilters ) {
2988
+ ts.filter.hideFilters( table, c );
2885
2989
  }
2886
2990
 
2887
2991
  // show processing icon
2888
- if (c.showProcessing) {
2992
+ if ( c.showProcessing ) {
2993
+ txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' );
2889
2994
  c.$table
2890
- .unbind( ('filterStart filterEnd '.split(' ').join(c.namespace + 'filter ')).replace(/\s+/g, ' ') )
2891
- .bind( 'filterStart filterEnd '.split(' ').join(c.namespace + 'filter '), function(event, columns) {
2995
+ .unbind( txt.replace( /\s+/g, ' ' ) )
2996
+ .bind( txt, function( event, columns ) {
2892
2997
  // only add processing to certain columns to all columns
2893
- $header = (columns) ? c.$table.find('.' + tscss.header).filter('[data-column]').filter(function() {
2894
- return columns[$(this).data('column')] !== '';
2895
- }) : '';
2896
- ts.isProcessing(table, event.type === 'filterStart', columns ? $header : '');
2998
+ $header = ( columns ) ?
2999
+ c.$table
3000
+ .find( '.' + tscss.header )
3001
+ .filter( '[data-column]' )
3002
+ .filter( function() {
3003
+ return columns[ $( this ).data( 'column' ) ] !== '';
3004
+ }) : '';
3005
+ ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' );
2897
3006
  });
2898
3007
  }
2899
3008
 
2900
- // set filtered rows count (intially unfiltered)
3009
+ // set filtered rows count ( intially unfiltered )
2901
3010
  c.filteredRows = c.totalRows;
2902
3011
 
2903
3012
  // add default values
3013
+ txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' );
2904
3014
  c.$table
2905
- .unbind( ('tablesorter-initialized pagerBeforeInitialized '.split(' ').join(c.namespace + 'filter ')).replace(/\s+/g, ' ') )
2906
- .bind( 'tablesorter-initialized pagerBeforeInitialized '.split(' ').join(c.namespace + 'filter '), function() {
2907
- // redefine "wo" as it does not update properly inside this callback
3015
+ .unbind( txt.replace( /\s+/g, ' ' ) )
3016
+ .bind( txt, function() {
3017
+ // redefine 'wo' as it does not update properly inside this callback
2908
3018
  var wo = this.config.widgetOptions;
2909
- filters = ts.filter.setDefaults(table, c, wo) || [];
2910
- if (filters.length) {
3019
+ filters = ts.filter.setDefaults( table, c, wo ) || [];
3020
+ if ( filters.length ) {
2911
3021
  // prevent delayInit from triggering a cache build if filters are empty
2912
- if ( !(c.delayInit && filters.join('') === '') ) {
2913
- ts.setFilters(table, filters, true);
3022
+ if ( !( c.delayInit && filters.join( '' ) === '' ) ) {
3023
+ ts.setFilters( table, filters, true );
2914
3024
  }
2915
3025
  }
2916
- c.$table.trigger('filterFomatterUpdate');
3026
+ c.$table.trigger( 'filterFomatterUpdate' );
2917
3027
  // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers
2918
- setTimeout(function(){
2919
- if (!wo.filter_initialized) {
2920
- ts.filter.filterInitComplete(c);
3028
+ setTimeout( function() {
3029
+ if ( !wo.filter_initialized ) {
3030
+ ts.filter.filterInitComplete( c );
2921
3031
  }
2922
- }, 100);
3032
+ }, 100 );
2923
3033
  });
2924
3034
  // if filter widget is added after pager has initialized; then set filter init flag
2925
- if (c.pager && c.pager.initialized && !wo.filter_initialized) {
2926
- c.$table.trigger('filterFomatterUpdate');
2927
- setTimeout(function(){
2928
- ts.filter.filterInitComplete(c);
2929
- }, 100);
3035
+ if ( c.pager && c.pager.initialized && !wo.filter_initialized ) {
3036
+ c.$table.trigger( 'filterFomatterUpdate' );
3037
+ setTimeout( function() {
3038
+ ts.filter.filterInitComplete( c );
3039
+ }, 100 );
2930
3040
  }
2931
3041
  },
2932
- // $cell parameter, but not the config, is passed to the
2933
- // filter_formatters, so we have to work with it instead
2934
- formatterUpdated: function($cell, column) {
2935
- var wo = $cell.closest('table')[0].config.widgetOptions;
2936
- if (!wo.filter_initialized) {
3042
+ // $cell parameter, but not the config, is passed to the filter_formatters,
3043
+ // so we have to work with it instead
3044
+ formatterUpdated: function( $cell, column ) {
3045
+ var wo = $cell.closest( 'table' )[0].config.widgetOptions;
3046
+ if ( !wo.filter_initialized ) {
2937
3047
  // add updates by column since this function
2938
3048
  // may be called numerous times before initialization
2939
- wo.filter_formatterInit[column] = 1;
3049
+ wo.filter_formatterInit[ column ] = 1;
2940
3050
  }
2941
3051
  },
2942
- filterInitComplete: function(c){
3052
+ filterInitComplete: function( c ) {
2943
3053
  var indx, len,
2944
3054
  wo = c.widgetOptions,
2945
3055
  count = 0,
2946
- completed = function(){
3056
+ completed = function() {
2947
3057
  wo.filter_initialized = true;
2948
- c.$table.trigger('filterInit', c);
2949
- ts.filter.findRows(c.table, c.$table.data('lastSearch') || []);
3058
+ c.$table.trigger( 'filterInit', c );
3059
+ ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] );
2950
3060
  };
2951
3061
  if ( $.isEmptyObject( wo.filter_formatter ) ) {
2952
3062
  completed();
2953
3063
  } else {
2954
3064
  len = wo.filter_formatterInit.length;
2955
- for (indx = 0; indx < len; indx++) {
2956
- if (wo.filter_formatterInit[indx] === 1) {
3065
+ for ( indx = 0; indx < len; indx++ ) {
3066
+ if ( wo.filter_formatterInit[ indx ] === 1 ) {
2957
3067
  count++;
2958
3068
  }
2959
3069
  }
2960
- clearTimeout(wo.filter_initTimer);
2961
- if (!wo.filter_initialized && count === wo.filter_formatterCount) {
3070
+ clearTimeout( wo.filter_initTimer );
3071
+ if ( !wo.filter_initialized && count === wo.filter_formatterCount ) {
2962
3072
  // filter widget initialized
2963
3073
  completed();
2964
- } else if (!wo.filter_initialized) {
3074
+ } else if ( !wo.filter_initialized ) {
2965
3075
  // fall back in case a filter_formatter doesn't call
2966
- // $.tablesorter.filter.formatterUpdated($cell, column), and the count is off
2967
- wo.filter_initTimer = setTimeout(function(){
3076
+ // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off
3077
+ wo.filter_initTimer = setTimeout( function() {
2968
3078
  completed();
2969
- }, 500);
3079
+ }, 500 );
2970
3080
  }
2971
3081
  }
2972
3082
  },
2973
3083
 
2974
- setDefaults: function(table, c, wo) {
3084
+ setDefaults: function( table, c, wo ) {
2975
3085
  var isArray, saved, indx, col, $filters,
2976
- // get current (default) filters
2977
- filters = ts.getFilters(table) || [];
2978
- if (wo.filter_saveFilters && ts.storage) {
3086
+ // get current ( default ) filters
3087
+ filters = ts.getFilters( table ) || [];
3088
+ if ( wo.filter_saveFilters && ts.storage ) {
2979
3089
  saved = ts.storage( table, 'tablesorter-filters' ) || [];
2980
- isArray = $.isArray(saved);
3090
+ isArray = $.isArray( saved );
2981
3091
  // make sure we're not just getting an empty array
2982
- if ( !(isArray && saved.join('') === '' || !isArray) ) { filters = saved; }
3092
+ if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) {
3093
+ filters = saved;
3094
+ }
2983
3095
  }
2984
3096
  // if no filters saved, then check default settings
2985
- if (filters.join('') === '') {
3097
+ if ( filters.join( '' ) === '' ) {
2986
3098
  // allow adding default setting to external filters
2987
- $filters = c.$headers.add( wo.filter_$externalFilters ).filter('[' + wo.filter_defaultAttrib + ']');
2988
- for (indx = 0; indx <= c.columns; indx++) {
2989
- // include data-column="all" external filters
3099
+ $filters = c.$headers.add( wo.filter_$externalFilters )
3100
+ .filter( '[' + wo.filter_defaultAttrib + ']' );
3101
+ for ( indx = 0; indx <= c.columns; indx++ ) {
3102
+ // include data-column='all' external filters
2990
3103
  col = indx === c.columns ? 'all' : indx;
2991
- filters[indx] = $filters.filter('[data-column="' + col + '"]').attr(wo.filter_defaultAttrib) || filters[indx] || '';
3104
+ filters[indx] = $filters
3105
+ .filter( '[data-column="' + col + '"]' )
3106
+ .attr( wo.filter_defaultAttrib ) || filters[indx] || '';
2992
3107
  }
2993
3108
  }
2994
- c.$table.data('lastSearch', filters);
3109
+ c.$table.data( 'lastSearch', filters );
2995
3110
  return filters;
2996
3111
  },
2997
- parseFilter: function(c, filter, column, parsed, forceParse){
2998
- return forceParse || parsed ?
2999
- c.parsers[column].format( filter, c.table, [], column ) :
3000
- filter;
3112
+ parseFilter: function( c, filter, column, parsed ) {
3113
+ return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter;
3001
3114
  },
3002
- buildRow: function(table, c, wo) {
3003
- var col, column, $header, buildSelect, disabled, name, ffxn,
3115
+ buildRow: function( table, c, wo ) {
3116
+ var col, column, $header, buildSelect, disabled, name, ffxn, tmp,
3004
3117
  // c.columns defined in computeThIndexes()
3118
+ cellFilter = wo.filter_cellFilter,
3005
3119
  columns = c.columns,
3006
- arry = $.isArray(wo.filter_cellFilter),
3120
+ arry = $.isArray( cellFilter ),
3007
3121
  buildFilter = '<tr role="row" class="' + tscss.filterRow + ' ' + c.cssIgnoreRow + '">';
3008
- for (column = 0; column < columns; column++) {
3009
- if (arry) {
3010
- buildFilter += '<td' + ( wo.filter_cellFilter[column] ? ' class="' + wo.filter_cellFilter[column] + '"' : '' ) + '></td>';
3122
+ for ( column = 0; column < columns; column++ ) {
3123
+ buildFilter += '<td';
3124
+ if ( arry ) {
3125
+ buildFilter += ( cellFilter[ column ] ? ' class="' + cellFilter[ column ] + '"' : '' );
3011
3126
  } else {
3012
- buildFilter += '<td' + ( wo.filter_cellFilter !== '' ? ' class="' + wo.filter_cellFilter + '"' : '' ) + '></td>';
3127
+ buildFilter += ( cellFilter !== '' ? ' class="' + cellFilter + '"' : '' );
3013
3128
  }
3129
+ buildFilter += '></td>';
3014
3130
  }
3015
- c.$filters = $(buildFilter += '</tr>').appendTo( c.$table.children('thead').eq(0) ).find('td');
3131
+ c.$filters = $( buildFilter += '</tr>' )
3132
+ .appendTo( c.$table.children( 'thead' ).eq( 0 ) )
3133
+ .find( 'td' );
3016
3134
  // build each filter input
3017
- for (column = 0; column < columns; column++) {
3135
+ for ( column = 0; column < columns; column++ ) {
3018
3136
  disabled = false;
3019
3137
  // assuming last cell of a column is the main column
3020
- $header = c.$headerIndexed[column];
3138
+ $header = c.$headerIndexed[ column ];
3021
3139
  ffxn = ts.getColumnData( table, wo.filter_functions, column );
3022
- buildSelect = (wo.filter_functions && ffxn && typeof ffxn !== "function" ) ||
3023
- $header.hasClass('filter-select');
3140
+ buildSelect = ( wo.filter_functions && ffxn && typeof ffxn !== 'function' ) ||
3141
+ $header.hasClass( 'filter-select' );
3024
3142
  // get data from jQuery data, metadata, headers option or header class name
3025
3143
  col = ts.getColumnData( table, c.headers, column );
3026
- disabled = ts.getData($header[0], col, 'filter') === 'false' || ts.getData($header[0], col, 'parser') === 'false';
3144
+ disabled = ts.getData( $header[0], col, 'filter' ) === 'false' ||
3145
+ ts.getData( $header[0], col, 'parser' ) === 'false';
3027
3146
 
3028
- if (buildSelect) {
3029
- buildFilter = $('<select>').appendTo( c.$filters.eq(column) );
3147
+ if ( buildSelect ) {
3148
+ buildFilter = $( '<select>' ).appendTo( c.$filters.eq( column ) );
3030
3149
  } else {
3031
3150
  ffxn = ts.getColumnData( table, wo.filter_formatter, column );
3032
- if (ffxn) {
3151
+ if ( ffxn ) {
3033
3152
  wo.filter_formatterCount++;
3034
- buildFilter = ffxn( c.$filters.eq(column), column );
3153
+ buildFilter = ffxn( c.$filters.eq( column ), column );
3035
3154
  // no element returned, so lets go find it
3036
- if (buildFilter && buildFilter.length === 0) {
3037
- buildFilter = c.$filters.eq(column).children('input');
3155
+ if ( buildFilter && buildFilter.length === 0 ) {
3156
+ buildFilter = c.$filters.eq( column ).children( 'input' );
3038
3157
  }
3039
3158
  // element not in DOM, so lets attach it
3040
- if ( buildFilter && (buildFilter.parent().length === 0 ||
3041
- (buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column])) ) {
3042
- c.$filters.eq(column).append(buildFilter);
3159
+ if ( buildFilter && ( buildFilter.parent().length === 0 ||
3160
+ ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) {
3161
+ c.$filters.eq( column ).append( buildFilter );
3043
3162
  }
3044
3163
  } else {
3045
- buildFilter = $('<input type="search">').appendTo( c.$filters.eq(column) );
3164
+ buildFilter = $( '<input type="search">' ).appendTo( c.$filters.eq( column ) );
3046
3165
  }
3047
- if (buildFilter) {
3048
- buildFilter.attr('placeholder', $header.data('placeholder') || $header.attr('data-placeholder') || wo.filter_placeholder.search || '');
3166
+ if ( buildFilter ) {
3167
+ tmp = $header.data( 'placeholder' ) ||
3168
+ $header.attr( 'data-placeholder' ) ||
3169
+ wo.filter_placeholder.search || '';
3170
+ buildFilter.attr( 'placeholder', tmp );
3049
3171
  }
3050
3172
  }
3051
- if (buildFilter) {
3173
+ if ( buildFilter ) {
3052
3174
  // add filter class name
3053
- name = ( $.isArray(wo.filter_cssFilter) ?
3054
- (typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '') :
3175
+ name = ( $.isArray( wo.filter_cssFilter ) ?
3176
+ ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) :
3055
3177
  wo.filter_cssFilter ) || '';
3056
- buildFilter.addClass( tscss.filter + ' ' + name ).attr('data-column', column);
3057
- if (disabled) {
3058
- buildFilter.attr('placeholder', '').addClass(tscss.filterDisabled)[0].disabled = true; // disabled!
3178
+ buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column );
3179
+ if ( disabled ) {
3180
+ buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true;
3059
3181
  }
3060
3182
  }
3061
3183
  }
3062
3184
  },
3063
- bindSearch: function(table, $el, internal) {
3064
- table = $(table)[0];
3065
- $el = $($el); // allow passing a selector string
3066
- if (!$el.length) { return; }
3067
- var c = table.config,
3185
+ bindSearch: function( table, $el, internal ) {
3186
+ table = $( table )[0];
3187
+ $el = $( $el ); // allow passing a selector string
3188
+ if ( !$el.length ) { return; }
3189
+ var tmp,
3190
+ c = table.config,
3068
3191
  wo = c.widgetOptions,
3192
+ namespace = c.namespace + 'filter',
3069
3193
  $ext = wo.filter_$externalFilters;
3070
- if (internal !== true) {
3194
+ if ( internal !== true ) {
3071
3195
  // save anyMatch element
3072
- wo.filter_$anyMatch = $el.filter(wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector);
3073
- if ($ext && $ext.length) {
3196
+ tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector;
3197
+ wo.filter_$anyMatch = $el.filter( tmp );
3198
+ if ( $ext && $ext.length ) {
3074
3199
  wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el );
3075
3200
  } else {
3076
3201
  wo.filter_$externalFilters = $el;
3077
3202
  }
3078
- // update values (external filters added after table initialization)
3079
- ts.setFilters(table, c.$table.data('lastSearch') || [], internal === false);
3203
+ // update values ( external filters added after table initialization )
3204
+ ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false );
3080
3205
  }
3206
+ // unbind events
3207
+ tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) );
3081
3208
  $el
3082
- // use data attribute instead of jQuery data since the head is cloned without including the data/binding
3083
- .attr('data-lastSearchTime', new Date().getTime())
3084
- .unbind( ('keypress keyup search change '.split(' ').join(c.namespace + 'filter ')).replace(/\s+/g, ' ') )
3209
+ // use data attribute instead of jQuery data since the head is cloned without including
3210
+ // the data/binding
3211
+ .attr( 'data-lastSearchTime', new Date().getTime() )
3212
+ .unbind( tmp.replace( /\s+/g, ' ' ) )
3085
3213
  // include change for select - fixes #473
3086
- .bind('keyup' + c.namespace + 'filter', function(event) {
3087
- $(this).attr('data-lastSearchTime', new Date().getTime());
3214
+ .bind( 'keyup' + namespace, function( event ) {
3215
+ $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
3088
3216
  // emulate what webkit does.... escape clears the filter
3089
- if (event.which === 27) {
3217
+ if ( event.which === 27 ) {
3090
3218
  this.value = '';
3091
3219
  // live search
3092
3220
  } else if ( wo.filter_liveSearch === false ) {
3093
3221
  return;
3094
- // don't return if the search value is empty (all rows need to be revealed)
3222
+ // don't return if the search value is empty ( all rows need to be revealed )
3095
3223
  } else if ( this.value !== '' && (
3096
3224
  // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace
3097
3225
  ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) ||
3098
3226
  // let return & backspace continue on, but ignore arrows & non-valid characters
3099
- ( event.which !== 13 && event.which !== 8 && ( event.which < 32 || (event.which >= 37 && event.which <= 40) ) ) ) ) {
3227
+ ( event.which !== 13 && event.which !== 8 &&
3228
+ ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) {
3100
3229
  return;
3101
3230
  }
3102
3231
  // change event = no delay; last true flag tells getFilters to skip newest timed input
3103
3232
  ts.filter.searching( table, true, true );
3104
3233
  })
3105
- .bind( 'search change keypress '.split(' ').join(c.namespace + 'filter '), function(event){
3106
- var column = $(this).data('column');
3107
- // don't allow "change" event to process if the input value is the same - fixes #685
3108
- if (event.which === 13 || event.type === 'search' || event.type === 'change' && this.value !== c.lastSearch[column]) {
3234
+ .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) {
3235
+ var column = $( this ).data( 'column' );
3236
+ // don't allow 'change' event to process if the input value is the same - fixes #685
3237
+ if ( event.which === 13 || event.type === 'search' ||
3238
+ event.type === 'change' && this.value !== c.lastSearch[column] ) {
3109
3239
  event.preventDefault();
3110
3240
  // init search with no delay
3111
- $(this).attr('data-lastSearchTime', new Date().getTime());
3241
+ $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
3112
3242
  ts.filter.searching( table, false, true );
3113
3243
  }
3114
3244
  });
3115
3245
  },
3116
- searching: function(table, filter, skipFirst) {
3246
+ searching: function( table, filter, skipFirst ) {
3117
3247
  var wo = table.config.widgetOptions;
3118
- clearTimeout(wo.searchTimer);
3119
- if (typeof filter === 'undefined' || filter === true) {
3248
+ clearTimeout( wo.searchTimer );
3249
+ if ( typeof filter === 'undefined' || filter === true ) {
3120
3250
  // delay filtering
3121
- wo.searchTimer = setTimeout(function() {
3122
- ts.filter.checkFilters(table, filter, skipFirst );
3123
- }, wo.filter_liveSearch ? wo.filter_searchDelay : 10);
3251
+ wo.searchTimer = setTimeout( function() {
3252
+ ts.filter.checkFilters( table, filter, skipFirst );
3253
+ }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 );
3124
3254
  } else {
3125
3255
  // skip delay
3126
- ts.filter.checkFilters(table, filter, skipFirst);
3256
+ ts.filter.checkFilters( table, filter, skipFirst );
3127
3257
  }
3128
3258
  },
3129
- checkFilters: function(table, filter, skipFirst) {
3259
+ checkFilters: function( table, filter, skipFirst ) {
3130
3260
  var c = table.config,
3131
3261
  wo = c.widgetOptions,
3132
- filterArray = $.isArray(filter),
3133
- filters = (filterArray) ? filter : ts.getFilters(table, true),
3134
- combinedFilters = (filters || []).join(''); // combined filter values
3262
+ filterArray = $.isArray( filter ),
3263
+ filters = ( filterArray ) ? filter : ts.getFilters( table, true ),
3264
+ combinedFilters = ( filters || [] ).join( '' ); // combined filter values
3135
3265
  // prevent errors if delay init is set
3136
- if ($.isEmptyObject(c.cache)) {
3137
- // update cache if delayInit set & pager has initialized (after user initiates a search)
3138
- if (c.delayInit && c.pager && c.pager.initialized) {
3139
- c.$table.trigger('updateCache', [function(){
3140
- ts.filter.checkFilters(table, false, skipFirst);
3141
- }] );
3266
+ if ( $.isEmptyObject( c.cache ) ) {
3267
+ // update cache if delayInit set & pager has initialized ( after user initiates a search )
3268
+ if ( c.delayInit && c.pager && c.pager.initialized ) {
3269
+ c.$table.trigger( 'updateCache', [ function() {
3270
+ ts.filter.checkFilters( table, false, skipFirst );
3271
+ } ] );
3142
3272
  }
3143
3273
  return;
3144
3274
  }
3145
3275
  // add filter array back into inputs
3146
- if (filterArray) {
3276
+ if ( filterArray ) {
3147
3277
  ts.setFilters( table, filters, false, skipFirst !== true );
3148
- if (!wo.filter_initialized) { c.lastCombinedFilter = ''; }
3278
+ if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; }
3149
3279
  }
3150
- if (wo.filter_hideFilters) {
3280
+ if ( wo.filter_hideFilters ) {
3151
3281
  // show/hide filter row as needed
3152
- c.$table.find('.' + tscss.filterRow).trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
3282
+ c.$table
3283
+ .find( '.' + tscss.filterRow )
3284
+ .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
3153
3285
  }
3154
3286
  // return if the last search is the same; but filter === false when updating the search
3155
3287
  // see example-widget-filter.html filter toggle buttons
3156
- if (c.lastCombinedFilter === combinedFilters && filter !== false) {
3288
+ if ( c.lastCombinedFilter === combinedFilters && filter !== false ) {
3157
3289
  return;
3158
- } else if (filter === false) {
3290
+ } else if ( filter === false ) {
3159
3291
  // force filter refresh
3160
3292
  c.lastCombinedFilter = null;
3161
3293
  c.lastSearch = [];
3162
3294
  }
3163
- if (wo.filter_initialized) { c.$table.trigger('filterStart', [filters]); }
3164
- if (c.showProcessing) {
3295
+ if ( wo.filter_initialized ) {
3296
+ c.$table.trigger( 'filterStart', [filters] );
3297
+ }
3298
+ if ( c.showProcessing ) {
3165
3299
  // give it time for the processing icon to kick in
3166
- setTimeout(function() {
3167
- ts.filter.findRows(table, filters, combinedFilters);
3300
+ setTimeout( function() {
3301
+ ts.filter.findRows( table, filters, combinedFilters );
3168
3302
  return false;
3169
- }, 30);
3303
+ }, 30 );
3170
3304
  } else {
3171
- ts.filter.findRows(table, filters, combinedFilters);
3305
+ ts.filter.findRows( table, filters, combinedFilters );
3172
3306
  return false;
3173
3307
  }
3174
3308
  },
3175
- hideFilters: function(table, c) {
3309
+ hideFilters: function( table, c ) {
3176
3310
  var $filterRow, $filterRow2, timer;
3177
- $(table)
3178
- .find('.' + tscss.filterRow)
3179
- .addClass(tscss.filterRowHide)
3180
- .bind('mouseenter mouseleave', function(e) {
3311
+ $( table )
3312
+ .find( '.' + tscss.filterRow )
3313
+ .addClass( tscss.filterRowHide )
3314
+ .bind( 'mouseenter mouseleave', function( e ) {
3181
3315
  // save event object - http://bugs.jquery.com/ticket/12140
3182
3316
  var event = e;
3183
- $filterRow = $(this);
3184
- clearTimeout(timer);
3185
- timer = setTimeout(function() {
3186
- if ( /enter|over/.test(event.type) ) {
3187
- $filterRow.removeClass(tscss.filterRowHide);
3317
+ $filterRow = $( this );
3318
+ clearTimeout( timer );
3319
+ timer = setTimeout( function() {
3320
+ if ( /enter|over/.test( event.type ) ) {
3321
+ $filterRow.removeClass( tscss.filterRowHide );
3188
3322
  } else {
3189
3323
  // don't hide if input has focus
3190
- // $(':focus') needs jQuery 1.6+
3191
- if ( $(document.activeElement).closest('tr')[0] !== $filterRow[0] ) {
3324
+ // $( ':focus' ) needs jQuery 1.6+
3325
+ if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) {
3192
3326
  // don't hide row if any filter has a value
3193
- if (c.lastCombinedFilter === '') {
3194
- $filterRow.addClass(tscss.filterRowHide);
3327
+ if ( c.lastCombinedFilter === '' ) {
3328
+ $filterRow.addClass( tscss.filterRowHide );
3195
3329
  }
3196
3330
  }
3197
3331
  }
3198
- }, 200);
3332
+ }, 200 );
3199
3333
  })
3200
- .find('input, select').bind('focus blur', function(e) {
3201
- $filterRow2 = $(this).closest('tr');
3202
- clearTimeout(timer);
3334
+ .find( 'input, select' ).bind( 'focus blur', function( e ) {
3335
+ $filterRow2 = $( this ).closest( 'tr' );
3336
+ clearTimeout( timer );
3203
3337
  var event = e;
3204
- timer = setTimeout(function() {
3338
+ timer = setTimeout( function() {
3205
3339
  // don't hide row if any filter has a value
3206
- if (ts.getFilters(c.$table).join('') === '') {
3207
- $filterRow2[ event.type === 'focus' ? 'removeClass' : 'addClass'](tscss.filterRowHide);
3340
+ if ( ts.getFilters( c.$table ).join( '' ) === '' ) {
3341
+ $filterRow2.toggleClass( tscss.filterRowHide, event.type === 'focus' );
3208
3342
  }
3209
- }, 200);
3343
+ }, 200 );
3210
3344
  });
3211
3345
  },
3212
- defaultFilter: function(filter, mask){
3213
- if (filter === '') { return filter; }
3346
+ defaultFilter: function( filter, mask ) {
3347
+ if ( filter === '' ) { return filter; }
3214
3348
  var regex = ts.filter.regex.iQuery,
3215
3349
  maskLen = mask.match( ts.filter.regex.igQuery ).length,
3216
- query = maskLen > 1 ? $.trim(filter).split(/\s/) : [ $.trim(filter) ],
3350
+ query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ],
3217
3351
  len = query.length - 1,
3218
3352
  indx = 0,
3219
3353
  val = mask;
3220
3354
  if ( len < 1 && maskLen > 1 ) {
3221
- // only one "word" in query but mask has >1 slots
3355
+ // only one 'word' in query but mask has >1 slots
3222
3356
  query[1] = query[0];
3223
3357
  }
3224
3358
  // replace all {query} with query words...
3225
- // if query = "Bob", then convert mask from "!{query}" to "!Bob"
3226
- // if query = "Bob Joe Frank", then convert mask "{q} OR {q}" to "Bob OR Joe OR Frank"
3227
- while (regex.test(val)) {
3228
- val = val.replace(regex, query[indx++] || '');
3229
- if (regex.test(val) && indx < len && (query[indx] || '') !== '') {
3230
- val = mask.replace(regex, val);
3359
+ // if query = 'Bob', then convert mask from '!{query}' to '!Bob'
3360
+ // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank'
3361
+ while ( regex.test( val ) ) {
3362
+ val = val.replace( regex, query[indx++] || '' );
3363
+ if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) {
3364
+ val = mask.replace( regex, val );
3231
3365
  }
3232
3366
  }
3233
3367
  return val;
3234
3368
  },
3235
3369
  getLatestSearch: function( $input ) {
3236
- if ($input) {
3237
- return $input.sort(function(a, b) {
3238
- return $(b).attr('data-lastSearchTime') - $(a).attr('data-lastSearchTime');
3370
+ if ( $input ) {
3371
+ return $input.sort( function( a, b ) {
3372
+ return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' );
3239
3373
  });
3240
3374
  }
3241
3375
  return $();
3242
3376
  },
3243
3377
  multipleColumns: function( c, $input ) {
3244
- // look for multiple columns "1-3,4-6,8" in data-column
3378
+ // look for multiple columns '1-3,4-6,8' in data-column
3245
3379
  var temp, ranges, range, start, end, singles, i, indx, len,
3246
3380
  wo = c.widgetOptions,
3247
- // only target "all" column inputs on initialization
3248
- // & don't target "all" column inputs if they don't exist
3249
- targets = wo.filter_initialized || !$input.filter(wo.filter_anyColumnSelector).length,
3381
+ // only target 'all' column inputs on initialization
3382
+ // & don't target 'all' column inputs if they don't exist
3383
+ targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length,
3250
3384
  columns = [],
3251
- val = $.trim( ts.filter.getLatestSearch( $input ).attr('data-column') || '' );
3385
+ val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' );
3252
3386
  // process column range
3253
3387
  if ( targets && /-/.test( val ) ) {
3254
3388
  ranges = val.match( /(\d+)\s*-\s*(\d+)/g );
3255
3389
  len = ranges.length;
3256
- for (indx = 0; indx < len; indx++) {
3390
+ for ( indx = 0; indx < len; indx++ ) {
3257
3391
  range = ranges[indx].split( /\s*-\s*/ );
3258
3392
  start = parseInt( range[0], 10 ) || 0;
3259
3393
  end = parseInt( range[1], 10 ) || ( c.columns - 1 );
3260
- if ( start > end ) { temp = start; start = end; end = temp; } // swap
3261
- if ( end >= c.columns ) { end = c.columns - 1; }
3394
+ if ( start > end ) {
3395
+ temp = start; start = end; end = temp; // swap
3396
+ }
3397
+ if ( end >= c.columns ) {
3398
+ end = c.columns - 1;
3399
+ }
3262
3400
  for ( ; start <= end; start++ ) {
3263
- columns.push(start);
3401
+ columns.push( start );
3264
3402
  }
3265
3403
  // remove processed range from val
3266
- val = val.replace( ranges[indx], '' );
3404
+ val = val.replace( ranges[ indx ], '' );
3267
3405
  }
3268
3406
  }
3269
3407
  // process single columns
3270
3408
  if ( targets && /,/.test( val ) ) {
3271
3409
  singles = val.split( /\s*,\s*/ );
3272
3410
  len = singles.length;
3273
- for (i = 0; i < len; i++) {
3274
- if (singles[i] !== '') {
3275
- indx = parseInt( singles[i], 10 );
3411
+ for ( i = 0; i < len; i++ ) {
3412
+ if ( singles[ i ] !== '' ) {
3413
+ indx = parseInt( singles[ i ], 10 );
3276
3414
  if ( indx < c.columns ) {
3277
3415
  columns.push( indx );
3278
3416
  }
@@ -3280,382 +3418,472 @@ ts.filter = {
3280
3418
  }
3281
3419
  }
3282
3420
  // return all columns
3283
- if (!columns.length) {
3421
+ if ( !columns.length ) {
3284
3422
  for ( indx = 0; indx < c.columns; indx++ ) {
3285
3423
  columns.push( indx );
3286
3424
  }
3287
3425
  }
3288
3426
  return columns;
3289
3427
  },
3290
- findRows: function(table, filters, combinedFilters) {
3291
- if (table.config.lastCombinedFilter === combinedFilters || !table.config.widgetOptions.filter_initialized) { return; }
3292
- var len, norm_rows, $rows, rowIndex, tbodyIndex, $tbody, $cells, $cell, columnIndex,
3293
- childRow, lastSearch, hasSelect, matches, result, showRow, time, val, indx,
3294
- notFiltered, searchFiltered, filterMatched, excludeMatch, fxn, ffxn,
3295
- query, injected, res, id,
3428
+ processRow: function( c, data, vars ) {
3429
+ var $cell, columnIndex, hasSelect, matches, result, val, filterMatched, excludeMatch,
3430
+ fxn, ffxn, txt,
3431
+ regex = ts.filter.regex,
3432
+ wo = c.widgetOptions,
3433
+ showRow = true;
3434
+ data.$cells = data.$row.children();
3435
+
3436
+ if ( data.anyMatchFlag ) {
3437
+ // look for multiple columns '1-3,4-6,8'
3438
+ columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
3439
+ data.anyMatch = true;
3440
+ data.rowArray = data.$cells.map( function( i ) {
3441
+ if ( $.inArray( i, columnIndex ) > -1 ) {
3442
+ if ( data.parsed[ i ] ) {
3443
+ txt = data.cacheArray[ i ];
3444
+ } else {
3445
+ txt = data.rawArray[ i ];
3446
+ txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt );
3447
+ if ( c.sortLocaleCompare ) {
3448
+ txt = ts.replaceAccents( txt );
3449
+ }
3450
+ }
3451
+ return txt;
3452
+ }
3453
+ }).get();
3454
+ data.filter = data.anyMatchFilter;
3455
+ data.iFilter = data.iAnyMatchFilter;
3456
+ data.exact = data.rowArray.join( ' ' );
3457
+ data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
3458
+ data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' );
3459
+ filterMatched = null;
3460
+ matches = null;
3461
+ for ( ffxn in ts.filter.types ) {
3462
+ if ( $.inArray( ffxn, vars.noAnyMatch ) < 0 && matches === null ) {
3463
+ matches = ts.filter.types[ffxn]( c, data );
3464
+ if ( matches !== null ) {
3465
+ filterMatched = matches;
3466
+ }
3467
+ }
3468
+ }
3469
+ if ( filterMatched !== null ) {
3470
+ showRow = filterMatched;
3471
+ } else {
3472
+ if ( wo.filter_startsWith ) {
3473
+ showRow = false;
3474
+ columnIndex = c.columns;
3475
+ while ( !showRow && columnIndex > 0 ) {
3476
+ columnIndex--;
3477
+ showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0;
3478
+ }
3479
+ } else {
3480
+ showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0;
3481
+ }
3482
+ }
3483
+ data.anyMatch = false;
3484
+ // no other filters to process
3485
+ if ( data.filters.join( '' ) === data.filter ) {
3486
+ return showRow;
3487
+ }
3488
+ }
3489
+
3490
+ for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) {
3491
+ data.filter = data.filters[ columnIndex ];
3492
+ data.index = columnIndex;
3493
+
3494
+ // filter types to exclude, per column
3495
+ excludeMatch = vars.excludeFilter[ columnIndex ];
3496
+
3497
+ // ignore if filter is empty or disabled
3498
+ if ( data.filter ) {
3499
+ data.cache = data.cacheArray[ columnIndex ];
3500
+ // check if column data should be from the cell or from parsed data
3501
+ if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) {
3502
+ data.exact = data.cache;
3503
+ } else {
3504
+ result = data.rawArray[ columnIndex ] || '';
3505
+ data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405
3506
+ }
3507
+ data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ?
3508
+ data.exact.toLowerCase() : data.exact;
3509
+ result = showRow; // if showRow is true, show that row
3510
+
3511
+ // in case select filter option has a different value vs text 'a - z|A through Z'
3512
+ ffxn = wo.filter_columnFilters ?
3513
+ c.$filters.add( c.$externalFilters )
3514
+ .filter( '[data-column="'+ columnIndex + '"]' )
3515
+ .find( 'select option:selected' )
3516
+ .attr( 'data-function-name' ) || '' : '';
3517
+ // replace accents - see #357
3518
+ if ( c.sortLocaleCompare ) {
3519
+ data.filter = ts.replaceAccents( data.filter );
3520
+ }
3521
+
3522
+ val = true;
3523
+ if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) {
3524
+ data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] );
3525
+ // val is used to indicate that a filter select is using a default filter;
3526
+ // so we override the exact & partial matches
3527
+ val = false;
3528
+ }
3529
+ // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ),
3530
+ // data.filter = case sensitive
3531
+ data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter;
3532
+ fxn = vars.functions[ columnIndex ];
3533
+ $cell = c.$headerIndexed[ columnIndex ];
3534
+ hasSelect = $cell.hasClass( 'filter-select' );
3535
+ filterMatched = null;
3536
+ if ( fxn || ( hasSelect && val ) ) {
3537
+ if ( fxn === true || hasSelect ) {
3538
+ // default selector uses exact match unless 'filter-match' class is found
3539
+ filterMatched = $cell.hasClass( 'filter-match' ) ?
3540
+ data.iExact.search( data.iFilter ) >= 0 :
3541
+ data.filter === data.exact;
3542
+ } else if ( typeof fxn === 'function' ) {
3543
+ // filter callback( exact cell content, parser normalized content,
3544
+ // filter input value, column index, jQuery row object )
3545
+ filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data );
3546
+ } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) {
3547
+ // selector option function
3548
+ txt = ffxn || data.filter;
3549
+ filterMatched =
3550
+ fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data );
3551
+ }
3552
+ }
3553
+ if ( filterMatched === null ) {
3554
+ // cycle through the different filters
3555
+ // filters return a boolean or null if nothing matches
3556
+ matches = null;
3557
+ for ( ffxn in ts.filter.types ) {
3558
+ if ( $.inArray( ffxn, excludeMatch ) < 0 && matches === null ) {
3559
+ matches = ts.filter.types[ ffxn ]( c, data );
3560
+ if ( matches !== null ) {
3561
+ filterMatched = matches;
3562
+ }
3563
+ }
3564
+ }
3565
+ if ( filterMatched !== null ) {
3566
+ result = filterMatched;
3567
+ // Look for match, and add child row data for matching
3568
+ } else {
3569
+ txt = ( data.iExact + data.childRowText )
3570
+ .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) );
3571
+ result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) );
3572
+ }
3573
+ } else {
3574
+ result = filterMatched;
3575
+ }
3576
+ showRow = ( result ) ? showRow : false;
3577
+ }
3578
+ }
3579
+ return showRow;
3580
+ },
3581
+ findRows: function( table, filters, combinedFilters ) {
3582
+ if ( table.config.lastCombinedFilter === combinedFilters ||
3583
+ !table.config.widgetOptions.filter_initialized ) {
3584
+ return;
3585
+ }
3586
+ var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex,
3587
+ isChild, childRow, lastSearch, showRow, time, val, indx,
3588
+ notFiltered, searchFiltered, query, injected, res, id, txt,
3589
+ storedFilters = $.extend( [], filters ),
3296
3590
  regex = ts.filter.regex,
3297
3591
  c = table.config,
3298
3592
  wo = c.widgetOptions,
3299
3593
  // data object passed to filters; anyMatch is a flag for the filters
3300
- data = { anyMatch: false },
3301
- // anyMatch really screws up with these types of filters
3302
- noAnyMatch = [ 'range', 'notMatch', 'operators' ];
3594
+ data = {
3595
+ anyMatch: false,
3596
+ filters: filters,
3597
+ // regex filter type cache
3598
+ filter_regexCache : [],
3599
+ },
3600
+ vars = {
3601
+ // anyMatch really screws up with these types of filters
3602
+ noAnyMatch: [ 'range', 'notMatch', 'operators' ],
3603
+ // cache filter variables that use ts.getColumnData in the main loop
3604
+ functions : [],
3605
+ excludeFilter : [],
3606
+ defaultColFilter : [],
3607
+ defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || ''
3608
+ };
3303
3609
 
3304
3610
  // parse columns after formatter, in case the class is added at that point
3305
- data.parsed = c.$headers.map(function(columnIndex) {
3306
- return c.parsers && c.parsers[columnIndex] && c.parsers[columnIndex].parsed ||
3307
- // getData won't return "parsed" if other "filter-" class names exist (e.g. <th class="filter-select filter-parsed">)
3308
- ts.getData && ts.getData(c.$headerIndexed[columnIndex], ts.getColumnData( table, c.headers, columnIndex ), 'filter') === 'parsed' ||
3309
- $(this).hasClass('filter-parsed');
3611
+ data.parsed = c.$headers.map( function( columnIndex ) {
3612
+ return c.parsers && c.parsers[ columnIndex ] &&
3613
+ // force parsing if parser type is numeric
3614
+ ( c.parsers[ columnIndex ].parsed || c.parsers[ columnIndex ].type === 'numeric' ) ||
3615
+ // getData won't return 'parsed' if other 'filter-' class names exist
3616
+ // ( e.g. <th class="filter-select filter-parsed"> )
3617
+ ts.getData && ts.getData( c.$headerIndexed[ columnIndex ],
3618
+ ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' ||
3619
+ $( this ).hasClass( 'filter-parsed' );
3310
3620
  }).get();
3311
3621
 
3312
- // cache filter variables that use ts.getColumnData in the main loop
3313
- wo.filter_indexed = {
3314
- functions : [],
3315
- excludeFilter : [],
3316
- defaultColFilter : [],
3317
- defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || ''
3318
- };
3319
3622
  for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) {
3320
- wo.filter_indexed.functions[ columnIndex ] = ts.getColumnData( table, wo.filter_functions, columnIndex );
3321
- wo.filter_indexed.defaultColFilter[ columnIndex ] = ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || '';
3322
- wo.filter_indexed.excludeFilter[ columnIndex ] = ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split(/\s+/);
3623
+ vars.functions[ columnIndex ] =
3624
+ ts.getColumnData( table, wo.filter_functions, columnIndex );
3625
+ vars.defaultColFilter[ columnIndex ] =
3626
+ ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || '';
3627
+ vars.excludeFilter[ columnIndex ] =
3628
+ ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ );
3323
3629
  }
3324
3630
 
3325
- if (c.debug) {
3326
- ts.log('Filter: Starting filter widget search', filters);
3631
+ if ( c.debug ) {
3632
+ ts.log( 'Filter: Starting filter widget search', filters );
3327
3633
  time = new Date();
3328
3634
  }
3329
3635
  // filtered rows count
3330
3636
  c.filteredRows = 0;
3331
3637
  c.totalRows = 0;
3332
3638
  // combindedFilters are undefined on init
3333
- combinedFilters = (filters || []).join('');
3639
+ combinedFilters = ( storedFilters || [] ).join( '' );
3334
3640
 
3335
- for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
3336
- $tbody = ts.processTbody(table, c.$tbodies.eq(tbodyIndex), true);
3337
- // skip child rows & widget added (removable) rows - fixes #448 thanks to @hempel!
3338
- // $rows = $tbody.children('tr').not(c.selectorRemove);
3641
+ for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
3642
+ $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true );
3643
+ // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel!
3644
+ // $rows = $tbody.children( 'tr' ).not( c.selectorRemove );
3339
3645
  columnIndex = c.columns;
3340
3646
  // convert stored rows into a jQuery object
3341
- norm_rows = c.cache[tbodyIndex].normalized;
3342
- $rows = $( $.map(norm_rows, function(el){ return el[columnIndex].$row.get(); }) );
3343
-
3344
- if (combinedFilters === '' || wo.filter_serversideFiltering) {
3345
- $rows.removeClass(wo.filter_filteredRow).not('.' + c.cssChildRow).css('display', '');
3647
+ norm_rows = c.cache[ tbodyIndex ].normalized;
3648
+ $rows = $( $.map( norm_rows, function( el ) {
3649
+ return el[ columnIndex ].$row.get();
3650
+ }) );
3651
+
3652
+ if ( combinedFilters === '' || wo.filter_serversideFiltering ) {
3653
+ $rows
3654
+ .removeClass( wo.filter_filteredRow )
3655
+ .not( '.' + c.cssChildRow )
3656
+ .css( 'display', '' );
3346
3657
  } else {
3347
3658
  // filter out child rows
3348
- $rows = $rows.not('.' + c.cssChildRow);
3659
+ $rows = $rows.not( '.' + c.cssChildRow );
3349
3660
  len = $rows.length;
3350
3661
 
3351
- if ( (wo.filter_$anyMatch && wo.filter_$anyMatch.length) || typeof filters[c.columns] !== 'undefined' ) {
3662
+ if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) ||
3663
+ typeof filters[c.columns] !== 'undefined' ) {
3352
3664
  data.anyMatchFlag = true;
3353
- data.anyMatchFilter = wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || ( '' + filters[c.columns] ) || '';
3354
- if (wo.filter_columnAnyMatch) {
3665
+ data.anyMatchFilter = '' + (
3666
+ filters[ c.columns ] ||
3667
+ wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() ||
3668
+ ''
3669
+ );
3670
+ if ( wo.filter_columnAnyMatch ) {
3355
3671
  // specific columns search
3356
- query = data.anyMatchFilter.split( ts.filter.regex.andSplit );
3672
+ query = data.anyMatchFilter.split( regex.andSplit );
3357
3673
  injected = false;
3358
- for (indx = 0; indx < query.length; indx++) {
3359
- res = query[indx].split(':');
3674
+ for ( indx = 0; indx < query.length; indx++ ) {
3675
+ res = query[ indx ].split( ':' );
3360
3676
  if ( res.length > 1 ) {
3361
3677
  // make the column a one-based index ( non-developers start counting from one :P )
3362
3678
  id = parseInt( res[0], 10 ) - 1;
3363
3679
  if ( id >= 0 && id < c.columns ) { // if id is an integer
3364
- filters[id] = res[1];
3365
- query.splice(indx, 1);
3680
+ filters[ id ] = res[1];
3681
+ query.splice( indx, 1 );
3366
3682
  indx--;
3367
3683
  injected = true;
3368
3684
  }
3369
3685
  }
3370
3686
  }
3371
- if (injected) {
3372
- data.anyMatchFilter = query.join(' && ');
3687
+ if ( injected ) {
3688
+ data.anyMatchFilter = query.join( ' && ' );
3373
3689
  }
3374
3690
  }
3375
3691
  }
3376
3692
 
3377
3693
  // optimize searching only through already filtered rows - see #313
3378
3694
  searchFiltered = wo.filter_searchFiltered;
3379
- lastSearch = c.lastSearch || c.$table.data('lastSearch') || [];
3380
- if (searchFiltered) {
3381
- // cycle through all filters; include last (columnIndex + 1 = match any column). Fixes #669
3382
- for (indx = 0; indx < columnIndex + 1; indx++) {
3695
+ lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || [];
3696
+ if ( searchFiltered ) {
3697
+ // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669
3698
+ for ( indx = 0; indx < columnIndex + 1; indx++ ) {
3383
3699
  val = filters[indx] || '';
3384
3700
  // break out of loop if we've already determined not to search filtered rows
3385
- if (!searchFiltered) { indx = columnIndex; }
3701
+ if ( !searchFiltered ) { indx = columnIndex; }
3386
3702
  // search already filtered rows if...
3387
3703
  searchFiltered = searchFiltered && lastSearch.length &&
3388
3704
  // there are no changes from beginning of filter
3389
- val.indexOf(lastSearch[indx] || '') === 0 &&
3390
- // if there is NOT a logical "or", or range ("to" or "-") in the string
3391
- !regex.alreadyFiltered.test(val) &&
3392
- // if we are not doing exact matches, using "|" (logical or) or not "!"
3393
- !/[=\"\|!]/.test(val) &&
3394
- // don't search only filtered if the value is negative ('> -10' => '> -100' will ignore hidden rows)
3395
- !(/(>=?\s*-\d)/.test(val) || /(<=?\s*\d)/.test(val)) &&
3396
- // if filtering using a select without a "filter-match" class (exact match) - fixes #593
3397
- !( val !== '' && c.$filters && c.$filters.eq(indx).find('select').length && !c.$headerIndexed[indx].hasClass('filter-match') );
3705
+ val.indexOf( lastSearch[indx] || '' ) === 0 &&
3706
+ // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string
3707
+ !regex.alreadyFiltered.test( val ) &&
3708
+ // if we are not doing exact matches, using '|' ( logical or ) or not '!'
3709
+ !/[=\"\|!]/.test( val ) &&
3710
+ // don't search only filtered if the value is negative
3711
+ // ( '> -10' => '> -100' will ignore hidden rows )
3712
+ !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) &&
3713
+ // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593
3714
+ !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length &&
3715
+ !c.$headerIndexed[indx].hasClass( 'filter-match' ) );
3398
3716
  }
3399
3717
  }
3400
- notFiltered = $rows.not('.' + wo.filter_filteredRow).length;
3718
+ notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length;
3401
3719
  // can't search when all rows are hidden - this happens when looking for exact matches
3402
- if (searchFiltered && notFiltered === 0) { searchFiltered = false; }
3403
- if (c.debug) {
3404
- ts.log( 'Filter: Searching through ' + ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' );
3720
+ if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; }
3721
+ if ( c.debug ) {
3722
+ ts.log( 'Filter: Searching through ' +
3723
+ ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' );
3405
3724
  }
3406
- if (data.anyMatchFlag) {
3407
- if (c.sortLocaleCompare) {
3725
+ if ( data.anyMatchFlag ) {
3726
+ if ( c.sortLocaleCompare ) {
3408
3727
  // replace accents
3409
- data.anyMatchFilter = ts.replaceAccents(data.anyMatchFilter);
3728
+ data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter );
3410
3729
  }
3411
- if ( wo.filter_defaultFilter && regex.iQuery.test( wo.filter_indexed.defaultAnyFilter ) ) {
3412
- data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, wo.filter_indexed.defaultAnyFilter );
3730
+ if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) {
3731
+ data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter );
3413
3732
  // clear search filtered flag because default filters are not saved to the last search
3414
3733
  searchFiltered = false;
3415
3734
  }
3416
3735
  // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true
3417
3736
  // when c.ignoreCase is true, the cache contains all lower case data
3418
- data.iAnyMatchFilter = !(wo.filter_ignoreCase && c.ignoreCase) ? data.anyMatchFilter : data.anyMatchFilter.toLocaleLowerCase();
3737
+ data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ?
3738
+ data.anyMatchFilter :
3739
+ data.anyMatchFilter.toLowerCase();
3419
3740
  }
3420
3741
 
3421
3742
  // loop through the rows
3422
- for (rowIndex = 0; rowIndex < len; rowIndex++) {
3743
+ for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
3423
3744
 
3424
- data.cacheArray = norm_rows[rowIndex];
3425
-
3426
- childRow = $rows[rowIndex].className;
3745
+ txt = $rows[ rowIndex ].className;
3746
+ // the first row can never be a child row
3747
+ isChild = rowIndex && regex.child.test( txt );
3427
3748
  // skip child rows & already filtered rows
3428
- if ( regex.child.test(childRow) || (searchFiltered && regex.filtered.test(childRow)) ) { continue; }
3429
- showRow = true;
3430
- // *** nextAll/nextUntil not supported by Zepto! ***
3431
- childRow = $rows.eq(rowIndex).nextUntil('tr:not(.' + c.cssChildRow + ')');
3432
- // so, if "table.config.widgetOptions.filter_childRows" is true and there is
3433
- // a match anywhere in the child row, then it will make the row visible
3434
- // checked here so the option can be changed dynamically
3435
- data.childRowText = (childRow.length && wo.filter_childRows) ? childRow.text() : '';
3436
- data.childRowText = wo.filter_ignoreCase ? data.childRowText.toLocaleLowerCase() : data.childRowText;
3437
- $cells = $rows.eq(rowIndex).children();
3438
- if (data.anyMatchFlag) {
3439
- // look for multiple columns "1-3,4-6,8"
3440
- columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
3441
- data.anyMatch = true;
3442
- data.rowArray = $cells.map(function(i){
3443
- if ( $.inArray(i, columnIndex) > -1 ) {
3444
- var txt;
3445
- if (data.parsed[i]) {
3446
- txt = data.cacheArray[i];
3447
- } else {
3448
- txt = this ? this.getAttribute( c.textAttribute ) || this.textContent || $(this).text() : '';
3449
- txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt );
3450
- if (c.sortLocaleCompare) {
3451
- txt = ts.replaceAccents(txt);
3452
- }
3453
- }
3454
- return txt;
3455
- }
3456
- }).get();
3457
- data.filter = data.anyMatchFilter;
3458
- data.iFilter = data.iAnyMatchFilter;
3459
- data.exact = data.rowArray.join(' ');
3460
- data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
3461
- data.cache = data.cacheArray.slice(0,-1).join(' ');
3462
- filterMatched = null;
3463
- $.each(ts.filter.types, function(type, typeFunction) {
3464
- if ($.inArray(type, noAnyMatch) < 0) {
3465
- matches = typeFunction( c, data );
3466
- if (matches !== null) {
3467
- filterMatched = matches;
3468
- return false;
3469
- }
3470
- }
3471
- });
3472
- if (filterMatched !== null) {
3473
- showRow = filterMatched;
3474
- } else {
3475
- if (wo.filter_startsWith) {
3476
- showRow = false;
3477
- columnIndex = c.columns;
3478
- while (!showRow && columnIndex > 0) {
3479
- columnIndex--;
3480
- showRow = showRow || data.rowArray[columnIndex].indexOf(data.iFilter) === 0;
3481
- }
3482
- } else {
3483
- showRow = (data.iExact + data.childRowText).indexOf(data.iFilter) >= 0;
3484
- }
3485
- }
3486
- data.anyMatch = false;
3749
+ if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) {
3750
+ continue;
3487
3751
  }
3488
3752
 
3489
- for (columnIndex = 0; columnIndex < c.columns; columnIndex++) {
3490
- data.filter = filters[columnIndex];
3491
- data.index = columnIndex;
3492
-
3493
- // filter types to exclude, per column
3494
- excludeMatch = wo.filter_indexed.excludeFilter[ columnIndex ];
3495
-
3496
- // ignore if filter is empty or disabled
3497
- if (data.filter) {
3498
- data.cache = data.cacheArray[columnIndex];
3499
- // check if column data should be from the cell or from parsed data
3500
- if (wo.filter_useParsedData || data.parsed[columnIndex]) {
3501
- data.exact = data.cache;
3502
- } else {
3503
- val = $cells[columnIndex];
3504
- result = val ? $.trim( val.getAttribute( c.textAttribute ) || val.textContent || $cells.eq(columnIndex).text() ) : '';
3505
- data.exact = c.sortLocaleCompare ? ts.replaceAccents(result) : result; // issue #405
3506
- }
3507
- data.iExact = !regex.type.test(typeof data.exact) && wo.filter_ignoreCase ? data.exact.toLocaleLowerCase() : data.exact;
3508
- result = showRow; // if showRow is true, show that row
3509
-
3510
- // in case select filter option has a different value vs text "a - z|A through Z"
3511
- ffxn = wo.filter_columnFilters ?
3512
- c.$filters.add(c.$externalFilters).filter('[data-column="'+ columnIndex + '"]').find('select option:selected').attr('data-function-name') || '' : '';
3513
- // replace accents - see #357
3514
- if (c.sortLocaleCompare) {
3515
- data.filter = ts.replaceAccents(data.filter);
3516
- }
3753
+ data.$row = $rows.eq( rowIndex );
3754
+ data.cacheArray = norm_rows[ rowIndex ];
3755
+ rowData = data.cacheArray[ c.columns ];
3756
+ data.rawArray = rowData.raw;
3757
+ data.childRowText = '';
3758
+
3759
+ if ( !wo.filter_childByColumn ) {
3760
+ txt = '';
3761
+ // child row cached text
3762
+ childRow = rowData.child;
3763
+ // so, if 'table.config.widgetOptions.filter_childRows' is true and there is
3764
+ // a match anywhere in the child row, then it will make the row visible
3765
+ // checked here so the option can be changed dynamically
3766
+ for ( indx = 0; indx < childRow.length; indx++ ) {
3767
+ txt += ' ' + childRow[indx].join( '' ) || '';
3768
+ }
3769
+ data.childRowText = wo.filter_childRows ?
3770
+ ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) :
3771
+ '';
3772
+ }
3517
3773
 
3518
- val = true;
3519
- if (wo.filter_defaultFilter && regex.iQuery.test( wo.filter_indexed.defaultColFilter[ columnIndex ] )) {
3520
- data.filter = ts.filter.defaultFilter( data.filter, wo.filter_indexed.defaultColFilter[ columnIndex ] );
3521
- // val is used to indicate that a filter select is using a default filter; so we override the exact & partial matches
3522
- val = false;
3523
- }
3524
- // data.iFilter = case insensitive (if wo.filter_ignoreCase is true), data.filter = case sensitive
3525
- data.iFilter = wo.filter_ignoreCase ? (data.filter || '').toLocaleLowerCase() : data.filter;
3526
- fxn = wo.filter_indexed.functions[ columnIndex ];
3527
- $cell = c.$headerIndexed[columnIndex];
3528
- hasSelect = $cell.hasClass('filter-select');
3529
- filterMatched = null;
3530
- if ( fxn || ( hasSelect && val ) ) {
3531
- if (fxn === true || hasSelect) {
3532
- // default selector uses exact match unless "filter-match" class is found
3533
- filterMatched = ($cell.hasClass('filter-match')) ? data.iExact.search(data.iFilter) >= 0 : data.filter === data.exact;
3534
- } else if (typeof fxn === 'function') {
3535
- // filter callback( exact cell content, parser normalized content, filter input value, column index, jQuery row object )
3536
- filterMatched = fxn(data.exact, data.cache, data.filter, columnIndex, $rows.eq(rowIndex), c);
3537
- } else if (typeof fxn[ffxn || data.filter] === 'function') {
3538
- // selector option function
3539
- filterMatched = fxn[ffxn || data.filter](data.exact, data.cache, data.filter, columnIndex, $rows.eq(rowIndex), c);
3540
- }
3774
+ showRow = ts.filter.processRow( c, data, vars );
3775
+ childRow = rowData.$row.filter( ':gt( 0 )' );
3776
+
3777
+ if ( wo.filter_childRows && childRow.length ) {
3778
+ if ( wo.filter_childByColumn ) {
3779
+ // cycle through each child row
3780
+ for ( indx = 0; indx < childRow.length; indx++ ) {
3781
+ data.$row = childRow.eq( indx );
3782
+ data.cacheArray = rowData.child[ indx ];
3783
+ data.rawArray = data.cacheArray;
3784
+ // use OR comparison on child rows
3785
+ showRow = showRow || ts.filter.processRow( c, data, vars );
3541
3786
  }
3542
- if (filterMatched === null) {
3543
- // cycle through the different filters
3544
- // filters return a boolean or null if nothing matches
3545
- $.each(ts.filter.types, function(type, typeFunction) {
3546
- if ($.inArray(type, excludeMatch) < 0) {
3547
- matches = typeFunction( c, data );
3548
- if (matches !== null) {
3549
- filterMatched = matches;
3550
- return false;
3551
- }
3552
- }
3553
- });
3554
- if (filterMatched !== null) {
3555
- result = filterMatched;
3556
- // Look for match, and add child row data for matching
3557
- } else {
3558
- data.exact = (data.iExact + data.childRowText).indexOf( ts.filter.parseFilter(c, data.iFilter, columnIndex, data.parsed[columnIndex]) );
3559
- result = ( (!wo.filter_startsWith && data.exact >= 0) || (wo.filter_startsWith && data.exact === 0) );
3560
- }
3561
- } else {
3562
- result = filterMatched;
3563
- }
3564
- showRow = (result) ? showRow : false;
3565
3787
  }
3788
+ childRow.toggleClass( wo.filter_filteredRow, !showRow );
3566
3789
  }
3567
- $rows.eq(rowIndex)
3568
- .toggleClass(wo.filter_filteredRow, !showRow)[0]
3790
+
3791
+ rowData.$row
3792
+ .toggleClass( wo.filter_filteredRow, !showRow )[0]
3569
3793
  .display = showRow ? '' : 'none';
3570
- if (childRow.length) {
3571
- childRow.toggleClass(wo.filter_filteredRow, !showRow);
3572
- }
3573
3794
  }
3574
3795
  }
3575
- c.filteredRows += $rows.not('.' + wo.filter_filteredRow).length;
3796
+ c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length;
3576
3797
  c.totalRows += $rows.length;
3577
- ts.processTbody(table, $tbody, false);
3798
+ ts.processTbody( table, $tbody, false );
3578
3799
  }
3579
3800
  c.lastCombinedFilter = combinedFilters; // save last search
3580
- c.lastSearch = filters;
3581
- c.$table.data('lastSearch', filters);
3582
- if (wo.filter_saveFilters && ts.storage) {
3583
- ts.storage( table, 'tablesorter-filters', filters );
3801
+ // don't save 'filters' directly since it may have altered ( AnyMatch column searches )
3802
+ c.lastSearch = storedFilters;
3803
+ c.$table.data( 'lastSearch', storedFilters );
3804
+ if ( wo.filter_saveFilters && ts.storage ) {
3805
+ ts.storage( table, 'tablesorter-filters', storedFilters );
3584
3806
  }
3585
- if (c.debug) {
3586
- ts.benchmark("Completed filter widget search", time);
3807
+ if ( c.debug ) {
3808
+ ts.benchmark( 'Completed filter widget search', time );
3809
+ }
3810
+ if ( wo.filter_initialized ) {
3811
+ c.$table.trigger( 'filterEnd', c );
3587
3812
  }
3588
- if (wo.filter_initialized) { c.$table.trigger('filterEnd', c ); }
3589
- setTimeout(function(){
3590
- c.$table.trigger('applyWidgets'); // make sure zebra widget is applied
3591
- }, 0);
3813
+ setTimeout( function() {
3814
+ c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied
3815
+ }, 0 );
3592
3816
  },
3593
- getOptionSource: function(table, column, onlyAvail) {
3594
- table = $(table)[0];
3817
+ getOptionSource: function( table, column, onlyAvail ) {
3818
+ table = $( table )[0];
3595
3819
  var cts, indx, len,
3596
3820
  c = table.config,
3597
3821
  wo = c.widgetOptions,
3598
3822
  parsed = [],
3599
3823
  arry = false,
3600
3824
  source = wo.filter_selectSource,
3601
- last = c.$table.data('lastSearch') || [],
3602
- fxn = $.isFunction(source) ? true : ts.getColumnData( table, source, column );
3825
+ last = c.$table.data( 'lastSearch' ) || [],
3826
+ fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column );
3603
3827
 
3604
- if (onlyAvail && last[column] !== '') {
3828
+ if ( onlyAvail && last[column] !== '' ) {
3605
3829
  onlyAvail = false;
3606
3830
  }
3607
3831
 
3608
3832
  // filter select source option
3609
- if (fxn === true) {
3833
+ if ( fxn === true ) {
3610
3834
  // OVERALL source
3611
- arry = source(table, column, onlyAvail);
3612
- } else if ( fxn instanceof $ || ($.type(fxn) === 'string' && fxn.indexOf('</option>') >= 0) ) {
3835
+ arry = source( table, column, onlyAvail );
3836
+ } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '</option>' ) >= 0 ) ) {
3613
3837
  // selectSource is a jQuery object or string of options
3614
3838
  return fxn;
3615
- } else if ($.isArray(fxn)) {
3839
+ } else if ( $.isArray( fxn ) ) {
3616
3840
  arry = fxn;
3617
- } else if ($.type(source) === 'object' && fxn) {
3841
+ } else if ( $.type( source ) === 'object' && fxn ) {
3618
3842
  // custom select source function for a SPECIFIC COLUMN
3619
- arry = fxn(table, column, onlyAvail);
3843
+ arry = fxn( table, column, onlyAvail );
3620
3844
  }
3621
- if (arry === false) {
3845
+ if ( arry === false ) {
3622
3846
  // fall back to original method
3623
- arry = ts.filter.getOptions(table, column, onlyAvail);
3847
+ arry = ts.filter.getOptions( table, column, onlyAvail );
3624
3848
  }
3625
3849
 
3626
3850
  // get unique elements and sort the list
3627
- // if $.tablesorter.sortText exists (not in the original tablesorter),
3851
+ // if $.tablesorter.sortText exists ( not in the original tablesorter ),
3628
3852
  // then natural sort the list otherwise use a basic sort
3629
- arry = $.grep(arry, function(value, indx) {
3630
- return $.inArray(value, arry) === indx;
3853
+ arry = $.grep( arry, function( value, indx ) {
3854
+ return $.inArray( value, arry ) === indx;
3631
3855
  });
3632
3856
 
3633
- if (c.$headerIndexed[column].hasClass('filter-select-nosort')) {
3857
+ if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) {
3634
3858
  // unsorted select options
3635
3859
  return arry;
3636
3860
  } else {
3637
3861
  len = arry.length;
3638
3862
  // parse select option values
3639
- for (indx = 0; indx < len; indx++) {
3863
+ for ( indx = 0; indx < len; indx++ ) {
3640
3864
  // parse array data using set column parser; this DOES NOT pass the original
3641
3865
  // table cell to the parser format function
3642
- parsed.push({ t : arry[indx], p : c.parsers && c.parsers[column].format( arry[indx], table, [], column ) });
3866
+ parsed.push({
3867
+ t : arry[ indx ],
3868
+ p : c.parsers && c.parsers[ column ].format( arry[ indx ], table, [], column )
3869
+ });
3643
3870
  }
3644
3871
 
3645
3872
  // sort parsed select options
3646
3873
  cts = c.textSorter || '';
3647
- parsed.sort(function(a, b){
3874
+ parsed.sort( function( a, b ) {
3648
3875
  // sortNatural breaks if you don't pass it strings
3649
- var x = a.p.toString(), y = b.p.toString();
3650
- if ($.isFunction(cts)) {
3876
+ var x = a.p.toString(),
3877
+ y = b.p.toString();
3878
+ if ( $.isFunction( cts ) ) {
3651
3879
  // custom OVERALL text sorter
3652
- return cts(x, y, true, column, table);
3653
- } else if (typeof(cts) === 'object' && cts.hasOwnProperty(column)) {
3880
+ return cts( x, y, true, column, table );
3881
+ } else if ( typeof( cts ) === 'object' && cts.hasOwnProperty( column ) ) {
3654
3882
  // custom text sorter for a SPECIFIC COLUMN
3655
- return cts[column](x, y, true, column, table);
3656
- } else if (ts.sortNatural) {
3883
+ return cts[column]( x, y, true, column, table );
3884
+ } else if ( ts.sortNatural ) {
3657
3885
  // fall back to natural sort
3658
- return ts.sortNatural(x, y);
3886
+ return ts.sortNatural( x, y );
3659
3887
  }
3660
3888
  // using an older version! do a basic sort
3661
3889
  return true;
@@ -3663,187 +3891,224 @@ ts.filter = {
3663
3891
  // rebuild arry from sorted parsed data
3664
3892
  arry = [];
3665
3893
  len = parsed.length;
3666
- for (indx = 0; indx < len; indx++) {
3894
+ for ( indx = 0; indx < len; indx++ ) {
3667
3895
  arry.push( parsed[indx].t );
3668
3896
  }
3669
3897
  return arry;
3670
3898
  }
3671
3899
  },
3672
- getOptions: function(table, column, onlyAvail) {
3673
- table = $(table)[0];
3674
- var rowIndex, tbodyIndex, len, row, cache, cell,
3900
+ getOptions: function( table, column, onlyAvail ) {
3901
+ table = $( table )[0];
3902
+ var rowIndex, tbodyIndex, len, row, cache,
3675
3903
  c = table.config,
3676
3904
  wo = c.widgetOptions,
3677
3905
  arry = [];
3678
- for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
3906
+ for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
3679
3907
  cache = c.cache[tbodyIndex];
3680
3908
  len = c.cache[tbodyIndex].normalized.length;
3681
3909
  // loop through the rows
3682
- for (rowIndex = 0; rowIndex < len; rowIndex++) {
3683
- // get cached row from cache.row (old) or row data object (new; last item in normalized array)
3684
- row = cache.row ? cache.row[rowIndex] : cache.normalized[rowIndex][c.columns].$row[0];
3910
+ for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
3911
+ // get cached row from cache.row ( old ) or row data object
3912
+ // ( new; last item in normalized array )
3913
+ row = cache.row ?
3914
+ cache.row[ rowIndex ] :
3915
+ cache.normalized[ rowIndex ][ c.columns ].$row[0];
3685
3916
  // check if has class filtered
3686
- if (onlyAvail && row.className.match(wo.filter_filteredRow)) { continue; }
3917
+ if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) {
3918
+ continue;
3919
+ }
3687
3920
  // get non-normalized cell content
3688
- if (wo.filter_useParsedData || c.parsers[column].parsed || c.$headerIndexed[column].hasClass('filter-parsed')) {
3689
- arry.push( '' + cache.normalized[rowIndex][column] );
3921
+ if ( wo.filter_useParsedData ||
3922
+ c.parsers[column].parsed ||
3923
+ c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) {
3924
+ arry.push( '' + cache.normalized[ rowIndex ][ column ] );
3690
3925
  } else {
3691
- cell = row.cells[column];
3692
- if (cell) {
3693
- arry.push( $.trim( cell.getAttribute( c.textAttribute ) || cell.textContent || $(cell).text() ) );
3694
- }
3926
+ // get raw cached data instead of content directly from the cells
3927
+ arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] );
3695
3928
  }
3696
3929
  }
3697
3930
  }
3698
3931
  return arry;
3699
3932
  },
3700
- buildSelect: function(table, column, arry, updating, onlyAvail) {
3701
- table = $(table)[0];
3702
- column = parseInt(column, 10);
3703
- if (!table.config.cache || $.isEmptyObject(table.config.cache)) { return; }
3933
+ buildSelect: function( table, column, arry, updating, onlyAvail ) {
3934
+ table = $( table )[0];
3935
+ column = parseInt( column, 10 );
3936
+ if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) {
3937
+ return;
3938
+ }
3704
3939
  var indx, val, txt, t, $filters, $filter,
3705
3940
  c = table.config,
3706
3941
  wo = c.widgetOptions,
3707
- node = c.$headerIndexed[column],
3708
- // t.data('placeholder') won't work in jQuery older than 1.4.3
3709
- options = '<option value="">' + ( node.data('placeholder') || node.attr('data-placeholder') || wo.filter_placeholder.select || '' ) + '</option>',
3942
+ node = c.$headerIndexed[ column ],
3943
+ // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3
3944
+ options = '<option value="">' +
3945
+ ( node.data( 'placeholder' ) ||
3946
+ node.attr( 'data-placeholder' ) ||
3947
+ wo.filter_placeholder.select || ''
3948
+ ) + '</option>',
3710
3949
  // Get curent filter value
3711
- currentValue = c.$table.find('thead').find('select.' + tscss.filter + '[data-column="' + column + '"]').val();
3712
- // nothing included in arry (external source), so get the options from filter_selectSource or column data
3713
- if (typeof arry === 'undefined' || arry === '') {
3714
- arry = ts.filter.getOptionSource(table, column, onlyAvail);
3950
+ currentValue = c.$table
3951
+ .find( 'thead' )
3952
+ .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' )
3953
+ .val();
3954
+ // nothing included in arry ( external source ), so get the options from
3955
+ // filter_selectSource or column data
3956
+ if ( typeof arry === 'undefined' || arry === '' ) {
3957
+ arry = ts.filter.getOptionSource( table, column, onlyAvail );
3715
3958
  }
3716
3959
 
3717
- if ($.isArray(arry)) {
3960
+ if ( $.isArray( arry ) ) {
3718
3961
  // build option list
3719
- for (indx = 0; indx < arry.length; indx++) {
3720
- txt = arry[indx] = ('' + arry[indx]).replace(/\"/g, "&quot;");
3962
+ for ( indx = 0; indx < arry.length; indx++ ) {
3963
+ txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '&quot;' );
3721
3964
  val = txt;
3722
3965
  // allow including a symbol in the selectSource array
3723
- // "a-z|A through Z" so that "a-z" becomes the option value
3724
- // and "A through Z" becomes the option text
3725
- if (txt.indexOf(wo.filter_selectSourceSeparator) >= 0) {
3726
- t = txt.split(wo.filter_selectSourceSeparator);
3966
+ // 'a-z|A through Z' so that 'a-z' becomes the option value
3967
+ // and 'A through Z' becomes the option text
3968
+ if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
3969
+ t = txt.split( wo.filter_selectSourceSeparator );
3727
3970
  val = t[0];
3728
3971
  txt = t[1];
3729
3972
  }
3730
- // replace quotes - fixes #242 & ignore empty strings - see http://stackoverflow.com/q/14990971/145346
3731
- options += arry[indx] !== '' ? '<option ' + (val === txt ? '' : 'data-function-name="' + arry[indx] + '" ') + 'value="' + val + '">' + txt + '</option>' : '';
3973
+ // replace quotes - fixes #242 & ignore empty strings
3974
+ // see http://stackoverflow.com/q/14990971/145346
3975
+ options += arry[indx] !== '' ?
3976
+ '<option ' +
3977
+ ( val === txt ? '' : 'data-function-name="' + arry[indx] + '" ' ) +
3978
+ 'value="' + val + '">' + txt +
3979
+ '</option>' : '';
3732
3980
  }
3733
3981
  // clear arry so it doesn't get appended twice
3734
3982
  arry = [];
3735
3983
  }
3736
3984
 
3737
- // update all selects in the same column (clone thead in sticky headers & any external selects) - fixes 473
3738
- $filters = ( c.$filters ? c.$filters : c.$table.children('thead') ).find('.' + tscss.filter);
3739
- if (wo.filter_$externalFilters) {
3740
- $filters = $filters && $filters.length ? $filters.add(wo.filter_$externalFilters) : wo.filter_$externalFilters;
3985
+ // update all selects in the same column ( clone thead in sticky headers &
3986
+ // any external selects ) - fixes 473
3987
+ $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) )
3988
+ .find( '.' + tscss.filter );
3989
+ if ( wo.filter_$externalFilters ) {
3990
+ $filters = $filters && $filters.length ?
3991
+ $filters.add( wo.filter_$externalFilters ) :
3992
+ wo.filter_$externalFilters;
3741
3993
  }
3742
- $filter = $filters.filter('select[data-column="' + column + '"]');
3994
+ $filter = $filters.filter( 'select[data-column="' + column + '"]' );
3743
3995
 
3744
3996
  // make sure there is a select there!
3745
- if ($filter.length) {
3746
- $filter[ updating ? 'html' : 'append' ](options);
3747
- if (!$.isArray(arry)) {
3997
+ if ( $filter.length ) {
3998
+ $filter[ updating ? 'html' : 'append' ]( options );
3999
+ if ( !$.isArray( arry ) ) {
3748
4000
  // append options if arry is provided externally as a string or jQuery object
3749
- // options (default value) was already added
3750
- $filter.append(arry).val(currentValue);
4001
+ // options ( default value ) was already added
4002
+ $filter.append( arry ).val( currentValue );
3751
4003
  }
3752
- $filter.val(currentValue);
4004
+ $filter.val( currentValue );
3753
4005
  }
3754
4006
  },
3755
- buildDefault: function(table, updating) {
4007
+ buildDefault: function( table, updating ) {
3756
4008
  var columnIndex, $header, noSelect,
3757
4009
  c = table.config,
3758
4010
  wo = c.widgetOptions,
3759
4011
  columns = c.columns;
3760
4012
  // build default select dropdown
3761
- for (columnIndex = 0; columnIndex < columns; columnIndex++) {
4013
+ for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) {
3762
4014
  $header = c.$headerIndexed[columnIndex];
3763
- noSelect = !($header.hasClass('filter-false') || $header.hasClass('parser-false'));
4015
+ noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
3764
4016
  // look for the filter-select class; build/update it if found
3765
- if (($header.hasClass('filter-select') || ts.getColumnData( table, wo.filter_functions, columnIndex ) === true) && noSelect) {
3766
- ts.filter.buildSelect(table, columnIndex, '', updating, $header.hasClass(wo.filter_onlyAvail));
4017
+ if ( ( $header.hasClass( 'filter-select' ) ||
4018
+ ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) {
4019
+ ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) );
3767
4020
  }
3768
4021
  }
3769
4022
  }
3770
4023
  };
3771
4024
 
3772
- ts.getFilters = function(table, getRaw, setFilters, skipFirst) {
4025
+ ts.getFilters = function( table, getRaw, setFilters, skipFirst ) {
3773
4026
  var i, $filters, $column, cols,
3774
4027
  filters = false,
3775
- c = table ? $(table)[0].config : '',
4028
+ c = table ? $( table )[0].config : '',
3776
4029
  wo = c ? c.widgetOptions : '';
3777
- if (getRaw !== true && wo && !wo.filter_columnFilters) {
3778
- return $(table).data('lastSearch');
4030
+ if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) ||
4031
+ // setFilters called, but last search is exactly the same as the current
4032
+ // fixes issue #733 & #903 where calling update causes the input values to reset
4033
+ ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) {
4034
+ return $( table ).data( 'lastSearch' );
3779
4035
  }
3780
- if (c) {
3781
- if (c.$filters) {
3782
- $filters = c.$filters.find('.' + tscss.filter);
4036
+ if ( c ) {
4037
+ if ( c.$filters ) {
4038
+ $filters = c.$filters.find( '.' + tscss.filter );
3783
4039
  }
3784
- if (wo.filter_$externalFilters) {
3785
- $filters = $filters && $filters.length ? $filters.add(wo.filter_$externalFilters) : wo.filter_$externalFilters;
4040
+ if ( wo.filter_$externalFilters ) {
4041
+ $filters = $filters && $filters.length ?
4042
+ $filters.add( wo.filter_$externalFilters ) :
4043
+ wo.filter_$externalFilters;
3786
4044
  }
3787
- if ($filters && $filters.length) {
4045
+ if ( $filters && $filters.length ) {
3788
4046
  filters = setFilters || [];
3789
- for (i = 0; i < c.columns + 1; i++) {
4047
+ for ( i = 0; i < c.columns + 1; i++ ) {
3790
4048
  cols = ( i === c.columns ?
3791
- // "all" columns can now include a range or set of columms (data-column="0-2,4,6-7")
4049
+ // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' )
3792
4050
  wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector :
3793
4051
  '[data-column="' + i + '"]' );
3794
- $column = $filters.filter(cols);
3795
- if ($column.length) {
4052
+ $column = $filters.filter( cols );
4053
+ if ( $column.length ) {
3796
4054
  // move the latest search to the first slot in the array
3797
4055
  $column = ts.filter.getLatestSearch( $column );
3798
- if ($.isArray(setFilters)) {
3799
- // skip first (latest input) to maintain cursor position while typing
3800
- if (skipFirst) { $column.slice(1); }
3801
- if (i === c.columns) {
3802
- // prevent data-column="all" from filling data-column="0,1" (etc)
3803
- cols = $column.filter(wo.filter_anyColumnSelector);
4056
+ if ( $.isArray( setFilters ) ) {
4057
+ // skip first ( latest input ) to maintain cursor position while typing
4058
+ if ( skipFirst ) {
4059
+ $column.slice( 1 );
4060
+ }
4061
+ if ( i === c.columns ) {
4062
+ // prevent data-column='all' from filling data-column='0,1' ( etc )
4063
+ cols = $column.filter( wo.filter_anyColumnSelector );
3804
4064
  $column = cols.length ? cols : $column;
3805
4065
  }
3806
4066
  $column
3807
- .val( setFilters[i] )
3808
- .trigger('change.tsfilter');
4067
+ .val( setFilters[ i ] )
4068
+ .trigger( 'change.tsfilter' );
3809
4069
  } else {
3810
4070
  filters[i] = $column.val() || '';
3811
4071
  // don't change the first... it will move the cursor
3812
- if (i === c.columns) {
3813
- // don't update range columns from "all" setting
3814
- $column.slice(1).filter('[data-column*="' + $column.attr('data-column') + '"]').val( filters[i] );
4072
+ if ( i === c.columns ) {
4073
+ // don't update range columns from 'all' setting
4074
+ $column
4075
+ .slice( 1 )
4076
+ .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' )
4077
+ .val( filters[ i ] );
3815
4078
  } else {
3816
- $column.slice(1).val( filters[i] );
4079
+ $column
4080
+ .slice( 1 )
4081
+ .val( filters[ i ] );
3817
4082
  }
3818
4083
  }
3819
4084
  // save any match input dynamically
3820
- if (i === c.columns && $column.length) {
4085
+ if ( i === c.columns && $column.length ) {
3821
4086
  wo.filter_$anyMatch = $column;
3822
4087
  }
3823
4088
  }
3824
4089
  }
3825
4090
  }
3826
4091
  }
3827
- if (filters.length === 0) {
4092
+ if ( filters.length === 0 ) {
3828
4093
  filters = false;
3829
4094
  }
3830
4095
  return filters;
3831
4096
  };
3832
4097
 
3833
- ts.setFilters = function(table, filter, apply, skipFirst) {
3834
- var c = table ? $(table)[0].config : '',
3835
- valid = ts.getFilters(table, true, filter, skipFirst);
3836
- if (c && apply) {
4098
+ ts.setFilters = function( table, filter, apply, skipFirst ) {
4099
+ var c = table ? $( table )[0].config : '',
4100
+ valid = ts.getFilters( table, true, filter, skipFirst );
4101
+ if ( c && apply ) {
3837
4102
  // ensure new set filters are applied, even if the search is the same
3838
4103
  c.lastCombinedFilter = null;
3839
4104
  c.lastSearch = [];
3840
- ts.filter.searching(c.table, filter, skipFirst);
3841
- c.$table.trigger('filterFomatterUpdate');
4105
+ ts.filter.searching( c.table, filter, skipFirst );
4106
+ c.$table.trigger( 'filterFomatterUpdate' );
3842
4107
  }
3843
4108
  return !!valid;
3844
4109
  };
3845
4110
 
3846
- })(jQuery);
4111
+ })( jQuery );
3847
4112
 
3848
4113
  /*! Widget: stickyHeaders - updated 3/26/2015 (v2.21.3) *//*
3849
4114
  * Requires tablesorter v2.8+ and jQuery 1.4.3+
@@ -3851,7 +4116,7 @@ ts.setFilters = function(table, filter, apply, skipFirst) {
3851
4116
  */
3852
4117
  ;(function ($, window) {
3853
4118
  'use strict';
3854
- var ts = $.tablesorter = $.tablesorter || {};
4119
+ var ts = $.tablesorter || {};
3855
4120
 
3856
4121
  $.extend(ts.css, {
3857
4122
  sticky : 'tablesorter-stickyHeader', // stickyHeader
@@ -4117,10 +4382,10 @@ ts.addWidget({
4117
4382
 
4118
4383
  })(jQuery, window);
4119
4384
 
4120
- /*! Widget: resizable - updated 4/2/2015 (v2.21.5) */
4385
+ /*! Widget: resizable - updated 5/17/2015 (v2.22.0) */
4121
4386
  ;(function ($, window) {
4122
4387
  'use strict';
4123
- var ts = $.tablesorter = $.tablesorter || {};
4388
+ var ts = $.tablesorter || {};
4124
4389
 
4125
4390
  $.extend(ts.css, {
4126
4391
  resizableContainer : 'tablesorter-resizable-container',
@@ -4409,7 +4674,7 @@ ts.addWidget({
4409
4674
  init: function(table, thisWidget, c, wo) {
4410
4675
  ts.resizable.init( c, wo );
4411
4676
  },
4412
- remove: function( table, c, wo ) {
4677
+ remove: function( table, c, wo, refreshing ) {
4413
4678
  if (wo.$resizable_container) {
4414
4679
  var namespace = c.namespace + 'tsresize';
4415
4680
  c.$table.add( $( c.namespace + '_extra_table' ) )
@@ -4418,13 +4683,13 @@ ts.addWidget({
4418
4683
 
4419
4684
  wo.$resizable_container.remove();
4420
4685
  ts.resizable.toggleTextSelection( c, false );
4421
- ts.resizableReset( table );
4686
+ ts.resizableReset( table, refreshing );
4422
4687
  $( document ).unbind( 'mousemove' + namespace + ' mouseup' + namespace );
4423
4688
  }
4424
4689
  }
4425
4690
  });
4426
4691
 
4427
- ts.resizableReset = function( table, nosave ) {
4692
+ ts.resizableReset = function( table, refreshing ) {
4428
4693
  $( table ).each(function(){
4429
4694
  var index, $t,
4430
4695
  c = this.config,
@@ -4441,7 +4706,7 @@ ts.resizableReset = function( table, nosave ) {
4441
4706
  }
4442
4707
  // reset stickyHeader widths
4443
4708
  $( window ).trigger( 'resize' );
4444
- if ( ts.storage && !nosave ) {
4709
+ if ( ts.storage && !refreshing ) {
4445
4710
  ts.storage( this, ts.css.resizableStorage, {} );
4446
4711
  }
4447
4712
  }
@@ -4453,7 +4718,7 @@ ts.resizableReset = function( table, nosave ) {
4453
4718
  /*! Widget: saveSort */
4454
4719
  ;(function ($) {
4455
4720
  'use strict';
4456
- var ts = $.tablesorter = $.tablesorter || {};
4721
+ var ts = $.tablesorter || {};
4457
4722
 
4458
4723
  // this widget saves the last sort only if the
4459
4724
  // saveSort widget option is true AND the