jquery-tablesorter 1.16.5 → 1.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/jquery-tablesorter/version.rb +1 -1
  4. data/vendor/assets/javascripts/jquery-tablesorter/addons/pager/jquery.tablesorter.pager.js +38 -27
  5. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.combined.js +1104 -839
  6. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.js +167 -123
  7. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.widgets.js +938 -717
  8. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date.js +5 -5
  9. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-globalize.js +46 -0
  10. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-input-select.js +96 -72
  11. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-named-numbers.js +6 -5
  12. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-network.js +26 -17
  13. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-columns.js +1 -1
  14. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-editable.js +95 -42
  15. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-filter.js +921 -700
  16. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-grouping.js +5 -3
  17. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-math.js +22 -20
  18. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-output.js +7 -5
  19. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-pager.js +40 -29
  20. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-resizable.js +6 -6
  21. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-saveSort.js +1 -1
  22. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-scroller.js +53 -31
  23. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-stickyHeaders.js +1 -1
  24. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-storage.js +1 -1
  25. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-uitheme.js +1 -1
  26. data/vendor/assets/stylesheets/jquery-tablesorter/theme.black-ice.css +2 -1
  27. data/vendor/assets/stylesheets/jquery-tablesorter/theme.blue.css +2 -1
  28. data/vendor/assets/stylesheets/jquery-tablesorter/theme.bootstrap.css +2 -1
  29. data/vendor/assets/stylesheets/jquery-tablesorter/theme.bootstrap_2.css +8 -6
  30. data/vendor/assets/stylesheets/jquery-tablesorter/theme.dark.css +2 -1
  31. data/vendor/assets/stylesheets/jquery-tablesorter/theme.default.css +1 -1
  32. metadata +3 -2
@@ -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