jquery-tablesorter 1.18.1 → 1.18.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- /*! TableSorter (FORK) v2.23.1 *//*
1
+ /*! TableSorter (FORK) v2.23.2 *//*
2
2
  * Client-side table sorting with ease!
3
3
  * @requires jQuery v1.2.6+
4
4
  *
@@ -26,7 +26,7 @@
26
26
 
27
27
  var ts = this;
28
28
 
29
- ts.version = '2.23.1';
29
+ ts.version = '2.23.2';
30
30
 
31
31
  ts.parsers = [];
32
32
  ts.widgets = [];
@@ -151,6 +151,15 @@
151
151
  nextNone : 'activate to remove the sort'
152
152
  };
153
153
 
154
+ ts.regex = {
155
+ templateContent : /\{content\}/g,
156
+ templateIcon : /\{icon\}/g,
157
+ templateName : /\{name\}/i,
158
+ spaces : /\s+/g,
159
+ nonWord : /\W/g,
160
+ formElements : /(input|select|button|textarea)/i
161
+ };
162
+
154
163
  // These methods can be applied on table.config instance
155
164
  ts.instanceMethods = {};
156
165
 
@@ -443,7 +452,9 @@
443
452
  // if headerTemplate is empty, don't reformat the header cell
444
453
  if ( c.headerTemplate !== '' && !$t.find('.' + ts.css.headerIn).length ) {
445
454
  // set up header template
446
- t = c.headerTemplate.replace(/\{content\}/g, $t.html()).replace(/\{icon\}/g, $t.find('.' + ts.css.icon).length ? '' : i);
455
+ t = c.headerTemplate
456
+ .replace(ts.regex.templateContent, $t.html())
457
+ .replace(ts.regex.templateIcon, $t.find('.' + ts.css.icon).length ? '' : i);
447
458
  if (c.onRenderTemplate) {
448
459
  h = c.onRenderTemplate.apply( $t, [ index, t ] );
449
460
  if (h && typeof h === 'string') { t = h; } // only change t if something is returned
@@ -856,7 +867,7 @@
856
867
  .join( c.namespace + ' ' );
857
868
  // apply easy methods that trigger bound events
858
869
  $table
859
- .unbind( events.replace( /\s+/g, ' ' ) )
870
+ .unbind( events.replace( ts.regex.spaces, ' ' ) )
860
871
  .bind( 'sortReset' + c.namespace, function( e, callback ) {
861
872
  e.stopPropagation();
862
873
  // using this.config to ensure functions are getting a non-cached version of the config
@@ -1003,7 +1014,7 @@
1003
1014
  c.namespace = '.tablesorter' + Math.random().toString(16).slice(2);
1004
1015
  } else {
1005
1016
  // make sure namespace starts with a period & doesn't have weird characters
1006
- c.namespace = '.' + c.namespace.replace(/\W/g, '');
1017
+ c.namespace = '.' + c.namespace.replace(ts.regex.nonWord, '');
1007
1018
  }
1008
1019
 
1009
1020
  c.$table.children().children('tr').attr('role', 'row');
@@ -1231,7 +1242,7 @@
1231
1242
  }
1232
1243
  }
1233
1244
  t = ( c.pointerDown + ' ' + c.pointerUp + ' ' + c.pointerClick + ' sort keyup ' )
1234
- .replace(/\s+/g, ' ')
1245
+ .replace(ts.regex.spaces, ' ')
1235
1246
  .split(' ')
1236
1247
  .join(c.namespace + ' ');
1237
1248
  // apply event handling to headers and/or additional headers (stickyheaders, scroller, etc)
@@ -1265,7 +1276,7 @@
1265
1276
  }
1266
1277
  downTarget = null;
1267
1278
  // prevent sort being triggered on form elements
1268
- if ( /(input|select|button|textarea)/i.test(e.target.nodeName) ||
1279
+ if ( ts.regex.formElements.test(e.target.nodeName) ||
1269
1280
  // nosort class name, or elements within a nosort container
1270
1281
  $target.hasClass(c.cssNoSort) || $target.parents('.' + c.cssNoSort).length > 0 ||
1271
1282
  // elements within a button
@@ -1551,13 +1562,13 @@
1551
1562
  .join(c.namespace + ' ');
1552
1563
  $t
1553
1564
  .removeData('tablesorter')
1554
- .unbind( events.replace(/\s+/g, ' ') );
1565
+ .unbind( events.replace(ts.regex.spaces, ' ') );
1555
1566
  c.$headers.add($f)
1556
1567
  .removeClass( [ ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone ].join(' ') )
1557
1568
  .removeAttr('data-column')
1558
1569
  .removeAttr('aria-label')
1559
1570
  .attr('aria-disabled', 'true');
1560
- $r.find(c.selectorSort).unbind( ('mousedown mouseup keypress '.split(' ').join(c.namespace + ' ')).replace(/\s+/g, ' ') );
1571
+ $r.find(c.selectorSort).unbind( ('mousedown mouseup keypress '.split(' ').join(c.namespace + ' ')).replace(ts.regex.spaces, ' ') );
1561
1572
  ts.restoreHeaders(table);
1562
1573
  $t.toggleClass(ts.css.table + ' ' + c.tableClass + ' tablesorter-' + c.theme, removeClasses === false);
1563
1574
  // clear flag in case the plugin is initialized again
@@ -1573,11 +1584,9 @@
1573
1584
 
1574
1585
  // *** sort functions ***
1575
1586
  // regex used in natural sort
1576
- ts.regex = {
1577
- chunk : /(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi, // chunk/tokenize numbers & letters
1578
- chunks: /(^\\0|\\0$)/, // replace chunks @ ends
1579
- hex: /^0x[0-9a-f]+$/i // hex
1580
- };
1587
+ ts.regex.chunk = /(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi; // chunk/tokenize numbers & letters
1588
+ ts.regex.chunks = /(^\\0|\\0$)/; // replace chunks @ ends
1589
+ ts.regex.hex = /^0x[0-9a-f]+$/i; // hex
1581
1590
 
1582
1591
  // Natural sort - https://github.com/overset/javascript-natural-sort (date sorting removed)
1583
1592
  // this function will only accept strings, or you'll see 'TypeError: undefined is not a function'
@@ -1804,7 +1813,7 @@
1804
1813
  if (c.debug) { time = new Date(); }
1805
1814
  // look for widgets to apply from in table class
1806
1815
  // stop using \b otherwise this matches 'ui-widget-content' & adds 'content' widget
1807
- wd = new RegExp( '\\s' + c.widgetClass.replace( /\{name\}/i, '([\\w-]+)' ) + '\\s', 'g' );
1816
+ wd = new RegExp( '\\s' + c.widgetClass.replace( ts.regex.templateName, '([\\w-]+)' ) + '\\s', 'g' );
1808
1817
  if ( tableClass.match( wd ) ) {
1809
1818
  // extract out the widget id from the table class (widget id's can include dashes)
1810
1819
  w = tableClass.match( wd );
@@ -2030,6 +2039,10 @@
2030
2039
  return $.trim(val);
2031
2040
  };
2032
2041
 
2042
+ ts.regex.comma = /,/g;
2043
+ ts.regex.digitNonUS = /[\s|\.]/g;
2044
+ ts.regex.digitNegativeTest = /^\s*\([.\d]+\)/;
2045
+ ts.regex.digitNegativeReplace = /^\s*\(([.\d]+)\)/;
2033
2046
  ts.formatFloat = function(s, table) {
2034
2047
  if (typeof s !== 'string' || s === '') { return s; }
2035
2048
  // allow using formatFloat without a table; defaults to US number format
@@ -2038,24 +2051,28 @@
2038
2051
  typeof table !== 'undefined' ? table : true;
2039
2052
  if (t) {
2040
2053
  // US Format - 1,234,567.89 -> 1234567.89
2041
- s = s.replace(/,/g, '');
2054
+ s = s.replace(ts.regex.comma, '');
2042
2055
  } else {
2043
2056
  // German Format = 1.234.567,89 -> 1234567.89
2044
2057
  // French Format = 1 234 567,89 -> 1234567.89
2045
- s = s.replace(/[\s|\.]/g, '').replace(/,/g, '.');
2058
+ s = s.replace(ts.regex.digitNonUS, '').replace(ts.regex.comma, '.');
2046
2059
  }
2047
- if (/^\s*\([.\d]+\)/.test(s)) {
2060
+ if (ts.regex.digitNegativeTest.test(s)) {
2048
2061
  // make (#) into a negative number -> (10) = -10
2049
- s = s.replace(/^\s*\(([.\d]+)\)/, '-$1');
2062
+ s = s.replace(ts.regex.digitNegativeReplace, '-$1');
2050
2063
  }
2051
2064
  i = parseFloat(s);
2052
2065
  // return the text instead of zero
2053
2066
  return isNaN(i) ? $.trim(s) : i;
2054
2067
  };
2055
2068
 
2069
+ ts.regex.digitTest = /^[\-+(]?\d+[)]?$/;
2070
+ ts.regex.digitReplace = /[,.'"\s]/g;
2056
2071
  ts.isDigit = function(s) {
2057
2072
  // replace all unwanted chars and match
2058
- return isNaN(s) ? (/^[\-+(]?\d+[)]?$/).test(s.toString().replace(/[,.'"\s]/g, '')) : s !== '';
2073
+ return isNaN(s) ?
2074
+ ts.regex.digitTest.test( s.toString().replace( ts.regex.digitReplace, '' ) ) :
2075
+ s !== '';
2059
2076
  };
2060
2077
 
2061
2078
  }()
@@ -2114,65 +2131,76 @@
2114
2131
  type: 'text'
2115
2132
  });
2116
2133
 
2134
+ ts.regex.nondigit = /[^\w,. \-()]/g;
2117
2135
  ts.addParser({
2118
2136
  id: 'digit',
2119
2137
  is: function(s) {
2120
2138
  return ts.isDigit(s);
2121
2139
  },
2122
2140
  format: function(s, table) {
2123
- var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ''), table);
2141
+ var n = ts.formatFloat((s || '').replace(ts.regex.nondigit, ''), table);
2124
2142
  return s && typeof n === 'number' ? n :
2125
2143
  s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
2126
2144
  },
2127
2145
  type: 'numeric'
2128
2146
  });
2129
2147
 
2148
+ ts.regex.currencyReplace = /[+\-,. ]/g;
2149
+ ts.regex.currencyTest = /^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/;
2130
2150
  ts.addParser({
2131
2151
  id: 'currency',
2132
2152
  is: function(s) {
2133
- s = (s || '').replace(/[+\-,. ]/g, '');
2153
+ s = (s || '').replace(ts.regex.currencyReplace, '');
2134
2154
  // test for £$€¤¥¢
2135
- return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test(s);
2155
+ return ts.regex.currencyTest.test(s);
2136
2156
  },
2137
2157
  format: function(s, table) {
2138
- var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ''), table);
2158
+ var n = ts.formatFloat((s || '').replace(ts.regex.nondigit, ''), table);
2139
2159
  return s && typeof n === 'number' ? n :
2140
2160
  s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
2141
2161
  },
2142
2162
  type: 'numeric'
2143
2163
  });
2144
2164
 
2165
+ // too many protocols to add them all https://en.wikipedia.org/wiki/URI_scheme
2166
+ // now, this regex can be updated before initialization
2167
+ ts.regex.urlProtocolTest = /^(https?|ftp|file):\/\//;
2168
+ ts.regex.urlProtocolReplace = /(https?|ftp|file):\/\//;
2145
2169
  ts.addParser({
2146
2170
  id: 'url',
2147
2171
  is: function(s) {
2148
- return (/^(https?|ftp|file):\/\//).test(s);
2172
+ return ts.regex.urlProtocolTest.test(s);
2149
2173
  },
2150
2174
  format: function(s) {
2151
- return s ? $.trim(s.replace(/(https?|ftp|file):\/\//, '')) : s;
2175
+ return s ? $.trim(s.replace(ts.regex.urlProtocolReplace, '')) : s;
2152
2176
  },
2153
2177
  parsed : true, // filter widget flag
2154
2178
  type: 'text'
2155
2179
  });
2156
2180
 
2181
+ ts.regex.dash = /-/g;
2182
+ ts.regex.isoDate = /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/;
2157
2183
  ts.addParser({
2158
2184
  id: 'isoDate',
2159
2185
  is: function(s) {
2160
- return (/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/).test(s);
2186
+ return ts.regex.isoDate.test(s);
2161
2187
  },
2162
2188
  format: function(s, table) {
2163
- var date = s ? new Date( s.replace(/-/g, '/') ) : s;
2189
+ var date = s ? new Date( s.replace(ts.regex.dash, '/') ) : s;
2164
2190
  return date instanceof Date && isFinite(date) ? date.getTime() : s;
2165
2191
  },
2166
2192
  type: 'numeric'
2167
2193
  });
2168
2194
 
2195
+ ts.regex.percent = /%/g;
2196
+ ts.regex.percentTest = /(\d\s*?%|%\s*?\d)/;
2169
2197
  ts.addParser({
2170
2198
  id: 'percent',
2171
2199
  is: function(s) {
2172
- return (/(\d\s*?%|%\s*?\d)/).test(s) && s.length < 15;
2200
+ return ts.regex.percentTest.test(s) && s.length < 15;
2173
2201
  },
2174
2202
  format: function(s, table) {
2175
- return s ? ts.formatFloat(s.replace(/%/g, ''), table) : s;
2203
+ return s ? ts.formatFloat(s.replace(ts.regex.percent, ''), table) : s;
2176
2204
  },
2177
2205
  type: 'numeric'
2178
2206
  });
@@ -2190,27 +2218,35 @@
2190
2218
  type: 'text'
2191
2219
  });
2192
2220
 
2221
+ ts.regex.dateReplace = /(\S)([AP]M)$/i; // used by usLongDate & time parser
2222
+ ts.regex.usLongDateTest1 = /^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i;
2223
+ ts.regex.usLongDateTest2 = /^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i;
2193
2224
  ts.addParser({
2194
2225
  id: 'usLongDate',
2195
2226
  is: function(s) {
2196
2227
  // two digit years are not allowed cross-browser
2197
2228
  // Jan 01, 2013 12:34:56 PM or 01 Jan 2013
2198
- return (/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i).test(s) ||
2199
- (/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i).test(s);
2229
+ return ts.regex.usLongDateTest1.test(s) || ts.regex.usLongDateTest2.test(s);
2200
2230
  },
2201
2231
  format: function(s, table) {
2202
- var date = s ? new Date( s.replace(/(\S)([AP]M)$/i, '$1 $2') ) : s;
2232
+ var date = s ? new Date( s.replace(ts.regex.dateReplace, '$1 $2') ) : s;
2203
2233
  return date instanceof Date && isFinite(date) ? date.getTime() : s;
2204
2234
  },
2205
2235
  type: 'numeric'
2206
2236
  });
2207
2237
 
2238
+ // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
2239
+ ts.regex.shortDateTest = /(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/;
2240
+ // escaped "-" because JSHint in Firefox was showing it as an error
2241
+ ts.regex.shortDateReplace = /[\-.,]/g;
2242
+ // XXY covers MDY & DMY formats
2243
+ ts.regex.shortDateXXY = /(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/;
2244
+ ts.regex.shortDateYMD = /(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/;
2208
2245
  ts.addParser({
2209
2246
  id: 'shortDate', // 'mmddyyyy', 'ddmmyyyy' or 'yyyymmdd'
2210
2247
  is: function(s) {
2211
- s = (s || '').replace(/\s+/g, ' ').replace(/[\-.,]/g, '/');
2212
- // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
2213
- return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test(s);
2248
+ s = (s || '').replace(ts.regex.spaces, ' ').replace(ts.regex.shortDateReplace, '/');
2249
+ return ts.regex.shortDateTest.test(s);
2214
2250
  },
2215
2251
  format: function(s, table, cell, cellIndex) {
2216
2252
  if (s) {
@@ -2220,14 +2256,13 @@
2220
2256
  format = ci.length && ci[0].dateFormat ||
2221
2257
  ts.getData( ci, ts.getColumnData( table, c.headers, cellIndex ), 'dateFormat') ||
2222
2258
  c.dateFormat;
2223
- // escaped "-" because JSHint in Firefox was showing it as an error
2224
- d = s.replace(/\s+/g, ' ').replace(/[\-.,]/g, '/');
2259
+ d = s.replace(ts.regex.spaces, ' ').replace(ts.regex.shortDateReplace, '/');
2225
2260
  if (format === 'mmddyyyy') {
2226
- d = d.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, '$3/$1/$2');
2261
+ d = d.replace(ts.regex.shortDateXXY, '$3/$1/$2');
2227
2262
  } else if (format === 'ddmmyyyy') {
2228
- d = d.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, '$3/$2/$1');
2263
+ d = d.replace(ts.regex.shortDateXXY, '$3/$2/$1');
2229
2264
  } else if (format === 'yyyymmdd') {
2230
- d = d.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, '$1/$2/$3');
2265
+ d = d.replace(ts.regex.shortDateYMD, '$1/$2/$3');
2231
2266
  }
2232
2267
  date = new Date(d);
2233
2268
  return date instanceof Date && isFinite(date) ? date.getTime() : s;
@@ -2237,13 +2272,14 @@
2237
2272
  type: 'numeric'
2238
2273
  });
2239
2274
 
2275
+ ts.regex.timeTest = /^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i;
2240
2276
  ts.addParser({
2241
2277
  id: 'time',
2242
2278
  is: function(s) {
2243
- return (/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s);
2279
+ return ts.regex.timeTest.test(s);
2244
2280
  },
2245
2281
  format: function(s, table) {
2246
- var date = s ? new Date( '2000/01/01 ' + s.replace(/(\S)([AP]M)$/i, '$1 $2') ) : s;
2282
+ var date = s ? new Date( '2000/01/01 ' + s.replace(ts.regex.dateReplace, '$1 $2') ) : s;
2247
2283
  return date instanceof Date && isFinite(date) ? date.getTime() : s;
2248
2284
  },
2249
2285
  type: 'numeric'
@@ -4,7 +4,7 @@
4
4
  ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▀▀ ▀▀▀▀██
5
5
  █████▀ ▀████▀ ██ ██ ▀████▀ ██ ██ ██ ██ ▀████▀ █████▀ ██ ██ █████▀
6
6
  */
