jquery-tablesorter 1.18.1 → 1.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4345dc6b65a0109332a377216626cf9dbc0fc3d8
4
- data.tar.gz: 017a83402c042c26a78ac3fd08ec03b9d6523ad2
3
+ metadata.gz: 886efa143e551b3b1b348bfc4a5ceb244cba60d1
4
+ data.tar.gz: b3ae2ede803d0ef6014d497114a502a3128b4d27
5
5
  SHA512:
6
- metadata.gz: 88e0d936b1eb66d037f5b101f16823a66a83af852e5cb84ba3098d4d2eb74694885001d18f1511976afd11493adb8502f9bce312a46b4a954105a48a2bc8704c
7
- data.tar.gz: 2532d0968b7aa11095ee68870b529401980ec5a7179ef267f6847e3a7c1b74f7c6b0a26b49ce12176a415ab224fd92806871a8bf8ccae6b552bfdc32aa7e543a
6
+ metadata.gz: 5a526f91f99c7d413b1d4c92b5b3ff03cb539bc4fda82e6f23cee1ffb162338b698b2c7ab91c5779fd824e52813f7eb4747e7dbd37ec7d9cc3f7d9f83cbb26d3
7
+ data.tar.gz: e42ae380bbddbca7bb6c7ccb74d2581cad6eb2d5273cd07f39bbd2ec804dec5ac2b8a63080b37e9eda3d236d8fdfd51c8fdb47dd19cf8cc6b4af7dbacddbe72f
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.23.1 (8/19/2015), [documentation]
7
+ Current tablesorter version: 2.23.2 (8/23/2015), [documentation]
8
8
 
9
9
  Any issue associated with the js/css files, please report to [Mottie's fork].
10
10
 
@@ -24,9 +24,8 @@ Or install it yourself as:
24
24
 
25
25
  ## Requirements
26
26
 
27
- Rails 3.2 and higher (tested up to 4.2)
28
-
29
- Tested with ruby 1.9.3 - 2.2.2
27
+ It should work with Rails 3.2 and higher (tested up to 4.2) as well as with ruby 1.9.3 - 2.2.x.
28
+ Each release is always tested with the latest version of both.
30
29
 
31
30
  ## Usage
32
31
 
@@ -1,3 +1,3 @@
1
1
  module JqueryTablesorter
2
- VERSION = '1.18.1'
2
+ VERSION = '1.18.2'
3
3
  end
@@ -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) {
@@ -16,7 +16,7 @@
16
16
  }
