jquery-tablesorter 1.16.5 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/jquery-tablesorter/version.rb +1 -1
  4. data/vendor/assets/javascripts/jquery-tablesorter/addons/pager/jquery.tablesorter.pager.js +38 -27
  5. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.combined.js +1104 -839
  6. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.js +167 -123
  7. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.widgets.js +938 -717
  8. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date.js +5 -5
  9. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-globalize.js +46 -0
  10. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-input-select.js +96 -72
  11. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-named-numbers.js +6 -5
  12. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-network.js +26 -17
  13. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-columns.js +1 -1
  14. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-editable.js +95 -42
  15. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-filter.js +921 -700
  16. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-grouping.js +5 -3
  17. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-math.js +22 -20
  18. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-output.js +7 -5
  19. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-pager.js +40 -29
  20. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-resizable.js +6 -6
  21. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-saveSort.js +1 -1
  22. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-scroller.js +53 -31
  23. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-stickyHeaders.js +1 -1
  24. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-storage.js +1 -1
  25. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-uitheme.js +1 -1
  26. data/vendor/assets/stylesheets/jquery-tablesorter/theme.black-ice.css +2 -1
  27. data/vendor/assets/stylesheets/jquery-tablesorter/theme.blue.css +2 -1
  28. data/vendor/assets/stylesheets/jquery-tablesorter/theme.bootstrap.css +2 -1
  29. data/vendor/assets/stylesheets/jquery-tablesorter/theme.bootstrap_2.css +8 -6
  30. data/vendor/assets/stylesheets/jquery-tablesorter/theme.dark.css +2 -1
  31. data/vendor/assets/stylesheets/jquery-tablesorter/theme.default.css +1 -1
  32. metadata +3 -2