7
- /*! tablesorter (FORK) - updated 08-19-2015 (v2.23.1)*/
7
+ /*! tablesorter (FORK) - updated 08-23-2015 (v2.23.2)*/
8
8
  /* Includes widgets ( storage,uitheme,columns,filter,stickyHeaders,resizable,saveSort ) */
9
9
  (function(factory) {
10
10
  if (typeof define === 'function' && define.amd) {
@@ -372,14 +372,15 @@
372
372
 
373
373
  })(jQuery);
374
374
 
375
- /*! Widget: filter - updated 8/17/2015 (v2.23.0) *//*
375
+ /*! Widget: filter - updated 8/23/2015 (v2.23.2) *//*
376
376
  * Requires tablesorter v2.8+ and jQuery 1.7+
377
377
  * by Rob Garrison
378
378
  */
379
379
  ;( function ( $ ) {
380
380
  'use strict';
381
- var ts = $.tablesorter || {},
382
- tscss = ts.css;
381
+ var tsf,
382
+ ts = $.tablesorter || {},
383
+ tscss = ts.css;
383
384
 
384
385
  $.extend( tscss, {
385
386
  filterRow : 'tablesorter-filter-row',
@@ -423,7 +424,7 @@
423
424
  },
424
425
  format: function( table, c, wo ) {
425
426
  if ( !c.$table.hasClass( 'hasFilters' ) ) {
426
- ts.filter.init( table, c, wo );
427
+ tsf.init( table, c, wo );
427
428
  }
428
429
  },
429
430
  remove: function( table, c, wo, refreshing ) {
@@ -435,7 +436,7 @@
435
436
  $table
436
437
  .removeClass( 'hasFilters' )
437
438
  // add .tsfilter namespace to all BUT search
438
- .unbind( events.replace( /\s+/g, ' ' ) )
439
+ .unbind( events.replace( ts.regex.spaces, ' ' ) )
439
440
  // remove the filter row even if refreshing, because the column might have been moved
440
441
  .find( '.' + tscss.filterRow ).remove();
441
442
  if ( refreshing ) { return; }
@@ -450,7 +451,7 @@
450
451
  }
451
452
  });
452
453
 
453
- ts.filter = {
454
+ tsf = ts.filter = {
454
455
 
455
456
  // regex used in filter 'check' functions - not for general use and not documented
456
457
  regex: {
@@ -459,9 +460,13 @@
459
460
  filtered : /filtered/, // filtered (hidden) row class name; updated in the script
460
461
  type : /undefined|number/, // check type
461
462
  exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==')
462
- nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser)
463
463
  operators : /[<>=]/g, // replace operators
464
- query : '(q|query)' // replace filter queries
464
+ query : '(q|query)', // replace filter queries
465
+ wild01 : /\?/g, // wild card match 0 or 1
466
+ wild0More : /\*/g, // wild care match 0 or more
467
+ quote : /\"/g,
468
+ isNeg1 : /(>=?\s*-\d)/,
469
+ isNeg2 : /(<=?\s*\d)/
465
470
  },
466
471
  // function( c, data ) { }
467
472
  // c = table.config
@@ -478,27 +483,27 @@
478
483
  // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
479
484
  types: {
480
485
  or : function( c, data, vars ) {
481
- if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) {
486
+ if ( tsf.regex.orTest.test( data.iFilter ) || tsf.regex.orSplit.test( data.filter ) ) {
482
487
  var indx, filterMatched, query, regex,
483
488
  // duplicate data but split filter
484
489
  data2 = $.extend( {}, data ),
485
490
  index = data.index,
486
491
  parsed = data.parsed[ index ],
487
- filter = data.filter.split( ts.filter.regex.orSplit ),
488
- iFilter = data.iFilter.split( ts.filter.regex.orSplit ),
492
+ filter = data.filter.split( tsf.regex.orSplit ),
493
+ iFilter = data.iFilter.split( tsf.regex.orSplit ),
489
494
  len = filter.length;
490
495
  for ( indx = 0; indx < len; indx++ ) {
491
496
  data2.nestedFilters = true;
492
- data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
493
- data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
494
- query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')';
497
+ data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], index, parsed ) || '' );
498
+ data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
499
+ query = '(' + ( tsf.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')';
495
500
  try {
496
501
  // use try/catch, because query may not be a valid regex if "|" is contained within a partial regex search,
497
502
  // e.g "/(Alex|Aar" -> Uncaught SyntaxError: Invalid regular expression: /(/(Alex)/: Unterminated group
498
503
  regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
499
504
  // filterMatched = data2.filter === '' && indx > 0 ? true
500
505
  // look for an exact match with the 'or' unless the 'filter-match' class is found
501
- filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars );
506
+ filterMatched = regex.test( data2.exact ) || tsf.processTypes( c, data2, vars );
502
507
  if ( filterMatched ) {
503
508
  return filterMatched;
504
509
  }
@@ -513,27 +518,27 @@
513
518
  },
514
519
  // Look for an AND or && operator ( logical and )
515
520
  and : function( c, data, vars ) {
516
- if ( ts.filter.regex.andTest.test( data.filter ) ) {
521
+ if ( tsf.regex.andTest.test( data.filter ) ) {
517
522
  var indx, filterMatched, result, query, regex,
518
523
  // duplicate data but split filter
519
524
  data2 = $.extend( {}, data ),
520
525
  index = data.index,
521
526
  parsed = data.parsed[ index ],
522
- filter = data.filter.split( ts.filter.regex.andSplit ),
523
- iFilter = data.iFilter.split( ts.filter.regex.andSplit ),
527
+ filter = data.filter.split( tsf.regex.andSplit ),
528
+ iFilter = data.iFilter.split( tsf.regex.andSplit ),
524
529
  len = filter.length;
525
530
  for ( indx = 0; indx < len; indx++ ) {
526
531
  data2.nestedFilters = true;
527
- data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
528
- data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
529
- query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' )
532
+ data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], index, parsed ) || '' );
533
+ data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
534
+ query = ( '(' + ( tsf.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' )
530
535
  // replace wild cards since /(a*)/i will match anything
531
- .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' );
536
+ .replace( tsf.regex.wild01, '\\S{1}' ).replace( tsf.regex.wild0More, '\\S*' );
532
537
  try {
533
538
  // use try/catch just in case RegExp is invalid
534
539
  regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
535
540
  // look for an exact match with the 'and' unless the 'filter-match' class is found
536
- result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) );
541
+ result = ( regex.test( data2.exact ) || tsf.processTypes( c, data2, vars ) );
537
542
  if ( indx === 0 ) {
538
543
  filterMatched = result;
539
544
  } else {
@@ -550,10 +555,10 @@
550
555
  },
551
556
  // Look for regex
552
557
  regex: function( c, data ) {
553
- if ( ts.filter.regex.regex.test( data.filter ) ) {
558
+ if ( tsf.regex.regex.test( data.filter ) ) {
554
559
  var matches,
555
560
  // cache regex per column for optimal speed
556
- regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ),
561
+ regex = data.filter_regexCache[ data.index ] || tsf.regex.regex.exec( data.filter ),
557
562
  isRegex = regex instanceof RegExp;
558
563
  try {
559
564
  if ( !isRegex ) {
@@ -572,18 +577,18 @@
572
577
  // Look for operators >, >=, < or <=
573
578
  operators: function( c, data ) {
574
579
  // ignore empty strings... because '' < 10 is true
575
- if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) {
580
+ if ( tsf.regex.operTest.test( data.iFilter ) && data.iExact !== '' ) {
576
581
  var cachedValue, result, txt,
577
582
  table = c.table,
578
583
  index = data.index,
579
584
  parsed = data.parsed[index],
580
- query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ),
585
+ query = ts.formatFloat( data.iFilter.replace( tsf.regex.operators, '' ), table ),
581
586
  parser = c.parsers[index],
582
587
  savedSearch = query;
583
588
  // parse filter value in case we're comparing numbers ( dates )
584
589
  if ( parsed || parser.type === 'numeric' ) {
585
- txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) );
586
- result = ts.filter.parseFilter( c, txt, index, true );
590
+ txt = $.trim( '' + data.iFilter.replace( tsf.regex.operators, '' ) );
591
+ result = tsf.parseFilter( c, txt, index, true );
587
592
  query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query;
588
593
  }
589
594
  // iExact may be numeric - see issue #149;
@@ -592,13 +597,13 @@
592
597
  typeof data.cache !== 'undefined' ) {
593
598
  cachedValue = data.cache;
594
599
  } else {
595
- txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
600
+ txt = isNaN( data.iExact ) ? data.iExact.replace( ts.regex.nondigit, '' ) : data.iExact;
596
601
  cachedValue = ts.formatFloat( txt, table );
597
602
  }
598
- if ( />/.test( data.iFilter ) ) {
599
- result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query;
600
- } else if ( /</.test( data.iFilter ) ) {
601
- result = /<=/.test( data.iFilter ) ? cachedValue <= query : cachedValue < query;
603
+ if ( tsf.regex.gtTest.test( data.iFilter ) ) {
604
+ result = tsf.regex.gteTest.test( data.iFilter ) ? cachedValue >= query : cachedValue > query;
605
+ } else if ( tsf.regex.ltTest.test( data.iFilter ) ) {
606
+ result = tsf.regex.lteTest.test( data.iFilter ) ? cachedValue <= query : cachedValue < query;
602
607
  }
603
608
  // keep showing all rows if nothing follows the operator
604
609
  if ( !result && savedSearch === '' ) {
@@ -610,13 +615,13 @@
610
615
  },
611
616
  // Look for a not match
612
617
  notMatch: function( c, data ) {
613
- if ( /^\!/.test( data.iFilter ) ) {
618
+ if ( tsf.regex.notTest.test( data.iFilter ) ) {
614
619
  var indx,
615
620
  txt = data.iFilter.replace( '!', '' ),
616
- filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
617
- if ( ts.filter.regex.exact.test( filter ) ) {
621
+ filter = tsf.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
622
+ if ( tsf.regex.exact.test( filter ) ) {
618
623
  // look for exact not matches - see #628
619
- filter = filter.replace( ts.filter.regex.exact, '' );
624
+ filter = filter.replace( tsf.regex.exact, '' );
620
625
  return filter === '' ? true : $.trim( filter ) !== data.iExact;
621
626
  } else {
622
627
  indx = data.iExact.search( $.trim( filter ) );
@@ -628,27 +633,27 @@
628
633
  // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
629
634
  exact: function( c, data ) {
630
635
  /*jshint eqeqeq:false */
631
- if ( ts.filter.regex.exact.test( data.iFilter ) ) {
632
- var txt = data.iFilter.replace( ts.filter.regex.exact, '' ),
633
- filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
636
+ if ( tsf.regex.exact.test( data.iFilter ) ) {
637
+ var txt = data.iFilter.replace( tsf.regex.exact, '' ),
638
+ filter = tsf.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
634
639
  return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact;
635
640
  }
636
641
  return null;
637
642
  },
638
643
  // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
639
644
  range : function( c, data ) {
640
- if ( ts.filter.regex.toTest.test( data.iFilter ) ) {
645
+ if ( tsf.regex.toTest.test( data.iFilter ) ) {
641
646
  var result, tmp, range1, range2,
642
647
  table = c.table,
643
648
  index = data.index,
644
649
  parsed = data.parsed[index],
645
650
  // make sure the dash is for a range and not indicating a negative number
646
- query = data.iFilter.split( ts.filter.regex.toSplit );
651
+ query = data.iFilter.split( tsf.regex.toSplit );
647
652
 
648
- tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || '';
649
- range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
650
- tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || '';
651
- range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
653
+ tmp = query[0].replace( ts.regex.nondigit, '' ) || '';
654
+ range1 = ts.formatFloat( tsf.parseFilter( c, tmp, index, parsed ), table );
655
+ tmp = query[1].replace( ts.regex.nondigit, '' ) || '';
656
+ range2 = ts.formatFloat( tsf.parseFilter( c, tmp, index, parsed ), table );
652
657
  // parse filter value in case we're comparing numbers ( dates )
653
658
  if ( parsed || c.parsers[index].type === 'numeric' ) {
654
659
  result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index );
@@ -659,7 +664,7 @@
659
664
  if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) {
660
665
  result = data.cache;
661
666
  } else {
662
- tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
667
+ tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.regex.nondigit, '' ) : data.iExact;
663
668
  result = ts.formatFloat( tmp, table );
664
669
  }
665
670
  if ( range1 > range2 ) {
@@ -671,18 +676,18 @@
671
676
  },
672
677
  // Look for wild card: ? = single, * = multiple, or | = logical OR
673
678
  wild : function( c, data ) {
674
- if ( /[\?\*\|]/.test( data.iFilter ) ) {
679
+ if ( tsf.regex.wildOrTest.test( data.iFilter ) ) {
675
680
  var index = data.index,
676
681
  parsed = data.parsed[ index ],
677
- query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' );
682
+ query = '' + ( tsf.parseFilter( c, data.iFilter, index, parsed ) || '' );
678
683
  // look for an exact match with the 'or' unless the 'filter-match' class is found
679
- if ( !/\?\*/.test( query ) && data.nestedFilters ) {
684
+ if ( !tsf.regex.wildTest.test( query ) && data.nestedFilters ) {
680
685
  query = data.isMatch ? query : '^(' + query + ')$';
681
686
  }
682
687
  // parsing the filter may not work properly when using wildcards =/
683
688
  try {
684
689
  return new RegExp(
685
- query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ),
690
+ query.replace( tsf.regex.wild01, '\\S{1}' ).replace( tsf.regex.wild0More, '\\S*' ),
686
691
  c.widgetOptions.filter_ignoreCase ? 'i' : ''
687
692
  )
688
693
  .test( data.exact );
@@ -694,12 +699,12 @@
694
699
  },
695
700
  // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license )
696
701
  fuzzy: function( c, data ) {
697
- if ( /^~/.test( data.iFilter ) ) {
702
+ if ( tsf.regex.fuzzyTest.test( data.iFilter ) ) {
698
703
  var indx,
699
704
  patternIndx = 0,
700
705
  len = data.iExact.length,
701
706
  txt = data.iFilter.slice( 1 ),
702
- pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
707
+ pattern = tsf.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
703
708
  for ( indx = 0; indx < len; indx++ ) {
704
709
  if ( data.iExact[ indx ] === pattern[ patternIndx ] ) {
705
710
  patternIndx += 1;
@@ -722,7 +727,7 @@
722
727
  }, ts.language );
723
728
 
724
729
  var options, string, txt, $header, column, filters, val, fxn, noSelect,
725
- regex = ts.filter.regex;
730
+ regex = tsf.regex;
726
731
  c.$table.addClass( 'hasFilters' );
727
732
 
728
733
  // define timers so using clearTimeout won't cause an undefined error
@@ -733,7 +738,7 @@
733
738
  wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]';
734
739
  wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]';
735
740
 
736
- val = '\\{' + ts.filter.regex.query + '\\}';
741
+ val = '\\{' + tsf.regex.query + '\\}';
737
742
  $.extend( regex, {
738
743
  child : new RegExp( c.cssChildRow ),
739
744
  filtered : new RegExp( wo.filter_filteredRow ),
@@ -742,9 +747,20 @@
742
747
  toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)', 'gi' ),
743
748
  andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ),
744
749
  andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ),
750
+ orTest : /\|/,
745
751
  orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ),
746
752
  iQuery : new RegExp( val, 'i' ),
747
- igQuery : new RegExp( val, 'ig' )
753
+ igQuery : new RegExp( val, 'ig' ),
754
+ operTest : /^[<>]=?/,
755
+ gtTest : />/,
756
+ gteTest : />=/,
757
+ ltTest : /</,
758
+ lteTest : /<=/,
759
+ notTest : /^\!/,
760
+ wildOrTest : /[\?\*\|]/,
761
+ wildTest : /\?\*/,
762
+ fuzzyTest : /^~/,
763
+ exactTest : /[=\"\|!]/
748
764
  });
749
765
 
750
766
  // don't build filter row if columnFilters is false or all columns are set to 'filter-false'
@@ -752,7 +768,7 @@
752
768
  val = c.$headers.filter( '.filter-false, .parser-false' ).length;
753
769
  if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) {
754
770
  // build filter row
755
- ts.filter.buildRow( table, c, wo );
771
+ tsf.buildRow( table, c, wo );
756
772
  }
757
773
 
758
774
  txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '
@@ -765,13 +781,13 @@
765
781
  c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450
766
782
  if ( !/(search|filter)/.test( event.type ) ) {
767
783
  event.stopPropagation();
768
- ts.filter.buildDefault( table, true );
784
+ tsf.buildDefault( table, true );
769
785
  }
770
786
  if ( event.type === 'filterReset' ) {
771
787
  c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' );
772
- ts.filter.searching( table, [] );
788
+ tsf.searching( table, [] );
773
789
  } else if ( event.type === 'filterEnd' ) {
774
- ts.filter.buildDefault( table, true );
790
+ tsf.buildDefault( table, true );
775
791
  } else {
776
792
  // send false argument to force a new search; otherwise if the filter hasn't changed,
777
793
  // it will return
@@ -785,7 +801,7 @@
785
801
  // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first
786
802
  // input ensures all inputs are updated when a search is triggered on the table
787
803
  // $( 'table' ).trigger( 'search', [...] );
788
- ts.filter.searching( table, filter, true );
804
+ tsf.searching( table, filter, true );
789
805
  }
790
806
  return false;
791
807
  });
@@ -818,7 +834,7 @@
818
834
  noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
819
835
  options = '';
820
836
  if ( fxn === true && noSelect ) {
821
- ts.filter.buildSelect( table, column );
837
+ tsf.buildSelect( table, column );
822
838
  } else if ( typeof fxn === 'object' && noSelect ) {
823
839
  // add custom drop down list
824
840
  for ( string in fxn ) {
@@ -851,7 +867,7 @@
851
867
  fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column );
852
868
  if ( fxn ) {
853
869
  // updating so the extra options are appended
854
- ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) );
870
+ tsf.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) );
855
871
  }
856
872
  }
857
873
  }
@@ -859,22 +875,22 @@
859
875
  }
860
876
  // not really updating, but if the column has both the 'filter-select' class &
861
877
  // filter_functions set to true, it would append the same options twice.
862
- ts.filter.buildDefault( table, true );
878
+ tsf.buildDefault( table, true );
863
879
 
864
- ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true );
880
+ tsf.bindSearch( table, c.$table.find( '.' + tscss.filter ), true );
865
881
  if ( wo.filter_external ) {
866
- ts.filter.bindSearch( table, wo.filter_external );
882
+ tsf.bindSearch( table, wo.filter_external );
867
883
  }
868
884
 
869
885
  if ( wo.filter_hideFilters ) {
870
- ts.filter.hideFilters( table, c );
886
+ tsf.hideFilters( table, c );
871
887
  }
872
888
 
873
889
  // show processing icon
874
890
  if ( c.showProcessing ) {
875
891
  txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' );
876
892
  c.$table
877
- .unbind( txt.replace( /\s+/g, ' ' ) )
893
+ .unbind( txt.replace( ts.regex.spaces, ' ' ) )
878
894
  .bind( txt, function( event, columns ) {
879
895
  // only add processing to certain columns to all columns
880
896
  $header = ( columns ) ?
@@ -894,11 +910,11 @@
894
910
  // add default values
895
911
  txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' );
896
912
  c.$table
897
- .unbind( txt.replace( /\s+/g, ' ' ) )
913
+ .unbind( txt.replace( ts.regex.spaces, ' ' ) )
898
914
  .bind( txt, function() {
899
915
  // redefine 'wo' as it does not update properly inside this callback
900
916
  var wo = this.config.widgetOptions;
901
- filters = ts.filter.setDefaults( table, c, wo ) || [];
917
+ filters = tsf.setDefaults( table, c, wo ) || [];
902
918
  if ( filters.length ) {
903
919
  // prevent delayInit from triggering a cache build if filters are empty
904
920
  if ( !( c.delayInit && filters.join( '' ) === '' ) ) {
@@ -909,7 +925,7 @@
909
925
  // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers
910
926
  setTimeout( function() {
911
927
  if ( !wo.filter_initialized ) {
912
- ts.filter.filterInitComplete( c );
928
+ tsf.filterInitComplete( c );
913
929
  }
914
930
  }, 100 );
915
931
  });
@@ -917,7 +933,7 @@
917
933
  if ( c.pager && c.pager.initialized && !wo.filter_initialized ) {
918
934
  c.$table.trigger( 'filterFomatterUpdate' );
919
935
  setTimeout( function() {
920
- ts.filter.filterInitComplete( c );
936
+ tsf.filterInitComplete( c );
921
937
  }, 100 );
922
938
  }
923
939
  },
@@ -938,7 +954,7 @@
938
954
  completed = function() {
939
955
  wo.filter_initialized = true;
940
956
  c.$table.trigger( 'filterInit', c );
941
- ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] );
957
+ tsf.findRows( c.table, c.$table.data( 'lastSearch' ) || [] );
942
958
  };
943
959
  if ( $.isEmptyObject( wo.filter_formatter ) ) {
944
960
  completed();
@@ -1090,7 +1106,7 @@
1090
1106
  // use data attribute instead of jQuery data since the head is cloned without including
1091
1107
  // the data/binding
1092
1108
  .attr( 'data-lastSearchTime', new Date().getTime() )
1093
- .unbind( tmp.replace( /\s+/g, ' ' ) )
1109
+ .unbind( tmp.replace( ts.regex.spaces, ' ' ) )
1094
1110
  // include change for select - fixes #473
1095
1111
  .bind( 'keyup' + namespace, function( event ) {
1096
1112
  $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
@@ -1110,17 +1126,18 @@
1110
1126
  return;
1111
1127
  }
1112
1128
  // change event = no delay; last true flag tells getFilters to skip newest timed input
1113
- ts.filter.searching( table, true, true );
1129
+ tsf.searching( table, true, true );
1114
1130
  })
1115
1131
  .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) {
1116
- var column = $( this ).data( 'column' );
1132
+ // don't get cached data, in case data-column changes dynamically
1133
+ var column = parseInt( $( this ).attr( 'data-column' ), 10 );
1117
1134
  // don't allow 'change' event to process if the input value is the same - fixes #685
1118
1135
  if ( event.which === 13 || event.type === 'search' ||
1119
1136
  event.type === 'change' && this.value !== c.lastSearch[column] ) {
1120
1137
  event.preventDefault();
1121
1138
  // init search with no delay
1122
1139
  $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
1123
- ts.filter.searching( table, false, true );
1140
+ tsf.searching( table, false, true );
1124
1141
  }
1125
1142
  });
1126
1143
  },
@@ -1130,11 +1147,11 @@
1130
1147
  if ( typeof filter === 'undefined' || filter === true ) {
1131
1148
  // delay filtering
1132
1149
  wo.searchTimer = setTimeout( function() {
1133
- ts.filter.checkFilters( table, filter, skipFirst );
1150
+ tsf.checkFilters( table, filter, skipFirst );
1134
1151
  }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 );
1135
1152
  } else {
1136
1153
  // skip delay
1137
- ts.filter.checkFilters( table, filter, skipFirst );
1154
+ tsf.checkFilters( table, filter, skipFirst );
1138
1155
  }
1139
1156
  },
1140
1157
  checkFilters: function( table, filter, skipFirst ) {
@@ -1148,7 +1165,7 @@
1148
1165
  // update cache if delayInit set & pager has initialized ( after user initiates a search )
1149
1166
  if ( c.delayInit && c.pager && c.pager.initialized ) {
1150
1167
  c.$table.trigger( 'updateCache', [ function() {
1151
- ts.filter.checkFilters( table, false, skipFirst );
1168
+ tsf.checkFilters( table, false, skipFirst );
1152
1169
  } ] );
1153
1170
  }
1154
1171
  return;
@@ -1179,11 +1196,11 @@
1179
1196
  if ( c.showProcessing ) {
1180
1197
  // give it time for the processing icon to kick in
1181
1198
  setTimeout( function() {
1182
- ts.filter.findRows( table, filters, combinedFilters );
1199
+ tsf.findRows( table, filters, combinedFilters );
1183
1200
  return false;
1184
1201
  }, 30 );
1185
1202
  } else {
1186
- ts.filter.findRows( table, filters, combinedFilters );
1203
+ tsf.findRows( table, filters, combinedFilters );
1187
1204
  return false;
1188
1205
  }
1189
1206
  },
@@ -1226,8 +1243,8 @@
1226
1243
  },
1227
1244
  defaultFilter: function( filter, mask ) {
1228
1245
  if ( filter === '' ) { return filter; }
1229
- var regex = ts.filter.regex.iQuery,
1230
- maskLen = mask.match( ts.filter.regex.igQuery ).length,
1246
+ var regex = tsf.regex.iQuery,
1247
+ maskLen = mask.match( tsf.regex.igQuery ).length,
1231
1248
  query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ],
1232
1249
  len = query.length - 1,
1233
1250
  indx = 0,
@@ -1263,7 +1280,10 @@
1263
1280
  // & don't target 'all' column inputs if they don't exist
1264
1281
  targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length,
1265
1282
  columns = [],
1266
- val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' );
1283
+ val = $.trim( tsf.getLatestSearch( $input ).attr( 'data-column' ) || '' );
1284
+ if ( !/[,-]/.test(val) && val.length === 1 ) {
1285
+ return parseInt( val, 10 );
1286
+ }
1267
1287
  // process column range
1268
1288
  if ( targets && /-/.test( val ) ) {
1269
1289
  ranges = val.match( /(\d+)\s*-\s*(\d+)/g );
@@ -1310,9 +1330,9 @@
1310
1330
  var ffxn,
1311
1331
  filterMatched = null,
1312
1332
  matches = null;
1313
- for ( ffxn in ts.filter.types ) {
1333
+ for ( ffxn in tsf.types ) {
1314
1334
  if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) {
1315
- matches = ts.filter.types[ffxn]( c, data, vars );
1335
+ matches = tsf.types[ffxn]( c, data, vars );
1316
1336
  if ( matches !== null ) {
1317
1337
  filterMatched = matches;
1318
1338
  }
@@ -1321,16 +1341,23 @@
1321
1341
  return filterMatched;
1322
1342
  },
1323
1343
  processRow: function( c, data, vars ) {
1324
- var columnIndex, hasSelect, result, val, filterMatched,
1344
+ var hasSelect, result, val, filterMatched,
1325
1345
  fxn, ffxn, txt,
1326
- regex = ts.filter.regex,
1346
+ regex = tsf.regex,
1327
1347
  wo = c.widgetOptions,
1328
- showRow = true;
1348
+ showRow = true,
1349
+
1350
+ // if wo.filter_$anyMatch data-column attribute is changed dynamically
1351
+ // we don't want to do an "anyMatch" search on one column using data
1352
+ // for the entire row - see #998
1353
+ columnIndex = wo.filter_$anyMatch && wo.filter_$anyMatch.length ?
1354
+ // look for multiple columns '1-3,4-6,8'
1355
+ tsf.multipleColumns( c, wo.filter_$anyMatch ) :
1356
+ [];
1357
+
1329
1358
  data.$cells = data.$row.children();
1330
1359
 
1331
- if ( data.anyMatchFlag ) {
1332
- // look for multiple columns '1-3,4-6,8'
1333
- columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
1360
+ if ( data.anyMatchFlag && columnIndex.length > 1 ) {
1334
1361
  data.anyMatch = true;
1335
1362
  data.isMatch = true;
1336
1363
  data.rowArray = data.$cells.map( function( i ) {
@@ -1354,7 +1381,7 @@
1354
1381
  data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' );
1355
1382
 
1356
1383
  vars.excludeMatch = vars.noAnyMatch;
1357
- filterMatched = ts.filter.processTypes( c, data, vars );
1384
+ filterMatched = tsf.processTypes( c, data, vars );
1358
1385
 
1359
1386
  if ( filterMatched !== null ) {
1360
1387
  showRow = filterMatched;
@@ -1415,7 +1442,7 @@
1415
1442
 
1416
1443
  val = true;
1417
1444
  if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) {
1418
- data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] );
1445
+ data.filter = tsf.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] );
1419
1446
  // val is used to indicate that a filter select is using a default filter;
1420
1447
  // so we override the exact & partial matches
1421
1448
  val = false;
@@ -1446,13 +1473,13 @@
1446
1473
  if ( filterMatched === null ) {
1447
1474
  // cycle through the different filters
1448
1475
  // filters return a boolean or null if nothing matches
1449
- filterMatched = ts.filter.processTypes( c, data, vars );
1476
+ filterMatched = tsf.processTypes( c, data, vars );
1450
1477
  if ( filterMatched !== null ) {
1451
1478
  result = filterMatched;
1452
1479
  // Look for match, and add child row data for matching
1453
1480
  } else {
1454
1481
  txt = ( data.iExact + data.childRowText )
1455
- .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) );
1482
+ .indexOf( tsf.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) );
1456
1483
  result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) );
1457
1484
  }
1458
1485
  } else {
@@ -1472,7 +1499,7 @@
1472
1499
  isChild, childRow, lastSearch, showRow, time, val, indx,
1473
1500
  notFiltered, searchFiltered, query, injected, res, id, txt,
1474
1501
  storedFilters = $.extend( [], filters ),
1475
- regex = ts.filter.regex,
1502
+ regex = tsf.regex,
1476
1503
  c = table.config,
1477
1504
  wo = c.widgetOptions,
1478
1505
  // data object passed to filters; anyMatch is a flag for the filters
@@ -1549,7 +1576,7 @@
1549
1576
  data.anyMatchFlag = true;
1550
1577
  data.anyMatchFilter = '' + (
1551
1578
  filters[ c.columns ] ||
1552
- wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() ||
1579
+ wo.filter_$anyMatch && tsf.getLatestSearch( wo.filter_$anyMatch ).val() ||
1553
1580
  ''
1554
1581
  );
1555
1582
  if ( wo.filter_columnAnyMatch ) {
@@ -1591,10 +1618,10 @@
1591
1618
  // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string
1592
1619
  !regex.alreadyFiltered.test( val ) &&
1593
1620
  // if we are not doing exact matches, using '|' ( logical or ) or not '!'
1594
- !/[=\"\|!]/.test( val ) &&
1621
+ !regex.exactTest.test( val ) &&
1595
1622
  // don't search only filtered if the value is negative
1596
1623
  // ( '> -10' => '> -100' will ignore hidden rows )
1597
- !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) &&
1624
+ !( regex.isNeg1.test( val ) || regex.isNeg2.test( val ) ) &&
1598
1625
  // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593
1599
1626
  !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length &&
1600
1627
  !c.$headerIndexed[indx].hasClass( 'filter-match' ) );
@@ -1613,7 +1640,7 @@
1613
1640
  data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter );
1614
1641
  }
1615
1642
  if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) {
1616
- data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter );
1643
+ data.anyMatchFilter = tsf.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter );
1617
1644
  // clear search filtered flag because default filters are not saved to the last search