17
17
  }(function($) {
18
18
 
19
- /*! TableSorter (FORK) v2.23.1 *//*
19
+ /*! TableSorter (FORK) v2.23.2 *//*
20
20
  * Client-side table sorting with ease!
21
21
  * @requires jQuery v1.2.6+
22
22
  *
@@ -44,7 +44,7 @@
44
44
 
45
45
  var ts = this;
46
46
 
47
- ts.version = '2.23.1';
47
+ ts.version = '2.23.2';
48
48
 
49
49
  ts.parsers = [];
50
50
  ts.widgets = [];
@@ -169,6 +169,15 @@
169
169
  nextNone : 'activate to remove the sort'
170
170
  };
171
171
 
172
+ ts.regex = {
173
+ templateContent : /\{content\}/g,
174
+ templateIcon : /\{icon\}/g,
175
+ templateName : /\{name\}/i,
176
+ spaces : /\s+/g,
177
+ nonWord : /\W/g,
178
+ formElements : /(input|select|button|textarea)/i
179
+ };
180
+
172
181
  // These methods can be applied on table.config instance
173
182
  ts.instanceMethods = {};
174
183
 
@@ -461,7 +470,9 @@
461
470
  // if headerTemplate is empty, don't reformat the header cell
462
471
  if ( c.headerTemplate !== '' && !$t.find('.' + ts.css.headerIn).length ) {
463
472
  // set up header template
464
- t = c.headerTemplate.replace(/\{content\}/g, $t.html()).replace(/\{icon\}/g, $t.find('.' + ts.css.icon).length ? '' : i);
473
+ t = c.headerTemplate
474
+ .replace(ts.regex.templateContent, $t.html())
475
+ .replace(ts.regex.templateIcon, $t.find('.' + ts.css.icon).length ? '' : i);
465
476
  if (c.onRenderTemplate) {
466
477
  h = c.onRenderTemplate.apply( $t, [ index, t ] );
467
478
  if (h && typeof h === 'string') { t = h; } // only change t if something is returned
@@ -874,7 +885,7 @@
874
885
  .join( c.namespace + ' ' );
875
886
  // apply easy methods that trigger bound events
876
887
  $table
877
- .unbind( events.replace( /\s+/g, ' ' ) )
888
+ .unbind( events.replace( ts.regex.spaces, ' ' ) )
878
889
  .bind( 'sortReset' + c.namespace, function( e, callback ) {
879
890
  e.stopPropagation();
880
891
  // using this.config to ensure functions are getting a non-cached version of the config
@@ -1021,7 +1032,7 @@
1021
1032
  c.namespace = '.tablesorter' + Math.random().toString(16).slice(2);
1022
1033
  } else {
1023
1034
  // make sure namespace starts with a period & doesn't have weird characters
1024
- c.namespace = '.' + c.namespace.replace(/\W/g, '');
1035
+ c.namespace = '.' + c.namespace.replace(ts.regex.nonWord, '');
1025
1036
  }
1026
1037
 
1027
1038
  c.$table.children().children('tr').attr('role', 'row');
@@ -1249,7 +1260,7 @@
1249
1260
  }
1250
1261
  }
1251
1262
  t = ( c.pointerDown + ' ' + c.pointerUp + ' ' + c.pointerClick + ' sort keyup ' )
1252
- .replace(/\s+/g, ' ')
1263
+ .replace(ts.regex.spaces, ' ')
1253
1264
  .split(' ')
1254
1265
  .join(c.namespace + ' ');
1255
1266
  // apply event handling to headers and/or additional headers (stickyheaders, scroller, etc)
@@ -1283,7 +1294,7 @@
1283
1294
  }
1284
1295
  downTarget = null;
1285
1296
  // prevent sort being triggered on form elements
1286
- if ( /(input|select|button|textarea)/i.test(e.target.nodeName) ||
1297
+ if ( ts.regex.formElements.test(e.target.nodeName) ||
1287
1298
  // nosort class name, or elements within a nosort container
1288
1299
  $target.hasClass(c.cssNoSort) || $target.parents('.' + c.cssNoSort).length > 0 ||
1289
1300
  // elements within a button
@@ -1569,13 +1580,13 @@
1569
1580
  .join(c.namespace + ' ');
1570
1581
  $t
1571
1582
  .removeData('tablesorter')
1572
- .unbind( events.replace(/\s+/g, ' ') );
1583
+ .unbind( events.replace(ts.regex.spaces, ' ') );
1573
1584
  c.$headers.add($f)
1574
1585
  .removeClass( [ ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone ].join(' ') )
1575
1586
  .removeAttr('data-column')
1576
1587
  .removeAttr('aria-label')
1577
1588
  .attr('aria-disabled', 'true');
1578
- $r.find(c.selectorSort).unbind( ('mousedown mouseup keypress '.split(' ').join(c.namespace + ' ')).replace(/\s+/g, ' ') );
1589
+ $r.find(c.selectorSort).unbind( ('mousedown mouseup keypress '.split(' ').join(c.namespace + ' ')).replace(ts.regex.spaces, ' ') );
1579
1590
  ts.restoreHeaders(table);
1580
1591
  $t.toggleClass(ts.css.table + ' ' + c.tableClass + ' tablesorter-' + c.theme, removeClasses === false);
1581
1592
  // clear flag in case the plugin is initialized again
@@ -1591,11 +1602,9 @@
1591
1602
 
1592
1603
  // *** sort functions ***
1593
1604
  // regex used in natural sort
1594
- ts.regex = {
1595
- chunk : /(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi, // chunk/tokenize numbers & letters
1596
- chunks: /(^\\0|\\0$)/, // replace chunks @ ends
1597
- hex: /^0x[0-9a-f]+$/i // hex
1598
- };
1605
+ ts.regex.chunk = /(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi; // chunk/tokenize numbers & letters
1606
+ ts.regex.chunks = /(^\\0|\\0$)/; // replace chunks @ ends
1607
+ ts.regex.hex = /^0x[0-9a-f]+$/i; // hex
1599
1608
 
1600
1609
  // Natural sort - https://github.com/overset/javascript-natural-sort (date sorting removed)
1601
1610
  // this function will only accept strings, or you'll see 'TypeError: undefined is not a function'
@@ -1822,7 +1831,7 @@
1822
1831
  if (c.debug) { time = new Date(); }
1823
1832
  // look for widgets to apply from in table class
1824
1833
  // stop using \b otherwise this matches 'ui-widget-content' & adds 'content' widget
1825
- wd = new RegExp( '\\s' + c.widgetClass.replace( /\{name\}/i, '([\\w-]+)' ) + '\\s', 'g' );
1834
+ wd = new RegExp( '\\s' + c.widgetClass.replace( ts.regex.templateName, '([\\w-]+)' ) + '\\s', 'g' );
1826
1835
  if ( tableClass.match( wd ) ) {
1827
1836
  // extract out the widget id from the table class (widget id's can include dashes)
1828
1837
  w = tableClass.match( wd );
@@ -2048,6 +2057,10 @@
2048
2057
  return $.trim(val);
2049
2058
  };
2050
2059
 
2060
+ ts.regex.comma = /,/g;
2061
+ ts.regex.digitNonUS = /[\s|\.]/g;
2062
+ ts.regex.digitNegativeTest = /^\s*\([.\d]+\)/;
2063
+ ts.regex.digitNegativeReplace = /^\s*\(([.\d]+)\)/;
2051
2064
  ts.formatFloat = function(s, table) {
2052
2065
  if (typeof s !== 'string' || s === '') { return s; }
2053
2066
  // allow using formatFloat without a table; defaults to US number format
@@ -2056,24 +2069,28 @@
2056
2069
  typeof table !== 'undefined' ? table : true;
2057
2070
  if (t) {
2058
2071
  // US Format - 1,234,567.89 -> 1234567.89
2059
- s = s.replace(/,/g, '');
2072
+ s = s.replace(ts.regex.comma, '');
2060
2073
  } else {
2061
2074
  // German Format = 1.234.567,89 -> 1234567.89
2062
2075
  // French Format = 1 234 567,89 -> 1234567.89
2063
- s = s.replace(/[\s|\.]/g, '').replace(/,/g, '.');
2076
+ s = s.replace(ts.regex.digitNonUS, '').replace(ts.regex.comma, '.');
2064
2077
  }
2065
- if (/^\s*\([.\d]+\)/.test(s)) {
2078
+ if (ts.regex.digitNegativeTest.test(s)) {
2066
2079
  // make (#) into a negative number -> (10) = -10
2067
- s = s.replace(/^\s*\(([.\d]+)\)/, '-$1');
2080
+ s = s.replace(ts.regex.digitNegativeReplace, '-$1');
2068
2081
  }
2069
2082
  i = parseFloat(s);
2070
2083
  // return the text instead of zero
2071
2084
  return isNaN(i) ? $.trim(s) : i;
2072
2085
  };
2073
2086
 
2087
+ ts.regex.digitTest = /^[\-+(]?\d+[)]?$/;
2088
+ ts.regex.digitReplace = /[,.'"\s]/g;
2074
2089
  ts.isDigit = function(s) {
2075
2090
  // replace all unwanted chars and match
2076
- return isNaN(s) ? (/^[\-+(]?\d+[)]?$/).test(s.toString().replace(/[,.'"\s]/g, '')) : s !== '';
2091
+ return isNaN(s) ?
2092
+ ts.regex.digitTest.test( s.toString().replace( ts.regex.digitReplace, '' ) ) :
2093
+ s !== '';
2077
2094
  };
2078
2095
 
2079
2096
  }()
@@ -2132,65 +2149,76 @@
2132
2149
  type: 'text'
2133
2150
  });
2134
2151
 
2152
+ ts.regex.nondigit = /[^\w,. \-()]/g;
2135
2153
  ts.addParser({
2136
2154
  id: 'digit',
2137
2155
  is: function(s) {
2138
2156
  return ts.isDigit(s);
2139
2157
  },
2140
2158
  format: function(s, table) {
2141
- var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ''), table);
2159
+ var n = ts.formatFloat((s || '').replace(ts.regex.nondigit, ''), table);
2142
2160
  return s && typeof n === 'number' ? n :
2143
2161
  s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
2144
2162
  },
2145
2163
  type: 'numeric'
2146
2164
  });
2147
2165
 
2166
+ ts.regex.currencyReplace = /[+\-,. ]/g;
2167
+ ts.regex.currencyTest = /^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/;
2148
2168
  ts.addParser({
2149
2169
  id: 'currency',
2150
2170
  is: function(s) {
2151
- s = (s || '').replace(/[+\-,. ]/g, '');
2171
+ s = (s || '').replace(ts.regex.currencyReplace, '');
2152
2172
  // test for £$€¤¥¢
2153
- return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test(s);
2173
+ return ts.regex.currencyTest.test(s);
2154
2174
  },
2155
2175
  format: function(s, table) {
2156
- var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ''), table);
2176
+ var n = ts.formatFloat((s || '').replace(ts.regex.nondigit, ''), table);
2157
2177
  return s && typeof n === 'number' ? n :
2158
2178
  s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
2159
2179
  },
2160
2180
  type: 'numeric'
2161
2181
  });
2162
2182
 
2183
+ // too many protocols to add them all https://en.wikipedia.org/wiki/URI_scheme
2184
+ // now, this regex can be updated before initialization
2185
+ ts.regex.urlProtocolTest = /^(https?|ftp|file):\/\//;
2186
+ ts.regex.urlProtocolReplace = /(https?|ftp|file):\/\//;
2163
2187
  ts.addParser({
2164
2188
  id: 'url',
2165
2189
  is: function(s) {
2166
- return (/^(https?|ftp|file):\/\//).test(s);
2190
+ return ts.regex.urlProtocolTest.test(s);
2167
2191
  },
2168
2192
  format: function(s) {
2169
- return s ? $.trim(s.replace(/(https?|ftp|file):\/\//, '')) : s;
2193
+ return s ? $.trim(s.replace(ts.regex.urlProtocolReplace, '')) : s;
2170
2194
  },
2171
2195
  parsed : true, // filter widget flag
2172
2196
  type: 'text'
2173
2197
  });
2174
2198
 
2199
+ ts.regex.dash = /-/g;
2200
+ ts.regex.isoDate = /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/;
2175
2201
  ts.addParser({
2176
2202
  id: 'isoDate',
2177
2203
  is: function(s) {
2178
- return (/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/).test(s);
2204
+ return ts.regex.isoDate.test(s);
2179
2205
  },
2180
2206
  format: function(s, table) {
2181
- var date = s ? new Date( s.replace(/-/g, '/') ) : s;
2207
+ var date = s ? new Date( s.replace(ts.regex.dash, '/') ) : s;
2182
2208
  return date instanceof Date && isFinite(date) ? date.getTime() : s;
2183
2209
  },
2184
2210
  type: 'numeric'
2185
2211
  });
2186
2212
 
2213
+ ts.regex.percent = /%/g;
2214
+ ts.regex.percentTest = /(\d\s*?%|%\s*?\d)/;
2187
2215
  ts.addParser({
2188
2216
  id: 'percent',
2189
2217
  is: function(s) {
2190
- return (/(\d\s*?%|%\s*?\d)/).test(s) && s.length < 15;
2218
+ return ts.regex.percentTest.test(s) && s.length < 15;
2191
2219
  },
2192
2220
  format: function(s, table) {
2193
- return s ? ts.formatFloat(s.replace(/%/g, ''), table) : s;
2221
+ return s ? ts.formatFloat(s.replace(ts.regex.percent, ''), table) : s;
2194
2222
  },
2195
2223
  type: 'numeric'
2196
2224
  });
@@ -2208,27 +2236,35 @@
2208
2236
  type: 'text'
2209
2237
  });
2210
2238
 
2239
+ ts.regex.dateReplace = /(\S)([AP]M)$/i; // used by usLongDate & time parser
2240
+ 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;
2241
+ ts.regex.usLongDateTest2 = /^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i;
2211
2242
  ts.addParser({
2212
2243
  id: 'usLongDate',
2213
2244
  is: function(s) {
2214
2245
  // two digit years are not allowed cross-browser
2215
2246
  // Jan 01, 2013 12:34:56 PM or 01 Jan 2013
2216
- 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) ||
2217
- (/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i).test(s);
2247
+ return ts.regex.usLongDateTest1.test(s) || ts.regex.usLongDateTest2.test(s);
2218
2248
  },
2219
2249
  format: function(s, table) {
2220
- var date = s ? new Date( s.replace(/(\S)([AP]M)$/i, '$1 $2') ) : s;
2250
+ var date = s ? new Date( s.replace(ts.regex.dateReplace, '$1 $2') ) : s;
2221
2251
  return date instanceof Date && isFinite(date) ? date.getTime() : s;
2222
2252
  },
2223
2253
  type: 'numeric'
2224
2254
  });
2225
2255
 
2256
+ // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
2257
+ ts.regex.shortDateTest = /(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/;
2258
+ // escaped "-" because JSHint in Firefox was showing it as an error
2259
+ ts.regex.shortDateReplace = /[\-.,]/g;
2260
+ // XXY covers MDY & DMY formats
2261
+ ts.regex.shortDateXXY = /(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/;
2262
+ ts.regex.shortDateYMD = /(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/;
2226
2263
  ts.addParser({
2227
2264
  id: 'shortDate', // 'mmddyyyy', 'ddmmyyyy' or 'yyyymmdd'
2228
2265
  is: function(s) {
2229
- s = (s || '').replace(/\s+/g, ' ').replace(/[\-.,]/g, '/');
2230
- // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
2231
- return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test(s);
2266
+ s = (s || '').replace(ts.regex.spaces, ' ').replace(ts.regex.shortDateReplace, '/');
2267
+ return ts.regex.shortDateTest.test(s);
2232
2268
  },
2233
2269
  format: function(s, table, cell, cellIndex) {
2234
2270
  if (s) {
@@ -2238,14 +2274,13 @@
2238
2274
  format = ci.length && ci[0].dateFormat ||
2239
2275
  ts.getData( ci, ts.getColumnData( table, c.headers, cellIndex ), 'dateFormat') ||
2240
2276
  c.dateFormat;
2241
- // escaped "-" because JSHint in Firefox was showing it as an error
2242
- d = s.replace(/\s+/g, ' ').replace(/[\-.,]/g, '/');
2277
+ d = s.replace(ts.regex.spaces, ' ').replace(ts.regex.shortDateReplace, '/');
2243
2278
  if (format === 'mmddyyyy') {
2244
- d = d.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, '$3/$1/$2');
2279
+ d = d.replace(ts.regex.shortDateXXY, '$3/$1/$2');
2245
2280
  } else if (format === 'ddmmyyyy') {
2246
- d = d.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, '$3/$2/$1');
2281
+ d = d.replace(ts.regex.shortDateXXY, '$3/$2/$1');
2247
2282
  } else if (format === 'yyyymmdd') {
2248
- d = d.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, '$1/$2/$3');
2283
+ d = d.replace(ts.regex.shortDateYMD, '$1/$2/$3');
2249
2284
  }
2250
2285
  date = new Date(d);
2251
2286
  return date instanceof Date && isFinite(date) ? date.getTime() : s;
@@ -2255,13 +2290,14 @@
2255
2290
  type: 'numeric'
2256
2291
  });
2257
2292
 
2293
+ ts.regex.timeTest = /^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i;
2258
2294
  ts.addParser({
2259
2295
  id: 'time',
2260
2296
  is: function(s) {
2261
- return (/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s);
2297
+ return ts.regex.timeTest.test(s);
2262
2298
  },
2263
2299
  format: function(s, table) {
2264
- var date = s ? new Date( '2000/01/01 ' + s.replace(/(\S)([AP]M)$/i, '$1 $2') ) : s;
2300
+ var date = s ? new Date( '2000/01/01 ' + s.replace(ts.regex.dateReplace, '$1 $2') ) : s;
2265
2301
  return date instanceof Date && isFinite(date) ? date.getTime() : s;
2266
2302
  },
2267
2303
  type: 'numeric'
@@ -2678,14 +2714,15 @@
2678
2714
 
2679
2715
  })(jQuery);
2680
2716
 
2681
- /*! Widget: filter - updated 8/17/2015 (v2.23.0) *//*
2717
+ /*! Widget: filter - updated 8/23/2015 (v2.23.2) *//*
2682
2718
  * Requires tablesorter v2.8+ and jQuery 1.7+
2683
2719
  * by Rob Garrison
2684
2720
  */
2685
2721
  ;( function ( $ ) {
2686
2722
  'use strict';
2687
- var ts = $.tablesorter || {},
2688
- tscss = ts.css;
2723
+ var tsf,
2724
+ ts = $.tablesorter || {},
2725
+ tscss = ts.css;
2689
2726
 
2690
2727
  $.extend( tscss, {
2691
2728
  filterRow : 'tablesorter-filter-row',
@@ -2729,7 +2766,7 @@
2729
2766
  },
2730
2767
  format: function( table, c, wo ) {
2731
2768
  if ( !c.$table.hasClass( 'hasFilters' ) ) {
2732
- ts.filter.init( table, c, wo );
2769
+ tsf.init( table, c, wo );
2733
2770
  }
2734
2771
  },
2735
2772
  remove: function( table, c, wo, refreshing ) {
@@ -2741,7 +2778,7 @@
2741
2778
  $table
2742
2779
  .removeClass( 'hasFilters' )
2743
2780
  // add .tsfilter namespace to all BUT search
2744
- .unbind( events.replace( /\s+/g, ' ' ) )
2781
+ .unbind( events.replace( ts.regex.spaces, ' ' ) )
2745
2782
  // remove the filter row even if refreshing, because the column might have been moved
2746
2783
  .find( '.' + tscss.filterRow ).remove();
2747
2784
  if ( refreshing ) { return; }
@@ -2756,7 +2793,7 @@
2756
2793
  }
2757
2794
  });
2758
2795
 
2759
- ts.filter = {
2796
+ tsf = ts.filter = {
2760
2797
 
2761
2798
  // regex used in filter 'check' functions - not for general use and not documented
2762
2799
  regex: {
@@ -2765,9 +2802,13 @@
2765
2802
  filtered : /filtered/, // filtered (hidden) row class name; updated in the script
2766
2803
  type : /undefined|number/, // check type
2767
2804
  exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==')
2768
- nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser)
2769
2805
  operators : /[<>=]/g, // replace operators
2770
- query : '(q|query)' // replace filter queries
2806
+ query : '(q|query)', // replace filter queries
2807
+ wild01 : /\?/g, // wild card match 0 or 1
2808
+ wild0More : /\*/g, // wild care match 0 or more
2809
+ quote : /\"/g,
2810
+ isNeg1 : /(>=?\s*-\d)/,
2811
+ isNeg2 : /(<=?\s*\d)/
2771
2812
  },
2772
2813
  // function( c, data ) { }
2773
2814
  // c = table.config
@@ -2784,27 +2825,27 @@
2784
2825
  // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
2785
2826
  types: {
2786
2827
  or : function( c, data, vars ) {
2787
- if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) {
2828
+ if ( tsf.regex.orTest.test( data.iFilter ) || tsf.regex.orSplit.test( data.filter ) ) {
2788
2829
  var indx, filterMatched, query, regex,
2789
2830
  // duplicate data but split filter
2790
2831
  data2 = $.extend( {}, data ),
2791
2832
  index = data.index,
2792
2833
  parsed = data.parsed[ index ],
2793
- filter = data.filter.split( ts.filter.regex.orSplit ),
2794
- iFilter = data.iFilter.split( ts.filter.regex.orSplit ),
2834
+ filter = data.filter.split( tsf.regex.orSplit ),
2835
+ iFilter = data.iFilter.split( tsf.regex.orSplit ),
2795
2836
  len = filter.length;
2796
2837
  for ( indx = 0; indx < len; indx++ ) {
2797
2838
  data2.nestedFilters = true;
2798
- data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
2799
- data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
2800
- query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')';
2839
+ data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], index, parsed ) || '' );
2840
+ data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
2841
+ query = '(' + ( tsf.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')';
2801
2842
  try {
2802
2843
  // use try/catch, because query may not be a valid regex if "|" is contained within a partial regex search,
2803
2844
  // e.g "/(Alex|Aar" -> Uncaught SyntaxError: Invalid regular expression: /(/(Alex)/: Unterminated group
2804
2845
  regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
2805
2846
  // filterMatched = data2.filter === '' && indx > 0 ? true
2806
2847
  // look for an exact match with the 'or' unless the 'filter-match' class is found
2807
- filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars );
2848
+ filterMatched = regex.test( data2.exact ) || tsf.processTypes( c, data2, vars );
2808
2849
  if ( filterMatched ) {
2809
2850
  return filterMatched;
2810
2851
  }
@@ -2819,27 +2860,27 @@
2819
2860
  },
2820
2861
  // Look for an AND or && operator ( logical and )
2821
2862
  and : function( c, data, vars ) {
2822
- if ( ts.filter.regex.andTest.test( data.filter ) ) {
2863
+ if ( tsf.regex.andTest.test( data.filter ) ) {
2823
2864
  var indx, filterMatched, result, query, regex,
2824
2865
  // duplicate data but split filter
2825
2866
  data2 = $.extend( {}, data ),
2826
2867
  index = data.index,
2827
2868
  parsed = data.parsed[ index ],
2828
- filter = data.filter.split( ts.filter.regex.andSplit ),
2829
- iFilter = data.iFilter.split( ts.filter.regex.andSplit ),
2869
+ filter = data.filter.split( tsf.regex.andSplit ),
2870
+ iFilter = data.iFilter.split( tsf.regex.andSplit ),
2830
2871
  len = filter.length;
2831
2872
  for ( indx = 0; indx < len; indx++ ) {
2832
2873
  data2.nestedFilters = true;
2833
- data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
2834
- data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
2835
- query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' )
2874
+ data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], index, parsed ) || '' );
2875
+ data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
2876
+ query = ( '(' + ( tsf.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' )
2836
2877
  // replace wild cards since /(a*)/i will match anything
2837
- .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' );
2878
+ .replace( tsf.regex.wild01, '\\S{1}' ).replace( tsf.regex.wild0More, '\\S*' );
2838
2879
  try {
2839
2880
  // use try/catch just in case RegExp is invalid
2840
2881
  regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
2841
2882
  // look for an exact match with the 'and' unless the 'filter-match' class is found
2842
- result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) );
2883
+ result = ( regex.test( data2.exact ) || tsf.processTypes( c, data2, vars ) );
2843
2884
  if ( indx === 0 ) {
2844
2885
  filterMatched = result;
2845
2886
  } else {
@@ -2856,10 +2897,10 @@
2856
2897
  },
2857
2898
  // Look for regex
2858
2899
  regex: function( c, data ) {
2859
- if ( ts.filter.regex.regex.test( data.filter ) ) {
2900
+ if ( tsf.regex.regex.test( data.filter ) ) {
2860
2901
  var matches,
2861
2902
  // cache regex per column for optimal speed
2862
- regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ),
2903
+ regex = data.filter_regexCache[ data.index ] || tsf.regex.regex.exec( data.filter ),
2863
2904
  isRegex = regex instanceof RegExp;
2864
2905
  try {
2865
2906
  if ( !isRegex ) {
@@ -2878,18 +2919,18 @@
2878
2919
  // Look for operators >, >=, < or <=
2879
2920
  operators: function( c, data ) {
2880
2921
  // ignore empty strings... because '' < 10 is true
2881
- if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) {
2922
+ if ( tsf.regex.operTest.test( data.iFilter ) && data.iExact !== '' ) {
2882
2923
  var cachedValue, result, txt,
2883
2924
  table = c.table,
2884
2925
  index = data.index,
2885
2926
  parsed = data.parsed[index],
2886
- query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ),
2927
+ query = ts.formatFloat( data.iFilter.replace( tsf.regex.operators, '' ), table ),
2887
2928
  parser = c.parsers[index],
2888
2929
  savedSearch = query;
2889
2930
  // parse filter value in case we're comparing numbers ( dates )
2890
2931
  if ( parsed || parser.type === 'numeric' ) {
2891
- txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) );
2892
- result = ts.filter.parseFilter( c, txt, index, true );
2932
+ txt = $.trim( '' + data.iFilter.replace( tsf.regex.operators, '' ) );
2933
+ result = tsf.parseFilter( c, txt, index, true );
2893
2934
  query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query;
2894
2935
  }
2895
2936
  // iExact may be numeric - see issue #149;
@@ -2898,13 +2939,13 @@
2898
2939
  typeof data.cache !== 'undefined' ) {
2899
2940
  cachedValue = data.cache;
2900
2941
  } else {
2901
- txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
2942
+ txt = isNaN( data.iExact ) ? data.iExact.replace( ts.regex.nondigit, '' ) : data.iExact;
2902
2943
  cachedValue = ts.formatFloat( txt, table );
2903
2944
  }
2904
- if ( />/.test( data.iFilter ) ) {
2905
- result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query;
2906
- } else if ( /</.test( data.iFilter ) ) {
2907
- result = /<=/.test( data.iFilter ) ? cachedValue <= query : cachedValue < query;
2945
+ if ( tsf.regex.gtTest.test( data.iFilter ) ) {
2946
+ result = tsf.regex.gteTest.test( data.iFilter ) ? cachedValue >= query : cachedValue > query;
2947
+ } else if ( tsf.regex.ltTest.test( data.iFilter ) ) {
2948
+ result = tsf.regex.lteTest.test( data.iFilter ) ? cachedValue <= query : cachedValue < query;
2908
2949
  }
2909
2950
  // keep showing all rows if nothing follows the operator
2910
2951
  if ( !result && savedSearch === '' ) {
@@ -2916,13 +2957,13 @@
2916
2957
  },
2917
2958
  // Look for a not match
2918
2959
  notMatch: function( c, data ) {
2919
- if ( /^\!/.test( data.iFilter ) ) {
2960
+ if ( tsf.regex.notTest.test( data.iFilter ) ) {
2920
2961
  var indx,
2921
2962
  txt = data.iFilter.replace( '!', '' ),
2922
- filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
2923
- if ( ts.filter.regex.exact.test( filter ) ) {
2963
+ filter = tsf.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
2964
+ if ( tsf.regex.exact.test( filter ) ) {
2924
2965
  // look for exact not matches - see #628
2925
- filter = filter.replace( ts.filter.regex.exact, '' );
2966
+ filter = filter.replace( tsf.regex.exact, '' );
2926
2967
  return filter === '' ? true : $.trim( filter ) !== data.iExact;
2927
2968
  } else {
2928
2969
  indx = data.iExact.search( $.trim( filter ) );
@@ -2934,27 +2975,27 @@
2934
2975
  // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
2935
2976
  exact: function( c, data ) {
2936
2977
  /*jshint eqeqeq:false */
2937
- if ( ts.filter.regex.exact.test( data.iFilter ) ) {
2938
- var txt = data.iFilter.replace( ts.filter.regex.exact, '' ),
2939
- filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
2978
+ if ( tsf.regex.exact.test( data.iFilter ) ) {
2979
+ var txt = data.iFilter.replace( tsf.regex.exact, '' ),
2980
+ filter = tsf.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
2940
2981
  return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact;
2941
2982
  }
2942
2983
  return null;
2943
2984
  },
2944
2985
  // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
2945
2986
  range : function( c, data ) {
2946
- if ( ts.filter.regex.toTest.test( data.iFilter ) ) {
2987
+ if ( tsf.regex.toTest.test( data.iFilter ) ) {
2947
2988
  var result, tmp, range1, range2,
2948
2989
  table = c.table,
2949
2990
  index = data.index,
2950
2991
  parsed = data.parsed[index],
2951
2992
  // make sure the dash is for a range and not indicating a negative number
2952
- query = data.iFilter.split( ts.filter.regex.toSplit );
2993
+ query = data.iFilter.split( tsf.regex.toSplit );
2953
2994
 
2954
- tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || '';
2955
- range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
2956
- tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || '';
2957
- range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
2995
+ tmp = query[0].replace( ts.regex.nondigit, '' ) || '';
2996
+ range1 = ts.formatFloat( tsf.parseFilter( c, tmp, index, parsed ), table );
2997
+ tmp = query[1].replace( ts.regex.nondigit, '' ) || '';
2998
+ range2 = ts.formatFloat( tsf.parseFilter( c, tmp, index, parsed ), table );
2958
2999
  // parse filter value in case we're comparing numbers ( dates )
2959
3000
  if ( parsed || c.parsers[index].type === 'numeric' ) {
2960
3001
  result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index );
@@ -2965,7 +3006,7 @@
2965
3006
  if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) {
2966
3007
  result = data.cache;
2967
3008
  } else {
2968
- tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
3009
+ tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.regex.nondigit, '' ) : data.iExact;
2969
3010
  result = ts.formatFloat( tmp, table );
2970
3011
  }
2971
3012
  if ( range1 > range2 ) {
@@ -2977,18 +3018,18 @@
2977
3018
  },
2978
3019
  // Look for wild card: ? = single, * = multiple, or | = logical OR
2979
3020
  wild : function( c, data ) {
2980
- if ( /[\?\*\|]/.test( data.iFilter ) ) {
3021
+ if ( tsf.regex.wildOrTest.test( data.iFilter ) ) {
2981
3022
  var index = data.index,
2982
3023
  parsed = data.parsed[ index ],
2983
- query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' );
3024
+ query = '' + ( tsf.parseFilter( c, data.iFilter, index, parsed ) || '' );
2984
3025
  // look for an exact match with the 'or' unless the 'filter-match' class is found
2985
- if ( !/\?\*/.test( query ) && data.nestedFilters ) {
3026
+ if ( !tsf.regex.wildTest.test( query ) && data.nestedFilters ) {
2986
3027
  query = data.isMatch ? query : '^(' + query + ')$';
2987
3028
  }
2988
3029
  // parsing the filter may not work properly when using wildcards =/
2989
3030
  try {
2990
3031
  return new RegExp(
2991
- query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ),
3032
+ query.replace( tsf.regex.wild01, '\\S{1}' ).replace( tsf.regex.wild0More, '\\S*' ),
2992
3033
  c.widgetOptions.filter_ignoreCase ? 'i' : ''
2993
3034
  )
2994
3035
  .test( data.exact );
@@ -3000,12 +3041,12 @@
3000
3041
  },
3001
3042
  // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license )
3002
3043
  fuzzy: function( c, data ) {
3003
- if ( /^~/.test( data.iFilter ) ) {
3044
+ if ( tsf.regex.fuzzyTest.test( data.iFilter ) ) {
3004
3045
  var indx,
3005
3046
  patternIndx = 0,
3006
3047
  len = data.iExact.length,
3007
3048
  txt = data.iFilter.slice( 1 ),
3008
- pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
3049
+ pattern = tsf.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
3009
3050
  for ( indx = 0; indx < len; indx++ ) {
3010
3051
  if ( data.iExact[ indx ] === pattern[ patternIndx ] ) {
3011
3052
  patternIndx += 1;
@@ -3028,7 +3069,7 @@
3028
3069
  }, ts.language );
3029
3070
 
3030
3071
  var options, string, txt, $header, column, filters, val, fxn, noSelect,
3031
- regex = ts.filter.regex;
3072
+ regex = tsf.regex;
3032
3073
  c.$table.addClass( 'hasFilters' );
3033
3074
 
3034
3075
  // define timers so using clearTimeout won't cause an undefined error
@@ -3039,7 +3080,7 @@
3039
3080
  wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]';
3040
3081
  wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]';
3041
3082
 
3042
- val = '\\{' + ts.filter.regex.query + '\\}';
3083
+ val = '\\{' + tsf.regex.query + '\\}';
3043
3084
  $.extend( regex, {
3044
3085
  child : new RegExp( c.cssChildRow ),
3045
3086
  filtered : new RegExp( wo.filter_filteredRow ),
@@ -3048,9 +3089,20 @@
3048
3089
  toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)', 'gi' ),
3049
3090
  andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ),
3050
3091
  andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ),
3092
+ orTest : /\|/,
3051
3093
  orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ),
3052
3094
  iQuery : new RegExp( val, 'i' ),
3053
- igQuery : new RegExp( val, 'ig' )
3095
+ igQuery : new RegExp( val, 'ig' ),
3096
+ operTest : /^[<>]=?/,
3097
+ gtTest : />/,
3098
+ gteTest : />=/,
3099
+ ltTest : /</,
3100
+ lteTest : /<=/,
3101
+ notTest : /^\!/,
3102
+ wildOrTest : /[\?\*\|]/,
3103
+ wildTest : /\?\*/,
3104
+ fuzzyTest : /^~/,
3105
+ exactTest : /[=\"\|!]/
3054
3106
  });
3055
3107
 
3056
3108
  // don't build filter row if columnFilters is false or all columns are set to 'filter-false'
@@ -3058,7 +3110,7 @@
3058
3110
  val = c.$headers.filter( '.filter-false, .parser-false' ).length;
3059
3111
  if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) {
3060
3112
  // build filter row
3061
- ts.filter.buildRow( table, c, wo );
3113
+ tsf.buildRow( table, c, wo );
3062
3114
  }
3063
3115
 
3064
3116
  txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '
@@ -3071,13 +3123,13 @@
3071
3123
  c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450
3072
3124
  if ( !/(search|filter)/.test( event.type ) ) {
3073
3125
  event.stopPropagation();
3074
- ts.filter.buildDefault( table, true );
3126
+ tsf.buildDefault( table, true );
3075
3127
  }
3076
3128
  if ( event.type === 'filterReset' ) {
3077
3129
  c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' );
3078
- ts.filter.searching( table, [] );
3130
+ tsf.searching( table, [] );
3079
3131
  } else if ( event.type === 'filterEnd' ) {
3080
- ts.filter.buildDefault( table, true );
3132
+ tsf.buildDefault( table, true );
3081
3133
  } else {
3082
3134
  // send false argument to force a new search; otherwise if the filter hasn't changed,
3083
3135
  // it will return
@@ -3091,7 +3143,7 @@
3091
3143
  // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first
3092
3144
  // input ensures all inputs are updated when a search is triggered on the table
3093
3145
  // $( 'table' ).trigger( 'search', [...] );
3094
- ts.filter.searching( table, filter, true );
3146
+ tsf.searching( table, filter, true );
3095
3147
  }
3096
3148
  return false;
3097
3149
  });
@@ -3124,7 +3176,7 @@
3124
3176
  noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
3125
3177
  options = '';
3126
3178
  if ( fxn === true && noSelect ) {
3127
- ts.filter.buildSelect( table, column );
3179
+ tsf.buildSelect( table, column );
3128
3180
  } else if ( typeof fxn === 'object' && noSelect ) {
3129
3181
  // add custom drop down list
3130
3182
  for ( string in fxn ) {
@@ -3157,7 +3209,7 @@
3157
3209
  fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column );
3158
3210
  if ( fxn ) {
3159
3211
  // updating so the extra options are appended
3160
- ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) );
3212
+ tsf.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) );
3161
3213
  }
3162
3214
  }
3163
3215
  }
@@ -3165,22 +3217,22 @@
3165
3217
  }
3166
3218
  // not really updating, but if the column has both the 'filter-select' class &
3167
3219
  // filter_functions set to true, it would append the same options twice.
3168
- ts.filter.buildDefault( table, true );
3220
+ tsf.buildDefault( table, true );
3169
3221
 
3170
- ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true );
3222
+ tsf.bindSearch( table, c.$table.find( '.' + tscss.filter ), true );
3171
3223
  if ( wo.filter_external ) {
3172
- ts.filter.bindSearch( table, wo.filter_external );
3224
+ tsf.bindSearch( table, wo.filter_external );
3173
3225
  }
3174
3226
 
3175
3227
  if ( wo.filter_hideFilters ) {
3176
- ts.filter.hideFilters( table, c );
3228
+ tsf.hideFilters( table, c );
3177
3229
  }
3178
3230
 
3179
3231
  // show processing icon
3180
3232
  if ( c.showProcessing ) {
3181
3233
  txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' );
3182
3234
  c.$table
3183
- .unbind( txt.replace( /\s+/g, ' ' ) )
3235
+ .unbind( txt.replace( ts.regex.spaces, ' ' ) )
3184
3236
  .bind( txt, function( event, columns ) {
3185
3237
  // only add processing to certain columns to all columns
3186
3238
  $header = ( columns ) ?
@@ -3200,11 +3252,11 @@
3200
3252
  // add default values
3201
3253
  txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' );
3202
3254
  c.$table
3203
- .unbind( txt.replace( /\s+/g, ' ' ) )
3255
+ .unbind( txt.replace( ts.regex.spaces, ' ' ) )
3204
3256
  .bind( txt, function() {
3205
3257
  // redefine 'wo' as it does not update properly inside this callback
3206
3258
  var wo = this.config.widgetOptions;
3207
- filters = ts.filter.setDefaults( table, c, wo ) || [];
3259
+ filters = tsf.setDefaults( table, c, wo ) || [];
3208
3260
  if ( filters.length ) {
3209
3261
  // prevent delayInit from triggering a cache build if filters are empty
3210
3262
  if ( !( c.delayInit && filters.join( '' ) === '' ) ) {
@@ -3215,7 +3267,7 @@
3215
3267
  // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers
3216
3268
  setTimeout( function() {
3217
3269
  if ( !wo.filter_initialized ) {
3218
- ts.filter.filterInitComplete( c );
3270
+ tsf.filterInitComplete( c );
3219
3271
  }
3220
3272
  }, 100 );
3221
3273
  });
@@ -3223,7 +3275,7 @@
3223
3275
  if ( c.pager && c.pager.initialized && !wo.filter_initialized ) {
3224
3276
  c.$table.trigger( 'filterFomatterUpdate' );
3225
3277
  setTimeout( function() {
3226
- ts.filter.filterInitComplete( c );
3278
+ tsf.filterInitComplete( c );
3227
3279
  }, 100 );
3228
3280
  }
3229
3281
  },
@@ -3244,7 +3296,7 @@
3244
3296
  completed = function() {
3245
3297
  wo.filter_initialized = true;
3246
3298
  c.$table.trigger( 'filterInit', c );
3247
- ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] );
3299
+ tsf.findRows( c.table, c.$table.data( 'lastSearch' ) || [] );
3248
3300
  };
3249
3301
  if ( $.isEmptyObject( wo.filter_formatter ) ) {
3250
3302
  completed();
@@ -3396,7 +3448,7 @@
3396
3448
  // use data attribute instead of jQuery data since the head is cloned without including
3397
3449
  // the data/binding
3398
3450
  .attr( 'data-lastSearchTime', new Date().getTime() )
3399
- .unbind( tmp.replace( /\s+/g, ' ' ) )
3451
+ .unbind( tmp.replace( ts.regex.spaces, ' ' ) )
3400
3452
  // include change for select - fixes #473
3401
3453
  .bind( 'keyup' + namespace, function( event ) {
3402
3454
  $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
@@ -3416,17 +3468,18 @@
3416
3468
  return;
3417
3469
  }
3418
3470
  // change event = no delay; last true flag tells getFilters to skip newest timed input
3419
- ts.filter.searching( table, true, true );
3471
+ tsf.searching( table, true, true );
3420
3472
  })
3421
3473
  .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) {
3422
- var column = $( this ).data( 'column' );
3474
+ // don't get cached data, in case data-column changes dynamically
3475
+ var column = parseInt( $( this ).attr( 'data-column' ), 10 );
3423
3476
  // don't allow 'change' event to process if the input value is the same - fixes #685
3424
3477
  if ( event.which === 13 || event.type === 'search' ||
3425
3478
  event.type === 'change' && this.value !== c.lastSearch[column] ) {
3426
3479
  event.preventDefault();
3427
3480
  // init search with no delay
3428
3481
  $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
3429
- ts.filter.searching( table, false, true );
3482
+ tsf.searching( table, false, true );
3430
3483
  }
3431
3484
  });
3432
3485
  },
@@ -3436,11 +3489,11 @@
3436
3489
  if ( typeof filter === 'undefined' || filter === true ) {
3437
3490
  // delay filtering
3438
3491
  wo.searchTimer = setTimeout( function() {
3439
- ts.filter.checkFilters( table, filter, skipFirst );
3492
+ tsf.checkFilters( table, filter, skipFirst );
3440
3493
  }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 );
3441
3494
  } else {
3442
3495
  // skip delay
3443
- ts.filter.checkFilters( table, filter, skipFirst );
3496
+ tsf.checkFilters( table, filter, skipFirst );
3444
3497
  }
3445
3498
  },
3446
3499
  checkFilters: function( table, filter, skipFirst ) {
@@ -3454,7 +3507,7 @@
3454
3507
  // update cache if delayInit set & pager has initialized ( after user initiates a search )
3455
3508
  if ( c.delayInit && c.pager && c.pager.initialized ) {
3456
3509
  c.$table.trigger( 'updateCache', [ function() {
3457
- ts.filter.checkFilters( table, false, skipFirst );
3510
+ tsf.checkFilters( table, false, skipFirst );
3458
3511
  } ] );
3459
3512
  }
3460
3513
  return;
@@ -3485,11 +3538,11 @@
3485
3538
  if ( c.showProcessing ) {
3486
3539
  // give it time for the processing icon to kick in
3487
3540
  setTimeout( function() {
3488
- ts.filter.findRows( table, filters, combinedFilters );
3541
+ tsf.findRows( table, filters, combinedFilters );
3489
3542
  return false;
3490
3543
  }, 30 );
3491
3544
  } else {
3492
- ts.filter.findRows( table, filters, combinedFilters );
3545
+ tsf.findRows( table, filters, combinedFilters );
3493
3546
  return false;
3494
3547
  }
3495
3548
  },
@@ -3532,8 +3585,8 @@
3532
3585
  },
3533
3586
  defaultFilter: function( filter, mask ) {
3534
3587
  if ( filter === '' ) { return filter; }
3535
- var regex = ts.filter.regex.iQuery,
3536
- maskLen = mask.match( ts.filter.regex.igQuery ).length,
3588
+ var regex = tsf.regex.iQuery,
3589
+ maskLen = mask.match( tsf.regex.igQuery ).length,
3537
3590
  query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ],
3538
3591
  len = query.length - 1,
3539
3592
  indx = 0,
@@ -3569,7 +3622,10 @@
3569
3622
  // & don't target 'all' column inputs if they don't exist
3570
3623
  targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length,
3571
3624
  columns = [],
3572
- val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' );
3625
+ val = $.trim( tsf.getLatestSearch( $input ).attr( 'data-column' ) || '' );
3626
+ if ( !/[,-]/.test(val) && val.length === 1 ) {
3627
+ return parseInt( val, 10 );
3628
+ }
3573
3629
  // process column range
3574
3630
  if ( targets && /-/.test( val ) ) {
3575
3631
  ranges = val.match( /(\d+)\s*-\s*(\d+)/g );
@@ -3616,9 +3672,9 @@
3616
3672
  var ffxn,
3617
3673
  filterMatched = null,
3618
3674
  matches = null;
3619
- for ( ffxn in ts.filter.types ) {
3675
+ for ( ffxn in tsf.types ) {
3620
3676
  if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) {
3621
- matches = ts.filter.types[ffxn]( c, data, vars );
3677
+ matches = tsf.types[ffxn]( c, data, vars );
3622
3678
  if ( matches !== null ) {
3623
3679
  filterMatched = matches;
3624
3680
  }
@@ -3627,16 +3683,23 @@
3627
3683
  return filterMatched;
3628
3684
  },
3629
3685
  processRow: function( c, data, vars ) {
3630
- var columnIndex, hasSelect, result, val, filterMatched,
3686
+ var hasSelect, result, val, filterMatched,
3631
3687
  fxn, ffxn, txt,
3632
- regex = ts.filter.regex,
3688
+ regex = tsf.regex,
3633
3689
  wo = c.widgetOptions,
3634
- showRow = true;
3690
+ showRow = true,
3691
+
3692
+ // if wo.filter_$anyMatch data-column attribute is changed dynamically
3693
+ // we don't want to do an "anyMatch" search on one column using data
3694
+ // for the entire row - see #998
3695
+ columnIndex = wo.filter_$anyMatch && wo.filter_$anyMatch.length ?
3696
+ // look for multiple columns '1-3,4-6,8'
3697
+ tsf.multipleColumns( c, wo.filter_$anyMatch ) :
3698
+ [];
3699
+
3635
3700
  data.$cells = data.$row.children();
3636
3701
 
3637
- if ( data.anyMatchFlag ) {
3638
- // look for multiple columns '1-3,4-6,8'
3639
- columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
3702
+ if ( data.anyMatchFlag && columnIndex.length > 1 ) {
3640
3703
  data.anyMatch = true;
3641
3704
  data.isMatch = true;
3642
3705
  data.rowArray = data.$cells.map( function( i ) {
@@ -3660,7 +3723,7 @@
3660
3723
  data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' );
3661
3724
 
3662
3725
  vars.excludeMatch = vars.noAnyMatch;
3663
- filterMatched = ts.filter.processTypes( c, data, vars );
3726
+ filterMatched = tsf.processTypes( c, data, vars );
3664
3727
 
3665
3728
  if ( filterMatched !== null ) {
3666
3729
  showRow = filterMatched;
@@ -3721,7 +3784,7 @@
3721
3784
 
3722
3785
  val = true;
3723
3786
  if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) {
3724
- data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] );
3787
+ data.filter = tsf.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] );
3725
3788
  // val is used to indicate that a filter select is using a default filter;
3726
3789
  // so we override the exact & partial matches
3727
3790
  val = false;
@@ -3752,13 +3815,13 @@
3752
3815
  if ( filterMatched === null ) {
3753
3816
  // cycle through the different filters
3754
3817
  // filters return a boolean or null if nothing matches
3755
- filterMatched = ts.filter.processTypes( c, data, vars );
3818
+ filterMatched = tsf.processTypes( c, data, vars );
3756
3819
  if ( filterMatched !== null ) {
3757
3820
  result = filterMatched;
3758
3821
  // Look for match, and add child row data for matching
3759
3822
  } else {
3760
3823
  txt = ( data.iExact + data.childRowText )
3761
- .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) );
3824
+ .indexOf( tsf.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) );
3762
3825
  result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) );
3763
3826
  }
3764
3827
  } else {
@@ -3778,7 +3841,7 @@
3778
3841
  isChild, childRow, lastSearch, showRow, time, val, indx,
3779
3842
  notFiltered, searchFiltered, query, injected, res, id, txt,
3780
3843
  storedFilters = $.extend( [], filters ),
3781
- regex = ts.filter.regex,
3844
+ regex = tsf.regex,
3782
3845
  c = table.config,
3783
3846
  wo = c.widgetOptions,
3784
3847
  // data object passed to filters; anyMatch is a flag for the filters
@@ -3855,7 +3918,7 @@
3855
3918
  data.anyMatchFlag = true;
3856
3919
  data.anyMatchFilter = '' + (
3857
3920
  filters[ c.columns ] ||
3858
- wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() ||
3921
+ wo.filter_$anyMatch && tsf.getLatestSearch( wo.filter_$anyMatch ).val() ||
3859
3922
  ''
3860
3923
  );
3861
3924
  if ( wo.filter_columnAnyMatch ) {
@@ -3897,10 +3960,10 @@
3897
3960
  // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string
3898
3961
  !regex.alreadyFiltered.test( val ) &&
3899
3962
  // if we are not doing exact matches, using '|' ( logical or ) or not '!'
3900
- !/[=\"\|!]/.test( val ) &&
3963
+ !regex.exactTest.test( val ) &&
3901
3964
  // don't search only filtered if the value is negative
3902
3965
  // ( '> -10' => '> -100' will ignore hidden rows )
3903
- !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) &&
3966
+ !( regex.isNeg1.test( val ) || regex.isNeg2.test( val ) ) &&
3904
3967
  // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593
3905
3968
  !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length &&
3906
3969
  !c.$headerIndexed[indx].hasClass( 'filter-match' ) );
@@ -3919,7 +3982,7 @@
3919
3982
  data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter );
3920
3983
  }
3921
3984
  if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) {
3922
- data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter );
3985
+ data.anyMatchFilter = tsf.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter );
3923
3986
  // clear search filtered flag because default filters are not saved to the last search
3924
3987
  searchFiltered = false;
3925
3988
  }