@@ -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) {
@@ -20,7 +20,7 @@
20
20
  ;(function ($, window, document) {
21
21
  'use strict';
22
22
 
23
- var ts = $.tablesorter = $.tablesorter || {};
23
+ var ts = $.tablesorter || {};
24
24
  // *** Store data in local storage, with a cookie fallback ***
25
25
  /* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json)
26
26
  if you need it, then include https://github.com/douglascrockford/JSON-js
@@ -109,7 +109,7 @@ ts.storage = function(table, key, value, options) {
109
109
  /*! Widget: uitheme - updated 3/26/2015 (v2.21.3) */
110
110
  ;(function ($) {
111
111
  'use strict';
112
- var ts = $.tablesorter = $.tablesorter || {};
112
+ var ts = $.tablesorter || {};
113
113
 
114
114
  ts.themes = {
115
115
  'bootstrap' : {
@@ -295,7 +295,7 @@ ts.addWidget({
295
295
  /*! Widget: columns */
296
296
  ;(function ($) {
297
297
  'use strict';
298
- var ts = $.tablesorter = $.tablesorter || {};
298
+ var ts = $.tablesorter || {};
299
299
 
300
300
  ts.addWidget({
301
301
  id: "columns",
@@ -371,16 +371,16 @@ ts.addWidget({
371
371
 
372
372
  })(jQuery);
373
373
 
374
- /*! Widget: filter - updated 3/26/2015 (v2.21.3) *//*
374
+ /*! Widget: filter - updated 5/17/2015 (v2.22.0) *//*
375
375
  * Requires tablesorter v2.8+ and jQuery 1.7+
376
376
  * by Rob Garrison
377
377
  */
378
- ;(function ($) {
378
+ ;( function ( $ ) {
379
379
  'use strict';
380
- var ts = $.tablesorter = $.tablesorter || {},
380
+ var ts = $.tablesorter || {},
381
381
  tscss = ts.css;
382
382
 
383
- $.extend(tscss, {
383
+ $.extend( tscss, {
384
384
  filterRow : 'tablesorter-filter-row',
385
385
  filter : 'tablesorter-filter',
386
386
  filterDisabled : 'disabled',
@@ -388,26 +388,27 @@ $.extend(tscss, {
388
388
  });
389
389
 
390
390
  ts.addWidget({
391
- id: "filter",
391
+ id: 'filter',
392
392
  priority: 50,
393
393
  options : {
394
394
  filter_childRows : false, // if true, filter includes child row content in the search
395
+ filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped
395
396
  filter_columnFilters : true, // if true, a filter will be added to the top of each table column
396
- filter_columnAnyMatch: true, // if true, allows using "#:{query}" in AnyMatch searches (column:query)
397
- filter_cellFilter : '', // css class name added to the filter cell (string or array)
398
- filter_cssFilter : '', // css class name added to the filter row & each input in the row (tablesorter-filter is ALWAYS added)
399
- 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.
397
+ filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query )
398
+ filter_cellFilter : '', // css class name added to the filter cell ( string or array )
399
+ filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added )
400
+ 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.
400
401
  filter_excludeFilter : {}, // filters to exclude, per column
401
- filter_external : '', // jQuery selector string (or jQuery object) of external filters
402
+ filter_external : '', // jQuery selector string ( or jQuery object ) of external filters
402
403
  filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin
403
404
  filter_formatter : null, // add custom filter elements to the filter row
404
405
  filter_functions : null, // add custom filter functions using this option
405
406
  filter_hideEmpty : true, // hide filter row when table is empty
406
407
  filter_hideFilters : false, // collapse filter row when mouse leaves the area
407
408
  filter_ignoreCase : true, // if true, make all searches case-insensitive
408
- filter_liveSearch : true, // if true, search column content while the user types (with a delay)
409
- filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available (visible) options within the drop down
410
- filter_placeholder : { search : '', select : '' }, // default placeholder text (overridden by any header "data-placeholder" setting)
409
+ filter_liveSearch : true, // if true, search column content while the user types ( with a delay )
410
+ filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down
411
+ filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting )
411
412
  filter_reset : null, // jQuery selector string of an element used to reset the filters
412
413
  filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters
413
414
  filter_searchDelay : 300, // typing delay in milliseconds before starting a search
@@ -419,37 +420,38 @@ ts.addWidget({
419
420
  filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value
420
421
  filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text
421
422
  },
422
- format: function(table, c, wo) {
423
- if (!c.$table.hasClass('hasFilters')) {
424
- ts.filter.init(table, c, wo);
423
+ format: function( table, c, wo ) {
424
+ if ( !c.$table.hasClass( 'hasFilters' ) ) {
425
+ ts.filter.init( table, c, wo );
425
426
  }
426
427
  },
427
- remove: function(table, c, wo, refreshing) {
428
+ remove: function( table, c, wo, refreshing ) {
428
429
  var tbodyIndex, $tbody,
429
430
  $table = c.$table,
430
431
  $tbodies = c.$tbodies,
431
- events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '.split(' ').join(c.namespace + 'filter ');
432
+ events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '
433
+ .split( ' ' ).join( c.namespace + 'filter ' );
432
434
  $table
433
- .removeClass('hasFilters')
435
+ .removeClass( 'hasFilters' )
434
436
  // add .tsfilter namespace to all BUT search
435
- .unbind( events.replace(/\s+/g, ' ') )
437
+ .unbind( events.replace( /\s+/g, ' ' ) )
436
438
  // remove the filter row even if refreshing, because the column might have been moved
437
- .find('.' + tscss.filterRow).remove();
438
- if (refreshing) { return; }
439
- for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
440
- $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody
441
- $tbody.children().removeClass(wo.filter_filteredRow).show();
442
- ts.processTbody(table, $tbody, false); // restore tbody
439
+ .find( '.' + tscss.filterRow ).remove();
440
+ if ( refreshing ) { return; }
441
+ for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
442
+ $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody
443
+ $tbody.children().removeClass( wo.filter_filteredRow ).show();
444
+ ts.processTbody( table, $tbody, false ); // restore tbody
443
445
  }
444
- if (wo.filter_reset) {
445
- $(document).undelegate(wo.filter_reset, 'click.tsfilter');
446
+ if ( wo.filter_reset ) {
447
+ $( document ).undelegate( wo.filter_reset, 'click.tsfilter' );
446
448
  }
447
449
  }
448
450
  });
449
451
 
450
452
  ts.filter = {
451
453
 
452
- // regex used in filter "check" functions - not for general use and not documented
454
+ // regex used in filter 'check' functions - not for general use and not documented
453
455
  regex: {
454
456
  regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex
455
457
  child : /tablesorter-childRow/, // child row class name; this gets updated in the script
@@ -462,22 +464,33 @@ ts.filter = {
462
464
  },
463
465
  // function( c, data ) { }
464
466
  // c = table.config
465
- // data.filter = array of filter input values;
466
- // data.iFilter = same array, except lowercase (if wo.filter_ignoreCase is true)
467
- // data.exact = table cell text (or parsed data if column parser enabled)
468
- // data.iExact = same as data.exact, except lowercase (if wo.filter_ignoreCase is true)
469
- // data.cache = table cell text from cache, so it has been parsed (& in all lower case if config.ignoreCase is true)
470
- // data.index = column index; table = table element (DOM)
471
- // data.parsed = array (by column) of boolean values (from filter_useParsedData or "filter-parsed" class)
467
+ // data.$row = jQuery object of the row currently being processed
468
+ // data.$cells = jQuery object of all cells within the current row
469
+ // data.filters = array of filters for all columns ( some may be undefined )
470
+ // data.filter = filter for the current column
471
+ // data.iFilter = same as data.filter, except lowercase ( if wo.filter_ignoreCase is true )
472
+ // data.exact = table cell text ( or parsed data if column parser enabled )
473
+ // data.iExact = same as data.exact, except lowercase ( if wo.filter_ignoreCase is true )
474
+ // data.cache = table cell text from cache, so it has been parsed ( & in all lower case if c.ignoreCase is true )
475
+ // data.cacheArray = An array of parsed content from each table cell in the row being processed
476
+ // data.index = column index; table = table element ( DOM )
477
+ // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
472
478
  types: {
473
479
  // Look for regex
474
480
  regex: function( c, data ) {
475
- if ( ts.filter.regex.regex.test(data.iFilter) ) {
481
+ if ( ts.filter.regex.regex.test( data.filter ) ) {
476
482
  var matches,
477
- regex = ts.filter.regex.regex.exec(data.iFilter);
483
+ // cache regex per column for optimal speed
484
+ regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ),
485
+ isRegex = regex instanceof RegExp;
478
486
  try {
479
- matches = new RegExp(regex[1], regex[2]).test( data.iExact );
480
- } catch (error) {
487
+ if ( !isRegex ) {
488
+ // force case insensitive search if ignoreCase option set?
489
+ // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; }
490
+ data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] );
491
+ }
492
+ matches = regex.test( data.exact );
493
+ } catch ( error ) {
481
494
  matches = false;
482
495
  }
483
496
  return matches;
@@ -486,46 +499,56 @@ ts.filter = {
486
499
  },
487
500
  // Look for operators >, >=, < or <=
488
501
  operators: function( c, data ) {
489
- if ( /^[<>]=?/.test(data.iFilter) ) {
490
- var cachedValue, result,
502
+ // ignore empty strings... because '' < 10 is true
503
+ if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) {
504
+ var cachedValue, result, txt,
491
505
  table = c.table,
492
506
  index = data.index,
493
507
  parsed = data.parsed[index],
494
- query = ts.formatFloat( data.iFilter.replace(ts.filter.regex.operators, ''), table ),
508
+ query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ),
495
509
  parser = c.parsers[index],
496
510
  savedSearch = query;
497
- // parse filter value in case we're comparing numbers (dates)
498
- if (parsed || parser.type === 'numeric') {
499
- result = ts.filter.parseFilter(c, $.trim('' + data.iFilter.replace(ts.filter.regex.operators, '')), index, parsed, true);
500
- query = ( typeof result === "number" && result !== '' && !isNaN(result) ) ? result : query;
511
+ // parse filter value in case we're comparing numbers ( dates )
512
+ if ( parsed || parser.type === 'numeric' ) {
513
+ txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) );
514
+ result = ts.filter.parseFilter( c, txt, index, true );
515
+ query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query;
501
516
  }
502
-
503
517
  // iExact may be numeric - see issue #149;
504
- // check if cached is defined, because sometimes j goes out of range? (numeric columns)
505
- cachedValue = ( parsed || parser.type === 'numeric' ) && !isNaN(query) && typeof data.cache !== 'undefined' ? data.cache :
506
- isNaN(data.iExact) ? ts.formatFloat( data.iExact.replace(ts.filter.regex.nondigit, ''), table) :
507
- ts.formatFloat( data.iExact, table );
508
-
509
- if ( />/.test(data.iFilter) ) { result = />=/.test(data.iFilter) ? cachedValue >= query : cachedValue > query; }
510
- if ( /</.test(data.iFilter) ) { result = /<=/.test(data.iFilter) ? cachedValue <= query : cachedValue < query; }
518
+ // check if cached is defined, because sometimes j goes out of range? ( numeric columns )
519
+ if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) &&
520
+ typeof data.cache !== 'undefined' ) {
521
+ cachedValue = data.cache;
522
+ } else {
523
+ txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
524
+ cachedValue = ts.formatFloat( txt, table );
525
+ }
526
+ if ( />/.test( data.iFilter ) ) {
527
+ result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query;
528
+ } else if ( /</.test( data.iFilter ) ) {
529
+ result = /<=/.test( data.iFilter ) ? cachedValue <= query : cachedValue < query;
530
+ }
511
531
  // keep showing all rows if nothing follows the operator
512
- if ( !result && savedSearch === '' ) { result = true; }
532
+ if ( !result && savedSearch === '' ) {
533
+ result = true;
534
+ }
513
535
  return result;
514
536
  }
515
537
  return null;
516
538
  },
517
539
  // Look for a not match
518
540
  notMatch: function( c, data ) {
519
- if ( /^\!/.test(data.iFilter) ) {
541
+ if ( /^\!/.test( data.iFilter ) ) {
520
542
  var indx,
521
- filter = ts.filter.parseFilter(c, data.iFilter.replace('!', ''), data.index, data.parsed[data.index]) || '';
522
- if (ts.filter.regex.exact.test(filter)) {
543
+ txt = data.iFilter.replace( '!', '' ),
544
+ filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
545
+ if ( ts.filter.regex.exact.test( filter ) ) {
523
546
  // look for exact not matches - see #628
524
- filter = filter.replace(ts.filter.regex.exact, '');
525
- return filter === '' ? true : $.trim(filter) !== data.iExact;
547
+ filter = filter.replace( ts.filter.regex.exact, '' );
548
+ return filter === '' ? true : $.trim( filter ) !== data.iExact;
526
549
  } else {
527
- indx = data.iExact.search( $.trim(filter) );
528
- return filter === '' ? true : !(c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 0);
550
+ indx = data.iExact.search( $.trim( filter ) );
551
+ return filter === '' ? true : !( c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 0 );
529
552
  }
530
553
  }
531
554
  return null;
@@ -533,84 +556,101 @@ ts.filter = {
533
556
  // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
534
557
  exact: function( c, data ) {
535
558
  /*jshint eqeqeq:false */
536
- if (ts.filter.regex.exact.test(data.iFilter)) {
537
- var filter = ts.filter.parseFilter(c, data.iFilter.replace(ts.filter.regex.exact, ''), data.index, data.parsed[data.index]) || '';
538
- return data.anyMatch ? $.inArray(filter, data.rowArray) >= 0 : filter == data.iExact;
559
+ if ( ts.filter.regex.exact.test( data.iFilter ) ) {
560
+ var txt = data.iFilter.replace( ts.filter.regex.exact, '' ),
561
+ filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
562
+ return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact;
539
563
  }
540
564
  return null;
541
565
  },
542
- // Look for an AND or && operator (logical and)
566
+ // Look for an AND or && operator ( logical and )
543
567
  and : function( c, data ) {
544
- if ( ts.filter.regex.andTest.test(data.filter) ) {
568
+ if ( ts.filter.regex.andTest.test( data.filter ) ) {
545
569
  var index = data.index,
546
570
  parsed = data.parsed[index],
547
571
  query = data.iFilter.split( ts.filter.regex.andSplit ),
548
- result = data.iExact.search( $.trim( ts.filter.parseFilter(c, query[0], index, parsed) ) ) >= 0,
572
+ result = data.iExact.search( $.trim( ts.filter.parseFilter( c, query[0], index, parsed ) ) ) >= 0,
549
573
  indx = query.length - 1;
550
- while (result && indx) {
551
- result = result && data.iExact.search( $.trim( ts.filter.parseFilter(c, query[indx], index, parsed) ) ) >= 0;
574
+ while ( result && indx ) {
575
+ result = result &&
576
+ data.iExact.search( $.trim( ts.filter.parseFilter( c, query[indx], index, parsed ) ) ) >= 0;
552
577
  indx--;
553
578
  }
554
579
  return result;
555
580
  }
556
581
  return null;
557
582
  },
558
- // Look for a range (using " to " or " - ") - see issue #166; thanks matzhu!
583
+ // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
559
584
  range : function( c, data ) {
560
- if ( ts.filter.regex.toTest.test(data.iFilter) ) {
561
- var result, tmp,
585
+ if ( ts.filter.regex.toTest.test( data.iFilter ) ) {
586
+ var result, tmp, range1, range2,
562
587
  table = c.table,
563
588
  index = data.index,
564
589
  parsed = data.parsed[index],
565
590
  // make sure the dash is for a range and not indicating a negative number
566
- query = data.iFilter.split( ts.filter.regex.toSplit ),
567
- range1 = ts.formatFloat( ts.filter.parseFilter(c, query[0].replace(ts.filter.regex.nondigit, '') || '', index, parsed), table ),
568
- range2 = ts.formatFloat( ts.filter.parseFilter(c, query[1].replace(ts.filter.regex.nondigit, '') || '', index, parsed), table );
569
- // parse filter value in case we're comparing numbers (dates)
570
- if (parsed || c.parsers[index].type === 'numeric') {
571
- result = c.parsers[index].format('' + query[0], table, c.$headers.eq(index), index);
572
- range1 = (result !== '' && !isNaN(result)) ? result : range1;
573
- result = c.parsers[index].format('' + query[1], table, c.$headers.eq(index), index);
574
- range2 = (result !== '' && !isNaN(result)) ? result : range2;
591
+ query = data.iFilter.split( ts.filter.regex.toSplit );
592
+
593
+ tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || '';
594
+ range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
595
+ tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || '';
596
+ range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
597
+ // parse filter value in case we're comparing numbers ( dates )
598
+ if ( parsed || c.parsers[index].type === 'numeric' ) {
599
+ result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index );
600
+ range1 = ( result !== '' && !isNaN( result ) ) ? result : range1;
601
+ result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index );
602
+ range2 = ( result !== '' && !isNaN( result ) ) ? result : range2;
575
603
  }
576
- result = ( parsed || c.parsers[index].type === 'numeric' ) && !isNaN(range1) && !isNaN(range2) ? data.cache :
577
- isNaN(data.iExact) ? ts.formatFloat( data.iExact.replace(ts.filter.regex.nondigit, ''), table) :
578
- ts.formatFloat( data.iExact, table );
579
- if (range1 > range2) { tmp = range1; range1 = range2; range2 = tmp; } // swap
580
- return (result >= range1 && result <= range2) || (range1 === '' || range2 === '');
604
+ if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) {
605
+ result = data.cache;
606
+ } else {
607
+ tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
608
+ result = ts.formatFloat( tmp, table );
609
+ }
610
+ if ( range1 > range2 ) {
611
+ tmp = range1; range1 = range2; range2 = tmp; // swap
612
+ }
613
+ return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' );
581
614
  }
582
615
  return null;
583
616
  },
584
617
  // Look for wild card: ? = single, * = multiple, or | = logical OR
585
618
  wild : function( c, data ) {
586
- if ( /[\?\*\|]/.test(data.iFilter) || ts.filter.regex.orReplace.test(data.filter) ) {
619
+ if ( /[\?\*\|]/.test( data.iFilter ) || ts.filter.regex.orReplace.test( data.filter ) ) {
587
620
  var index = data.index,
588
- parsed = data.parsed[index],
589
- query = ts.filter.parseFilter(c, data.iFilter.replace(ts.filter.regex.orReplace, "|"), index, parsed) || '';
590
- // look for an exact match with the "or" unless the "filter-match" class is found
591
- if (!c.$headerIndexed[index].hasClass('filter-match') && /\|/.test(query)) {
621
+ parsed = data.parsed[ index ],
622
+ txt = data.iFilter.replace( ts.filter.regex.orReplace, '|' ),
623
+ query = ts.filter.parseFilter( c, txt, index, parsed ) || '';
624
+ // look for an exact match with the 'or' unless the 'filter-match' class is found
625
+ if ( !c.$headerIndexed[ index ].hasClass( 'filter-match' ) && /\|/.test( query ) ) {
592
626
  // show all results while using filter match. Fixes #727
593
- if (query[ query.length - 1 ] === '|') { query += '*'; }
594
- query = data.anyMatch && $.isArray(data.rowArray) ? '(' + query + ')' : '^(' + query + ')$';
627
+ if ( query[ query.length - 1 ] === '|' ) {
628
+ query += '*';
629
+ }
630
+ query = data.anyMatch && $.isArray( data.rowArray ) ?
631
+ '(' + query + ')' :
632
+ '^(' + query + ')$';
595
633
  }
596
634
  // parsing the filter may not work properly when using wildcards =/
597
- return new RegExp( query.replace(/\?/g, '\\S{1}').replace(/\*/g, '\\S*') ).test(data.iExact);
635
+ return new RegExp( query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ) )
636
+ .test( data.iExact );
598
637
  }
599
638
  return null;
600
639
  },
601
- // fuzzy text search; modified from https://github.com/mattyork/fuzzy (MIT license)
640
+ // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license )
602
641
  fuzzy: function( c, data ) {
603
- if ( /^~/.test(data.iFilter) ) {
642
+ if ( /^~/.test( data.iFilter ) ) {
604
643
  var indx,
605
644
  patternIndx = 0,
606
645
  len = data.iExact.length,
607
- pattern = ts.filter.parseFilter(c, data.iFilter.slice(1), data.index, data.parsed[data.index]) || '';
608
- for (indx = 0; indx < len; indx++) {
609
- if (data.iExact[indx] === pattern[patternIndx]) {
646
+ txt = data.iFilter.slice( 1 ),
647
+ pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
648
+ for ( indx = 0; indx < len; indx++ ) {
649
+ if ( data.iExact[ indx ] === pattern[ patternIndx ] ) {
610
650
  patternIndx += 1;
611
651
  }
612
652
  }
613
- if (patternIndx === pattern.length) {
653
+ if ( patternIndx === pattern.length ) {
614
654
  return true;
615
655
  }
616
656
  return false;
@@ -618,17 +658,17 @@ ts.filter = {
618
658
  return null;
619
659
  }
620
660
  },
621
- init: function(table, c, wo) {
661
+ init: function( table, c, wo ) {
622
662
  // filter language options
623
- ts.language = $.extend(true, {}, {
663
+ ts.language = $.extend( true, {}, {
624
664
  to : 'to',
625
665
  or : 'or',
626
666
  and : 'and'
627
- }, ts.language);
667
+ }, ts.language );
628
668
 
629
669
  var options, string, txt, $header, column, filters, val, fxn, noSelect,
630
670
  regex = ts.filter.regex;
631
- c.$table.addClass('hasFilters');
671
+ c.$table.addClass( 'hasFilters' );
632
672
 
633
673
  // define timers so using clearTimeout won't cause an undefined error
634
674
  wo.searchTimer = null;
@@ -638,512 +678,566 @@ ts.filter = {
638
678
  wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]';
639
679
  wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]';
640
680
 
641
- txt = '\\{' + ts.filter.regex.query + '\\}';
681
+ val = '\\{' + ts.filter.regex.query + '\\}';
642
682
  $.extend( regex, {
643
- child : new RegExp(c.cssChildRow),
644
- filtered : new RegExp(wo.filter_filteredRow),
645
- alreadyFiltered : new RegExp('(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i'),
646
- toTest : new RegExp('\\s+(-|' + ts.language.to + ')\\s+', 'i'),
647
- toSplit : new RegExp('(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi'),
648
- andTest : new RegExp('\\s+(' + ts.language.and + '|&&)\\s+', 'i'),
649
- andSplit : new RegExp('(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi'),
650
- orReplace : new RegExp('\\s+(' + ts.language.or + ')\\s+', 'gi'),
651
- iQuery : new RegExp(txt, 'i'),
652
- igQuery : new RegExp(txt, 'ig')
683
+ child : new RegExp( c.cssChildRow ),
684
+ filtered : new RegExp( wo.filter_filteredRow ),
685
+ alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ),
686
+ toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ),
687
+ toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ),
688
+ andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ),
689
+ andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ),
690
+ orReplace : new RegExp( '\\s+(' + ts.language.or + ')\\s+', 'gi' ),
691
+ iQuery : new RegExp( val, 'i' ),
692
+ igQuery : new RegExp( val, 'ig' )
653
693
  });
654
694
 
655
- // don't build filter row if columnFilters is false or all columns are set to "filter-false" - issue #156
656
- if (wo.filter_columnFilters !== false && c.$headers.filter('.filter-false, .parser-false').length !== c.$headers.length) {
695
+ // don't build filter row if columnFilters is false or all columns are set to 'filter-false'
696
+ // see issue #156
697
+ val = c.$headers.filter( '.filter-false, .parser-false' ).length;
698
+ if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) {
657
699
  // build filter row
658
- ts.filter.buildRow(table, c, wo);
659
- }
660
-
661
- txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '.split(' ').join(c.namespace + 'filter ');
662
- c.$table.bind( txt, function(event, filter) {
663
- val = (wo.filter_hideEmpty && $.isEmptyObject(c.cache) && !(c.delayInit && event.type === 'appendCache'));
664
- // hide filter row using the "filtered" class name
665
- c.$table.find('.' + tscss.filterRow).toggleClass(wo.filter_filteredRow, val ); // fixes #450
666
- if ( !/(search|filter)/.test(event.type) ) {
700
+ ts.filter.buildRow( table, c, wo );
701
+ }
702
+
703
+ txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '
704
+ .split( ' ' ).join( c.namespace + 'filter ' );
705
+ c.$table.bind( txt, function( event, filter ) {
706
+ val = wo.filter_hideEmpty &&
707
+ $.isEmptyObject( c.cache ) &&
708
+ !( c.delayInit && event.type === 'appendCache' );
709
+ // hide filter row using the 'filtered' class name
710
+ c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450
711
+ if ( !/(search|filter)/.test( event.type ) ) {
667
712
  event.stopPropagation();
668
- ts.filter.buildDefault(table, true);
713
+ ts.filter.buildDefault( table, true );
669
714
  }
670
- if (event.type === 'filterReset') {
671
- c.$table.find('.' + tscss.filter).add(wo.filter_$externalFilters).val('');
672
- ts.filter.searching(table, []);
673
- } else if (event.type === 'filterEnd') {
674
- ts.filter.buildDefault(table, true);
715
+ if ( event.type === 'filterReset' ) {
716
+ c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' );
717
+ ts.filter.searching( table, [] );
718
+ } else if ( event.type === 'filterEnd' ) {
719
+ ts.filter.buildDefault( table, true );
675
720
  } else {
676
- // send false argument to force a new search; otherwise if the filter hasn't changed, it will return
677
- filter = event.type === 'search' ? filter : event.type === 'updateComplete' ? c.$table.data('lastSearch') : '';
678
- if (/(update|add)/.test(event.type) && event.type !== "updateComplete") {
721
+ // send false argument to force a new search; otherwise if the filter hasn't changed,
722
+ // it will return
723
+ filter = event.type === 'search' ? filter :
724
+ event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : '';
725
+ if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) {
679
726
  // force a new search since content has changed
680
727
  c.lastCombinedFilter = null;
681
728
  c.lastSearch = [];
682
729
  }
683
- // pass true (skipFirst) to prevent the tablesorter.setFilters function from skipping the first input
684
- // ensures all inputs are updated when a search is triggered on the table $('table').trigger('search', [...]);
685
- ts.filter.searching(table, filter, true);
730
+ // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first
731
+ // input ensures all inputs are updated when a search is triggered on the table
732
+ // $( 'table' ).trigger( 'search', [...] );
733
+ ts.filter.searching( table, filter, true );
686
734
  }
687
735
  return false;
688
736
  });
689
737
 
690
738
  // reset button/link
691
- if (wo.filter_reset) {
692
- if (wo.filter_reset instanceof $) {
739
+ if ( wo.filter_reset ) {
740
+ if ( wo.filter_reset instanceof $ ) {
693
741
  // reset contains a jQuery object, bind to it
694
- wo.filter_reset.click(function(){
695
- c.$table.trigger('filterReset');
742
+ wo.filter_reset.click( function() {
743
+ c.$table.trigger( 'filterReset' );
696
744
  });
697
- } else if ($(wo.filter_reset).length) {
745
+ } else if ( $( wo.filter_reset ).length ) {
698
746
  // reset is a jQuery selector, use event delegation
699
- $(document)
700
- .undelegate(wo.filter_reset, 'click.tsfilter')
701
- .delegate(wo.filter_reset, 'click.tsfilter', function() {
702
- // trigger a reset event, so other functions (filter_formatter) know when to reset
703
- c.$table.trigger('filterReset');
704
- });
747
+ $( document )
748
+ .undelegate( wo.filter_reset, 'click.tsfilter' )
749
+ .delegate( wo.filter_reset, 'click.tsfilter', function() {
750
+ // trigger a reset event, so other functions ( filter_formatter ) know when to reset
751
+ c.$table.trigger( 'filterReset' );
752
+ });
705
753
  }
706
754
  }
707
- if (wo.filter_functions) {
708
- for (column = 0; column < c.columns; column++) {
755
+ if ( wo.filter_functions ) {
756
+ for ( column = 0; column < c.columns; column++ ) {
709
757
  fxn = ts.getColumnData( table, wo.filter_functions, column );
710
- if (fxn) {
711
- // remove "filter-select" from header otherwise the options added here are replaced with all options
712
- $header = c.$headerIndexed[column].removeClass('filter-select');
713
- // don't build select if "filter-false" or "parser-false" set
714
- noSelect = !($header.hasClass('filter-false') || $header.hasClass('parser-false'));
758
+ if ( fxn ) {
759
+ // remove 'filter-select' from header otherwise the options added here are replaced with
760
+ // all options
761
+ $header = c.$headerIndexed[ column ].removeClass( 'filter-select' );
762
+ // don't build select if 'filter-false' or 'parser-false' set
763
+ noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
715
764
  options = '';
716
765
  if ( fxn === true && noSelect ) {
717
- ts.filter.buildSelect(table, column);
766
+ ts.filter.buildSelect( table, column );
718
767
  } else if ( typeof fxn === 'object' && noSelect ) {
719
768
  // add custom drop down list
720
- for (string in fxn) {
721
- if (typeof string === 'string') {
769
+ for ( string in fxn ) {
770
+ if ( typeof string === 'string' ) {
722
771
  options += options === '' ?
723
- '<option value="">' + ($header.data('placeholder') || $header.attr('data-placeholder') || wo.filter_placeholder.select || '') + '</option>' : '';
772
+ '<option value="">' +
773
+ ( $header.data( 'placeholder' ) ||
774
+ $header.attr( 'data-placeholder' ) ||
775
+ wo.filter_placeholder.select ||
776
+ ''
777
+ ) +
778
+ '</option>' : '';
724
779
  val = string;
725
780
  txt = string;
726
- if (string.indexOf(wo.filter_selectSourceSeparator) >= 0) {
727
- val = string.split(wo.filter_selectSourceSeparator);
781
+ if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
782
+ val = string.split( wo.filter_selectSourceSeparator );
728
783
  txt = val[1];
729
784
  val = val[0];
730
785
  }
731
- options += '<option ' + (txt === val ? '' : 'data-function-name="' + string + '" ') + 'value="' + val + '">' + txt + '</option>';
786
+ options += '<option ' +
787
+ ( txt === val ? '' : 'data-function-name="' + string + '" ' ) +
788
+ 'value="' + val + '">' + txt + '</option>';
732
789
  }
733
790
  }
734
- c.$table.find('thead').find('select.' + tscss.filter + '[data-column="' + column + '"]').append(options);
791
+ c.$table
792
+ .find( 'thead' )
793
+ .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' )
794
+ .append( options );
735
795
  txt = wo.filter_selectSource;
736
- fxn = $.isFunction(txt) ? true : ts.getColumnData( table, txt, column );
737
- if (fxn) {
796
+ fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column );
797
+ if ( fxn ) {
738
798
  // updating so the extra options are appended
739
- ts.filter.buildSelect(c.table, column, '', true, $header.hasClass(wo.filter_onlyAvail));
799
+ ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) );
740
800
  }
741
801
  }
742
802
  }
743
803
  }
744
804
  }
745
- // not really updating, but if the column has both the "filter-select" class & filter_functions set to true,
746
- // it would append the same options twice.
747
- ts.filter.buildDefault(table, true);
805
+ // not really updating, but if the column has both the 'filter-select' class &
806
+ // filter_functions set to true, it would append the same options twice.
807
+ ts.filter.buildDefault( table, true );
748
808
 
749
- ts.filter.bindSearch( table, c.$table.find('.' + tscss.filter), true );
750
- if (wo.filter_external) {
809
+ ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true );
810
+ if ( wo.filter_external ) {
751
811
  ts.filter.bindSearch( table, wo.filter_external );
752
812
  }
753
813
 
754
- if (wo.filter_hideFilters) {
755
- ts.filter.hideFilters(table, c);
814
+ if ( wo.filter_hideFilters ) {
815
+ ts.filter.hideFilters( table, c );
756
816
  }
757
817
 
758
818
  // show processing icon
759
- if (c.showProcessing) {
819
+ if ( c.showProcessing ) {
820
+ txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' );
760
821
  c.$table
761
- .unbind( ('filterStart filterEnd '.split(' ').join(c.namespace + 'filter ')).replace(/\s+/g, ' ') )
762
- .bind( 'filterStart filterEnd '.split(' ').join(c.namespace + 'filter '), function(event, columns) {
822
+ .unbind( txt.replace( /\s+/g, ' ' ) )
823
+ .bind( txt, function( event, columns ) {
763
824
  // only add processing to certain columns to all columns
764
- $header = (columns) ? c.$table.find('.' + tscss.header).filter('[data-column]').filter(function() {
765
- return columns[$(this).data('column')] !== '';
766
- }) : '';
767
- ts.isProcessing(table, event.type === 'filterStart', columns ? $header : '');
825
+ $header = ( columns ) ?
826
+ c.$table
827
+ .find( '.' + tscss.header )
828
+ .filter( '[data-column]' )
829
+ .filter( function() {
830
+ return columns[ $( this ).data( 'column' ) ] !== '';
831
+ }) : '';
832
+ ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' );
768
833
  });
769
834
  }
770
835
 
771
- // set filtered rows count (intially unfiltered)
836
+ // set filtered rows count ( intially unfiltered )
772
837
  c.filteredRows = c.totalRows;
773
838
 
774
839
  // add default values
840
+ txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' );
775
841
  c.$table
776
- .unbind( ('tablesorter-initialized pagerBeforeInitialized '.split(' ').join(c.namespace + 'filter ')).replace(/\s+/g, ' ') )
777
- .bind( 'tablesorter-initialized pagerBeforeInitialized '.split(' ').join(c.namespace + 'filter '), function() {
778
- // redefine "wo" as it does not update properly inside this callback
842
+ .unbind( txt.replace( /\s+/g, ' ' ) )
843
+ .bind( txt, function() {
844
+ // redefine 'wo' as it does not update properly inside this callback
779
845
  var wo = this.config.widgetOptions;
780
- filters = ts.filter.setDefaults(table, c, wo) || [];
781
- if (filters.length) {
846
+ filters = ts.filter.setDefaults( table, c, wo ) || [];
847
+ if ( filters.length ) {
782
848
  // prevent delayInit from triggering a cache build if filters are empty
783
- if ( !(c.delayInit && filters.join('') === '') ) {
784
- ts.setFilters(table, filters, true);
849
+ if ( !( c.delayInit && filters.join( '' ) === '' ) ) {
850
+ ts.setFilters( table, filters, true );
785
851
  }
786
852
  }
787
- c.$table.trigger('filterFomatterUpdate');
853
+ c.$table.trigger( 'filterFomatterUpdate' );
788
854
  // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers
789
- setTimeout(function(){
790
- if (!wo.filter_initialized) {
791
- ts.filter.filterInitComplete(c);
855
+ setTimeout( function() {
856
+ if ( !wo.filter_initialized ) {
857
+ ts.filter.filterInitComplete( c );
792
858
  }
793
- }, 100);
859
+ }, 100 );
794
860
  });
795
861
  // if filter widget is added after pager has initialized; then set filter init flag
796
- if (c.pager && c.pager.initialized && !wo.filter_initialized) {
797
- c.$table.trigger('filterFomatterUpdate');
798
- setTimeout(function(){
799
- ts.filter.filterInitComplete(c);
800
- }, 100);
862
+ if ( c.pager && c.pager.initialized && !wo.filter_initialized ) {
863
+ c.$table.trigger( 'filterFomatterUpdate' );
864
+ setTimeout( function() {
865
+ ts.filter.filterInitComplete( c );
866
+ }, 100 );
801
867
  }
802
868
  },
803
- // $cell parameter, but not the config, is passed to the
804
- // filter_formatters, so we have to work with it instead
805
- formatterUpdated: function($cell, column) {
806
- var wo = $cell.closest('table')[0].config.widgetOptions;
807
- if (!wo.filter_initialized) {
869
+ // $cell parameter, but not the config, is passed to the filter_formatters,
870
+ // so we have to work with it instead
871
+ formatterUpdated: function( $cell, column ) {
872
+ var wo = $cell.closest( 'table' )[0].config.widgetOptions;
873
+ if ( !wo.filter_initialized ) {
808
874
  // add updates by column since this function
809
875
  // may be called numerous times before initialization
810
- wo.filter_formatterInit[column] = 1;
876
+ wo.filter_formatterInit[ column ] = 1;
811
877
  }
812
878
  },
813
- filterInitComplete: function(c){
879
+ filterInitComplete: function( c ) {
814
880
  var indx, len,
815
881
  wo = c.widgetOptions,
816
882
  count = 0,
817
- completed = function(){
883
+ completed = function() {
818
884
  wo.filter_initialized = true;
819
- c.$table.trigger('filterInit', c);
820
- ts.filter.findRows(c.table, c.$table.data('lastSearch') || []);
885
+ c.$table.trigger( 'filterInit', c );
886
+ ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] );
821
887
  };
822
888
  if ( $.isEmptyObject( wo.filter_formatter ) ) {
823
889
  completed();
824
890
  } else {
825
891
  len = wo.filter_formatterInit.length;
826
- for (indx = 0; indx < len; indx++) {
827
- if (wo.filter_formatterInit[indx] === 1) {
892
+ for ( indx = 0; indx < len; indx++ ) {
893
+ if ( wo.filter_formatterInit[ indx ] === 1 ) {
828
894
  count++;
829
895
  }
830
896
  }
831
- clearTimeout(wo.filter_initTimer);
832
- if (!wo.filter_initialized && count === wo.filter_formatterCount) {
897
+ clearTimeout( wo.filter_initTimer );
898
+ if ( !wo.filter_initialized && count === wo.filter_formatterCount ) {
833
899
  // filter widget initialized
834
900
  completed();
835
- } else if (!wo.filter_initialized) {
901
+ } else if ( !wo.filter_initialized ) {
836
902
  // fall back in case a filter_formatter doesn't call
837
- // $.tablesorter.filter.formatterUpdated($cell, column), and the count is off
838
- wo.filter_initTimer = setTimeout(function(){
903
+ // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off
904
+ wo.filter_initTimer = setTimeout( function() {
839
905
  completed();
840
- }, 500);
906
+ }, 500 );
841
907
  }
842
908
  }
843
909
  },
844
910
 
845
- setDefaults: function(table, c, wo) {
911
+ setDefaults: function( table, c, wo ) {
846
912
  var isArray, saved, indx, col, $filters,
847
- // get current (default) filters
848
- filters = ts.getFilters(table) || [];
849
- if (wo.filter_saveFilters && ts.storage) {
913
+ // get current ( default ) filters
914
+ filters = ts.getFilters( table ) || [];
915
+ if ( wo.filter_saveFilters && ts.storage ) {
850
916
  saved = ts.storage( table, 'tablesorter-filters' ) || [];
851
- isArray = $.isArray(saved);
917
+ isArray = $.isArray( saved );
852
918
  // make sure we're not just getting an empty array
853
- if ( !(isArray && saved.join('') === '' || !isArray) ) { filters = saved; }
919
+ if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) {
920
+ filters = saved;
921
+ }
854
922
  }
855
923
  // if no filters saved, then check default settings
856
- if (filters.join('') === '') {
924
+ if ( filters.join( '' ) === '' ) {
857
925
  // allow adding default setting to external filters
858
- $filters = c.$headers.add( wo.filter_$externalFilters ).filter('[' + wo.filter_defaultAttrib + ']');
859
- for (indx = 0; indx <= c.columns; indx++) {
860
- // include data-column="all" external filters
926
+ $filters = c.$headers.add( wo.filter_$externalFilters )
927
+ .filter( '[' + wo.filter_defaultAttrib + ']' );
928
+ for ( indx = 0; indx <= c.columns; indx++ ) {
929
+ // include data-column='all' external filters
861
930
  col = indx === c.columns ? 'all' : indx;
862
- filters[indx] = $filters.filter('[data-column="' + col + '"]').attr(wo.filter_defaultAttrib) || filters[indx] || '';
931
+ filters[indx] = $filters
932
+ .filter( '[data-column="' + col + '"]' )
933
+ .attr( wo.filter_defaultAttrib ) || filters[indx] || '';
863
934
  }
864
935
  }
865
- c.$table.data('lastSearch', filters);
936
+ c.$table.data( 'lastSearch', filters );
866
937
  return filters;
867
938
  },
868
- parseFilter: function(c, filter, column, parsed, forceParse){
869
- return forceParse || parsed ?
870
- c.parsers[column].format( filter, c.table, [], column ) :
871
- filter;
939
+ parseFilter: function( c, filter, column, parsed ) {
940
+ return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter;
872
941
  },
873
- buildRow: function(table, c, wo) {
874
- var col, column, $header, buildSelect, disabled, name, ffxn,
942
+ buildRow: function( table, c, wo ) {
943
+ var col, column, $header, buildSelect, disabled, name, ffxn, tmp,
875
944
  // c.columns defined in computeThIndexes()
945
+ cellFilter = wo.filter_cellFilter,
876
946
  columns = c.columns,
877
- arry = $.isArray(wo.filter_cellFilter),
947
+ arry = $.isArray( cellFilter ),
878
948
  buildFilter = '<tr role="row" class="' + tscss.filterRow + ' ' + c.cssIgnoreRow + '">';
879
- for (column = 0; column < columns; column++) {
880
- if (arry) {
881
- buildFilter += '<td' + ( wo.filter_cellFilter[column] ? ' class="' + wo.filter_cellFilter[column] + '"' : '' ) + '></td>';
949
+ for ( column = 0; column < columns; column++ ) {
950
+ buildFilter += '<td';
951
+ if ( arry ) {
952
+ buildFilter += ( cellFilter[ column ] ? ' class="' + cellFilter[ column ] + '"' : '' );
882
953
  } else {
883
- buildFilter += '<td' + ( wo.filter_cellFilter !== '' ? ' class="' + wo.filter_cellFilter + '"' : '' ) + '></td>';
954
+ buildFilter += ( cellFilter !== '' ? ' class="' + cellFilter + '"' : '' );
884
955
  }
956
+ buildFilter += '></td>';
885
957
  }
886
- c.$filters = $(buildFilter += '</tr>').appendTo( c.$table.children('thead').eq(0) ).find('td');
958
+ c.$filters = $( buildFilter += '</tr>' )
959
+ .appendTo( c.$table.children( 'thead' ).eq( 0 ) )
960
+ .find( 'td' );
887
961
  // build each filter input
888
- for (column = 0; column < columns; column++) {
962
+ for ( column = 0; column < columns; column++ ) {
889
963
  disabled = false;
890
964
  // assuming last cell of a column is the main column
891
- $header = c.$headerIndexed[column];
965
+ $header = c.$headerIndexed[ column ];
892
966
  ffxn = ts.getColumnData( table, wo.filter_functions, column );
893
- buildSelect = (wo.filter_functions && ffxn && typeof ffxn !== "function" ) ||
894
- $header.hasClass('filter-select');
967
+ buildSelect = ( wo.filter_functions && ffxn && typeof ffxn !== 'function' ) ||
968
+ $header.hasClass( 'filter-select' );
895
969
  // get data from jQuery data, metadata, headers option or header class name
896
970
  col = ts.getColumnData( table, c.headers, column );
897
- disabled = ts.getData($header[0], col, 'filter') === 'false' || ts.getData($header[0], col, 'parser') === 'false';
971
+ disabled = ts.getData( $header[0], col, 'filter' ) === 'false' ||
972
+ ts.getData( $header[0], col, 'parser' ) === 'false';
898
973
 
899
- if (buildSelect) {
900
- buildFilter = $('<select>').appendTo( c.$filters.eq(column) );
974
+ if ( buildSelect ) {
975
+ buildFilter = $( '<select>' ).appendTo( c.$filters.eq( column ) );
901
976
  } else {
902
977
  ffxn = ts.getColumnData( table, wo.filter_formatter, column );
903
- if (ffxn) {
978
+ if ( ffxn ) {
904
979
  wo.filter_formatterCount++;
905
- buildFilter = ffxn( c.$filters.eq(column), column );
980
+ buildFilter = ffxn( c.$filters.eq( column ), column );
906
981
  // no element returned, so lets go find it
907
- if (buildFilter && buildFilter.length === 0) {
908
- buildFilter = c.$filters.eq(column).children('input');
982
+ if ( buildFilter && buildFilter.length === 0 ) {
983
+ buildFilter = c.$filters.eq( column ).children( 'input' );
909
984
  }
910
985
  // element not in DOM, so lets attach it
911
- if ( buildFilter && (buildFilter.parent().length === 0 ||
912
- (buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column])) ) {
913
- c.$filters.eq(column).append(buildFilter);
986
+ if ( buildFilter && ( buildFilter.parent().length === 0 ||
987
+ ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) {
988
+ c.$filters.eq( column ).append( buildFilter );
914
989
  }
915
990
  } else {
916
- buildFilter = $('<input type="search">').appendTo( c.$filters.eq(column) );
991
+ buildFilter = $( '<input type="search">' ).appendTo( c.$filters.eq( column ) );
917
992
  }
918
- if (buildFilter) {
919
- buildFilter.attr('placeholder', $header.data('placeholder') || $header.attr('data-placeholder') || wo.filter_placeholder.search || '');
993
+ if ( buildFilter ) {
994
+ tmp = $header.data( 'placeholder' ) ||
995
+ $header.attr( 'data-placeholder' ) ||
996
+ wo.filter_placeholder.search || '';
997
+ buildFilter.attr( 'placeholder', tmp );
920
998
  }
921
999
  }
922
- if (buildFilter) {
1000
+ if ( buildFilter ) {
923
1001
  // add filter class name
924
- name = ( $.isArray(wo.filter_cssFilter) ?
925
- (typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '') :
1002
+ name = ( $.isArray( wo.filter_cssFilter ) ?
1003
+ ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) :
926
1004
  wo.filter_cssFilter ) || '';
927
- buildFilter.addClass( tscss.filter + ' ' + name ).attr('data-column', column);
928
- if (disabled) {
929
- buildFilter.attr('placeholder', '').addClass(tscss.filterDisabled)[0].disabled = true; // disabled!
1005
+ buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column );
1006
+ if ( disabled ) {
1007
+ buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true;
930
1008
  }
931
1009
  }
932
1010
  }
933
1011
  },
934
- bindSearch: function(table, $el, internal) {
935
- table = $(table)[0];
936
- $el = $($el); // allow passing a selector string
937
- if (!$el.length) { return; }
938
- var c = table.config,
1012
+ bindSearch: function( table, $el, internal ) {
1013
+ table = $( table )[0];
1014
+ $el = $( $el ); // allow passing a selector string
1015
+ if ( !$el.length ) { return; }
1016
+ var tmp,
1017
+ c = table.config,
939
1018
  wo = c.widgetOptions,
1019
+ namespace = c.namespace + 'filter',
940
1020
  $ext = wo.filter_$externalFilters;
941
- if (internal !== true) {
1021
+ if ( internal !== true ) {
942
1022
  // save anyMatch element
943
- wo.filter_$anyMatch = $el.filter(wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector);
944
- if ($ext && $ext.length) {
1023
+ tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector;
1024
+ wo.filter_$anyMatch = $el.filter( tmp );
1025
+ if ( $ext && $ext.length ) {
945
1026
  wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el );
946
1027
  } else {
947
1028
  wo.filter_$externalFilters = $el;
948
1029
  }
949
- // update values (external filters added after table initialization)
950
- ts.setFilters(table, c.$table.data('lastSearch') || [], internal === false);
1030
+ // update values ( external filters added after table initialization )
1031
+ ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false );
951
1032
  }
1033
+ // unbind events
1034
+ tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) );
952
1035
  $el
953
- // use data attribute instead of jQuery data since the head is cloned without including the data/binding
954
- .attr('data-lastSearchTime', new Date().getTime())
955
- .unbind( ('keypress keyup search change '.split(' ').join(c.namespace + 'filter ')).replace(/\s+/g, ' ') )
1036
+ // use data attribute instead of jQuery data since the head is cloned without including
1037
+ // the data/binding
1038
+ .attr( 'data-lastSearchTime', new Date().getTime() )
1039
+ .unbind( tmp.replace( /\s+/g, ' ' ) )
956
1040
  // include change for select - fixes #473
957
- .bind('keyup' + c.namespace + 'filter', function(event) {
958
- $(this).attr('data-lastSearchTime', new Date().getTime());
1041
+ .bind( 'keyup' + namespace, function( event ) {
1042
+ $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
959
1043
  // emulate what webkit does.... escape clears the filter
960
- if (event.which === 27) {
1044
+ if ( event.which === 27 ) {
961
1045
  this.value = '';
962
1046
  // live search
963
1047
  } else if ( wo.filter_liveSearch === false ) {
964
1048
  return;
965
- // don't return if the search value is empty (all rows need to be revealed)
1049
+ // don't return if the search value is empty ( all rows need to be revealed )
966
1050
  } else if ( this.value !== '' && (
967
1051
  // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace
968
1052
  ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) ||
969
1053
  // let return & backspace continue on, but ignore arrows & non-valid characters
970
- ( event.which !== 13 && event.which !== 8 && ( event.which < 32 || (event.which >= 37 && event.which <= 40) ) ) ) ) {
1054
+ ( event.which !== 13 && event.which !== 8 &&
1055
+ ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) {
971
1056
  return;
972
1057
  }
973
1058
  // change event = no delay; last true flag tells getFilters to skip newest timed input
974
1059
  ts.filter.searching( table, true, true );
975
1060
  })
976
- .bind( 'search change keypress '.split(' ').join(c.namespace + 'filter '), function(event){
977
- var column = $(this).data('column');
978
- // don't allow "change" event to process if the input value is the same - fixes #685
979
- if (event.which === 13 || event.type === 'search' || event.type === 'change' && this.value !== c.lastSearch[column]) {
1061
+ .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) {
1062
+ var column = $( this ).data( 'column' );
1063
+ // don't allow 'change' event to process if the input value is the same - fixes #685
1064
+ if ( event.which === 13 || event.type === 'search' ||
1065
+ event.type === 'change' && this.value !== c.lastSearch[column] ) {
980
1066
  event.preventDefault();
981
1067
  // init search with no delay
982
- $(this).attr('data-lastSearchTime', new Date().getTime());
1068
+ $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
983
1069
  ts.filter.searching( table, false, true );
984
1070
  }
985
1071
  });
986
1072
  },
987
- searching: function(table, filter, skipFirst) {
1073
+ searching: function( table, filter, skipFirst ) {
988
1074
  var wo = table.config.widgetOptions;
989
- clearTimeout(wo.searchTimer);
990
- if (typeof filter === 'undefined' || filter === true) {
1075
+ clearTimeout( wo.searchTimer );
1076
+ if ( typeof filter === 'undefined' || filter === true ) {
991
1077
  // delay filtering
992
- wo.searchTimer = setTimeout(function() {
993
- ts.filter.checkFilters(table, filter, skipFirst );
994
- }, wo.filter_liveSearch ? wo.filter_searchDelay : 10);
1078
+ wo.searchTimer = setTimeout( function() {
1079
+ ts.filter.checkFilters( table, filter, skipFirst );
1080
+ }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 );
995
1081
  } else {
996
1082
  // skip delay
997
- ts.filter.checkFilters(table, filter, skipFirst);
1083
+ ts.filter.checkFilters( table, filter, skipFirst );
998
1084
  }
999
1085
  },
1000
- checkFilters: function(table, filter, skipFirst) {
1086
+ checkFilters: function( table, filter, skipFirst ) {
1001
1087
  var c = table.config,
1002
1088
  wo = c.widgetOptions,
1003
- filterArray = $.isArray(filter),
1004
- filters = (filterArray) ? filter : ts.getFilters(table, true),
1005
- combinedFilters = (filters || []).join(''); // combined filter values
1089
+ filterArray = $.isArray( filter ),
1090
+ filters = ( filterArray ) ? filter : ts.getFilters( table, true ),
1091
+ combinedFilters = ( filters || [] ).join( '' ); // combined filter values
1006
1092
  // prevent errors if delay init is set
1007
- if ($.isEmptyObject(c.cache)) {
1008
- // update cache if delayInit set & pager has initialized (after user initiates a search)
1009
- if (c.delayInit && c.pager && c.pager.initialized) {
1010
- c.$table.trigger('updateCache', [function(){
1011
- ts.filter.checkFilters(table, false, skipFirst);
1012
- }] );
1093
+ if ( $.isEmptyObject( c.cache ) ) {
1094
+ // update cache if delayInit set & pager has initialized ( after user initiates a search )
1095
+ if ( c.delayInit && c.pager && c.pager.initialized ) {
1096
+ c.$table.trigger( 'updateCache', [ function() {
1097
+ ts.filter.checkFilters( table, false, skipFirst );
1098
+ } ] );
1013
1099
  }
1014
1100
  return;
1015
1101
  }
1016
1102
  // add filter array back into inputs
1017
- if (filterArray) {
1103
+ if ( filterArray ) {
1018
1104
  ts.setFilters( table, filters, false, skipFirst !== true );
1019
- if (!wo.filter_initialized) { c.lastCombinedFilter = ''; }
1105
+ if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; }
1020
1106
  }
1021
- if (wo.filter_hideFilters) {
1107
+ if ( wo.filter_hideFilters ) {
1022
1108
  // show/hide filter row as needed
1023
- c.$table.find('.' + tscss.filterRow).trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
1109
+ c.$table
1110
+ .find( '.' + tscss.filterRow )
1111
+ .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
1024
1112
  }
1025
1113
  // return if the last search is the same; but filter === false when updating the search
1026
1114
  // see example-widget-filter.html filter toggle buttons
1027
- if (c.lastCombinedFilter === combinedFilters && filter !== false) {
1115
+ if ( c.lastCombinedFilter === combinedFilters && filter !== false ) {
1028
1116
  return;
1029
- } else if (filter === false) {
1117
+ } else if ( filter === false ) {
1030
1118
  // force filter refresh
1031
1119
  c.lastCombinedFilter = null;
1032
1120
  c.lastSearch = [];
1033
1121
  }
1034
- if (wo.filter_initialized) { c.$table.trigger('filterStart', [filters]); }
1035
- if (c.showProcessing) {
1122
+ if ( wo.filter_initialized ) {
1123
+ c.$table.trigger( 'filterStart', [filters] );
1124
+ }
1125
+ if ( c.showProcessing ) {
1036
1126
  // give it time for the processing icon to kick in
1037
- setTimeout(function() {
1038
- ts.filter.findRows(table, filters, combinedFilters);
1127
+ setTimeout( function() {
1128
+ ts.filter.findRows( table, filters, combinedFilters );
1039
1129
  return false;
1040
- }, 30);
1130
+ }, 30 );
1041
1131
  } else {
1042
- ts.filter.findRows(table, filters, combinedFilters);
1132
+ ts.filter.findRows( table, filters, combinedFilters );
1043
1133
  return false;
1044
1134
  }
1045
1135
  },
1046
- hideFilters: function(table, c) {
1136
+ hideFilters: function( table, c ) {
1047
1137
  var $filterRow, $filterRow2, timer;
1048
- $(table)
1049
- .find('.' + tscss.filterRow)
1050
- .addClass(tscss.filterRowHide)
1051
- .bind('mouseenter mouseleave', function(e) {
1138
+ $( table )
1139
+ .find( '.' + tscss.filterRow )
1140
+ .addClass( tscss.filterRowHide )
1141
+ .bind( 'mouseenter mouseleave', function( e ) {
1052
1142
  // save event object - http://bugs.jquery.com/ticket/12140
1053
1143
  var event = e;
1054
- $filterRow = $(this);
1055
- clearTimeout(timer);
1056
- timer = setTimeout(function() {
1057
- if ( /enter|over/.test(event.type) ) {
1058
- $filterRow.removeClass(tscss.filterRowHide);
1144
+ $filterRow = $( this );
1145
+ clearTimeout( timer );
1146
+ timer = setTimeout( function() {
1147
+ if ( /enter|over/.test( event.type ) ) {
1148
+ $filterRow.removeClass( tscss.filterRowHide );
1059
1149
  } else {
1060
1150
  // don't hide if input has focus
1061
- // $(':focus') needs jQuery 1.6+
1062
- if ( $(document.activeElement).closest('tr')[0] !== $filterRow[0] ) {
1151
+ // $( ':focus' ) needs jQuery 1.6+
1152
+ if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) {
1063
1153
  // don't hide row if any filter has a value
1064
- if (c.lastCombinedFilter === '') {
1065
- $filterRow.addClass(tscss.filterRowHide);
1154
+ if ( c.lastCombinedFilter === '' ) {
1155
+ $filterRow.addClass( tscss.filterRowHide );
1066
1156
  }
1067
1157
  }
1068
1158
  }
1069
- }, 200);
1159
+ }, 200 );
1070
1160
  })
1071
- .find('input, select').bind('focus blur', function(e) {
1072
- $filterRow2 = $(this).closest('tr');
1073
- clearTimeout(timer);
1161
+ .find( 'input, select' ).bind( 'focus blur', function( e ) {
1162
+ $filterRow2 = $( this ).closest( 'tr' );
1163
+ clearTimeout( timer );
1074
1164
  var event = e;
1075
- timer = setTimeout(function() {
1165
+ timer = setTimeout( function() {
1076
1166
  // don't hide row if any filter has a value
1077
- if (ts.getFilters(c.$table).join('') === '') {
1078
- $filterRow2[ event.type === 'focus' ? 'removeClass' : 'addClass'](tscss.filterRowHide);
1167
+ if ( ts.getFilters( c.$table ).join( '' ) === '' ) {
1168
+ $filterRow2.toggleClass( tscss.filterRowHide, event.type === 'focus' );
1079
1169
  }
1080
- }, 200);
1170
+ }, 200 );
1081
1171
  });
1082
1172
  },
1083
- defaultFilter: function(filter, mask){
1084
- if (filter === '') { return filter; }
1173
+ defaultFilter: function( filter, mask ) {
1174
+ if ( filter === '' ) { return filter; }
1085
1175
  var regex = ts.filter.regex.iQuery,
1086
1176
  maskLen = mask.match( ts.filter.regex.igQuery ).length,
1087
- query = maskLen > 1 ? $.trim(filter).split(/\s/) : [ $.trim(filter) ],
1177
+ query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ],
1088
1178
  len = query.length - 1,
1089
1179
  indx = 0,
1090
1180
  val = mask;
1091
1181
  if ( len < 1 && maskLen > 1 ) {
1092
- // only one "word" in query but mask has >1 slots
1182
+ // only one 'word' in query but mask has >1 slots
1093
1183
  query[1] = query[0];
1094
1184
  }
1095
1185
  // replace all {query} with query words...
1096
- // if query = "Bob", then convert mask from "!{query}" to "!Bob"
1097
- // if query = "Bob Joe Frank", then convert mask "{q} OR {q}" to "Bob OR Joe OR Frank"
1098
- while (regex.test(val)) {
1099
- val = val.replace(regex, query[indx++] || '');
1100
- if (regex.test(val) && indx < len && (query[indx] || '') !== '') {
1101
- val = mask.replace(regex, val);
1186
+ // if query = 'Bob', then convert mask from '!{query}' to '!Bob'
1187
+ // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank'
1188
+ while ( regex.test( val ) ) {
1189
+ val = val.replace( regex, query[indx++] || '' );
1190
+ if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) {
1191
+ val = mask.replace( regex, val );
1102
1192
  }
1103
1193
  }
1104
1194
  return val;
1105
1195
  },
1106
1196
  getLatestSearch: function( $input ) {
1107
- if ($input) {
1108
- return $input.sort(function(a, b) {
1109
- return $(b).attr('data-lastSearchTime') - $(a).attr('data-lastSearchTime');
1197
+ if ( $input ) {
1198
+ return $input.sort( function( a, b ) {
1199
+ return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' );
1110
1200
  });
1111
1201
  }
1112
1202
  return $();
1113
1203
  },
1114
1204
  multipleColumns: function( c, $input ) {
1115
- // look for multiple columns "1-3,4-6,8" in data-column
1205
+ // look for multiple columns '1-3,4-6,8' in data-column
1116
1206
  var temp, ranges, range, start, end, singles, i, indx, len,
1117
1207
  wo = c.widgetOptions,
1118
- // only target "all" column inputs on initialization
1119
- // & don't target "all" column inputs if they don't exist
1120
- targets = wo.filter_initialized || !$input.filter(wo.filter_anyColumnSelector).length,
1208
+ // only target 'all' column inputs on initialization
1209
+ // & don't target 'all' column inputs if they don't exist
1210
+ targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length,
1121
1211
  columns = [],
1122
- val = $.trim( ts.filter.getLatestSearch( $input ).attr('data-column') || '' );
1212
+ val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' );
1123
1213
  // process column range
1124
1214
  if ( targets && /-/.test( val ) ) {
1125
1215
  ranges = val.match( /(\d+)\s*-\s*(\d+)/g );
1126
1216
  len = ranges.length;
1127
- for (indx = 0; indx < len; indx++) {
1217
+ for ( indx = 0; indx < len; indx++ ) {
1128
1218
  range = ranges[indx].split( /\s*-\s*/ );
1129
1219
  start = parseInt( range[0], 10 ) || 0;
1130
1220
  end = parseInt( range[1], 10 ) || ( c.columns - 1 );
1131
- if ( start > end ) { temp = start; start = end; end = temp; } // swap
1132
- if ( end >= c.columns ) { end = c.columns - 1; }
1221
+ if ( start > end ) {
1222
+ temp = start; start = end; end = temp; // swap
1223
+ }
1224
+ if ( end >= c.columns ) {
1225
+ end = c.columns - 1;
1226
+ }
1133
1227
  for ( ; start <= end; start++ ) {
1134
- columns.push(start);
1228
+ columns.push( start );
1135
1229
  }
1136
1230
  // remove processed range from val
1137
- val = val.replace( ranges[indx], '' );
1231
+ val = val.replace( ranges[ indx ], '' );
1138
1232
  }
1139
1233
  }
1140
1234
  // process single columns
1141
1235
  if ( targets && /,/.test( val ) ) {
1142
1236
  singles = val.split( /\s*,\s*/ );
1143
1237
  len = singles.length;
1144
- for (i = 0; i < len; i++) {
1145
- if (singles[i] !== '') {
1146
- indx = parseInt( singles[i], 10 );
1238
+ for ( i = 0; i < len; i++ ) {
1239
+ if ( singles[ i ] !== '' ) {
1240
+ indx = parseInt( singles[ i ], 10 );
1147
1241
  if ( indx < c.columns ) {
1148
1242
  columns.push( indx );
1149
1243
  }
@@ -1151,382 +1245,472 @@ ts.filter = {
1151
1245
  }
1152
1246
  }
1153
1247
  // return all columns
1154
- if (!columns.length) {
1248
+ if ( !columns.length ) {
1155
1249
  for ( indx = 0; indx < c.columns; indx++ ) {
1156
1250
  columns.push( indx );
1157
1251
  }
1158
1252
  }
1159
1253
  return columns;
1160
1254
  },
1161
- findRows: function(table, filters, combinedFilters) {
1162
- if (table.config.lastCombinedFilter === combinedFilters || !table.config.widgetOptions.filter_initialized) { return; }
1163
- var len, norm_rows, $rows, rowIndex, tbodyIndex, $tbody, $cells, $cell, columnIndex,
1164
- childRow, lastSearch, hasSelect, matches, result, showRow, time, val, indx,
1165
- notFiltered, searchFiltered, filterMatched, excludeMatch, fxn, ffxn,
1166
- query, injected, res, id,
1255
+ processRow: function( c, data, vars ) {
1256
+ var $cell, columnIndex, hasSelect, matches, result, val, filterMatched, excludeMatch,
1257
+ fxn, ffxn, txt,
1258
+ regex = ts.filter.regex,
1259
+ wo = c.widgetOptions,
1260
+ showRow = true;
1261
+ data.$cells = data.$row.children();
1262
+
1263
+ if ( data.anyMatchFlag ) {
1264
+ // look for multiple columns '1-3,4-6,8'
1265
+ columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
1266
+ data.anyMatch = true;
1267
+ data.rowArray = data.$cells.map( function( i ) {
1268
+ if ( $.inArray( i, columnIndex ) > -1 ) {
1269
+ if ( data.parsed[ i ] ) {
1270
+ txt = data.cacheArray[ i ];
1271
+ } else {
1272
+ txt = data.rawArray[ i ];
1273
+ txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt );
1274
+ if ( c.sortLocaleCompare ) {
1275
+ txt = ts.replaceAccents( txt );
1276
+ }
1277
+ }
1278
+ return txt;
1279
+ }
1280
+ }).get();
1281
+ data.filter = data.anyMatchFilter;
1282
+ data.iFilter = data.iAnyMatchFilter;
1283
+ data.exact = data.rowArray.join( ' ' );
1284
+ data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
1285
+ data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' );
1286
+ filterMatched = null;
1287
+ matches = null;
1288
+ for ( ffxn in ts.filter.types ) {
1289
+ if ( $.inArray( ffxn, vars.noAnyMatch ) < 0 && matches === null ) {
1290
+ matches = ts.filter.types[ffxn]( c, data );
1291
+ if ( matches !== null ) {
1292
+ filterMatched = matches;
1293
+ }
1294
+ }
1295
+ }
1296
+ if ( filterMatched !== null ) {
1297
+ showRow = filterMatched;
1298
+ } else {
1299
+ if ( wo.filter_startsWith ) {
1300
+ showRow = false;
1301
+ columnIndex = c.columns;
1302
+ while ( !showRow && columnIndex > 0 ) {
1303
+ columnIndex--;
1304
+ showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0;
1305
+ }
1306
+ } else {
1307
+ showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0;
1308
+ }
1309
+ }
1310
+ data.anyMatch = false;
1311
+ // no other filters to process
1312
+ if ( data.filters.join( '' ) === data.filter ) {
1313
+ return showRow;
1314
+ }
1315
+ }
1316
+
1317
+ for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) {
1318
+ data.filter = data.filters[ columnIndex ];
1319
+ data.index = columnIndex;
1320
+
1321
+ // filter types to exclude, per column
1322
+ excludeMatch = vars.excludeFilter[ columnIndex ];
1323
+
1324
+ // ignore if filter is empty or disabled
1325
+ if ( data.filter ) {
1326
+ data.cache = data.cacheArray[ columnIndex ];
1327
+ // check if column data should be from the cell or from parsed data
1328
+ if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) {
1329
+ data.exact = data.cache;
1330
+ } else {
1331
+ result = data.rawArray[ columnIndex ] || '';
1332
+ data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405
1333
+ }
1334
+ data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ?
1335
+ data.exact.toLowerCase() : data.exact;
1336
+ result = showRow; // if showRow is true, show that row
1337
+
1338
+ // in case select filter option has a different value vs text 'a - z|A through Z'
1339
+ ffxn = wo.filter_columnFilters ?
1340
+ c.$filters.add( c.$externalFilters )
1341
+ .filter( '[data-column="'+ columnIndex + '"]' )
1342
+ .find( 'select option:selected' )
1343
+ .attr( 'data-function-name' ) || '' : '';
1344
+ // replace accents - see #357
1345
+ if ( c.sortLocaleCompare ) {
1346
+ data.filter = ts.replaceAccents( data.filter );
1347
+ }
1348
+
1349
+ val = true;
1350
+ if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) {
1351
+ data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] );
1352
+ // val is used to indicate that a filter select is using a default filter;
1353
+ // so we override the exact & partial matches
1354
+ val = false;
1355
+ }
1356
+ // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ),
1357
+ // data.filter = case sensitive
1358
+ data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter;
1359
+ fxn = vars.functions[ columnIndex ];
1360
+ $cell = c.$headerIndexed[ columnIndex ];
1361
+ hasSelect = $cell.hasClass( 'filter-select' );
1362
+ filterMatched = null;
1363
+ if ( fxn || ( hasSelect && val ) ) {
1364
+ if ( fxn === true || hasSelect ) {
1365
+ // default selector uses exact match unless 'filter-match' class is found
1366
+ filterMatched = $cell.hasClass( 'filter-match' ) ?
1367
+ data.iExact.search( data.iFilter ) >= 0 :
1368
+ data.filter === data.exact;
1369
+ } else if ( typeof fxn === 'function' ) {
1370
+ // filter callback( exact cell content, parser normalized content,
1371
+ // filter input value, column index, jQuery row object )
1372
+ filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data );
1373
+ } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) {
1374
+ // selector option function
1375
+ txt = ffxn || data.filter;
1376
+ filterMatched =
1377
+ fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data );
1378
+ }
1379
+ }
1380
+ if ( filterMatched === null ) {
1381
+ // cycle through the different filters
1382
+ // filters return a boolean or null if nothing matches
1383
+ matches = null;
1384
+ for ( ffxn in ts.filter.types ) {
1385
+ if ( $.inArray( ffxn, excludeMatch ) < 0 && matches === null ) {
1386
+ matches = ts.filter.types[ ffxn ]( c, data );
1387
+ if ( matches !== null ) {
1388
+ filterMatched = matches;
1389
+ }
1390
+ }
1391
+ }
1392
+ if ( filterMatched !== null ) {
1393
+ result = filterMatched;
1394
+ // Look for match, and add child row data for matching
1395
+ } else {
1396
+ txt = ( data.iExact + data.childRowText )
1397
+ .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) );
1398
+ result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) );
1399
+ }
1400
+ } else {
1401
+ result = filterMatched;
1402
+ }
1403
+ showRow = ( result ) ? showRow : false;
1404
+ }
1405
+ }
1406
+ return showRow;
1407
+ },
1408
+ findRows: function( table, filters, combinedFilters ) {
1409
+ if ( table.config.lastCombinedFilter === combinedFilters ||
1410
+ !table.config.widgetOptions.filter_initialized ) {
1411
+ return;
1412
+ }
1413
+ var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex,
1414
+ isChild, childRow, lastSearch, showRow, time, val, indx,
1415
+ notFiltered, searchFiltered, query, injected, res, id, txt,
1416
+ storedFilters = $.extend( [], filters ),
1167
1417
  regex = ts.filter.regex,
1168
1418
  c = table.config,
1169
1419
  wo = c.widgetOptions,
1170
1420
  // data object passed to filters; anyMatch is a flag for the filters
1171
- data = { anyMatch: false },
1172
- // anyMatch really screws up with these types of filters
1173
- noAnyMatch = [ 'range', 'notMatch', 'operators' ];
1421
+ data = {
1422
+ anyMatch: false,
1423
+ filters: filters,
1424
+ // regex filter type cache
1425
+ filter_regexCache : [],
1426
+ },
1427
+ vars = {
1428
+ // anyMatch really screws up with these types of filters
1429
+ noAnyMatch: [ 'range', 'notMatch', 'operators' ],
1430
+ // cache filter variables that use ts.getColumnData in the main loop
1431
+ functions : [],
1432
+ excludeFilter : [],
1433
+ defaultColFilter : [],
1434
+ defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || ''
1435
+ };
1174
1436
 
1175
1437
  // parse columns after formatter, in case the class is added at that point
1176
- data.parsed = c.$headers.map(function(columnIndex) {
1177
- return c.parsers && c.parsers[columnIndex] && c.parsers[columnIndex].parsed ||
1178
- // getData won't return "parsed" if other "filter-" class names exist (e.g. <th class="filter-select filter-parsed">)
1179
- ts.getData && ts.getData(c.$headerIndexed[columnIndex], ts.getColumnData( table, c.headers, columnIndex ), 'filter') === 'parsed' ||
1180
- $(this).hasClass('filter-parsed');
1438
+ data.parsed = c.$headers.map( function( columnIndex ) {
1439
+ return c.parsers && c.parsers[ columnIndex ] &&
1440
+ // force parsing if parser type is numeric
1441
+ ( c.parsers[ columnIndex ].parsed || c.parsers[ columnIndex ].type === 'numeric' ) ||
1442
+ // getData won't return 'parsed' if other 'filter-' class names exist
1443
+ // ( e.g. <th class="filter-select filter-parsed"> )
1444
+ ts.getData && ts.getData( c.$headerIndexed[ columnIndex ],
1445
+ ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' ||
1446
+ $( this ).hasClass( 'filter-parsed' );
1181
1447
  }).get();
1182
1448
 
1183
- // cache filter variables that use ts.getColumnData in the main loop
1184
- wo.filter_indexed = {
1185
- functions : [],
1186
- excludeFilter : [],
1187
- defaultColFilter : [],
1188
- defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || ''
1189
- };
1190
1449
  for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) {
1191
- wo.filter_indexed.functions[ columnIndex ] = ts.getColumnData( table, wo.filter_functions, columnIndex );
1192
- wo.filter_indexed.defaultColFilter[ columnIndex ] = ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || '';
1193
- wo.filter_indexed.excludeFilter[ columnIndex ] = ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split(/\s+/);
1450
+ vars.functions[ columnIndex ] =
1451
+ ts.getColumnData( table, wo.filter_functions, columnIndex );
1452
+ vars.defaultColFilter[ columnIndex ] =
1453
+ ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || '';
1454
+ vars.excludeFilter[ columnIndex ] =
1455
+ ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ );
1194
1456
  }
1195
1457
 
1196
- if (c.debug) {
1197
- ts.log('Filter: Starting filter widget search', filters);
1458
+ if ( c.debug ) {
1459
+ ts.log( 'Filter: Starting filter widget search', filters );
1198
1460
  time = new Date();
1199
1461
  }
1200
1462
  // filtered rows count
1201
1463
  c.filteredRows = 0;
1202
1464
  c.totalRows = 0;
1203
1465
  // combindedFilters are undefined on init
1204
- combinedFilters = (filters || []).join('');
1466
+ combinedFilters = ( storedFilters || [] ).join( '' );
1205
1467
 
1206
- for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
1207
- $tbody = ts.processTbody(table, c.$tbodies.eq(tbodyIndex), true);
1208
- // skip child rows & widget added (removable) rows - fixes #448 thanks to @hempel!
1209
- // $rows = $tbody.children('tr').not(c.selectorRemove);
1468
+ for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
1469
+ $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true );
1470
+ // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel!
1471
+ // $rows = $tbody.children( 'tr' ).not( c.selectorRemove );
1210
1472
  columnIndex = c.columns;
1211
1473
  // convert stored rows into a jQuery object
1212
- norm_rows = c.cache[tbodyIndex].normalized;
1213
- $rows = $( $.map(norm_rows, function(el){ return el[columnIndex].$row.get(); }) );
1214
-
1215
- if (combinedFilters === '' || wo.filter_serversideFiltering) {
1216
- $rows.removeClass(wo.filter_filteredRow).not('.' + c.cssChildRow).css('display', '');
1474
+ norm_rows = c.cache[ tbodyIndex ].normalized;
1475
+ $rows = $( $.map( norm_rows, function( el ) {
1476
+ return el[ columnIndex ].$row.get();
1477
+ }) );
1478
+
1479
+ if ( combinedFilters === '' || wo.filter_serversideFiltering ) {
1480
+ $rows
1481
+ .removeClass( wo.filter_filteredRow )
1482
+ .not( '.' + c.cssChildRow )
1483
+ .css( 'display', '' );
1217
1484
  } else {
1218
1485
  // filter out child rows
1219
- $rows = $rows.not('.' + c.cssChildRow);
1486
+ $rows = $rows.not( '.' + c.cssChildRow );
1220
1487
  len = $rows.length;
1221
1488
 
1222
- if ( (wo.filter_$anyMatch && wo.filter_$anyMatch.length) || typeof filters[c.columns] !== 'undefined' ) {
1489
+ if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) ||
1490
+ typeof filters[c.columns] !== 'undefined' ) {
1223
1491
  data.anyMatchFlag = true;
1224
- data.anyMatchFilter = wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || ( '' + filters[c.columns] ) || '';
1225
- if (wo.filter_columnAnyMatch) {
1492
+ data.anyMatchFilter = '' + (
1493
+ filters[ c.columns ] ||
1494
+ wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() ||
1495
+ ''
1496
+ );
1497
+ if ( wo.filter_columnAnyMatch ) {
1226
1498
  // specific columns search
1227
- query = data.anyMatchFilter.split( ts.filter.regex.andSplit );
1499
+ query = data.anyMatchFilter.split( regex.andSplit );
1228
1500
  injected = false;
1229
- for (indx = 0; indx < query.length; indx++) {
1230
- res = query[indx].split(':');
1501
+ for ( indx = 0; indx < query.length; indx++ ) {
1502
+ res = query[ indx ].split( ':' );
1231
1503
  if ( res.length > 1 ) {
1232
1504
  // make the column a one-based index ( non-developers start counting from one :P )
1233
1505
  id = parseInt( res[0], 10 ) - 1;
1234
1506
  if ( id >= 0 && id < c.columns ) { // if id is an integer
1235
- filters[id] = res[1];
1236
- query.splice(indx, 1);
1507
+ filters[ id ] = res[1];
1508
+ query.splice( indx, 1 );
1237
1509
  indx--;
1238
1510
  injected = true;
1239
1511
  }
1240
1512
  }
1241
1513
  }
1242
- if (injected) {
1243
- data.anyMatchFilter = query.join(' && ');
1514
+ if ( injected ) {
1515
+ data.anyMatchFilter = query.join( ' && ' );
1244
1516
  }
1245
1517
  }
1246
1518
  }
1247
1519
 
1248
1520
  // optimize searching only through already filtered rows - see #313
1249
1521
  searchFiltered = wo.filter_searchFiltered;
1250
- lastSearch = c.lastSearch || c.$table.data('lastSearch') || [];
1251
- if (searchFiltered) {
1252
- // cycle through all filters; include last (columnIndex + 1 = match any column). Fixes #669
1253
- for (indx = 0; indx < columnIndex + 1; indx++) {
1522
+ lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || [];
1523
+ if ( searchFiltered ) {
1524
+ // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669
1525
+ for ( indx = 0; indx < columnIndex + 1; indx++ ) {
1254
1526
  val = filters[indx] || '';
1255
1527
  // break out of loop if we've already determined not to search filtered rows
1256
- if (!searchFiltered) { indx = columnIndex; }
1528
+ if ( !searchFiltered ) { indx = columnIndex; }
1257
1529
  // search already filtered rows if...
1258
1530
  searchFiltered = searchFiltered && lastSearch.length &&
1259
1531
  // there are no changes from beginning of filter
1260
- val.indexOf(lastSearch[indx] || '') === 0 &&
1261
- // if there is NOT a logical "or", or range ("to" or "-") in the string
1262
- !regex.alreadyFiltered.test(val) &&
1263
- // if we are not doing exact matches, using "|" (logical or) or not "!"
1264
- !/[=\"\|!]/.test(val) &&
1265
- // don't search only filtered if the value is negative ('> -10' => '> -100' will ignore hidden rows)
1266
- !(/(>=?\s*-\d)/.test(val) || /(<=?\s*\d)/.test(val)) &&
1267
- // if filtering using a select without a "filter-match" class (exact match) - fixes #593
1268
- !( val !== '' && c.$filters && c.$filters.eq(indx).find('select').length && !c.$headerIndexed[indx].hasClass('filter-match') );
1532
+ val.indexOf( lastSearch[indx] || '' ) === 0 &&
1533
+ // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string
1534
+ !regex.alreadyFiltered.test( val ) &&
1535
+ // if we are not doing exact matches, using '|' ( logical or ) or not '!'
1536
+ !/[=\"\|!]/.test( val ) &&
1537
+ // don't search only filtered if the value is negative
1538
+ // ( '> -10' => '> -100' will ignore hidden rows )
1539
+ !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) &&
1540
+ // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593
1541
+ !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length &&
1542
+ !c.$headerIndexed[indx].hasClass( 'filter-match' ) );
1269
1543
  }
1270
1544
  }
1271
- notFiltered = $rows.not('.' + wo.filter_filteredRow).length;
1545
+ notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length;
1272
1546
  // can't search when all rows are hidden - this happens when looking for exact matches
1273
- if (searchFiltered && notFiltered === 0) { searchFiltered = false; }
1274
- if (c.debug) {
1275
- ts.log( 'Filter: Searching through ' + ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' );
1547
+ if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; }
1548
+ if ( c.debug ) {
1549
+ ts.log( 'Filter: Searching through ' +
1550
+ ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' );
1276
1551
  }
1277
- if (data.anyMatchFlag) {
1278
- if (c.sortLocaleCompare) {
1552
+ if ( data.anyMatchFlag ) {
1553
+ if ( c.sortLocaleCompare ) {
1279
1554
  // replace accents
1280
- data.anyMatchFilter = ts.replaceAccents(data.anyMatchFilter);
1555
+ data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter );
1281
1556
  }
1282
- if ( wo.filter_defaultFilter && regex.iQuery.test( wo.filter_indexed.defaultAnyFilter ) ) {
1283
- data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, wo.filter_indexed.defaultAnyFilter );
1557
+ if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) {
1558
+ data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter );
1284
1559
  // clear search filtered flag because default filters are not saved to the last search
1285
1560
  searchFiltered = false;
1286
1561
  }
1287
1562
  // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true
1288
1563
  // when c.ignoreCase is true, the cache contains all lower case data
1289
- data.iAnyMatchFilter = !(wo.filter_ignoreCase && c.ignoreCase) ? data.anyMatchFilter : data.anyMatchFilter.toLocaleLowerCase();
1564
+ data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ?
1565
+ data.anyMatchFilter :
1566
+ data.anyMatchFilter.toLowerCase();
1290
1567
  }
1291
1568
 
1292
1569
  // loop through the rows
1293
- for (rowIndex = 0; rowIndex < len; rowIndex++) {
1570
+ for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
1294
1571
 
1295
- data.cacheArray = norm_rows[rowIndex];
1296
-
1297
- childRow = $rows[rowIndex].className;
1572
+ txt = $rows[ rowIndex ].className;
1573
+ // the first row can never be a child row
1574
+ isChild = rowIndex && regex.child.test( txt );
1298
1575
  // skip child rows & already filtered rows
1299
- if ( regex.child.test(childRow) || (searchFiltered && regex.filtered.test(childRow)) ) { continue; }
1300
- showRow = true;
1301
- // *** nextAll/nextUntil not supported by Zepto! ***
1302
- childRow = $rows.eq(rowIndex).nextUntil('tr:not(.' + c.cssChildRow + ')');
1303
- // so, if "table.config.widgetOptions.filter_childRows" is true and there is
1304
- // a match anywhere in the child row, then it will make the row visible
1305
- // checked here so the option can be changed dynamically
1306
- data.childRowText = (childRow.length && wo.filter_childRows) ? childRow.text() : '';
1307
- data.childRowText = wo.filter_ignoreCase ? data.childRowText.toLocaleLowerCase() : data.childRowText;
1308
- $cells = $rows.eq(rowIndex).children();
1309
- if (data.anyMatchFlag) {
1310
- // look for multiple columns "1-3,4-6,8"
1311
- columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
1312
- data.anyMatch = true;
1313
- data.rowArray = $cells.map(function(i){
1314
- if ( $.inArray(i, columnIndex) > -1 ) {
1315
- var txt;
1316
- if (data.parsed[i]) {
1317
- txt = data.cacheArray[i];
1318
- } else {
1319
- txt = this ? this.getAttribute( c.textAttribute ) || this.textContent || $(this).text() : '';
1320
- txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt );
1321
- if (c.sortLocaleCompare) {
1322
- txt = ts.replaceAccents(txt);
1323
- }
1324
- }
1325
- return txt;
1326
- }
1327
- }).get();
1328
- data.filter = data.anyMatchFilter;
1329
- data.iFilter = data.iAnyMatchFilter;
1330
- data.exact = data.rowArray.join(' ');
1331
- data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
1332
- data.cache = data.cacheArray.slice(0,-1).join(' ');
1333
- filterMatched = null;
1334
- $.each(ts.filter.types, function(type, typeFunction) {
1335
- if ($.inArray(type, noAnyMatch) < 0) {
1336
- matches = typeFunction( c, data );
1337
- if (matches !== null) {
1338
- filterMatched = matches;
1339
- return false;
1340
- }
1341
- }
1342
- });
1343
- if (filterMatched !== null) {
1344
- showRow = filterMatched;
1345
- } else {
1346
- if (wo.filter_startsWith) {
1347
- showRow = false;
1348
- columnIndex = c.columns;
1349
- while (!showRow && columnIndex > 0) {
1350
- columnIndex--;
1351
- showRow = showRow || data.rowArray[columnIndex].indexOf(data.iFilter) === 0;
1352
- }
1353
- } else {
1354
- showRow = (data.iExact + data.childRowText).indexOf(data.iFilter) >= 0;
1355
- }
1356
- }
1357
- data.anyMatch = false;
1576
+ if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) {
1577
+ continue;
1358
1578
  }
1359
1579
 
1360
- for (columnIndex = 0; columnIndex < c.columns; columnIndex++) {
1361
- data.filter = filters[columnIndex];
1362
- data.index = columnIndex;
1363
-
1364
- // filter types to exclude, per column
1365
- excludeMatch = wo.filter_indexed.excludeFilter[ columnIndex ];
1366
-
1367
- // ignore if filter is empty or disabled
1368
- if (data.filter) {
1369
- data.cache = data.cacheArray[columnIndex];
1370
- // check if column data should be from the cell or from parsed data
1371
- if (wo.filter_useParsedData || data.parsed[columnIndex]) {
1372
- data.exact = data.cache;
1373
- } else {
1374
- val = $cells[columnIndex];
1375
- result = val ? $.trim( val.getAttribute( c.textAttribute ) || val.textContent || $cells.eq(columnIndex).text() ) : '';
1376
- data.exact = c.sortLocaleCompare ? ts.replaceAccents(result) : result; // issue #405
1377
- }
1378
- data.iExact = !regex.type.test(typeof data.exact) && wo.filter_ignoreCase ? data.exact.toLocaleLowerCase() : data.exact;
1379
- result = showRow; // if showRow is true, show that row
1380
-
1381
- // in case select filter option has a different value vs text "a - z|A through Z"
1382
- ffxn = wo.filter_columnFilters ?
1383
- c.$filters.add(c.$externalFilters).filter('[data-column="'+ columnIndex + '"]').find('select option:selected').attr('data-function-name') || '' : '';
1384
- // replace accents - see #357
1385
- if (c.sortLocaleCompare) {
1386
- data.filter = ts.replaceAccents(data.filter);
1387
- }
1580
+ data.$row = $rows.eq( rowIndex );
1581
+ data.cacheArray = norm_rows[ rowIndex ];
1582
+ rowData = data.cacheArray[ c.columns ];
1583
+ data.rawArray = rowData.raw;
1584
+ data.childRowText = '';
1585
+
1586
+ if ( !wo.filter_childByColumn ) {
1587
+ txt = '';
1588
+ // child row cached text
1589
+ childRow = rowData.child;
1590
+ // so, if 'table.config.widgetOptions.filter_childRows' is true and there is
1591
+ // a match anywhere in the child row, then it will make the row visible
1592
+ // checked here so the option can be changed dynamically
1593
+ for ( indx = 0; indx < childRow.length; indx++ ) {
1594
+ txt += ' ' + childRow[indx].join( '' ) || '';
1595
+ }
1596
+ data.childRowText = wo.filter_childRows ?
1597
+ ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) :
1598
+ '';
1599
+ }
1388
1600
 
1389
- val = true;
1390
- if (wo.filter_defaultFilter && regex.iQuery.test( wo.filter_indexed.defaultColFilter[ columnIndex ] )) {
1391
- data.filter = ts.filter.defaultFilter( data.filter, wo.filter_indexed.defaultColFilter[ columnIndex ] );
1392
- // val is used to indicate that a filter select is using a default filter; so we override the exact & partial matches
1393
- val = false;
1601
+ showRow = ts.filter.processRow( c, data, vars );
1602
+ childRow = rowData.$row.filter( ':gt( 0 )' );
1603
+
1604
+ if ( wo.filter_childRows && childRow.length ) {
1605
+ if ( wo.filter_childByColumn ) {
1606
+ // cycle through each child row
1607
+ for ( indx = 0; indx < childRow.length; indx++ ) {
1608
+ data.$row = childRow.eq( indx );
1609
+ data.cacheArray = rowData.child[ indx ];
1610
+ data.rawArray = data.cacheArray;
1611
+ // use OR comparison on child rows
1612
+ showRow = showRow || ts.filter.processRow( c, data, vars );
1394
1613
  }
1395
- // data.iFilter = case insensitive (if wo.filter_ignoreCase is true), data.filter = case sensitive
1396
- data.iFilter = wo.filter_ignoreCase ? (data.filter || '').toLocaleLowerCase() : data.filter;
1397
- fxn = wo.filter_indexed.functions[ columnIndex ];
1398
- $cell = c.$headerIndexed[columnIndex];
1399
- hasSelect = $cell.hasClass('filter-select');
1400
- filterMatched = null;
1401
- if ( fxn || ( hasSelect && val ) ) {
1402
- if (fxn === true || hasSelect) {
1403
- // default selector uses exact match unless "filter-match" class is found
1404
- filterMatched = ($cell.hasClass('filter-match')) ? data.iExact.search(data.iFilter) >= 0 : data.filter === data.exact;
1405
- } else if (typeof fxn === 'function') {
1406
- // filter callback( exact cell content, parser normalized content, filter input value, column index, jQuery row object )
1407
- filterMatched = fxn(data.exact, data.cache, data.filter, columnIndex, $rows.eq(rowIndex), c);
1408
- } else if (typeof fxn[ffxn || data.filter] === 'function') {
1409
- // selector option function
1410
- filterMatched = fxn[ffxn || data.filter](data.exact, data.cache, data.filter, columnIndex, $rows.eq(rowIndex), c);
1411
- }
1412
- }
1413
- if (filterMatched === null) {
1414
- // cycle through the different filters
1415
- // filters return a boolean or null if nothing matches
1416
- $.each(ts.filter.types, function(type, typeFunction) {
1417
- if ($.inArray(type, excludeMatch) < 0) {
1418
- matches = typeFunction( c, data );
1419
- if (matches !== null) {
1420
- filterMatched = matches;
1421
- return false;
1422
- }
1423
- }
1424
- });
1425
- if (filterMatched !== null) {
1426
- result = filterMatched;
1427
- // Look for match, and add child row data for matching
1428
- } else {
1429
- data.exact = (data.iExact + data.childRowText).indexOf( ts.filter.parseFilter(c, data.iFilter, columnIndex, data.parsed[columnIndex]) );
1430
- result = ( (!wo.filter_startsWith && data.exact >= 0) || (wo.filter_startsWith && data.exact === 0) );
1431
- }
1432
- } else {
1433
- result = filterMatched;
1434
- }
1435
- showRow = (result) ? showRow : false;
1436
1614
  }
1615
+ childRow.toggleClass( wo.filter_filteredRow, !showRow );
1437
1616
  }
1438
- $rows.eq(rowIndex)
1439
- .toggleClass(wo.filter_filteredRow, !showRow)[0]
1617
+
1618
+ rowData.$row
1619
+ .toggleClass( wo.filter_filteredRow, !showRow )[0]
1440
1620
  .display = showRow ? '' : 'none';
1441
- if (childRow.length) {
1442
- childRow.toggleClass(wo.filter_filteredRow, !showRow);
1443
- }
1444
1621
  }
1445
1622
  }
1446
- c.filteredRows += $rows.not('.' + wo.filter_filteredRow).length;
1623
+ c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length;
1447
1624
  c.totalRows += $rows.length;
1448
- ts.processTbody(table, $tbody, false);
1625
+ ts.processTbody( table, $tbody, false );
1449
1626
  }
1450
1627
  c.lastCombinedFilter = combinedFilters; // save last search
1451
- c.lastSearch = filters;
1452
- c.$table.data('lastSearch', filters);
1453
- if (wo.filter_saveFilters && ts.storage) {
1454
- ts.storage( table, 'tablesorter-filters', filters );
1628
+ // don't save 'filters' directly since it may have altered ( AnyMatch column searches )
1629
+ c.lastSearch = storedFilters;
1630
+ c.$table.data( 'lastSearch', storedFilters );
1631
+ if ( wo.filter_saveFilters && ts.storage ) {
1632
+ ts.storage( table, 'tablesorter-filters', storedFilters );
1455
1633
  }
1456
- if (c.debug) {
1457
- ts.benchmark("Completed filter widget search", time);
1634
+ if ( c.debug ) {
1635
+ ts.benchmark( 'Completed filter widget search', time );
1636
+ }
1637
+ if ( wo.filter_initialized ) {
1638
+ c.$table.trigger( 'filterEnd', c );
1458
1639
  }
1459
- if (wo.filter_initialized) { c.$table.trigger('filterEnd', c ); }
1460
- setTimeout(function(){
1461
- c.$table.trigger('applyWidgets'); // make sure zebra widget is applied
1462
- }, 0);
1640
+ setTimeout( function() {
1641
+ c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied
1642
+ }, 0 );
1463
1643
  },
1464
- getOptionSource: function(table, column, onlyAvail) {
1465
- table = $(table)[0];
1644
+ getOptionSource: function( table, column, onlyAvail ) {
1645
+ table = $( table )[0];
1466
1646
  var cts, indx, len,
1467
1647
  c = table.config,
1468
1648
  wo = c.widgetOptions,
1469
1649
  parsed = [],
1470
1650
  arry = false,
1471
1651
  source = wo.filter_selectSource,
1472
- last = c.$table.data('lastSearch') || [],
1473
- fxn = $.isFunction(source) ? true : ts.getColumnData( table, source, column );
1652
+ last = c.$table.data( 'lastSearch' ) || [],
1653
+ fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column );
1474
1654
 
1475
- if (onlyAvail && last[column] !== '') {
1655
+ if ( onlyAvail && last[column] !== '' ) {
1476
1656
  onlyAvail = false;
1477
1657
  }
1478
1658
 
1479
1659
  // filter select source option
1480
- if (fxn === true) {
1660
+ if ( fxn === true ) {
1481
1661
  // OVERALL source
1482
- arry = source(table, column, onlyAvail);
1483
- } else if ( fxn instanceof $ || ($.type(fxn) === 'string' && fxn.indexOf('</option>') >= 0) ) {
1662
+ arry = source( table, column, onlyAvail );
1663
+ } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '</option>' ) >= 0 ) ) {
1484
1664
  // selectSource is a jQuery object or string of options
1485
1665
  return fxn;
1486
- } else if ($.isArray(fxn)) {
1666
+ } else if ( $.isArray( fxn ) ) {
1487
1667
  arry = fxn;
1488
- } else if ($.type(source) === 'object' && fxn) {
1668
+ } else if ( $.type( source ) === 'object' && fxn ) {
1489
1669
  // custom select source function for a SPECIFIC COLUMN
1490
- arry = fxn(table, column, onlyAvail);
1670
+ arry = fxn( table, column, onlyAvail );
1491
1671
  }
1492
- if (arry === false) {
1672
+ if ( arry === false ) {
1493
1673
  // fall back to original method
1494
- arry = ts.filter.getOptions(table, column, onlyAvail);
1674
+ arry = ts.filter.getOptions( table, column, onlyAvail );
1495
1675
  }
1496
1676
 
1497
1677
  // get unique elements and sort the list
1498
- // if $.tablesorter.sortText exists (not in the original tablesorter),
1678
+ // if $.tablesorter.sortText exists ( not in the original tablesorter ),
1499
1679
  // then natural sort the list otherwise use a basic sort
1500
- arry = $.grep(arry, function(value, indx) {
1501
- return $.inArray(value, arry) === indx;
1680
+ arry = $.grep( arry, function( value, indx ) {
1681
+ return $.inArray( value, arry ) === indx;
1502
1682
  });
1503
1683
 
1504
- if (c.$headerIndexed[column].hasClass('filter-select-nosort')) {
1684
+ if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) {
1505
1685
  // unsorted select options
1506
1686
  return arry;
1507
1687
  } else {
1508
1688
  len = arry.length;
1509
1689
  // parse select option values
1510
- for (indx = 0; indx < len; indx++) {
1690
+ for ( indx = 0; indx < len; indx++ ) {
1511
1691
  // parse array data using set column parser; this DOES NOT pass the original
1512
1692
  // table cell to the parser format function
1513
- parsed.push({ t : arry[indx], p : c.parsers && c.parsers[column].format( arry[indx], table, [], column ) });
1693
+ parsed.push({
1694
+ t : arry[ indx ],
1695
+ p : c.parsers && c.parsers[ column ].format( arry[ indx ], table, [], column )
1696
+ });
1514
1697
  }
1515
1698
 
1516
1699
  // sort parsed select options
1517
1700
  cts = c.textSorter || '';
1518
- parsed.sort(function(a, b){
1701
+ parsed.sort( function( a, b ) {
1519
1702
  // sortNatural breaks if you don't pass it strings
1520
- var x = a.p.toString(), y = b.p.toString();
1521
- if ($.isFunction(cts)) {
1703
+ var x = a.p.toString(),
1704
+ y = b.p.toString();
1705
+ if ( $.isFunction( cts ) ) {
1522
1706
  // custom OVERALL text sorter
1523
- return cts(x, y, true, column, table);
1524
- } else if (typeof(cts) === 'object' && cts.hasOwnProperty(column)) {
1707
+ return cts( x, y, true, column, table );
1708
+ } else if ( typeof( cts ) === 'object' && cts.hasOwnProperty( column ) ) {
1525
1709
  // custom text sorter for a SPECIFIC COLUMN
1526
- return cts[column](x, y, true, column, table);
1527
- } else if (ts.sortNatural) {
1710
+ return cts[column]( x, y, true, column, table );
1711
+ } else if ( ts.sortNatural ) {
1528
1712
  // fall back to natural sort
1529
- return ts.sortNatural(x, y);
1713
+ return ts.sortNatural( x, y );
1530
1714
  }
1531
1715
  // using an older version! do a basic sort
1532
1716
  return true;
@@ -1534,187 +1718,224 @@ ts.filter = {
1534
1718
  // rebuild arry from sorted parsed data
1535
1719
  arry = [];
1536
1720
  len = parsed.length;
1537
- for (indx = 0; indx < len; indx++) {
1721
+ for ( indx = 0; indx < len; indx++ ) {
1538
1722
  arry.push( parsed[indx].t );
1539
1723
  }
1540
1724
  return arry;
1541
1725
  }
1542
1726
  },
1543
- getOptions: function(table, column, onlyAvail) {
1544
- table = $(table)[0];
1545
- var rowIndex, tbodyIndex, len, row, cache, cell,
1727
+ getOptions: function( table, column, onlyAvail ) {
1728
+ table = $( table )[0];
1729
+ var rowIndex, tbodyIndex, len, row, cache,
1546
1730
  c = table.config,
1547
1731
  wo = c.widgetOptions,
1548
1732
  arry = [];
1549
- for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
1733
+ for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
1550
1734
  cache = c.cache[tbodyIndex];
1551
1735
  len = c.cache[tbodyIndex].normalized.length;
1552
1736
  // loop through the rows
1553
- for (rowIndex = 0; rowIndex < len; rowIndex++) {
1554
- // get cached row from cache.row (old) or row data object (new; last item in normalized array)
1555
- row = cache.row ? cache.row[rowIndex] : cache.normalized[rowIndex][c.columns].$row[0];
1737
+ for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
1738
+ // get cached row from cache.row ( old ) or row data object
1739
+ // ( new; last item in normalized array )
1740
+ row = cache.row ?
1741
+ cache.row[ rowIndex ] :
1742
+ cache.normalized[ rowIndex ][ c.columns ].$row[0];
1556
1743
  // check if has class filtered
1557
- if (onlyAvail && row.className.match(wo.filter_filteredRow)) { continue; }
1744
+ if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) {
1745
+ continue;
1746
+ }
1558
1747
  // get non-normalized cell content
1559
- if (wo.filter_useParsedData || c.parsers[column].parsed || c.$headerIndexed[column].hasClass('filter-parsed')) {
1560
- arry.push( '' + cache.normalized[rowIndex][column] );
1748
+ if ( wo.filter_useParsedData ||
1749
+ c.parsers[column].parsed ||
1750
+ c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) {
1751
+ arry.push( '' + cache.normalized[ rowIndex ][ column ] );
1561
1752
  } else {
1562
- cell = row.cells[column];
1563
- if (cell) {
1564
- arry.push( $.trim( cell.getAttribute( c.textAttribute ) || cell.textContent || $(cell).text() ) );
1565
- }
1753
+ // get raw cached data instead of content directly from the cells
1754
+ arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] );
1566
1755
  }
1567
1756
  }
1568
1757
  }
1569
1758
  return arry;
1570
1759
  },
1571
- buildSelect: function(table, column, arry, updating, onlyAvail) {
1572
- table = $(table)[0];
1573
- column = parseInt(column, 10);
1574
- if (!table.config.cache || $.isEmptyObject(table.config.cache)) { return; }
1760
+ buildSelect: function( table, column, arry, updating, onlyAvail ) {
1761
+ table = $( table )[0];
1762
+ column = parseInt( column, 10 );
1763
+ if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) {
1764
+ return;
1765
+ }
1575
1766
  var indx, val, txt, t, $filters, $filter,
1576
1767
  c = table.config,
1577
1768
  wo = c.widgetOptions,
1578
- node = c.$headerIndexed[column],
1579
- // t.data('placeholder') won't work in jQuery older than 1.4.3
1580
- options = '<option value="">' + ( node.data('placeholder') || node.attr('data-placeholder') || wo.filter_placeholder.select || '' ) + '</option>',
1769
+ node = c.$headerIndexed[ column ],
1770
+ // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3
1771
+ options = '<option value="">' +
1772
+ ( node.data( 'placeholder' ) ||
1773
+ node.attr( 'data-placeholder' ) ||
1774
+ wo.filter_placeholder.select || ''
1775
+ ) + '</option>',
1581
1776
  // Get curent filter value
1582
- currentValue = c.$table.find('thead').find('select.' + tscss.filter + '[data-column="' + column + '"]').val();
1583
- // nothing included in arry (external source), so get the options from filter_selectSource or column data
1584
- if (typeof arry === 'undefined' || arry === '') {
1585
- arry = ts.filter.getOptionSource(table, column, onlyAvail);
1777
+ currentValue = c.$table
1778
+ .find( 'thead' )
1779
+ .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' )
1780
+ .val();
1781
+ // nothing included in arry ( external source ), so get the options from
1782
+ // filter_selectSource or column data
1783
+ if ( typeof arry === 'undefined' || arry === '' ) {
1784
+ arry = ts.filter.getOptionSource( table, column, onlyAvail );
1586
1785
  }
1587
1786
 
1588
- if ($.isArray(arry)) {
1787
+ if ( $.isArray( arry ) ) {
1589
1788
  // build option list
1590
- for (indx = 0; indx < arry.length; indx++) {
1591
- txt = arry[indx] = ('' + arry[indx]).replace(/\"/g, "&quot;");
1789
+ for ( indx = 0; indx < arry.length; indx++ ) {
1790
+ txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '&quot;' );
1592
1791
  val = txt;
1593
1792
  // allow including a symbol in the selectSource array
1594
- // "a-z|A through Z" so that "a-z" becomes the option value
1595
- // and "A through Z" becomes the option text
1596
- if (txt.indexOf(wo.filter_selectSourceSeparator) >= 0) {
1597
- t = txt.split(wo.filter_selectSourceSeparator);
1793
+ // 'a-z|A through Z' so that 'a-z' becomes the option value
1794
+ // and 'A through Z' becomes the option text
1795
+ if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
1796
+ t = txt.split( wo.filter_selectSourceSeparator );
1598
1797
  val = t[0];
1599
1798
  txt = t[1];
1600
1799
  }
1601
- // replace quotes - fixes #242 & ignore empty strings - see http://stackoverflow.com/q/14990971/145346
1602
- options += arry[indx] !== '' ? '<option ' + (val === txt ? '' : 'data-function-name="' + arry[indx] + '" ') + 'value="' + val + '">' + txt + '</option>' : '';
1800
+ // replace quotes - fixes #242 & ignore empty strings
1801
+ // see http://stackoverflow.com/q/14990971/145346
1802
+ options += arry[indx] !== '' ?
1803
+ '<option ' +
1804
+ ( val === txt ? '' : 'data-function-name="' + arry[indx] + '" ' ) +
1805
+ 'value="' + val + '">' + txt +
1806
+ '</option>' : '';
1603
1807
  }
1604
1808
  // clear arry so it doesn't get appended twice
1605
1809
  arry = [];
1606
1810
  }
1607
1811
 
1608
- // update all selects in the same column (clone thead in sticky headers & any external selects) - fixes 473
1609
- $filters = ( c.$filters ? c.$filters : c.$table.children('thead') ).find('.' + tscss.filter);
1610
- if (wo.filter_$externalFilters) {
1611
- $filters = $filters && $filters.length ? $filters.add(wo.filter_$externalFilters) : wo.filter_$externalFilters;
1812
+ // update all selects in the same column ( clone thead in sticky headers &
1813
+ // any external selects ) - fixes 473
1814
+ $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) )
1815
+ .find( '.' + tscss.filter );
1816
+ if ( wo.filter_$externalFilters ) {
1817
+ $filters = $filters && $filters.length ?
1818
+ $filters.add( wo.filter_$externalFilters ) :
1819
+ wo.filter_$externalFilters;
1612
1820
  }
1613
- $filter = $filters.filter('select[data-column="' + column + '"]');
1821
+ $filter = $filters.filter( 'select[data-column="' + column + '"]' );
1614
1822
 
1615
1823
  // make sure there is a select there!
1616
- if ($filter.length) {
1617
- $filter[ updating ? 'html' : 'append' ](options);
1618
- if (!$.isArray(arry)) {
1824
+ if ( $filter.length ) {
1825
+ $filter[ updating ? 'html' : 'append' ]( options );
1826
+ if ( !$.isArray( arry ) ) {
1619
1827
  // append options if arry is provided externally as a string or jQuery object
1620
- // options (default value) was already added
1621
- $filter.append(arry).val(currentValue);
1828
+ // options ( default value ) was already added
1829
+ $filter.append( arry ).val( currentValue );
1622
1830
  }
1623
- $filter.val(currentValue);
1831
+ $filter.val( currentValue );
1624
1832
  }
1625
1833
  },
1626
- buildDefault: function(table, updating) {
1834
+ buildDefault: function( table, updating ) {
1627
1835
  var columnIndex, $header, noSelect,
1628
1836
  c = table.config,
1629
1837
  wo = c.widgetOptions,
1630
1838
  columns = c.columns;
1631
1839
  // build default select dropdown
1632
- for (columnIndex = 0; columnIndex < columns; columnIndex++) {
1840
+ for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) {
1633
1841
  $header = c.$headerIndexed[columnIndex];
1634
- noSelect = !($header.hasClass('filter-false') || $header.hasClass('parser-false'));
1842
+ noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
1635
1843
  // look for the filter-select class; build/update it if found
1636
- if (($header.hasClass('filter-select') || ts.getColumnData( table, wo.filter_functions, columnIndex ) === true) && noSelect) {
1637
- ts.filter.buildSelect(table, columnIndex, '', updating, $header.hasClass(wo.filter_onlyAvail));
1844
+ if ( ( $header.hasClass( 'filter-select' ) ||
1845
+ ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) {
1846
+ ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) );
1638
1847
  }
1639
1848
  }
1640
1849
  }
1641
1850
  };
1642
1851
 
1643
- ts.getFilters = function(table, getRaw, setFilters, skipFirst) {
1852
+ ts.getFilters = function( table, getRaw, setFilters, skipFirst ) {
1644
1853
  var i, $filters, $column, cols,
1645
1854
  filters = false,
1646
- c = table ? $(table)[0].config : '',
1855
+ c = table ? $( table )[0].config : '',
1647
1856
  wo = c ? c.widgetOptions : '';
1648
- if (getRaw !== true && wo && !wo.filter_columnFilters) {
1649
- return $(table).data('lastSearch');
1857
+ if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) ||
1858
+ // setFilters called, but last search is exactly the same as the current
1859
+ // fixes issue #733 & #903 where calling update causes the input values to reset
1860
+ ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) {
1861
+ return $( table ).data( 'lastSearch' );
1650
1862
  }
1651
- if (c) {
1652
- if (c.$filters) {
1653
- $filters = c.$filters.find('.' + tscss.filter);
1863
+ if ( c ) {
1864
+ if ( c.$filters ) {
1865
+ $filters = c.$filters.find( '.' + tscss.filter );
1654
1866
  }
1655
- if (wo.filter_$externalFilters) {
1656
- $filters = $filters && $filters.length ? $filters.add(wo.filter_$externalFilters) : wo.filter_$externalFilters;
1867
+ if ( wo.filter_$externalFilters ) {
1868
+ $filters = $filters && $filters.length ?
1869
+ $filters.add( wo.filter_$externalFilters ) :
1870
+ wo.filter_$externalFilters;
1657
1871
  }
1658
- if ($filters && $filters.length) {
1872
+ if ( $filters && $filters.length ) {
1659
1873
  filters = setFilters || [];
1660
- for (i = 0; i < c.columns + 1; i++) {
1874
+ for ( i = 0; i < c.columns + 1; i++ ) {
1661
1875
  cols = ( i === c.columns ?
1662
- // "all" columns can now include a range or set of columms (data-column="0-2,4,6-7")
1876
+ // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' )
1663
1877
  wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector :
1664
1878
  '[data-column="' + i + '"]' );
1665
- $column = $filters.filter(cols);
1666
- if ($column.length) {
1879
+ $column = $filters.filter( cols );
1880
+ if ( $column.length ) {
1667
1881
  // move the latest search to the first slot in the array
1668
1882
  $column = ts.filter.getLatestSearch( $column );
1669
- if ($.isArray(setFilters)) {
1670
- // skip first (latest input) to maintain cursor position while typing
1671
- if (skipFirst) { $column.slice(1); }
1672
- if (i === c.columns) {
1673
- // prevent data-column="all" from filling data-column="0,1" (etc)
1674
- cols = $column.filter(wo.filter_anyColumnSelector);
1883
+ if ( $.isArray( setFilters ) ) {
1884
+ // skip first ( latest input ) to maintain cursor position while typing
1885
+ if ( skipFirst ) {
1886
+ $column.slice( 1 );
1887
+ }
1888
+ if ( i === c.columns ) {
1889
+ // prevent data-column='all' from filling data-column='0,1' ( etc )
1890
+ cols = $column.filter( wo.filter_anyColumnSelector );
1675
1891
  $column = cols.length ? cols : $column;
1676
1892
  }
1677
1893
  $column
1678
- .val( setFilters[i] )
1679
- .trigger('change.tsfilter');
1894
+ .val( setFilters[ i ] )
1895
+ .trigger( 'change.tsfilter' );
1680
1896
  } else {
1681
1897
  filters[i] = $column.val() || '';
1682
1898
  // don't change the first... it will move the cursor
1683
- if (i === c.columns) {
1684
- // don't update range columns from "all" setting
1685
- $column.slice(1).filter('[data-column*="' + $column.attr('data-column') + '"]').val( filters[i] );
1899
+ if ( i === c.columns ) {
1900
+ // don't update range columns from 'all' setting
1901
+ $column
1902
+ .slice( 1 )
1903
+ .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' )
1904
+ .val( filters[ i ] );
1686
1905
  } else {
1687
- $column.slice(1).val( filters[i] );
1906
+ $column
1907
+ .slice( 1 )
1908
+ .val( filters[ i ] );
1688
1909
  }
1689
1910
  }
1690
1911
  // save any match input dynamically
1691
- if (i === c.columns && $column.length) {
1912
+ if ( i === c.columns && $column.length ) {
1692
1913
  wo.filter_$anyMatch = $column;
1693
1914
  }
1694
1915
  }
1695
1916
  }
1696
1917
  }
1697
1918
  }
1698
- if (filters.length === 0) {
1919
+ if ( filters.length === 0 ) {
1699
1920
  filters = false;
1700
1921
  }
1701
1922
  return filters;
1702
1923
  };
1703
1924
 
1704
- ts.setFilters = function(table, filter, apply, skipFirst) {
1705
- var c = table ? $(table)[0].config : '',
1706
- valid = ts.getFilters(table, true, filter, skipFirst);
1707
- if (c && apply) {
1925
+ ts.setFilters = function( table, filter, apply, skipFirst ) {
1926
+ var c = table ? $( table )[0].config : '',
1927
+ valid = ts.getFilters( table, true, filter, skipFirst );
1928
+ if ( c && apply ) {
1708
1929
  // ensure new set filters are applied, even if the search is the same
1709
1930
  c.lastCombinedFilter = null;
1710
1931
  c.lastSearch = [];
1711
- ts.filter.searching(c.table, filter, skipFirst);
1712
- c.$table.trigger('filterFomatterUpdate');
1932
+ ts.filter.searching( c.table, filter, skipFirst );
1933
+ c.$table.trigger( 'filterFomatterUpdate' );
1713
1934
  }
1714
1935
  return !!valid;
1715
1936
  };
1716
1937
 
1717
- })(jQuery);
1938
+ })( jQuery );
1718
1939
 
1719
1940
  /*! Widget: stickyHeaders - updated 3/26/2015 (v2.21.3) *//*
1720
1941
  * Requires tablesorter v2.8+ and jQuery 1.4.3+
@@ -1722,7 +1943,7 @@ ts.setFilters = function(table, filter, apply, skipFirst) {
1722
1943
  */
1723
1944
  ;(function ($, window) {
1724
1945
  'use strict';
1725
- var ts = $.tablesorter = $.tablesorter || {};
1946
+ var ts = $.tablesorter || {};
1726
1947
 
1727
1948
  $.extend(ts.css, {
1728
1949
  sticky : 'tablesorter-stickyHeader', // stickyHeader
@@ -1988,10 +2209,10 @@ ts.addWidget({
1988
2209
 
1989
2210
  })(jQuery, window);
1990
2211
 
1991
- /*! Widget: resizable - updated 4/2/2015 (v2.21.5) */
2212
+ /*! Widget: resizable - updated 5/17/2015 (v2.22.0) */
1992
2213
  ;(function ($, window) {
1993
2214
  'use strict';
1994
- var ts = $.tablesorter = $.tablesorter || {};
2215
+ var ts = $.tablesorter || {};
1995
2216
 
1996
2217
  $.extend(ts.css, {
1997
2218
  resizableContainer : 'tablesorter-resizable-container',
@@ -2280,7 +2501,7 @@ ts.addWidget({
2280
2501
  init: function(table, thisWidget, c, wo) {
2281
2502
  ts.resizable.init( c, wo );
2282
2503
  },
2283
- remove: function( table, c, wo ) {
2504
+ remove: function( table, c, wo, refreshing ) {
2284
2505
  if (wo.$resizable_container) {
2285
2506
  var namespace = c.namespace + 'tsresize';
2286
2507
  c.$table.add( $( c.namespace + '_extra_table' ) )
@@ -2289,13 +2510,13 @@ ts.addWidget({
2289
2510
 
2290
2511
  wo.$resizable_container.remove();
2291
2512
  ts.resizable.toggleTextSelection( c, false );
2292
- ts.resizableReset( table );
2513
+ ts.resizableReset( table, refreshing );
2293
2514
  $( document ).unbind( 'mousemove' + namespace + ' mouseup' + namespace );
2294
2515
  }
2295
2516
  }
2296
2517
  });
2297
2518
 
2298
- ts.resizableReset = function( table, nosave ) {
2519
+ ts.resizableReset = function( table, refreshing ) {
2299
2520
  $( table ).each(function(){
2300
2521
  var index, $t,
2301
2522
  c = this.config,
@@ -2312,7 +2533,7 @@ ts.resizableReset = function( table, nosave ) {
2312
2533
  }
2313
2534
  // reset stickyHeader widths
2314
2535
  $( window ).trigger( 'resize' );
2315
- if ( ts.storage && !nosave ) {
2536
+ if ( ts.storage && !refreshing ) {
2316
2537
  ts.storage( this, ts.css.resizableStorage, {} );
2317
2538
  }
2318
2539
  }
@@ -2324,7 +2545,7 @@ ts.resizableReset = function( table, nosave ) {
2324
2545
  /*! Widget: saveSort */
2325
2546
  ;(function ($) {
2326
2547
  'use strict';
2327
- var ts = $.tablesorter = $.tablesorter || {};
2548
+ var ts = $.tablesorter || {};
2328
2549
 
2329
2550
  // this widget saves the last sort only if the
2330
2551
  // saveSort widget option is true AND the