jquery-tablesorter 1.12.7 → 1.12.8

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