1618
1645
  searchFiltered = false;
1619
1646
  }
@@ -1656,7 +1683,7 @@
1656
1683
  '';
1657
1684
  }
1658
1685
 
1659
- showRow = ts.filter.processRow( c, data, vars );
1686
+ showRow = tsf.processRow( c, data, vars );
1660
1687
  childRow = rowData.$row.filter( ':gt( 0 )' );
1661
1688
 
1662
1689
  if ( wo.filter_childRows && childRow.length ) {
@@ -1667,7 +1694,7 @@
1667
1694
  data.cacheArray = rowData.child[ indx ];
1668
1695
  data.rawArray = data.cacheArray;
1669
1696
  // use OR comparison on child rows
1670
- showRow = showRow || ts.filter.processRow( c, data, vars );
1697
+ showRow = showRow || tsf.processRow( c, data, vars );
1671
1698
  }
1672
1699
  }
1673
1700
  childRow.toggleClass( wo.filter_filteredRow, !showRow );
@@ -1729,7 +1756,7 @@
1729
1756
  }
1730
1757
  if ( arry === false ) {
1731
1758
  // fall back to original method
1732
- arry = ts.filter.getOptions( table, column, onlyAvail );
1759
+ arry = tsf.getOptions( table, column, onlyAvail );
1733
1760
  }
