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.
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;