jquery-tablesorter 1.12.7 → 1.12.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7c455a0bdd1d4c20268962380df23e8f5ddfb9a4
4
- data.tar.gz: a7f4ee044f7c9fdd6c0cdd3e212e3c9e4937c523
3
+ metadata.gz: 3f537540ad9cb3ae7519f0ede00b10a967df49b5
4
+ data.tar.gz: 4e043ff89f30abfa0bb964799a352dbe63d61b81
5
5
  SHA512:
6
- metadata.gz: a4a42e44805dc3e79d49dbfb1d9ab46331ff5c18861fb8833cf4db60566e4a2c5f75bbc71fca95267b2367fdb546482304826c05902463eb2235f5fe9c024f59
7
- data.tar.gz: 33b80af01864c6858f1e9dd8c65726683be68ed68f7338fe6265a2c46765863bb5456521fa2c92baad05287087f1a0db4d979810bfc0e2ef4f4c001755387a6d
6
+ metadata.gz: a1ee4421ddd5364f2f8471cdfa0ca6c1a9ef3794f53721092a859236b7492e756c3a925a7e9d4e6c5bc2725d2d8ba29be917ff82c9bb85972c218090ad54bf1a
7
+ data.tar.gz: c71c7fd64e88115f2a3fb66a3205c050001c4da2cfed4cf0a1178a4a18932d2ede7d40cc0b43b2ae25cfa3d1ad9e21bc32880acf0ceb5f001d2887817d54a4f3
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Simple integration of jquery-tablesorter into the asset pipeline.
6
6
 
7
- Current tablesorter version: 2.17.7 (8/9/2014), [documentation]
7
+ Current tablesorter version: 2.17.8 (9/15/2014), [documentation]
8
8
 
9
9
  Any issue associated with the js/css files, please report to [Mottie's fork].
10
10
 
@@ -1,3 +1,3 @@
1
1
  module JqueryTablesorter
2
- VERSION = '1.12.7'
2
+ VERSION = '1.12.8'
3
3
  end
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * tablesorter pager plugin
3
- * updated 8/1/2014 (v2.17.6)
3
+ * updated 9/15/2014 (v2.17.8)
4
4
  */
5
5
  /*jshint browser:true, jquery:true, unused:false */
6
6
  ;(function($) {
@@ -58,13 +58,16 @@
58
58
  // starting page of the pager (zero based index)
59
59
  page: 0,
60
60
 
61
- // reset pager after filtering; set to desired page #
62
- // set to false to not change page at filter start
61
+ // reset pager after filtering; set to desired page #
62
+ // set to false to not change page at filter start
63
63
  pageReset: 0,
64
64
 
65
65
  // Number of visible rows
66
66
  size: 10,
67
67
 
68
+ // Number of options to include in the pager number selector
69
+ maxOptionSize: 20,
70
+
68
71
  // Save pager page & size if the storage script is loaded (requires $.tablesorter.storage in jquery.tablesorter.widgets.js)
69
72
  savePages: true,
70
73
 
@@ -186,10 +189,37 @@
186
189
  if ( p.$goto.length ) {
187
190
  t = '';
188
191
  pg = Math.min( p.totalPages, p.filteredPages );
189
- for ( i = 1; i <= pg; i++ ) {
190
- t += '<option>' + i + '</option>';
192
+ // Filter the options page number link array if it's larger than 'maxOptionSize'
193
+ // as large page set links will slow the browser on large dom inserts
194
+ var skip_set_size = Math.floor(pg / p.maxOptionSize),
195
+ large_collection = pg > p.maxOptionSize,
196
+ current_page = p.page + 1,
197
+ start_page = 1,
198
+ end_page = pg,
199
+ option_pages = [];
200
+ //construct default options pages array
201
+ var option_pages_start_page = (large_collection && current_page == 1) ? skip_set_size : 1;
202
+ for (i = option_pages_start_page; i <= pg;) {
203
+ option_pages.push(i);
204
+ i = large_collection ? i + skip_set_size : i++;
205
+ }
206
+ if (large_collection) {
207
+ var central_focus_size = Math.floor(p.maxOptionSize / 2) - 1,
208
+ lower_focus_window = Math.abs(Math.floor(current_page - central_focus_size/2)),
209
+ focus_option_pages = [];
210
+ start_page = Math.min(current_page, lower_focus_window);
211
+ end_page = start_page + central_focus_size;
212
+ //construct an array to get a focus set around the current page
213
+ for (i = start_page; i <= end_page ; i++) focus_option_pages.push(i);
214
+ var insert_index = Math.floor(option_pages.length / 2) - Math.floor(focus_option_pages.length / 2);
215
+ Array.prototype.splice.apply(option_pages, [ insert_index, focus_option_pages.length ].concat(focus_option_pages));
216
+ option_pages.sort(function sortNumber(a,b) { return a - b; });
217
+ }
218
+ for ( i = 0; i < option_pages.length; i++) {
219
+ t += '<option>' + option_pages[i] + '</option>';
191
220
  }
192
- p.$goto.html(t).val( p.page + 1 );
221
+ p.$goto[0].innerHTML = t;
222
+ p.$goto[0].value = current_page;
193
223
  }
194
224
  // rebind startRow/page inputs
195
225
  $out.find('.ts-startRow, .ts-page').unbind('change').bind('change', function(){
@@ -532,9 +562,8 @@
532
562
  }
533
563
  updatePageDisplay(table, p);
534
564
  if ( !p.isDisabled ) { fixHeight(table, p); }
535
- $t.trigger('applyWidgets');
536
565
  if (table.isUpdating) {
537
- $t.trigger("updateComplete", table);
566
+ $t.trigger('updateComplete', [ table, true ]);
538
567
  }
539
568
  },
540
569
 
@@ -553,6 +582,7 @@
553
582
  .removeAttr('aria-describedby')
554
583
  .find('tr.pagerSavedHeightSpacer').remove();
555
584
  renderTable(table, table.config.rowsCopy, p);
585
+ $(table).trigger('applyWidgets');
556
586
  if (table.config.debug) {
557
587
  ts.log('pager disabled');
558
588
  }
@@ -621,7 +651,7 @@
621
651
  .trigger('pageMoved', p)
622
652
  .trigger('applyWidgets');
623
653
  if (table.isUpdating) {
624
- $t.trigger('updateComplete');
654
+ $t.trigger('updateComplete', [ table, true ]);
625
655
  }
626
656
  }
627
657
  },
@@ -751,7 +781,7 @@
751
781
  p.regexRows = new RegExp('(' + (wo.filter_filteredRow || 'filtered') + '|' + c.selectorRemove.replace(/^(\w+\.)/g,'') + '|' + c.cssChildRow + ')');
752
782
 
753
783
  $t
754
- .unbind('filterStart filterEnd sortEnd disable enable destroy update updateRows updateAll addRows pageSize '.split(' ').join('.pager '))
784
+ .unbind('filterStart filterEnd sortEnd disable enable destroy updateComplete pageSize '.split(' ').join('.pager '))
755
785
  .bind('filterStart.pager', function(e, filters) {
756
786
  p.currentFilters = filters;
757
787
  // don't change page is filters are the same (pager updating, etc)
@@ -769,6 +799,7 @@
769
799
  // update page display first, so we update p.filteredPages
770
800
  updatePageDisplay(table, p, false);
771
801
  moveToPage(table, p, false);
802
+ c.$table.trigger('applyWidgets');
772
803
  fixHeight(table, p);
773
804
  }
774
805
  })
@@ -784,12 +815,18 @@
784
815
  e.stopPropagation();
785
816
  destroyPager(table, p);
786
817
  })