@@ -3962,7 +4025,7 @@
3962
4025
  '';
3963
4026
  }
3964
4027
 
3965
- showRow = ts.filter.processRow( c, data, vars );
4028
+ showRow = tsf.processRow( c, data, vars );
3966
4029
  childRow = rowData.$row.filter( ':gt( 0 )' );
3967
4030
 
3968
4031
  if ( wo.filter_childRows && childRow.length ) {
@@ -3973,7 +4036,7 @@
3973
4036
  data.cacheArray = rowData.child[ indx ];
3974
4037
  data.rawArray = data.cacheArray;
3975
4038
  // use OR comparison on child rows
3976
- showRow = showRow || ts.filter.processRow( c, data, vars );
4039
+ showRow = showRow || tsf.processRow( c, data, vars );
3977
4040
  }
3978
4041
  }
3979
4042
  childRow.toggleClass( wo.filter_filteredRow, !showRow );
@@ -4035,7 +4098,7 @@
4035
4098
  }
4036
4099
  if ( arry === false ) {
4037
4100
  // fall back to original method
4038
- arry = ts.filter.getOptions( table, column, onlyAvail );
4101
+ arry = tsf.getOptions( table, column, onlyAvail );
4039
4102
  }
4040
4103
 
4041
4104
  // get unique elements and sort the list