1734
1761
 
1735
1762
  // get unique elements and sort the list
@@ -1841,13 +1868,13 @@
1841
1868
  // nothing included in arry ( external source ), so get the options from
1842
1869
  // filter_selectSource or column data
1843
1870
  if ( typeof arry === 'undefined' || arry === '' ) {
1844
- arry = ts.filter.getOptionSource( table, column, onlyAvail );
1871
+ arry = tsf.getOptionSource( table, column, onlyAvail );
1845
1872
  }
1846
1873
 
1847
1874
  if ( $.isArray( arry ) ) {
1848
1875
  // build option list
1849
1876
  for ( indx = 0; indx < arry.length; indx++ ) {
1850
- txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '&quot;' );
1877
+ txt = arry[indx] = ( '' + arry[indx] ).replace( tsf.regex.quote, '&quot;' );
1851
1878
  val = txt;
1852
1879
  // allow including a symbol in the selectSource array
1853
1880
  // 'a-z|A through Z' so that 'a-z' becomes the option value
@@ -1903,7 +1930,7 @@
1903
1930
  // look for the filter-select class; build/update it if found
1904
1931
  if ( ( $header.hasClass( 'filter-select' ) ||
1905
1932
  ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) {
1906
- ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) );
1933
+ tsf.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) );
1907
1934
  }
1908
1935
  }
1909
1936
  }
@@ -1939,7 +1966,7 @@
1939
1966
  $column = $filters.filter( cols );
1940
1967
  if ( $column.length ) {
1941
1968
  // move the latest search to the first slot in the array
1942
- $column = ts.filter.getLatestSearch( $column );
1969
+ $column = tsf.getLatestSearch( $column );
1943
1970
  if ( $.isArray( setFilters ) ) {
1944
1971
  // skip first ( latest input ) to maintain cursor position while typing
1945
1972
  if ( skipFirst && $column.length > 1 ) {
@@ -1989,7 +2016,7 @@
1989
2016
  // ensure new set filters are applied, even if the search is the same
1990
2017
  c.lastCombinedFilter = null;
1991
2018
  c.lastSearch = [];
1992
- ts.filter.searching( c.table, filter, skipFirst );
2019
+ tsf.searching( c.table, filter, skipFirst );
1993
2020
  c.$table.trigger( 'filterFomatterUpdate' );
1994
2021
  }
1995
2022
  return !!valid;