787
- .bind('update updateRows updateAll addRows '.split(' ').join('.pager '), function(e){
818
+ .bind('updateComplete.pager', function(e, table, triggered){
788
819
  e.stopPropagation();
820
+ // table can be unintentionally undefined in tablesorter v2.17.7 and earlier
821
+ if ( !table || triggered ) { return; }
789
822
  fixHeight(table, p);
790
- var $rows = c.$tbodies.eq(0).children('tr');
823
+ var $rows = c.$tbodies.eq(0).children('tr').not(c.selectorRemove);
791
824
  p.totalRows = $rows.length - ( p.countChildRows ? 0 : $rows.filter('.' + c.cssChildRow).length );
792
825
  p.totalPages = Math.ceil( p.totalRows / p.size );
826
+ if ($rows.length && c.rowsCopy && c.rowsCopy.length === 0) {
827
+ // make a copy of all table rows once the cache has been built
828
+ updateCache(table);
829
+ }
793
830
  updatePageDisplay(table, p);
794
831
  hideRows(table, p);
795
832
  })
@@ -1,5 +1,5 @@
1
1
  /**!
2
- * TableSorter 2.17.7 - Client-side table sorting with ease!
2
+ * TableSorter 2.17.8 - Client-side table sorting with ease!
3
3
  * @requires jQuery v1.2.6+
4
4
  *
5
5
  * Copyright (c) 2007 Christian Bach
@@ -24,7 +24,7 @@
24
24
 
25
25
  var ts = this;
26
26
 
27
- ts.version = "2.17.7";
27
+ ts.version = "2.17.8";
28
28
 
29
29
  ts.parsers = [];
30
30
  ts.widgets = [];
@@ -246,9 +246,9 @@
246
246
  p = ts.getParserById( ts.getData(h, ch, 'sorter') );
247
247
  np = ts.getData(h, ch, 'parser') === 'false';
248
248
  // empty cells behaviour - keeping emptyToBottom for backwards compatibility
249
- c.empties[i] = ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' );
249
+ c.empties[i] = ( ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' ) ).toLowerCase();
250
250
  // text strings behaviour in numerical sorts
251
- c.strings[i] = ts.getData(h, ch, 'string') || c.stringTo || 'max';
251
+ c.strings[i] = ( ts.getData(h, ch, 'string') || c.stringTo || 'max' ).toLowerCase();
252
252
  if (np) {
253
253
  p = ts.getParserById('no-parser');
254
254
  }
@@ -447,14 +447,16 @@
447
447
  ch = ts.getColumnData( table, c.headers, index, true );
448
448
  // save original header content
449
449
  c.headerContent[index] = $(this).html();
450
- // set up header template
451
- t = c.headerTemplate.replace(/\{content\}/g, $(this).html()).replace(/\{icon\}/g, i);
452
- if (c.onRenderTemplate) {
453
- h = c.onRenderTemplate.apply($t, [index, t]);
454
- if (h && typeof h === 'string') { t = h; } // only change t if something is returned
450
+ // if headerTemplate is empty, don't reformat the header cell
451
+ if ( c.headerTemplate !== '' ) {
452
+ // set up header template
453
+ t = c.headerTemplate.replace(/\{content\}/g, $(this).html()).replace(/\{icon\}/g, i);
454
+ if (c.onRenderTemplate) {
455
+ h = c.onRenderTemplate.apply($t, [index, t]);
456
+ if (h && typeof h === 'string') { t = h; } // only change t if something is returned
457
+ }
458
+ $(this).html('<div class="' + ts.css.headerIn + '">' + t + '</div>'); // faster than wrapInner
455
459
  }
456
- $(this).html('<div class="' + ts.css.headerIn + '">' + t + '</div>'); // faster than wrapInner
457
-
458
460
  if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index]); }
459
461
  this.column = parseInt( $(this).attr('data-column'), 10);
460
462
  this.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2];
@@ -560,14 +562,16 @@
560
562
 
561
563
  // automatically add col group, and column sizes if set
562
564
  function fixColumnWidth(table) {
563
- if (table.config.widthFixed && $(table).find('colgroup').length === 0) {
564
- var colgroup = $('<colgroup>'),
565
- overallWidth = $(table).width();
565
+ var colgroup, overallWidth,
566
+ c = table.config;
567
+ if (c.widthFixed && c.$table.find('colgroup').length === 0) {
568
+ colgroup = $('<colgroup>');
569
+ overallWidth = $(table).width();
566
570
  // only add col for visible columns - fixes #371
567
- $(table.tBodies[0]).find("tr:first").children(":visible").each(function() {
571
+ $(table.tBodies).not('.' + c.cssInfoBlock).find("tr:first").children(":visible").each(function() {
568
572
  colgroup.append($('<col>').css('width', parseInt(($(this).width()/overallWidth)*1000, 10)/10 + '%'));
569
573
  });
570
- $(table).prepend(colgroup);
574
+ c.$table.prepend(colgroup);
571
575
  }
572
576
  }
573
577
 
@@ -798,7 +802,7 @@
798
802
  function resortComplete($table, callback){
799
803
  var table = $table[0];
800
804
  if (table.isUpdating) {
801
- $table.trigger('updateComplete');
805
+ $table.trigger('updateComplete', table);
802
806
  }
803
807
  if ($.isFunction(callback)) {
804
808
  callback($table[0]);
@@ -1047,7 +1051,10 @@
1047
1051
  return (version[0] > 1) || (version[0] === 1 && parseInt(version[1], 10) >= 4);
1048
1052
  })($.fn.jquery.split("."));
1049
1053
  // digit sort text location; keeping max+/- for backwards compatibility
1050
- c.string = { 'max': 1, 'min': -1, 'emptyMin': 1, 'emptyMax': -1, 'zero': 0, 'none': 0, 'null': 0, 'top': true, 'bottom': false };
1054
+ c.string = { 'max': 1, 'min': -1, 'emptymin': 1, 'emptymax': -1, 'zero': 0, 'none': 0, 'null': 0, 'top': true, 'bottom': false };
1055
+ // ensure case insensitivity
1056
+ c.emptyTo = c.emptyTo.toLowerCase();
1057
+ c.stringTo = c.stringTo.toLowerCase();
1051
1058
  // add table theme class only if there isn't already one there
1052
1059
  if (!/tablesorter\-/.test($table.attr('class'))) {
1053
1060
  k = (c.theme !== '' ? ' tablesorter-' + c.theme : '');
@@ -1761,6 +1768,7 @@
1761
1768
  format: function(s) {
1762
1769
  return s ? $.trim(s.replace(/(https?|ftp|file):\/\//, '')) : s;
1763
1770
  },
1771
+ parsed : true, // filter widget flag
1764
1772
  type: "text"
1765
1773
  });
1766
1774
 
@@ -1853,7 +1861,7 @@
1853
1861
  id: "zebra",
1854
1862
  priority: 90,
1855
1863
  format: function(table, c, wo) {
1856
- var $tb, $tv, $tr, row, even, time, k, l,
1864
+ var $tb, $tv, $tr, row, even, time, k,
1857
1865
  child = new RegExp(c.cssChildRow, 'i'),
1858
1866
  b = c.$tbodies;
1859
1867
  if (c.debug) {
@@ -1861,21 +1869,18 @@
1861
1869
  }
1862
1870
  for (k = 0; k < b.length; k++ ) {
1863
1871
  // loop through the visible rows
1872
+ row = 0;
1864
1873
  $tb = b.eq(k);
1865
- l = $tb.children('tr').length;
1866
- if (l > 1) {
1867
- row = 0;
1868
- $tv = $tb.children('tr:visible').not(c.selectorRemove);
1869
- // revered back to using jQuery each - strangely it's the fastest method
1870
- /*jshint loopfunc:true */
1871
- $tv.each(function(){
1872
- $tr = $(this);
1873
- // style children rows the same way the parent row was styled
1874
- if (!child.test(this.className)) { row++; }
1875
- even = (row % 2 === 0);
1876
- $tr.removeClass(wo.zebra[even ? 1 : 0]).addClass(wo.zebra[even ? 0 : 1]);
1877
- });
1878
- }
1874
+ $tv = $tb.children('tr:visible').not(c.selectorRemove);
1875
+ // revered back to using jQuery each - strangely it's the fastest method
1876
+ /*jshint loopfunc:true */
1877
+ $tv.each(function(){
1878
+ $tr = $(this);
1879
+ // style child rows the same way the parent row was styled
1880
+ if (!child.test(this.className)) { row++; }
1881
+ even = (row % 2 === 0);
1882
+ $tr.removeClass(wo.zebra[even ? 1 : 0]).addClass(wo.zebra[even ? 0 : 1]);
1883
+ });
1879
1884
  }
