jquery-tablesorter 1.16.5 → 1.17.0

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