@@ -4147,13 +4210,13 @@
4147
4210
  // nothing included in arry ( external source ), so get the options from
4148
4211
  // filter_selectSource or column data
4149
4212
  if ( typeof arry === 'undefined' || arry === '' ) {
4150
- arry = ts.filter.getOptionSource( table, column, onlyAvail );
4213
+ arry = tsf.getOptionSource( table, column, onlyAvail );
4151
4214
  }
4152
4215
 
4153
4216
  if ( $.isArray( arry ) ) {
4154
4217
  // build option list
4155
4218
  for ( indx = 0; indx < arry.length; indx++ ) {
4156
- txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '&quot;' );
4219
+ txt = arry[indx] = ( '' + arry[indx] ).replace( tsf.regex.quote, '&quot;' );
4157
4220
  val = txt;
4158
4221
  // allow including a symbol in the selectSource array
4159
4222
  // 'a-z|A through Z' so that 'a-z' becomes the option value
@@ -4209,7 +4272,7 @@
4209
4272
  // look for the filter-select class; build/update it if found
4210
4273
  if ( ( $header.hasClass( 'filter-select' ) ||
4211
4274
  ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) {
4212
- ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) );
4275
+ tsf.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) );
4213
4276
  }
4214
4277
  }
4215
4278
  }
@@ -4245,7 +4308,7 @@
4245
4308
  $column = $filters.filter( cols );
4246
4309
  if ( $column.length ) {
4247
4310
  // move the latest search to the first slot in the array
4248
- $column = ts.filter.getLatestSearch( $column );
4311
+ $column = tsf.getLatestSearch( $column );
4249
4312
  if ( $.isArray( setFilters ) ) {
4250
4313
  // skip first ( latest input ) to maintain cursor position while typing
4251
4314
  if ( skipFirst && $column.length > 1 ) {
@@ -4295,7 +4358,7 @@
4295
4358
  // ensure new set filters are applied, even if the search is the same
4296
4359
  c.lastCombinedFilter = null;
4297
4360
  c.lastSearch = [];
4298
- ts.filter.searching( c.table, filter, skipFirst );
4361
+ tsf.searching( c.table, filter, skipFirst );
4299
4362
  c.$table.trigger( 'filterFomatterUpdate' );
4300
4363
  }
4301
4364
  return !!valid;