1880
1885
  if (c.debug) {
1881
1886
  ts.benchmark("Applying Zebra widget", time);
@@ -1,4 +1,4 @@
1
- /*! tableSorter 2.16+ widgets - updated 8/9/2014 (v2.17.7)
1
+ /*! tableSorter 2.16+ widgets - updated 9/15/2014 (v2.17.8)
2
2
  *
3
3
  * Column Styles
4
4
  * Column Filters
@@ -245,7 +245,7 @@ ts.addWidget({
245
245
  ts.benchmark("Applying " + theme + " theme", time);
246
246
  }
247
247
  },
248
- remove: function(table, c, wo) {
248
+ remove: function(table, c) {
249
249
  var $table = c.$table,
250
250
  theme = c.theme || 'jui',
251
251
  themes = ts.themes[ theme ] || ts.themes.jui,
@@ -354,6 +354,8 @@ ts.addWidget({
354
354
  filter_childRows : false, // if true, filter includes child row content in the search
355
355
  filter_columnFilters : true, // if true, a filter will be added to the top of each table column
356
356
  filter_cssFilter : '', // css class name added to the filter row & each input in the row (tablesorter-filter is ALWAYS added)
357
+ 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.
358
+ filter_excludeFilter : {}, // filters to exclude, per column
357
359
  filter_external : '', // jQuery selector string (or jQuery object) of external filters
358
360
  filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin
359
361
  filter_formatter : null, // add custom filter elements to the filter row
@@ -410,24 +412,25 @@ ts.filter = {
410
412
  type : /undefined|number/, // check type
411
413
  exact : /(^[\"|\'|=]+)|([\"|\'|=]+$)/g, // exact match (allow '==')
412
414
  nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser)
413
- operators : /[<>=]/g // replace operators
415
+ operators : /[<>=]/g, // replace operators
416
+ query : '(q|query)' // replace filter queries
414
417
  },
415
- // function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed )
416
- // filter = array of filter input values; iFilter = same array, except lowercase
417
- // exact = table cell text (or parsed data if column parser enabled)
418
- // iExact = same as exact, except lowercase
419
- // cached = table cell text from cache, so it has been parsed
420
- // index = column index; table = table element (DOM)
421
- // wo = widget options (table.config.widgetOptions)
422
- // parsed = array (by column) of boolean values (from filter_useParsedData or "filter-parsed" class)
418
+ // function( c, data ) { }
419
+ // c = table.config
420
+ // data.filter = array of filter input values; data.iFilter = same array, except lowercase
421
+ // data.exact = table cell text (or parsed data if column parser enabled)
422
+ // data.iExact = same as data.exact, except lowercase
423
+ // data.cache = table cell text from cache, so it has been parsed
424
+ // data.index = column index; table = table element (DOM)
425
+ // data.parsed = array (by column) of boolean values (from filter_useParsedData or "filter-parsed" class)
423
426
  types: {
424
427
  // Look for regex
425
- regex: function( filter, iFilter, exact, iExact ) {
426
- if ( ts.filter.regex.regex.test(iFilter) ) {
428
+ regex: function( c, data ) {
429
+ if ( ts.filter.regex.regex.test(data.iFilter) ) {
427
430
  var matches,
428
- regex = ts.filter.regex.regex.exec(iFilter);
431
+ regex = ts.filter.regex.regex.exec(data.iFilter);
429
432
  try {
430
- matches = new RegExp(regex[1], regex[2]).test( iExact );
433
+ matches = new RegExp(regex[1], regex[2]).test( data.iExact );
431
434
  } catch (error) {
432
435
  matches = false;
433
436
  }
@@ -436,27 +439,29 @@ ts.filter = {
436
439
  return null;
437
440
  },
438
441
  // Look for operators >, >=, < or <=
439
- operators: function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed ) {
440
- if ( /^[<>]=?/.test(iFilter) ) {
442
+ operators: function( c, data ) {
443
+ if ( /^[<>]=?/.test(data.iFilter) ) {
441
444
  var cachedValue, result,
442
- c = table.config,
443
- query = ts.formatFloat( iFilter.replace(ts.filter.regex.operators, ''), table ),
445
+ table = c.table,
446
+ index = data.index,
447
+ parsed = data.parsed[index],
448
+ query = ts.formatFloat( data.iFilter.replace(ts.filter.regex.operators, ''), table ),
444
449
  parser = c.parsers[index],
445
450
  savedSearch = query;
446
451
  // parse filter value in case we're comparing numbers (dates)
447
- if (parsed[index] || parser.type === 'numeric') {
448
- result = ts.filter.parseFilter(table, $.trim('' + iFilter.replace(ts.filter.regex.operators, '')), index, parsed[index], true);
452
+ if (parsed || parser.type === 'numeric') {
453
+ result = ts.filter.parseFilter(c, $.trim('' + data.iFilter.replace(ts.filter.regex.operators, '')), index, parsed, true);
449
454
  query = ( typeof result === "number" && result !== '' && !isNaN(result) ) ? result : query;
450
455
  }
451
456
 
452
457
  // iExact may be numeric - see issue #149;
453
458
  // check if cached is defined, because sometimes j goes out of range? (numeric columns)
454
- cachedValue = ( parsed[index] || parser.type === 'numeric' ) && !isNaN(query) && typeof cached !== 'undefined' ? cached :
455
- isNaN(iExact) ? ts.formatFloat( iExact.replace(ts.filter.regex.nondigit, ''), table) :
456
- ts.formatFloat( iExact, table );
459
+ cachedValue = ( parsed || parser.type === 'numeric' ) && !isNaN(query) && typeof data.cache !== 'undefined' ? data.cache :
460
+ isNaN(data.iExact) ? ts.formatFloat( data.iExact.replace(ts.filter.regex.nondigit, ''), table) :
461
+ ts.formatFloat( data.iExact, table );
457
462
 
458
- if ( />/.test(iFilter) ) { result = />=/.test(iFilter) ? cachedValue >= query : cachedValue > query; }
459
- if ( /</.test(iFilter) ) { result = /<=/.test(iFilter) ? cachedValue <= query : cachedValue < query; }
463
+ if ( />/.test(data.iFilter) ) { result = />=/.test(data.iFilter) ? cachedValue >= query : cachedValue > query; }
464
+ if ( /</.test(data.iFilter) ) { result = /<=/.test(data.iFilter) ? cachedValue <= query : cachedValue < query; }
460
465
  // keep showing all rows if nothing follows the operator
461
466
  if ( !result && savedSearch === '' ) { result = true; }
462
467
  return result;
@@ -464,37 +469,40 @@ ts.filter = {
464
469
  return null;
465
470
  },
466
471
  // Look for a not match
467
- notMatch: function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed ) {
468
- if ( /^\!/.test(iFilter) ) {
469
- iFilter = ts.filter.parseFilter(table, iFilter.replace('!', ''), index, parsed[index]);
470
- if (ts.filter.regex.exact.test(iFilter)) {
472
+ notMatch: function( c, data ) {
473
+ if ( /^\!/.test(data.iFilter) ) {
474
+ var indx,
475
+ filter = ts.filter.parseFilter(c, data.iFilter.replace('!', ''), data.index, data.parsed[data.index]);
476
+ if (ts.filter.regex.exact.test(filter)) {
471
477
  // look for exact not matches - see #628
472
- iFilter = iFilter.replace(ts.filter.regex.exact, '');
473
- return iFilter === '' ? true : $.trim(iFilter) !== iExact;
478
+ filter = filter.replace(ts.filter.regex.exact, '');
479
+ return filter === '' ? true : $.trim(filter) !== data.iExact;
474
480
  } else {
475
- var indx = iExact.search( $.trim(iFilter) );
476
- return iFilter === '' ? true : !(wo.filter_startsWith ? indx === 0 : indx >= 0);
481
+ indx = data.iExact.search( $.trim(filter) );
482
+ return filter === '' ? true : !(c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 0);
477
483
  }
478
484
  }
479
485
  return null;
480
486
  },
481
487
  // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
482
- exact: function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed, rowArray ) {
488
+ exact: function( c, data ) {
483
489
  /*jshint eqeqeq:false */
484
- if (ts.filter.regex.exact.test(iFilter)) {
485
- var fltr = ts.filter.parseFilter(table, iFilter.replace(ts.filter.regex.exact, ''), index, parsed[index]);
486
- return rowArray ? $.inArray(fltr, rowArray) >= 0 : fltr == iExact;
490
+ if (ts.filter.regex.exact.test(data.iFilter)) {
491
+ var filter = ts.filter.parseFilter(c, data.iFilter.replace(ts.filter.regex.exact, ''), data.index, data.parsed[data.index]);
492
+ return data.anyMatch ? $.inArray(filter, data.rowArray) >= 0 : filter == data.iExact;
487
493
  }
488
494
  return null;
489
495
  },
490
496
  // Look for an AND or && operator (logical and)
491
- and : function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed ) {
492
- if ( ts.filter.regex.andTest.test(filter) ) {
493
- var query = iFilter.split( ts.filter.regex.andSplit ),
494
- result = iExact.search( $.trim(ts.filter.parseFilter(table, query[0], index, parsed[index])) ) >= 0,
497
+ and : function( c, data ) {
498
+ if ( ts.filter.regex.andTest.test(data.filter) ) {
499
+ var index = data.index,
500
+ parsed = data.parsed[index],
501
+ query = data.iFilter.split( ts.filter.regex.andSplit ),
502
+ result = data.iExact.search( $.trim( ts.filter.parseFilter(c, query[0], index, parsed) ) ) >= 0,
495
503
  indx = query.length - 1;
496
504
  while (result && indx) {
497
- result = result && iExact.search( $.trim(ts.filter.parseFilter(table, query[indx], index, parsed[index])) ) >= 0;
505
+ result = result && data.iExact.search( $.trim( ts.filter.parseFilter(c, query[indx], index, parsed) ) ) >= 0;
498
506
  indx--;
499
507
  }
500
508
  return result;
@@ -502,52 +510,55 @@ ts.filter = {
502
510
  return null;
503
511
  },
504
512
  // Look for a range (using " to " or " - ") - see issue #166; thanks matzhu!
505
- range : function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed ) {
506
- if ( ts.filter.regex.toTest.test(iFilter) ) {
513
+ range : function( c, data ) {
514
+ if ( ts.filter.regex.toTest.test(data.iFilter) ) {
507
515
  var result, tmp,
508
- c = table.config,
516
+ table = c.table,
517
+ index = data.index,
518
+ parsed = data.parsed[index],
509
519
  // make sure the dash is for a range and not indicating a negative number
510
- query = iFilter.split( ts.filter.regex.toSplit ),
511
- range1 = ts.formatFloat( ts.filter.parseFilter(table, query[0].replace(ts.filter.regex.nondigit, ''), index, parsed[index]), table ),
512
- range2 = ts.formatFloat( ts.filter.parseFilter(table, query[1].replace(ts.filter.regex.nondigit, ''), index, parsed[index]), table );
520
+ query = data.iFilter.split( ts.filter.regex.toSplit ),
521
+ range1 = ts.formatFloat( ts.filter.parseFilter(c, query[0].replace(ts.filter.regex.nondigit, ''), index, parsed), table ),
522
+ range2 = ts.formatFloat( ts.filter.parseFilter(c, query[1].replace(ts.filter.regex.nondigit, ''), index, parsed), table );
513
523
  // parse filter value in case we're comparing numbers (dates)
514
- if (parsed[index] || c.parsers[index].type === 'numeric') {
524
+ if (parsed || c.parsers[index].type === 'numeric') {
515
525
  result = c.parsers[index].format('' + query[0], table, c.$headers.eq(index), index);
516
526
  range1 = (result !== '' && !isNaN(result)) ? result : range1;
517
527
  result = c.parsers[index].format('' + query[1], table, c.$headers.eq(index), index);
518
528
  range2 = (result !== '' && !isNaN(result)) ? result : range2;
519
529
  }
520
- result = ( parsed[index] || c.parsers[index].type === 'numeric' ) && !isNaN(range1) && !isNaN(range2) ? cached :
521
- isNaN(iExact) ? ts.formatFloat( iExact.replace(ts.filter.regex.nondigit, ''), table) :
522
- ts.formatFloat( iExact, table );
530
+ result = ( parsed || c.parsers[index].type === 'numeric' ) && !isNaN(range1) && !isNaN(range2) ? data.cache :
531
+ isNaN(data.iExact) ? ts.formatFloat( data.iExact.replace(ts.filter.regex.nondigit, ''), table) :
532
+ ts.formatFloat( data.iExact, table );
523
533
  if (range1 > range2) { tmp = range1; range1 = range2; range2 = tmp; } // swap
524
534
  return (result >= range1 && result <= range2) || (range1 === '' || range2 === '');
525
535
  }
526
536
  return null;
527
537
  },
528
538
  // Look for wild card: ? = single, * = multiple, or | = logical OR
529
- wild : function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed, rowArray ) {
530
- if ( /[\?|\*]/.test(iFilter) || ts.filter.regex.orReplace.test(filter) ) {
531
- var c = table.config,
532
- query = ts.filter.parseFilter(table, iFilter.replace(ts.filter.regex.orReplace, "|"), index, parsed[index]);
539
+ wild : function( c, data ) {
540
+ if ( /[\?|\*]/.test(data.iFilter) || ts.filter.regex.orReplace.test(data.filter) ) {
541
+ var index = data.index,
542
+ parsed = data.parsed[index],
543
+ query = ts.filter.parseFilter(c, data.iFilter.replace(ts.filter.regex.orReplace, "|"), index, parsed);
533
544
  // look for an exact match with the "or" unless the "filter-match" class is found
534
545
  if (!c.$headers.filter('[data-column="' + index + '"]:last').hasClass('filter-match') && /\|/.test(query)) {
535
- query = $.isArray(rowArray) ? '(' + query + ')' : '^(' + query + ')$';
546
+ query = data.anyMatch && $.isArray(data.rowArray) ? '(' + query + ')' : '^(' + query + ')$';
536
547
  }
537
548
  // parsing the filter may not work properly when using wildcards =/
538
- return new RegExp( query.replace(/\?/g, '\\S{1}').replace(/\*/g, '\\S*') ).test(iExact);
549
+ return new RegExp( query.replace(/\?/g, '\\S{1}').replace(/\*/g, '\\S*') ).test(data.iExact);
539
550
  }
540
551
  return null;
541
552
  },
542
553
  // fuzzy text search; modified from https://github.com/mattyork/fuzzy (MIT license)
543
- fuzzy: function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed ) {
544
- if ( /^~/.test(iFilter) ) {
554
+ fuzzy: function( c, data ) {
555
+ if ( /^~/.test(data.iFilter) ) {
545
556
  var indx,
546
557
  patternIndx = 0,
547
- len = iExact.length,
548
- pattern = ts.filter.parseFilter(table, iFilter.slice(1), index, parsed[index]);
558
+ len = data.iExact.length,
559
+ pattern = ts.filter.parseFilter(c, data.iFilter.slice(1), data.index, data.parsed[data.index]);
549
560
  for (indx = 0; indx < len; indx++) {
550
- if (iExact[indx] === pattern[patternIndx]) {
561
+ if (data.iExact[indx] === pattern[patternIndx]) {
551
562
  patternIndx += 1;
552
563
  }
553
564
  }
@@ -579,7 +590,9 @@ ts.filter = {
579
590
  wo.filter_initTimer = null;
580
591
  wo.filter_formatterCount = 0;
581
592
  wo.filter_formatterInit = [];
593
+ wo.filter_initializing = true;
582
594
 
595
+ txt = '\\{' + ts.filter.regex.query + '\\}';
583
596
  $.extend( regex, {
584
597
  child : new RegExp(c.cssChildRow),
585
598
  filtered : new RegExp(wo.filter_filteredRow),
@@ -588,7 +601,9 @@ ts.filter = {
588
601
  toSplit : new RegExp('(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi'),
589
602
  andTest : new RegExp('\\s+(' + ts.language.and + '|&&)\\s+', 'i'),
590
603
  andSplit : new RegExp('(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi'),
591
- orReplace : new RegExp('\\s+(' + ts.language.or + ')\\s+', 'gi')
604
+ orReplace : new RegExp('\\s+(' + ts.language.or + ')\\s+', 'gi'),
605
+ iQuery : new RegExp(txt, 'i'),
606
+ igQuery : new RegExp(txt, 'ig')
592
607
  });
593
608
 
594
609
  // don't build filter row if columnFilters is false or all columns are set to "filter-false" - issue #156
@@ -741,7 +756,13 @@ ts.filter = {
741
756
  },
742
757
  filterInitComplete: function(c){
743
758
  var wo = c.widgetOptions,
744
- count = 0;
759
+ count = 0,
760
+ completed = function(){
761
+ wo.filter_initialized = true;
762
+ wo.filter_initializing = false;
763
+ ts.filter.findRows(c.table, c.$table.data('lastSearch'), null);
764
+ c.$table.trigger('filterInit', c);
765
+ };
745
766
  $.each( wo.filter_formatterInit, function(i, val) {
746
767
  if (val === 1) {
747
768
  count++;
@@ -750,17 +771,16 @@ ts.filter = {
750
771
  clearTimeout(wo.filter_initTimer);
751
772
  if (!wo.filter_initialized && count === wo.filter_formatterCount) {
752
773
  // filter widget initialized
753
- wo.filter_initialized = true;
754
- c.$table.trigger('filterInit', c);
774
+ completed();
755
775
  } else if (!wo.filter_initialized) {
756
776
  // fall back in case a filter_formatter doesn't call
757
777
  // $.tablesorter.filter.formatterUpdated($cell, column), and the count is off
758
778
  wo.filter_initTimer = setTimeout(function(){
759
- wo.filter_initialized = true;
760
- c.$table.trigger('filterInit', c);
779
+ completed();
761
780
  }, 500);
762
781
  }
763
782
  },
783
+
764
784
  setDefaults: function(table, c, wo) {
765
785
  var isArray, saved, indx,
766
786
  // get current (default) filters
@@ -780,10 +800,9 @@ ts.filter = {
780
800
  c.$table.data('lastSearch', filters);
781
801
  return filters;
782
802
  },
783
- parseFilter: function(table, filter, column, parsed, forceParse){
784
- var c = table.config;
803
+ parseFilter: function(c, filter, column, parsed, forceParse){
785
804
  return forceParse || parsed ?
786
- c.parsers[column].format( filter, table, [], column ) :
805
+ c.parsers[column].format( filter, c.table, [], column ) :
787
806
  filter;
788
807
  },
789
808
  buildRow: function(table, c, wo) {
@@ -991,26 +1010,51 @@ ts.filter = {
991
1010
  }, 200);
992
1011
  });
993
1012
  },
1013
+ defaultFilter: function(filter, mask){
1014
+ if (filter === '') { return filter; }
1015
+ var regex = ts.filter.regex.iQuery,
1016
+ maskLen = mask.match( ts.filter.regex.igQuery ).length,
1017
+ query = maskLen > 1 ? $.trim(filter).split(/\s/) : [ $.trim(filter) ],
1018
+ len = query.length - 1,
1019
+ indx = 0,
1020
+ val = mask;
1021
+ if ( len < 1 && maskLen > 1 ) {
1022
+ // only one "word" in query but mask has >1 slots
1023
+ query[1] = query[0];
1024
+ }
1025
+ // replace all {query} with query words...
1026
+ // if query = "Bob", then convert mask from "!{query}" to "!Bob"
1027
+ // if query = "Bob Joe Frank", then convert mask "{q} OR {q}" to "Bob OR Joe OR Frank"
1028
+ while (regex.test(val)) {
1029
+ val = val.replace(regex, query[indx++] || '');
1030
+ if (regex.test(val) && indx < len && (query[indx] || '') !== '') {
1031
+ val = mask.replace(regex, val);
1032
+ }
1033
+ }
1034
+ return val;
1035
+ },
994
1036
  findRows: function(table, filters, combinedFilters) {
995
- if (table.config.lastCombinedFilter === combinedFilters) { return; }
996
- var cached, len, $rows, rowIndex, tbodyIndex, $tbody, $cells, columnIndex,
997
- childRow, childRowText, exact, iExact, iFilter, lastSearch, matches, result,
998
- notFiltered, searchFiltered, filterMatched, showRow, time, val, indx,
999
- anyMatch, iAnyMatch, rowArray, rowText, iRowText, rowCache, fxn, ffxn,
1037
+ if (table.config.lastCombinedFilter === combinedFilters || table.config.widgetOptions.filter_initializing) { return; }
1038
+ var len, $rows, rowIndex, tbodyIndex, $tbody, $cells, $cell, columnIndex,
1039
+ childRow, lastSearch, hasSelect, matches, result, showRow, time, val, indx,
1040
+ notFiltered, searchFiltered, filterMatched, excludeMatch, fxn, ffxn,
1000
1041
  regex = ts.filter.regex,
1001
1042
  c = table.config,
1002
1043
  wo = c.widgetOptions,
1003
- columns = c.columns,
1004
1044
  $tbodies = c.$table.children('tbody'), // target all tbodies #568
1045
+ // data object passed to filters; anyMatch is a flag for the filters
1046
+ data = { anyMatch: false },
1005
1047
  // anyMatch really screws up with these types of filters
1006
- anyMatchNotAllowedTypes = [ 'range', 'notMatch', 'operators' ],
1007
- // parse columns after formatter, in case the class is added at that point
1008
- parsed = c.$headers.map(function(columnIndex) {
1009
- return c.parsers && c.parsers[columnIndex] && c.parsers[columnIndex].parsed ||
1010
- // getData won't return "parsed" if other "filter-" class names exist (e.g. <th class="filter-select filter-parsed">)
1011
- ts.getData && ts.getData(c.$headers.filter('[data-column="' + columnIndex + '"]:last'), ts.getColumnData( table, c.headers, columnIndex ), 'filter') === 'parsed' ||
1012
- $(this).hasClass('filter-parsed');
1013
- }).get();
1048
+ noAnyMatch = [ 'range', 'notMatch', 'operators' ];
1049
+
1050
+ // parse columns after formatter, in case the class is added at that point
1051
+ data.parsed = c.$headers.map(function(columnIndex) {
1052
+ return c.parsers && c.parsers[columnIndex] && c.parsers[columnIndex].parsed ||
1053
+ // getData won't return "parsed" if other "filter-" class names exist (e.g. <th class="filter-select filter-parsed">)
1054
+ ts.getData && ts.getData(c.$headers.filter('[data-column="' + columnIndex + '"]:last'), ts.getColumnData( table, c.headers, columnIndex ), 'filter') === 'parsed' ||
1055
+ $(this).hasClass('filter-parsed');
1056
+ }).get();
1057
+
1014
1058
  if (c.debug) { time = new Date(); }
1015
1059
  // filtered rows count
1016
1060
  c.filteredRows = 0;
@@ -1060,15 +1104,25 @@ ts.filter = {
1060
1104
  ts.log( "Searching through " + ( searchFiltered && notFiltered < len ? notFiltered : "all" ) + " rows" );
1061
1105
  }
1062
1106
  if ((wo.filter_$anyMatch && wo.filter_$anyMatch.length) || filters[c.columns]) {
1063
- anyMatch = wo.filter_$anyMatch && wo.filter_$anyMatch.val() || filters[c.columns] || '';
1107
+ data.anyMatchFlag = true;
1108
+ data.anyMatchFilter = wo.filter_$anyMatch && wo.filter_$anyMatch.val() || filters[c.columns] || '';
1064
1109
  if (c.sortLocaleCompare) {
1065
1110
  // replace accents
1066
- anyMatch = ts.replaceAccents(anyMatch);
1111
+ data.anyMatchFilter = ts.replaceAccents(data.anyMatchFilter);
1067
1112
  }
1068
- iAnyMatch = anyMatch.toLowerCase();
1113
+ if (wo.filter_defaultFilter && regex.iQuery.test( ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || '')) {
1114
+ data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) );
1115
+ // clear search filtered flag because default filters are not saved to the last search
1116
+ searchFiltered = false;
1117
+ }
1118
+ data.iAnyMatchFilter = data.anyMatchFilter;
1069
1119
  }
1120
+
1070
1121
  // loop through the rows
1071
1122
  for (rowIndex = 0; rowIndex < len; rowIndex++) {
1123
+
1124
+ data.cacheArray = c.cache[tbodyIndex].normalized[rowIndex];
1125
+
1072
1126
  childRow = $rows[rowIndex].className;
1073
1127
  // skip child rows & already filtered rows
1074
1128
  if ( regex.child.test(childRow) || (searchFiltered && regex.filtered.test(childRow)) ) { continue; }
@@ -1078,15 +1132,16 @@ ts.filter = {
1078
1132
  // so, if "table.config.widgetOptions.filter_childRows" is true and there is
1079
1133
  // a match anywhere in the child row, then it will make the row visible
1080
1134
  // checked here so the option can be changed dynamically
1081
- childRowText = (childRow.length && wo.filter_childRows) ? childRow.text() : '';
1082
- childRowText = wo.filter_ignoreCase ? childRowText.toLocaleLowerCase() : childRowText;
1135
+ data.childRowText = (childRow.length && wo.filter_childRows) ? childRow.text() : '';
1136
+ data.childRowText = wo.filter_ignoreCase ? data.childRowText.toLocaleLowerCase() : data.childRowText;
1083
1137
  $cells = $rows.eq(rowIndex).children();
1084
1138
 
1085
- if (anyMatch) {
1086
- rowArray = $cells.map(function(i){
1139
+ if (data.anyMatchFlag) {
1140
+ data.anyMatch = true;
1141
+ data.rowArray = $cells.map(function(i){
1087
1142
  var txt;
1088
- if (parsed[i]) {
1089
- txt = c.cache[tbodyIndex].normalized[rowIndex][i];
1143
+ if (data.parsed[i]) {
1144
+ txt = data.cacheArray[i];
1090
1145
  } else {
1091
1146
  txt = wo.filter_ignoreCase ? $(this).text().toLowerCase() : $(this).text();
1092
1147
  if (c.sortLocaleCompare) {
@@ -1095,13 +1150,15 @@ ts.filter = {
1095
1150
  }
1096
1151
  return txt;
1097
1152
  }).get();
1098
- rowText = rowArray.join(' ');
1099
- iRowText = rowText.toLowerCase();
1100
- rowCache = c.cache[tbodyIndex].normalized[rowIndex].slice(0,-1).join(' ');
1153
+ data.filter = data.anyMatchFilter;
1154
+ data.iFilter = data.iAnyMatchFilter;
1155
+ data.exact = data.rowArray.join(' ');
1156
+ data.iExact = data.exact.toLowerCase();
1157
+ data.cache = data.cacheArray.slice(0,-1).join(' ');
1101
1158
  filterMatched = null;
1102
1159
  $.each(ts.filter.types, function(type, typeFunction) {
1103
- if ($.inArray(type, anyMatchNotAllowedTypes) < 0) {
1104
- matches = typeFunction( anyMatch, iAnyMatch, rowText, iRowText, rowCache, columns, table, wo, parsed, rowArray );
1160
+ if ($.inArray(type, noAnyMatch) < 0) {
1161
+ matches = typeFunction( c, data );
1105
1162
  if (matches !== null) {
1106
1163
  filterMatched = matches;
1107
1164
  return false;
@@ -1113,30 +1170,37 @@ ts.filter = {
1113
1170
  } else {
1114
1171
  if (wo.filter_startsWith) {
1115
1172
  showRow = false;
1116
- columnIndex = columns;
1173
+ columnIndex = c.columns;
1117
1174
  while (!showRow && columnIndex > 0) {
1118
1175
  columnIndex--;
1119
- showRow = showRow || rowArray[columnIndex].indexOf(iAnyMatch) === 0;
1176
+ showRow = showRow || data.rowArray[columnIndex].indexOf(data.iFilter) === 0;
1120
1177
  }
1121
1178
  } else {
1122
- showRow = (iRowText + childRowText).indexOf(iAnyMatch) >= 0;
1179
+ showRow = (data.iExact + data.childRowText).indexOf(data.iFilter) >= 0;
1123
1180
  }
1124
1181
  }
1182
+ data.anyMatch = false;
1125
1183
  }
1126
1184
 
1127
- for (columnIndex = 0; columnIndex < columns; columnIndex++) {
1185
+ for (columnIndex = 0; columnIndex < c.columns; columnIndex++) {
1186
+ data.filter = filters[columnIndex];
1187
+ data.index = columnIndex;
1188
+
1189
+ // filter types to exclude, per column
1190
+ excludeMatch = ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split(/\s+/);
1191
+
1128
1192
  // ignore if filter is empty or disabled
1129
- if (filters[columnIndex]) {
1130
- cached = c.cache[tbodyIndex].normalized[rowIndex][columnIndex];
1193
+ if (data.filter) {
1194
+ data.cache = data.cacheArray[columnIndex];
1131
1195
  // check if column data should be from the cell or from parsed data
1132
- if (wo.filter_useParsedData || parsed[columnIndex]) {
1133
- exact = cached;
1196
+ if (wo.filter_useParsedData || data.parsed[columnIndex]) {
1197
+ data.exact = data.cache;
1134
1198
  } else {
1135
1199
  // using older or original tablesorter
1136
- exact = $.trim($cells.eq(columnIndex).text());
1137
- exact = c.sortLocaleCompare ? ts.replaceAccents(exact) : exact; // issue #405
1200
+ data.exact = $.trim( $cells.eq(columnIndex).text() );
1201
+ data.exact = c.sortLocaleCompare ? ts.replaceAccents(data.exact) : data.exact; // issue #405
1138
1202
  }
1139
- iExact = !regex.type.test(typeof exact) && wo.filter_ignoreCase ? exact.toLocaleLowerCase() : exact;
1203
+ data.iExact = !regex.type.test(typeof data.exact) && wo.filter_ignoreCase ? data.exact.toLocaleLowerCase() : data.exact;
1140
1204
  result = showRow; // if showRow is true, show that row
1141
1205
 
1142
1206
  // in case select filter option has a different value vs text "a - z|A through Z"
@@ -1144,39 +1208,49 @@ ts.filter = {
1144
1208
  c.$filters.add(c.$externalFilters).filter('[data-column="'+ columnIndex + '"]').find('select option:selected').attr('data-function-name') || '' : '';
1145
1209
 
1146
1210
  // replace accents - see #357
1147
- filters[columnIndex] = c.sortLocaleCompare ? ts.replaceAccents(filters[columnIndex]) : filters[columnIndex];
1148
- // val = case insensitive, filters[columnIndex] = case sensitive
1149
- iFilter = wo.filter_ignoreCase ? (filters[columnIndex] || '').toLocaleLowerCase() : filters[columnIndex];
1211
+ data.filter = c.sortLocaleCompare ? ts.replaceAccents(data.filter) : data.filter;
1212
+
1213
+ val = true;
1214
+ if (wo.filter_defaultFilter && regex.iQuery.test( ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || '')) {
1215
+ data.filter = ts.filter.defaultFilter( data.filter, ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) );
1216
+ // val is used to indicate that a filter select is using a default filter; so we override the exact & partial matches
1217
+ val = false;
1218
+ }
1219
+ // data.iFilter = case insensitive, data.filter = case sensitive
1220
+ data.iFilter = wo.filter_ignoreCase ? (data.filter || '').toLocaleLowerCase() : data.filter;
1150
1221
  fxn = ts.getColumnData( table, wo.filter_functions, columnIndex );
1151
- if (fxn) {
1152
- if (fxn === true) {
1153
- // default selector; no "filter-select" class
1154
- result = (c.$headers.filter('[data-column="' + columnIndex + '"]:last').hasClass('filter-match')) ?
1155
- iExact.search(iFilter) >= 0 : filters[columnIndex] === exact;
1222
+ $cell = c.$headers.filter('[data-column="' + columnIndex + '"]:last');
1223
+ hasSelect = $cell.hasClass('filter-select');
1224
+ if ( fxn || ( hasSelect && val ) ) {
1225
+ if (fxn === true || hasSelect) {
1226
+ // default selector uses exact match unless "filter-match" class is found
1227
+ result = ($cell.hasClass('filter-match')) ? data.iExact.search(data.iFilter) >= 0 : data.filter === data.exact;
1156
1228
  } else if (typeof fxn === 'function') {
1157
1229
  // filter callback( exact cell content, parser normalized content, filter input value, column index, jQuery row object )
1158
- result = fxn(exact, cached, filters[columnIndex], columnIndex, $rows.eq(rowIndex));
1159
- } else if (typeof fxn[ffxn || filters[columnIndex]] === 'function') {
1230
+ result = fxn(data.exact, data.cache, data.filter, columnIndex, $rows.eq(rowIndex));
1231
+ } else if (typeof fxn[ffxn || data.filter] === 'function') {
1160
1232
  // selector option function
1161
- result = fxn[ffxn || filters[columnIndex]](exact, cached, filters[columnIndex], columnIndex, $rows.eq(rowIndex));
1233
+ result = fxn[ffxn || data.filter](data.exact, data.cache, data.filter, columnIndex, $rows.eq(rowIndex));
1162
1234
  }
1163
1235
  } else {
1164
1236
  filterMatched = null;
1165
1237
  // cycle through the different filters
1166
1238
  // filters return a boolean or null if nothing matches
1167
1239
  $.each(ts.filter.types, function(type, typeFunction) {
1168
- matches = typeFunction( filters[columnIndex], iFilter, exact, iExact, cached, columnIndex, table, wo, parsed );
1169
- if (matches !== null) {
1170
- filterMatched = matches;
1171
- return false;
1240
+ if ($.inArray(type, excludeMatch) < 0) {
1241
+ matches = typeFunction( c, data );
1242
+ if (matches !== null) {
1243
+ filterMatched = matches;
1244
+ return false;
1245
+ }
1172
1246
  }
1173
1247
  });
1174
1248
  if (filterMatched !== null) {
1175
1249
  result = filterMatched;
1176
1250
  // Look for match, and add child row data for matching
1177
1251
  } else {
1178
- exact = (iExact + childRowText).indexOf( ts.filter.parseFilter(table, iFilter, columnIndex, parsed[columnIndex]) );
1179
- result = ( (!wo.filter_startsWith && exact >= 0) || (wo.filter_startsWith && exact === 0) );
1252
+ data.exact = (data.iExact + data.childRowText).indexOf( ts.filter.parseFilter(c, data.iFilter, columnIndex, data.parsed[columnIndex]) );
1253
+ result = ( (!wo.filter_startsWith && data.exact >= 0) || (wo.filter_startsWith && data.exact === 0) );
1180
1254
  }
1181
1255
  }
1182
1256
  showRow = (result) ? showRow : false;
@@ -1469,7 +1543,7 @@ ts.addWidget({
1469
1543
  return;
1470
1544
  }
1471
1545
  var $table = c.$table,
1472
- $attach = $(wo.stickyHeaders_attachTo),
1546
+ $attach = $(wo.stickyHeaders_attachTo || 'window'),
1473
1547
  $thead = $table.children('thead:first'),
1474
1548
  $win = $attach.length ? $attach : $(window),
1475
1549
  $header = $thead.children('tr').not('.sticky-false').children(),