jquery-tablesorter 1.16.5 → 1.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/jquery-tablesorter/version.rb +1 -1
  4. data/vendor/assets/javascripts/jquery-tablesorter/addons/pager/jquery.tablesorter.pager.js +38 -27
  5. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.combined.js +1104 -839
  6. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.js +167 -123
  7. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.widgets.js +938 -717
  8. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date.js +5 -5
  9. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-globalize.js +46 -0
  10. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-input-select.js +96 -72
  11. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-named-numbers.js +6 -5
  12. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-network.js +26 -17
  13. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-columns.js +1 -1
  14. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-editable.js +95 -42
  15. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-filter.js +921 -700
  16. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-grouping.js +5 -3
  17. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-math.js +22 -20
  18. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-output.js +7 -5
  19. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-pager.js +40 -29
  20. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-resizable.js +6 -6
  21. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-saveSort.js +1 -1
  22. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-scroller.js +53 -31
  23. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-stickyHeaders.js +1 -1
  24. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-storage.js +1 -1
  25. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-uitheme.js +1 -1
  26. data/vendor/assets/stylesheets/jquery-tablesorter/theme.black-ice.css +2 -1
  27. data/vendor/assets/stylesheets/jquery-tablesorter/theme.blue.css +2 -1
  28. data/vendor/assets/stylesheets/jquery-tablesorter/theme.bootstrap.css +2 -1
  29. data/vendor/assets/stylesheets/jquery-tablesorter/theme.bootstrap_2.css +8 -6
  30. data/vendor/assets/stylesheets/jquery-tablesorter/theme.dark.css +2 -1
  31. data/vendor/assets/stylesheets/jquery-tablesorter/theme.default.css +1 -1
  32. metadata +3 -2
@@ -1,13 +1,13 @@
1
- /*! Widget: filter - updated 3/26/2015 (v2.21.3) *//*
1
+ /*! Widget: filter - updated 5/17/2015 (v2.22.0) *//*
2
2
  * Requires tablesorter v2.8+ and jQuery 1.7+
3
3
  * by Rob Garrison
4
4
  */
5
- ;(function ($) {
5
+ ;( function ( $ ) {
6
6
  'use strict';
7
- var ts = $.tablesorter = $.tablesorter || {},
7
+ var ts = $.tablesorter || {},
8
8
  tscss = ts.css;
9
9
 
10
- $.extend(tscss, {
10
+ $.extend( tscss, {
11
11
  filterRow : 'tablesorter-filter-row',
12
12
  filter : 'tablesorter-filter',
13
13
  filterDisabled : 'disabled',
@@ -15,26 +15,27 @@ $.extend(tscss, {
15
15
  });
16
16
 
17
17
  ts.addWidget({
18
- id: "filter",
18
+ id: 'filter',
19
19
  priority: 50,
20
20
  options : {
21
21
  filter_childRows : false, // if true, filter includes child row content in the search
22
+ filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped
22
23
  filter_columnFilters : true, // if true, a filter will be added to the top of each table column
23
- filter_columnAnyMatch: true, // if true, allows using "#:{query}" in AnyMatch searches (column:query)
24
- filter_cellFilter : '', // css class name added to the filter cell (string or array)
25
- filter_cssFilter : '', // css class name added to the filter row & each input in the row (tablesorter-filter is ALWAYS added)
26
- filter_defaultFilter : {}, // add a default column filter type "~{query}" to make fuzzy searches default; "{q1} AND {q2}" to make all searches use a logical AND.
24
+ filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query )
25
+ filter_cellFilter : '', // css class name added to the filter cell ( string or array )
26
+ filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added )
27
+ filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND.
27
28
  filter_excludeFilter : {}, // filters to exclude, per column
28
- filter_external : '', // jQuery selector string (or jQuery object) of external filters
29
+ filter_external : '', // jQuery selector string ( or jQuery object ) of external filters
29
30
  filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin
30
31
  filter_formatter : null, // add custom filter elements to the filter row
31
32
  filter_functions : null, // add custom filter functions using this option
32
33
  filter_hideEmpty : true, // hide filter row when table is empty
33
34
  filter_hideFilters : false, // collapse filter row when mouse leaves the area
34
35
  filter_ignoreCase : true, // if true, make all searches case-insensitive
35
- filter_liveSearch : true, // if true, search column content while the user types (with a delay)
36
- filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available (visible) options within the drop down
37
- filter_placeholder : { search : '', select : '' }, // default placeholder text (overridden by any header "data-placeholder" setting)
36
+ filter_liveSearch : true, // if true, search column content while the user types ( with a delay )
37
+ filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down
38
+ filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting )
38
39
  filter_reset : null, // jQuery selector string of an element used to reset the filters
39
40
  filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters
40
41
  filter_searchDelay : 300, // typing delay in milliseconds before starting a search
@@ -46,37 +47,38 @@ ts.addWidget({
46
47
  filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value
47
48
  filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text
48
49
  },
49
- format: function(table, c, wo) {
50
- if (!c.$table.hasClass('hasFilters')) {
51
- ts.filter.init(table, c, wo);
50
+ format: function( table, c, wo ) {
51
+ if ( !c.$table.hasClass( 'hasFilters' ) ) {
52
+ ts.filter.init( table, c, wo );
52
53
  }
53
54
  },
54
- remove: function(table, c, wo, refreshing) {
55
+ remove: function( table, c, wo, refreshing ) {
55
56
  var tbodyIndex, $tbody,
56
57
  $table = c.$table,
57
58
  $tbodies = c.$tbodies,
58
- events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '.split(' ').join(c.namespace + 'filter ');
59
+ events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '
60
+ .split( ' ' ).join( c.namespace + 'filter ' );
59
61
  $table
60
- .removeClass('hasFilters')
62
+ .removeClass( 'hasFilters' )
61
63
  // add .tsfilter namespace to all BUT search
62
- .unbind( events.replace(/\s+/g, ' ') )
64
+ .unbind( events.replace( /\s+/g, ' ' ) )
63
65
  // remove the filter row even if refreshing, because the column might have been moved
64
- .find('.' + tscss.filterRow).remove();
65
- if (refreshing) { return; }
66
- for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
67
- $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody
68
- $tbody.children().removeClass(wo.filter_filteredRow).show();
69
- ts.processTbody(table, $tbody, false); // restore tbody
66
+ .find( '.' + tscss.filterRow ).remove();
67
+ if ( refreshing ) { return; }
68
+ for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
69
+ $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody
70
+ $tbody.children().removeClass( wo.filter_filteredRow ).show();
71
+ ts.processTbody( table, $tbody, false ); // restore tbody
70
72
  }
71
- if (wo.filter_reset) {
72
- $(document).undelegate(wo.filter_reset, 'click.tsfilter');
73
+ if ( wo.filter_reset ) {
74
+ $( document ).undelegate( wo.filter_reset, 'click.tsfilter' );
73
75
  }
74
76
  }
75
77
  });
76
78
 
77
79
  ts.filter = {
78
80
 
79
- // regex used in filter "check" functions - not for general use and not documented
81
+ // regex used in filter 'check' functions - not for general use and not documented
80
82
  regex: {
81
83
  regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex
82
84
  child : /tablesorter-childRow/, // child row class name; this gets updated in the script
@@ -89,22 +91,33 @@ ts.filter = {
89
91
  },
90
92
  // function( c, data ) { }
91
93
  // c = table.config
92
- // data.filter = array of filter input values;
93
- // data.iFilter = same array, except lowercase (if wo.filter_ignoreCase is true)
94
- // data.exact = table cell text (or parsed data if column parser enabled)
95
- // data.iExact = same as data.exact, except lowercase (if wo.filter_ignoreCase is true)
96
- // data.cache = table cell text from cache, so it has been parsed (& in all lower case if config.ignoreCase is true)
97
- // data.index = column index; table = table element (DOM)
98
- // data.parsed = array (by column) of boolean values (from filter_useParsedData or "filter-parsed" class)
94
+ // data.$row = jQuery object of the row currently being processed
95
+ // data.$cells = jQuery object of all cells within the current row
96
+ // data.filters = array of filters for all columns ( some may be undefined )
97
+ // data.filter = filter for the current column
98
+ // data.iFilter = same as data.filter, except lowercase ( if wo.filter_ignoreCase is true )
99
+ // data.exact = table cell text ( or parsed data if column parser enabled )
100
+ // data.iExact = same as data.exact, except lowercase ( if wo.filter_ignoreCase is true )
101
+ // data.cache = table cell text from cache, so it has been parsed ( & in all lower case if c.ignoreCase is true )
102
+ // data.cacheArray = An array of parsed content from each table cell in the row being processed
103
+ // data.index = column index; table = table element ( DOM )
104
+ // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
99
105
  types: {
100
106
  // Look for regex
101
107
  regex: function( c, data ) {
102
- if ( ts.filter.regex.regex.test(data.iFilter) ) {
108
+ if ( ts.filter.regex.regex.test( data.filter ) ) {
103
109
  var matches,
104
- regex = ts.filter.regex.regex.exec(data.iFilter);
110
+ // cache regex per column for optimal speed
111
+ regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ),
112
+ isRegex = regex instanceof RegExp;
105
113
  try {
106
- matches = new RegExp(regex[1], regex[2]).test( data.iExact );
107
- } catch (error) {
114
+ if ( !isRegex ) {
115
+ // force case insensitive search if ignoreCase option set?
116
+ // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; }
117
+ data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] );
118
+ }
119
+ matches = regex.test( data.exact );
120
+ } catch ( error ) {
108
121
  matches = false;
109
122
  }
110
123
  return matches;
@@ -113,46 +126,56 @@ ts.filter = {
113
126
  },
114
127
  // Look for operators >, >=, < or <=
115
128
  operators: function( c, data ) {
116
- if ( /^[<>]=?/.test(data.iFilter) ) {
117
- var cachedValue, result,
129
+ // ignore empty strings... because '' < 10 is true
130
+ if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) {
131
+ var cachedValue, result, txt,
118
132
  table = c.table,
119
133
  index = data.index,
120
134
  parsed = data.parsed[index],
121
- query = ts.formatFloat( data.iFilter.replace(ts.filter.regex.operators, ''), table ),
135
+ query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ),
122
136
  parser = c.parsers[index],
123
137
  savedSearch = query;
124
- // parse filter value in case we're comparing numbers (dates)
125
- if (parsed || parser.type === 'numeric') {
126
- result = ts.filter.parseFilter(c, $.trim('' + data.iFilter.replace(ts.filter.regex.operators, '')), index, parsed, true);
127
- query = ( typeof result === "number" && result !== '' && !isNaN(result) ) ? result : query;
138
+ // parse filter value in case we're comparing numbers ( dates )
139
+ if ( parsed || parser.type === 'numeric' ) {
140
+ txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) );
141
+ result = ts.filter.parseFilter( c, txt, index, true );
142
+ query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query;
128
143
  }
129
-
130
144
  // iExact may be numeric - see issue #149;
131
- // check if cached is defined, because sometimes j goes out of range? (numeric columns)
132
- cachedValue = ( parsed || parser.type === 'numeric' ) && !isNaN(query) && typeof data.cache !== 'undefined' ? data.cache :
133
- isNaN(data.iExact) ? ts.formatFloat( data.iExact.replace(ts.filter.regex.nondigit, ''), table) :
134
- ts.formatFloat( data.iExact, table );
135
-
136
- if ( />/.test(data.iFilter) ) { result = />=/.test(data.iFilter) ? cachedValue >= query : cachedValue > query; }
137
- if ( /</.test(data.iFilter) ) { result = /<=/.test(data.iFilter) ? cachedValue <= query : cachedValue < query; }
145
+ // check if cached is defined, because sometimes j goes out of range? ( numeric columns )
146
+ if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) &&
147
+ typeof data.cache !== 'undefined' ) {
148
+ cachedValue = data.cache;
149
+ } else {
150
+ txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
151
+ cachedValue = ts.formatFloat( txt, table );
152
+ }
153
+ if ( />/.test( data.iFilter ) ) {
154
+ result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query;
155
+ } else if ( /</.test( data.iFilter ) ) {
156
+ result = /<=/.test( data.iFilter ) ? cachedValue <= query : cachedValue < query;
157
+ }
138
158
  // keep showing all rows if nothing follows the operator
139
- if ( !result && savedSearch === '' ) { result = true; }
159
+ if ( !result && savedSearch === '' ) {
160
+ result = true;
161
+ }
140
162
  return result;
141
163
  }
142
164
  return null;
143
165
  },
144
166
  // Look for a not match
145
167
  notMatch: function( c, data ) {
146
- if ( /^\!/.test(data.iFilter) ) {
168
+ if ( /^\!/.test( data.iFilter ) ) {
147
169
  var indx,
148
- filter = ts.filter.parseFilter(c, data.iFilter.replace('!', ''), data.index, data.parsed[data.index]) || '';
149
- if (ts.filter.regex.exact.test(filter)) {
170
+ txt = data.iFilter.replace( '!', '' ),
171
+ filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
172
+ if ( ts.filter.regex.exact.test( filter ) ) {
150
173
  // look for exact not matches - see #628
151
- filter = filter.replace(ts.filter.regex.exact, '');
152
- return filter === '' ? true : $.trim(filter) !== data.iExact;
174
+ filter = filter.replace( ts.filter.regex.exact, '' );
175
+ return filter === '' ? true : $.trim( filter ) !== data.iExact;
153
176
  } else {
154
- indx = data.iExact.search( $.trim(filter) );
155
- return filter === '' ? true : !(c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 0);
177
+ indx = data.iExact.search( $.trim( filter ) );
178
+ return filter === '' ? true : !( c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 0 );
156
179
  }
157
180
  }
158
181
  return null;
@@ -160,84 +183,101 @@ ts.filter = {
160
183
  // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
161
184
  exact: function( c, data ) {
162
185
  /*jshint eqeqeq:false */
163
- if (ts.filter.regex.exact.test(data.iFilter)) {
164
- var filter = ts.filter.parseFilter(c, data.iFilter.replace(ts.filter.regex.exact, ''), data.index, data.parsed[data.index]) || '';
165
- return data.anyMatch ? $.inArray(filter, data.rowArray) >= 0 : filter == data.iExact;
186
+ if ( ts.filter.regex.exact.test( data.iFilter ) ) {
187
+ var txt = data.iFilter.replace( ts.filter.regex.exact, '' ),
188
+ filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
189
+ return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact;
166
190
  }
167
191
  return null;
168
192
  },
169
- // Look for an AND or && operator (logical and)
193
+ // Look for an AND or && operator ( logical and )
170
194
  and : function( c, data ) {
171
- if ( ts.filter.regex.andTest.test(data.filter) ) {
195
+ if ( ts.filter.regex.andTest.test( data.filter ) ) {
172
196
  var index = data.index,
173
197
  parsed = data.parsed[index],
174
198
  query = data.iFilter.split( ts.filter.regex.andSplit ),
175
- result = data.iExact.search( $.trim( ts.filter.parseFilter(c, query[0], index, parsed) ) ) >= 0,
199
+ result = data.iExact.search( $.trim( ts.filter.parseFilter( c, query[0], index, parsed ) ) ) >= 0,
176
200
  indx = query.length - 1;
177
- while (result && indx) {
178
- result = result && data.iExact.search( $.trim( ts.filter.parseFilter(c, query[indx], index, parsed) ) ) >= 0;
201
+ while ( result && indx ) {
202
+ result = result &&
203
+ data.iExact.search( $.trim( ts.filter.parseFilter( c, query[indx], index, parsed ) ) ) >= 0;
179
204
  indx--;
180
205
  }
181
206
  return result;
182
207
  }
183
208
  return null;
184
209
  },
185
- // Look for a range (using " to " or " - ") - see issue #166; thanks matzhu!
210
+ // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
186
211
  range : function( c, data ) {
187
- if ( ts.filter.regex.toTest.test(data.iFilter) ) {
188
- var result, tmp,
212
+ if ( ts.filter.regex.toTest.test( data.iFilter ) ) {
213
+ var result, tmp, range1, range2,
189
214
  table = c.table,
190
215
  index = data.index,
191
216
  parsed = data.parsed[index],
192
217
  // make sure the dash is for a range and not indicating a negative number
193
- query = data.iFilter.split( ts.filter.regex.toSplit ),
194
- range1 = ts.formatFloat( ts.filter.parseFilter(c, query[0].replace(ts.filter.regex.nondigit, '') || '', index, parsed), table ),
195
- range2 = ts.formatFloat( ts.filter.parseFilter(c, query[1].replace(ts.filter.regex.nondigit, '') || '', index, parsed), table );
196
- // parse filter value in case we're comparing numbers (dates)
197
- if (parsed || c.parsers[index].type === 'numeric') {
198
- result = c.parsers[index].format('' + query[0], table, c.$headers.eq(index), index);
199
- range1 = (result !== '' && !isNaN(result)) ? result : range1;
200
- result = c.parsers[index].format('' + query[1], table, c.$headers.eq(index), index);
201
- range2 = (result !== '' && !isNaN(result)) ? result : range2;
218
+ query = data.iFilter.split( ts.filter.regex.toSplit );
219
+
220
+ tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || '';
221
+ range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
222
+ tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || '';
223
+ range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
224
+ // parse filter value in case we're comparing numbers ( dates )
225
+ if ( parsed || c.parsers[index].type === 'numeric' ) {
226
+ result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index );
227
+ range1 = ( result !== '' && !isNaN( result ) ) ? result : range1;
228
+ result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index );
229
+ range2 = ( result !== '' && !isNaN( result ) ) ? result : range2;
230
+ }
231
+ if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) {
232
+ result = data.cache;
233
+ } else {
234
+ tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
235
+ result = ts.formatFloat( tmp, table );
236
+ }
237
+ if ( range1 > range2 ) {
238
+ tmp = range1; range1 = range2; range2 = tmp; // swap
202
239
  }
203
- result = ( parsed || c.parsers[index].type === 'numeric' ) && !isNaN(range1) && !isNaN(range2) ? data.cache :
204
- isNaN(data.iExact) ? ts.formatFloat( data.iExact.replace(ts.filter.regex.nondigit, ''), table) :
205
- ts.formatFloat( data.iExact, table );
206
- if (range1 > range2) { tmp = range1; range1 = range2; range2 = tmp; } // swap
207
- return (result >= range1 && result <= range2) || (range1 === '' || range2 === '');
240
+ return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' );
208
241
  }
209
242
  return null;
210
243
  },
211
244
  // Look for wild card: ? = single, * = multiple, or | = logical OR
212
245
  wild : function( c, data ) {
213
- if ( /[\?\*\|]/.test(data.iFilter) || ts.filter.regex.orReplace.test(data.filter) ) {
246
+ if ( /[\?\*\|]/.test( data.iFilter ) || ts.filter.regex.orReplace.test( data.filter ) ) {
214
247
  var index = data.index,
215
- parsed = data.parsed[index],
216
- query = ts.filter.parseFilter(c, data.iFilter.replace(ts.filter.regex.orReplace, "|"), index, parsed) || '';
217
- // look for an exact match with the "or" unless the "filter-match" class is found
218
- if (!c.$headerIndexed[index].hasClass('filter-match') && /\|/.test(query)) {
248
+ parsed = data.parsed[ index ],
249
+ txt = data.iFilter.replace( ts.filter.regex.orReplace, '|' ),
250
+ query = ts.filter.parseFilter( c, txt, index, parsed ) || '';
251
+ // look for an exact match with the 'or' unless the 'filter-match' class is found
252
+ if ( !c.$headerIndexed[ index ].hasClass( 'filter-match' ) && /\|/.test( query ) ) {
219
253
  // show all results while using filter match. Fixes #727
220
- if (query[ query.length - 1 ] === '|') { query += '*'; }
221
- query = data.anyMatch && $.isArray(data.rowArray) ? '(' + query + ')' : '^(' + query + ')$';
254
+ if ( query[ query.length - 1 ] === '|' ) {
255
+ query += '*';
256
+ }
257
+ query = data.anyMatch && $.isArray( data.rowArray ) ?
258
+ '(' + query + ')' :
259
+ '^(' + query + ')$';
222
260
  }
223
261
  // parsing the filter may not work properly when using wildcards =/
224
- return new RegExp( query.replace(/\?/g, '\\S{1}').replace(/\*/g, '\\S*') ).test(data.iExact);
262
+ return new RegExp( query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ) )
263
+ .test( data.iExact );
225
264
  }
226
265
  return null;
227
266
  },
228
- // fuzzy text search; modified from https://github.com/mattyork/fuzzy (MIT license)
267
+ // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license )
229
268
  fuzzy: function( c, data ) {
230
- if ( /^~/.test(data.iFilter) ) {
269
+ if ( /^~/.test( data.iFilter ) ) {
231
270
  var indx,
232
271
  patternIndx = 0,
233
272
  len = data.iExact.length,
234
- pattern = ts.filter.parseFilter(c, data.iFilter.slice(1), data.index, data.parsed[data.index]) || '';
235
- for (indx = 0; indx < len; indx++) {
236
- if (data.iExact[indx] === pattern[patternIndx]) {
273
+ txt = data.iFilter.slice( 1 ),
274
+ pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
275
+ for ( indx = 0; indx < len; indx++ ) {
276
+ if ( data.iExact[ indx ] === pattern[ patternIndx ] ) {
237
277
  patternIndx += 1;
238
278
  }
239
279
  }
240
- if (patternIndx === pattern.length) {
280
+ if ( patternIndx === pattern.length ) {
241
281
  return true;
242
282
  }
243
283
  return false;
@@ -245,17 +285,17 @@ ts.filter = {
245
285
  return null;
246
286
  }
247
287
  },
248
- init: function(table, c, wo) {
288
+ init: function( table, c, wo ) {
249
289
  // filter language options
250
- ts.language = $.extend(true, {}, {
290
+ ts.language = $.extend( true, {}, {
251
291
  to : 'to',
252
292
  or : 'or',
253
293
  and : 'and'
254
- }, ts.language);
294
+ }, ts.language );
255
295
 
256
296
  var options, string, txt, $header, column, filters, val, fxn, noSelect,
257
297
  regex = ts.filter.regex;
258
- c.$table.addClass('hasFilters');
298
+ c.$table.addClass( 'hasFilters' );
259
299
 
260
300
  // define timers so using clearTimeout won't cause an undefined error
261
301
  wo.searchTimer = null;
@@ -265,512 +305,566 @@ ts.filter = {
265
305
  wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]';
266
306
  wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]';
267
307
 
268
- txt = '\\{' + ts.filter.regex.query + '\\}';
308
+ val = '\\{' + ts.filter.regex.query + '\\}';
269
309
  $.extend( regex, {
270
- child : new RegExp(c.cssChildRow),
271
- filtered : new RegExp(wo.filter_filteredRow),
272
- alreadyFiltered : new RegExp('(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i'),
273
- toTest : new RegExp('\\s+(-|' + ts.language.to + ')\\s+', 'i'),
274
- toSplit : new RegExp('(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi'),
275
- andTest : new RegExp('\\s+(' + ts.language.and + '|&&)\\s+', 'i'),
276
- andSplit : new RegExp('(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi'),
277
- orReplace : new RegExp('\\s+(' + ts.language.or + ')\\s+', 'gi'),
278
- iQuery : new RegExp(txt, 'i'),
279
- igQuery : new RegExp(txt, 'ig')
310
+ child : new RegExp( c.cssChildRow ),
311
+ filtered : new RegExp( wo.filter_filteredRow ),
312
+ alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ),
313
+ toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ),
314
+ toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ),
315
+ andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ),
316
+ andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ),
317
+ orReplace : new RegExp( '\\s+(' + ts.language.or + ')\\s+', 'gi' ),
318
+ iQuery : new RegExp( val, 'i' ),
319
+ igQuery : new RegExp( val, 'ig' )
280
320
  });
281
321
 
282
- // don't build filter row if columnFilters is false or all columns are set to "filter-false" - issue #156
283
- if (wo.filter_columnFilters !== false && c.$headers.filter('.filter-false, .parser-false').length !== c.$headers.length) {
322
+ // don't build filter row if columnFilters is false or all columns are set to 'filter-false'
323
+ // see issue #156
324
+ val = c.$headers.filter( '.filter-false, .parser-false' ).length;
325
+ if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) {
284
326
  // build filter row
285
- ts.filter.buildRow(table, c, wo);
327
+ ts.filter.buildRow( table, c, wo );
286
328
  }
287
329
 
288
- txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '.split(' ').join(c.namespace + 'filter ');
289
- c.$table.bind( txt, function(event, filter) {
290
- val = (wo.filter_hideEmpty && $.isEmptyObject(c.cache) && !(c.delayInit && event.type === 'appendCache'));
291
- // hide filter row using the "filtered" class name
292
- c.$table.find('.' + tscss.filterRow).toggleClass(wo.filter_filteredRow, val ); // fixes #450
293
- if ( !/(search|filter)/.test(event.type) ) {
330
+ txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '
331
+ .split( ' ' ).join( c.namespace + 'filter ' );
332
+ c.$table.bind( txt, function( event, filter ) {
333
+ val = wo.filter_hideEmpty &&
334
+ $.isEmptyObject( c.cache ) &&
335
+ !( c.delayInit && event.type === 'appendCache' );
336
+ // hide filter row using the 'filtered' class name
337
+ c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450
338
+ if ( !/(search|filter)/.test( event.type ) ) {
294
339
  event.stopPropagation();
295
- ts.filter.buildDefault(table, true);
340
+ ts.filter.buildDefault( table, true );
296
341
  }
297
- if (event.type === 'filterReset') {
298
- c.$table.find('.' + tscss.filter).add(wo.filter_$externalFilters).val('');
299
- ts.filter.searching(table, []);
300
- } else if (event.type === 'filterEnd') {
301
- ts.filter.buildDefault(table, true);
342
+ if ( event.type === 'filterReset' ) {
343
+ c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' );
344
+ ts.filter.searching( table, [] );
345
+ } else if ( event.type === 'filterEnd' ) {
346
+ ts.filter.buildDefault( table, true );
302
347
  } else {
303
- // send false argument to force a new search; otherwise if the filter hasn't changed, it will return
304
- filter = event.type === 'search' ? filter : event.type === 'updateComplete' ? c.$table.data('lastSearch') : '';
305
- if (/(update|add)/.test(event.type) && event.type !== "updateComplete") {
348
+ // send false argument to force a new search; otherwise if the filter hasn't changed,
349
+ // it will return
350
+ filter = event.type === 'search' ? filter :
351
+ event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : '';
352
+ if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) {
306
353
  // force a new search since content has changed
307
354
  c.lastCombinedFilter = null;
308
355
  c.lastSearch = [];
309
356
  }
310
- // pass true (skipFirst) to prevent the tablesorter.setFilters function from skipping the first input
311
- // ensures all inputs are updated when a search is triggered on the table $('table').trigger('search', [...]);
312
- ts.filter.searching(table, filter, true);
357
+ // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first
358
+ // input ensures all inputs are updated when a search is triggered on the table
359
+ // $( 'table' ).trigger( 'search', [...] );
360
+ ts.filter.searching( table, filter, true );
313
361
  }
314
362
  return false;
315
363
  });
316
364
 
317
365
  // reset button/link
318
- if (wo.filter_reset) {
319
- if (wo.filter_reset instanceof $) {
366
+ if ( wo.filter_reset ) {
367
+ if ( wo.filter_reset instanceof $ ) {
320
368
  // reset contains a jQuery object, bind to it
321
- wo.filter_reset.click(function(){
322
- c.$table.trigger('filterReset');
369
+ wo.filter_reset.click( function() {
370
+ c.$table.trigger( 'filterReset' );
323
371
  });
324
- } else if ($(wo.filter_reset).length) {
372
+ } else if ( $( wo.filter_reset ).length ) {
325
373
  // reset is a jQuery selector, use event delegation
326
- $(document)
327
- .undelegate(wo.filter_reset, 'click.tsfilter')
328
- .delegate(wo.filter_reset, 'click.tsfilter', function() {
329
- // trigger a reset event, so other functions (filter_formatter) know when to reset
330
- c.$table.trigger('filterReset');
331
- });
374
+ $( document )
375
+ .undelegate( wo.filter_reset, 'click.tsfilter' )
376
+ .delegate( wo.filter_reset, 'click.tsfilter', function() {
377
+ // trigger a reset event, so other functions ( filter_formatter ) know when to reset
378
+ c.$table.trigger( 'filterReset' );
379
+ });
332
380
  }
333
381
  }
334
- if (wo.filter_functions) {
335
- for (column = 0; column < c.columns; column++) {
382
+ if ( wo.filter_functions ) {
383
+ for ( column = 0; column < c.columns; column++ ) {
336
384
  fxn = ts.getColumnData( table, wo.filter_functions, column );
337
- if (fxn) {
338
- // remove "filter-select" from header otherwise the options added here are replaced with all options
339
- $header = c.$headerIndexed[column].removeClass('filter-select');
340
- // don't build select if "filter-false" or "parser-false" set
341
- noSelect = !($header.hasClass('filter-false') || $header.hasClass('parser-false'));
385
+ if ( fxn ) {
386
+ // remove 'filter-select' from header otherwise the options added here are replaced with
387
+ // all options
388
+ $header = c.$headerIndexed[ column ].removeClass( 'filter-select' );
389
+ // don't build select if 'filter-false' or 'parser-false' set
390
+ noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
342
391
  options = '';
343
392
  if ( fxn === true && noSelect ) {
344
- ts.filter.buildSelect(table, column);
393
+ ts.filter.buildSelect( table, column );
345
394
  } else if ( typeof fxn === 'object' && noSelect ) {
346
395
  // add custom drop down list
347
- for (string in fxn) {
348
- if (typeof string === 'string') {
396
+ for ( string in fxn ) {
397
+ if ( typeof string === 'string' ) {
349
398
  options += options === '' ?
350
- '<option value="">' + ($header.data('placeholder') || $header.attr('data-placeholder') || wo.filter_placeholder.select || '') + '</option>' : '';
399
+ '<option value="">' +
400
+ ( $header.data( 'placeholder' ) ||
401
+ $header.attr( 'data-placeholder' ) ||
402
+ wo.filter_placeholder.select ||
403
+ ''
404
+ ) +
405
+ '</option>' : '';
351
406
  val = string;
352
407
  txt = string;
353
- if (string.indexOf(wo.filter_selectSourceSeparator) >= 0) {
354
- val = string.split(wo.filter_selectSourceSeparator);
408
+ if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
409
+ val = string.split( wo.filter_selectSourceSeparator );
355
410
  txt = val[1];
356
411
  val = val[0];
357
412
  }
358
- options += '<option ' + (txt === val ? '' : 'data-function-name="' + string + '" ') + 'value="' + val + '">' + txt + '</option>';
413
+ options += '<option ' +
414
+ ( txt === val ? '' : 'data-function-name="' + string + '" ' ) +
415
+ 'value="' + val + '">' + txt + '</option>';
359
416
  }
360
417
  }
361
- c.$table.find('thead').find('select.' + tscss.filter + '[data-column="' + column + '"]').append(options);
418
+ c.$table
419
+ .find( 'thead' )
420
+ .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' )
421
+ .append( options );
362
422
  txt = wo.filter_selectSource;
363
- fxn = $.isFunction(txt) ? true : ts.getColumnData( table, txt, column );
364
- if (fxn) {
423
+ fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column );
424
+ if ( fxn ) {
365
425
  // updating so the extra options are appended
366
- ts.filter.buildSelect(c.table, column, '', true, $header.hasClass(wo.filter_onlyAvail));
426
+ ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) );
367
427
  }
368
428
  }
369
429
  }
370
430
  }
371
431
  }
372
- // not really updating, but if the column has both the "filter-select" class & filter_functions set to true,
373
- // it would append the same options twice.
374
- ts.filter.buildDefault(table, true);
432
+ // not really updating, but if the column has both the 'filter-select' class &
433
+ // filter_functions set to true, it would append the same options twice.
434
+ ts.filter.buildDefault( table, true );
375
435
 
376
- ts.filter.bindSearch( table, c.$table.find('.' + tscss.filter), true );
377
- if (wo.filter_external) {
436
+ ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true );
437
+ if ( wo.filter_external ) {
378
438
  ts.filter.bindSearch( table, wo.filter_external );
379
439
  }
380
440
 
381
- if (wo.filter_hideFilters) {
382
- ts.filter.hideFilters(table, c);
441
+ if ( wo.filter_hideFilters ) {
442
+ ts.filter.hideFilters( table, c );
383
443
  }
384
444
 
385
445
  // show processing icon
386
- if (c.showProcessing) {
446
+ if ( c.showProcessing ) {
447
+ txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' );
387
448
  c.$table
388
- .unbind( ('filterStart filterEnd '.split(' ').join(c.namespace + 'filter ')).replace(/\s+/g, ' ') )
389
- .bind( 'filterStart filterEnd '.split(' ').join(c.namespace + 'filter '), function(event, columns) {
449
+ .unbind( txt.replace( /\s+/g, ' ' ) )
450
+ .bind( txt, function( event, columns ) {
390
451
  // only add processing to certain columns to all columns
391
- $header = (columns) ? c.$table.find('.' + tscss.header).filter('[data-column]').filter(function() {
392
- return columns[$(this).data('column')] !== '';
393
- }) : '';
394
- ts.isProcessing(table, event.type === 'filterStart', columns ? $header : '');
452
+ $header = ( columns ) ?
453
+ c.$table
454
+ .find( '.' + tscss.header )
455
+ .filter( '[data-column]' )
456
+ .filter( function() {
457
+ return columns[ $( this ).data( 'column' ) ] !== '';
458
+ }) : '';
459
+ ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' );
395
460
  });
396
461
  }
397
462
 
398
- // set filtered rows count (intially unfiltered)
463
+ // set filtered rows count ( intially unfiltered )
399
464
  c.filteredRows = c.totalRows;
400
465
 
401
466
  // add default values
467
+ txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' );
402
468
  c.$table
403
- .unbind( ('tablesorter-initialized pagerBeforeInitialized '.split(' ').join(c.namespace + 'filter ')).replace(/\s+/g, ' ') )
404
- .bind( 'tablesorter-initialized pagerBeforeInitialized '.split(' ').join(c.namespace + 'filter '), function() {
405
- // redefine "wo" as it does not update properly inside this callback
469
+ .unbind( txt.replace( /\s+/g, ' ' ) )
470
+ .bind( txt, function() {
471
+ // redefine 'wo' as it does not update properly inside this callback
406
472
  var wo = this.config.widgetOptions;
407
- filters = ts.filter.setDefaults(table, c, wo) || [];
408
- if (filters.length) {
473
+ filters = ts.filter.setDefaults( table, c, wo ) || [];
474
+ if ( filters.length ) {
409
475
  // prevent delayInit from triggering a cache build if filters are empty
410
- if ( !(c.delayInit && filters.join('') === '') ) {
411
- ts.setFilters(table, filters, true);
476
+ if ( !( c.delayInit && filters.join( '' ) === '' ) ) {
477
+ ts.setFilters( table, filters, true );
412
478
  }
413
479
  }
414
- c.$table.trigger('filterFomatterUpdate');
480
+ c.$table.trigger( 'filterFomatterUpdate' );
415
481
  // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers
416
- setTimeout(function(){
417
- if (!wo.filter_initialized) {
418
- ts.filter.filterInitComplete(c);
482
+ setTimeout( function() {
483
+ if ( !wo.filter_initialized ) {
484
+ ts.filter.filterInitComplete( c );
419
485
  }
420
- }, 100);
486
+ }, 100 );
421
487
  });
422
488
  // if filter widget is added after pager has initialized; then set filter init flag
423
- if (c.pager && c.pager.initialized && !wo.filter_initialized) {
424
- c.$table.trigger('filterFomatterUpdate');
425
- setTimeout(function(){
426
- ts.filter.filterInitComplete(c);
427
- }, 100);
489
+ if ( c.pager && c.pager.initialized && !wo.filter_initialized ) {
490
+ c.$table.trigger( 'filterFomatterUpdate' );
491
+ setTimeout( function() {
492
+ ts.filter.filterInitComplete( c );
493
+ }, 100 );
428
494
  }
429
495
  },
430
- // $cell parameter, but not the config, is passed to the
431
- // filter_formatters, so we have to work with it instead
432
- formatterUpdated: function($cell, column) {
433
- var wo = $cell.closest('table')[0].config.widgetOptions;
434
- if (!wo.filter_initialized) {
496
+ // $cell parameter, but not the config, is passed to the filter_formatters,
497
+ // so we have to work with it instead
498
+ formatterUpdated: function( $cell, column ) {
499
+ var wo = $cell.closest( 'table' )[0].config.widgetOptions;
500
+ if ( !wo.filter_initialized ) {
435
501
  // add updates by column since this function
436
502
  // may be called numerous times before initialization
437
- wo.filter_formatterInit[column] = 1;
503
+ wo.filter_formatterInit[ column ] = 1;
438
504
  }
439
505
  },
440
- filterInitComplete: function(c){
506
+ filterInitComplete: function( c ) {
441
507
  var indx, len,
442
508
  wo = c.widgetOptions,
443
509
  count = 0,
444
- completed = function(){
510
+ completed = function() {
445
511
  wo.filter_initialized = true;
446
- c.$table.trigger('filterInit', c);
447
- ts.filter.findRows(c.table, c.$table.data('lastSearch') || []);
512
+ c.$table.trigger( 'filterInit', c );
513
+ ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] );
448
514
  };
449
515
  if ( $.isEmptyObject( wo.filter_formatter ) ) {
450
516
  completed();
451
517
  } else {
452
518
  len = wo.filter_formatterInit.length;
453
- for (indx = 0; indx < len; indx++) {
454
- if (wo.filter_formatterInit[indx] === 1) {
519
+ for ( indx = 0; indx < len; indx++ ) {
520
+ if ( wo.filter_formatterInit[ indx ] === 1 ) {
455
521
  count++;
456
522
  }
457
523
  }
458
- clearTimeout(wo.filter_initTimer);
459
- if (!wo.filter_initialized && count === wo.filter_formatterCount) {
524
+ clearTimeout( wo.filter_initTimer );
525
+ if ( !wo.filter_initialized && count === wo.filter_formatterCount ) {
460
526
  // filter widget initialized
461
527
  completed();
462
- } else if (!wo.filter_initialized) {
528
+ } else if ( !wo.filter_initialized ) {
463
529
  // fall back in case a filter_formatter doesn't call
464
- // $.tablesorter.filter.formatterUpdated($cell, column), and the count is off
465
- wo.filter_initTimer = setTimeout(function(){
530
+ // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off
531
+ wo.filter_initTimer = setTimeout( function() {
466
532
  completed();
467
- }, 500);
533
+ }, 500 );
468
534
  }
469
535
  }
470
536
  },
471
537
 
472
- setDefaults: function(table, c, wo) {
538
+ setDefaults: function( table, c, wo ) {
473
539
  var isArray, saved, indx, col, $filters,
474
- // get current (default) filters
475
- filters = ts.getFilters(table) || [];
476
- if (wo.filter_saveFilters && ts.storage) {
540
+ // get current ( default ) filters
541
+ filters = ts.getFilters( table ) || [];
542
+ if ( wo.filter_saveFilters && ts.storage ) {
477
543
  saved = ts.storage( table, 'tablesorter-filters' ) || [];
478
- isArray = $.isArray(saved);
544
+ isArray = $.isArray( saved );
479
545
  // make sure we're not just getting an empty array
480
- if ( !(isArray && saved.join('') === '' || !isArray) ) { filters = saved; }
546
+ if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) {
547
+ filters = saved;
548
+ }
481
549
  }
482
550
  // if no filters saved, then check default settings
483
- if (filters.join('') === '') {
551
+ if ( filters.join( '' ) === '' ) {
484
552
  // allow adding default setting to external filters
485
- $filters = c.$headers.add( wo.filter_$externalFilters ).filter('[' + wo.filter_defaultAttrib + ']');
486
- for (indx = 0; indx <= c.columns; indx++) {
487
- // include data-column="all" external filters
553
+ $filters = c.$headers.add( wo.filter_$externalFilters )
554
+ .filter( '[' + wo.filter_defaultAttrib + ']' );
555
+ for ( indx = 0; indx <= c.columns; indx++ ) {
556
+ // include data-column='all' external filters
488
557
  col = indx === c.columns ? 'all' : indx;
489
- filters[indx] = $filters.filter('[data-column="' + col + '"]').attr(wo.filter_defaultAttrib) || filters[indx] || '';
558
+ filters[indx] = $filters
559
+ .filter( '[data-column="' + col + '"]' )
560
+ .attr( wo.filter_defaultAttrib ) || filters[indx] || '';
490
561
  }
491
562
  }
492
- c.$table.data('lastSearch', filters);
563
+ c.$table.data( 'lastSearch', filters );
493
564
  return filters;
494
565
  },
495
- parseFilter: function(c, filter, column, parsed, forceParse){
496
- return forceParse || parsed ?
497
- c.parsers[column].format( filter, c.table, [], column ) :
498
- filter;
566
+ parseFilter: function( c, filter, column, parsed ) {
567
+ return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter;
499
568
  },
500
- buildRow: function(table, c, wo) {
501
- var col, column, $header, buildSelect, disabled, name, ffxn,
569
+ buildRow: function( table, c, wo ) {
570
+ var col, column, $header, buildSelect, disabled, name, ffxn, tmp,
502
571
  // c.columns defined in computeThIndexes()
572
+ cellFilter = wo.filter_cellFilter,
503
573
  columns = c.columns,
504
- arry = $.isArray(wo.filter_cellFilter),
574
+ arry = $.isArray( cellFilter ),
505
575
  buildFilter = '<tr role="row" class="' + tscss.filterRow + ' ' + c.cssIgnoreRow + '">';
506
- for (column = 0; column < columns; column++) {
507
- if (arry) {
508
- buildFilter += '<td' + ( wo.filter_cellFilter[column] ? ' class="' + wo.filter_cellFilter[column] + '"' : '' ) + '></td>';
576
+ for ( column = 0; column < columns; column++ ) {
577
+ buildFilter += '<td';
578
+ if ( arry ) {
579
+ buildFilter += ( cellFilter[ column ] ? ' class="' + cellFilter[ column ] + '"' : '' );
509
580
  } else {
510
- buildFilter += '<td' + ( wo.filter_cellFilter !== '' ? ' class="' + wo.filter_cellFilter + '"' : '' ) + '></td>';
581
+ buildFilter += ( cellFilter !== '' ? ' class="' + cellFilter + '"' : '' );
511
582
  }
583
+ buildFilter += '></td>';
512
584
  }
513
- c.$filters = $(buildFilter += '</tr>').appendTo( c.$table.children('thead').eq(0) ).find('td');
585
+ c.$filters = $( buildFilter += '</tr>' )
586
+ .appendTo( c.$table.children( 'thead' ).eq( 0 ) )
587
+ .find( 'td' );
514
588
  // build each filter input
515
- for (column = 0; column < columns; column++) {
589
+ for ( column = 0; column < columns; column++ ) {
516
590
  disabled = false;
517
591
  // assuming last cell of a column is the main column
518
- $header = c.$headerIndexed[column];
592
+ $header = c.$headerIndexed[ column ];
519
593
  ffxn = ts.getColumnData( table, wo.filter_functions, column );
520
- buildSelect = (wo.filter_functions && ffxn && typeof ffxn !== "function" ) ||
521
- $header.hasClass('filter-select');
594
+ buildSelect = ( wo.filter_functions && ffxn && typeof ffxn !== 'function' ) ||
595
+ $header.hasClass( 'filter-select' );
522
596
  // get data from jQuery data, metadata, headers option or header class name
523
597
  col = ts.getColumnData( table, c.headers, column );
524
- disabled = ts.getData($header[0], col, 'filter') === 'false' || ts.getData($header[0], col, 'parser') === 'false';
598
+ disabled = ts.getData( $header[0], col, 'filter' ) === 'false' ||
599
+ ts.getData( $header[0], col, 'parser' ) === 'false';
525
600
 
526
- if (buildSelect) {
527
- buildFilter = $('<select>').appendTo( c.$filters.eq(column) );
601
+ if ( buildSelect ) {
602
+ buildFilter = $( '<select>' ).appendTo( c.$filters.eq( column ) );
528
603
  } else {
529
604
  ffxn = ts.getColumnData( table, wo.filter_formatter, column );
530
- if (ffxn) {
605
+ if ( ffxn ) {
531
606
  wo.filter_formatterCount++;
532
- buildFilter = ffxn( c.$filters.eq(column), column );
607
+ buildFilter = ffxn( c.$filters.eq( column ), column );
533
608
  // no element returned, so lets go find it
534
- if (buildFilter && buildFilter.length === 0) {
535
- buildFilter = c.$filters.eq(column).children('input');
609
+ if ( buildFilter && buildFilter.length === 0 ) {
610
+ buildFilter = c.$filters.eq( column ).children( 'input' );
536
611
  }
537
612
  // element not in DOM, so lets attach it
538
- if ( buildFilter && (buildFilter.parent().length === 0 ||
539
- (buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column])) ) {
540
- c.$filters.eq(column).append(buildFilter);
613
+ if ( buildFilter && ( buildFilter.parent().length === 0 ||
614
+ ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) {
615
+ c.$filters.eq( column ).append( buildFilter );
541
616
  }
542
617
  } else {
543
- buildFilter = $('<input type="search">').appendTo( c.$filters.eq(column) );
618
+ buildFilter = $( '<input type="search">' ).appendTo( c.$filters.eq( column ) );
544
619
  }
545
- if (buildFilter) {
546
- buildFilter.attr('placeholder', $header.data('placeholder') || $header.attr('data-placeholder') || wo.filter_placeholder.search || '');
620
+ if ( buildFilter ) {
621
+ tmp = $header.data( 'placeholder' ) ||
622
+ $header.attr( 'data-placeholder' ) ||
623
+ wo.filter_placeholder.search || '';
624
+ buildFilter.attr( 'placeholder', tmp );
547
625
  }
548
626
  }
549
- if (buildFilter) {
627
+ if ( buildFilter ) {
550
628
  // add filter class name
551
- name = ( $.isArray(wo.filter_cssFilter) ?
552
- (typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '') :
629
+ name = ( $.isArray( wo.filter_cssFilter ) ?
630
+ ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) :
553
631
  wo.filter_cssFilter ) || '';
554
- buildFilter.addClass( tscss.filter + ' ' + name ).attr('data-column', column);
555
- if (disabled) {
556
- buildFilter.attr('placeholder', '').addClass(tscss.filterDisabled)[0].disabled = true; // disabled!
632
+ buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column );
633
+ if ( disabled ) {
634
+ buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true;
557
635
  }
558
636
  }
559
637
  }
560
638
  },
561
- bindSearch: function(table, $el, internal) {
562
- table = $(table)[0];
563
- $el = $($el); // allow passing a selector string
564
- if (!$el.length) { return; }
565
- var c = table.config,
639
+ bindSearch: function( table, $el, internal ) {
640
+ table = $( table )[0];
641
+ $el = $( $el ); // allow passing a selector string
642
+ if ( !$el.length ) { return; }
643
+ var tmp,
644
+ c = table.config,
566
645
  wo = c.widgetOptions,
646
+ namespace = c.namespace + 'filter',
567
647
  $ext = wo.filter_$externalFilters;
568
- if (internal !== true) {
648
+ if ( internal !== true ) {
569
649
  // save anyMatch element
570
- wo.filter_$anyMatch = $el.filter(wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector);
571
- if ($ext && $ext.length) {
650
+ tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector;
651
+ wo.filter_$anyMatch = $el.filter( tmp );
652
+ if ( $ext && $ext.length ) {
572
653
  wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el );
573
654
  } else {
574
655
  wo.filter_$externalFilters = $el;
575
656
  }
576
- // update values (external filters added after table initialization)
577
- ts.setFilters(table, c.$table.data('lastSearch') || [], internal === false);
657
+ // update values ( external filters added after table initialization )
658
+ ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false );
578
659
  }
660
+ // unbind events
661
+ tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) );
579
662
  $el
580
- // use data attribute instead of jQuery data since the head is cloned without including the data/binding
581
- .attr('data-lastSearchTime', new Date().getTime())
582
- .unbind( ('keypress keyup search change '.split(' ').join(c.namespace + 'filter ')).replace(/\s+/g, ' ') )
663
+ // use data attribute instead of jQuery data since the head is cloned without including
664
+ // the data/binding
665
+ .attr( 'data-lastSearchTime', new Date().getTime() )
666
+ .unbind( tmp.replace( /\s+/g, ' ' ) )
583
667
  // include change for select - fixes #473
584
- .bind('keyup' + c.namespace + 'filter', function(event) {
585
- $(this).attr('data-lastSearchTime', new Date().getTime());
668
+ .bind( 'keyup' + namespace, function( event ) {
669
+ $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
586
670
  // emulate what webkit does.... escape clears the filter
587
- if (event.which === 27) {
671
+ if ( event.which === 27 ) {
588
672
  this.value = '';
589
673
  // live search
590
674
  } else if ( wo.filter_liveSearch === false ) {
591
675
  return;
592
- // don't return if the search value is empty (all rows need to be revealed)
676
+ // don't return if the search value is empty ( all rows need to be revealed )
593
677
  } else if ( this.value !== '' && (
594
678
  // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace
595
679
  ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) ||
596
680
  // let return & backspace continue on, but ignore arrows & non-valid characters
597
- ( event.which !== 13 && event.which !== 8 && ( event.which < 32 || (event.which >= 37 && event.which <= 40) ) ) ) ) {
681
+ ( event.which !== 13 && event.which !== 8 &&
682
+ ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) {
598
683
  return;
599
684
  }
600
685
  // change event = no delay; last true flag tells getFilters to skip newest timed input
601
686
  ts.filter.searching( table, true, true );
602
687
  })
603
- .bind( 'search change keypress '.split(' ').join(c.namespace + 'filter '), function(event){
604
- var column = $(this).data('column');
605
- // don't allow "change" event to process if the input value is the same - fixes #685
606
- if (event.which === 13 || event.type === 'search' || event.type === 'change' && this.value !== c.lastSearch[column]) {
688
+ .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) {
689
+ var column = $( this ).data( 'column' );
690
+ // don't allow 'change' event to process if the input value is the same - fixes #685
691
+ if ( event.which === 13 || event.type === 'search' ||
692
+ event.type === 'change' && this.value !== c.lastSearch[column] ) {
607
693
  event.preventDefault();
608
694
  // init search with no delay
609
- $(this).attr('data-lastSearchTime', new Date().getTime());
695
+ $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
610
696
  ts.filter.searching( table, false, true );
611
697
  }
612
698
  });
613
699
  },
614
- searching: function(table, filter, skipFirst) {
700
+ searching: function( table, filter, skipFirst ) {
615
701
  var wo = table.config.widgetOptions;
616
- clearTimeout(wo.searchTimer);
617
- if (typeof filter === 'undefined' || filter === true) {
702
+ clearTimeout( wo.searchTimer );
703
+ if ( typeof filter === 'undefined' || filter === true ) {
618
704
  // delay filtering
619
- wo.searchTimer = setTimeout(function() {
620
- ts.filter.checkFilters(table, filter, skipFirst );
621
- }, wo.filter_liveSearch ? wo.filter_searchDelay : 10);
705
+ wo.searchTimer = setTimeout( function() {
706
+ ts.filter.checkFilters( table, filter, skipFirst );
707
+ }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 );
622
708
  } else {
623
709
  // skip delay
624
- ts.filter.checkFilters(table, filter, skipFirst);
710
+ ts.filter.checkFilters( table, filter, skipFirst );
625
711
  }
626
712
  },
627
- checkFilters: function(table, filter, skipFirst) {
713
+ checkFilters: function( table, filter, skipFirst ) {
628
714
  var c = table.config,
629
715
  wo = c.widgetOptions,
630
- filterArray = $.isArray(filter),
631
- filters = (filterArray) ? filter : ts.getFilters(table, true),
632
- combinedFilters = (filters || []).join(''); // combined filter values
716
+ filterArray = $.isArray( filter ),
717
+ filters = ( filterArray ) ? filter : ts.getFilters( table, true ),
718
+ combinedFilters = ( filters || [] ).join( '' ); // combined filter values
633
719
  // prevent errors if delay init is set
634
- if ($.isEmptyObject(c.cache)) {
635
- // update cache if delayInit set & pager has initialized (after user initiates a search)
636
- if (c.delayInit && c.pager && c.pager.initialized) {
637
- c.$table.trigger('updateCache', [function(){
638
- ts.filter.checkFilters(table, false, skipFirst);
639
- }] );
720
+ if ( $.isEmptyObject( c.cache ) ) {
721
+ // update cache if delayInit set & pager has initialized ( after user initiates a search )
722
+ if ( c.delayInit && c.pager && c.pager.initialized ) {
723
+ c.$table.trigger( 'updateCache', [ function() {
724
+ ts.filter.checkFilters( table, false, skipFirst );
725
+ } ] );
640
726
  }
641
727
  return;
642
728
  }
643
729
  // add filter array back into inputs
644
- if (filterArray) {
730
+ if ( filterArray ) {
645
731
  ts.setFilters( table, filters, false, skipFirst !== true );
646
- if (!wo.filter_initialized) { c.lastCombinedFilter = ''; }
732
+ if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; }
647
733
  }
648
- if (wo.filter_hideFilters) {
734
+ if ( wo.filter_hideFilters ) {
649
735
  // show/hide filter row as needed
650
- c.$table.find('.' + tscss.filterRow).trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
736
+ c.$table
737
+ .find( '.' + tscss.filterRow )
738
+ .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
651
739
  }
652
740
  // return if the last search is the same; but filter === false when updating the search
653
741
  // see example-widget-filter.html filter toggle buttons
654
- if (c.lastCombinedFilter === combinedFilters && filter !== false) {
742
+ if ( c.lastCombinedFilter === combinedFilters && filter !== false ) {
655
743
  return;
656
- } else if (filter === false) {
744
+ } else if ( filter === false ) {
657
745
  // force filter refresh
658
746
  c.lastCombinedFilter = null;
659
747
  c.lastSearch = [];
660
748
  }
661
- if (wo.filter_initialized) { c.$table.trigger('filterStart', [filters]); }
662
- if (c.showProcessing) {
749
+ if ( wo.filter_initialized ) {
750
+ c.$table.trigger( 'filterStart', [filters] );
751
+ }
752
+ if ( c.showProcessing ) {
663
753
  // give it time for the processing icon to kick in
664
- setTimeout(function() {
665
- ts.filter.findRows(table, filters, combinedFilters);
754
+ setTimeout( function() {
755
+ ts.filter.findRows( table, filters, combinedFilters );
666
756
  return false;
667
- }, 30);
757
+ }, 30 );
668
758
  } else {
669
- ts.filter.findRows(table, filters, combinedFilters);
759
+ ts.filter.findRows( table, filters, combinedFilters );
670
760
  return false;
671
761
  }
672
762
  },
673
- hideFilters: function(table, c) {
763
+ hideFilters: function( table, c ) {
674
764
  var $filterRow, $filterRow2, timer;
675
- $(table)
676
- .find('.' + tscss.filterRow)
677
- .addClass(tscss.filterRowHide)
678
- .bind('mouseenter mouseleave', function(e) {
765
+ $( table )
766
+ .find( '.' + tscss.filterRow )
767
+ .addClass( tscss.filterRowHide )
768
+ .bind( 'mouseenter mouseleave', function( e ) {
679
769
  // save event object - http://bugs.jquery.com/ticket/12140
680
770
  var event = e;
681
- $filterRow = $(this);
682
- clearTimeout(timer);
683
- timer = setTimeout(function() {
684
- if ( /enter|over/.test(event.type) ) {
685
- $filterRow.removeClass(tscss.filterRowHide);
771
+ $filterRow = $( this );
772
+ clearTimeout( timer );
773
+ timer = setTimeout( function() {
774
+ if ( /enter|over/.test( event.type ) ) {
775
+ $filterRow.removeClass( tscss.filterRowHide );
686
776
  } else {
687
777
  // don't hide if input has focus
688
- // $(':focus') needs jQuery 1.6+
689
- if ( $(document.activeElement).closest('tr')[0] !== $filterRow[0] ) {
778
+ // $( ':focus' ) needs jQuery 1.6+
779
+ if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) {
690
780
  // don't hide row if any filter has a value
691
- if (c.lastCombinedFilter === '') {
692
- $filterRow.addClass(tscss.filterRowHide);
781
+ if ( c.lastCombinedFilter === '' ) {
782
+ $filterRow.addClass( tscss.filterRowHide );
693
783
  }
694
784
  }
695
785
  }
696
- }, 200);
786
+ }, 200 );
697
787
  })
698
- .find('input, select').bind('focus blur', function(e) {
699
- $filterRow2 = $(this).closest('tr');
700
- clearTimeout(timer);
788
+ .find( 'input, select' ).bind( 'focus blur', function( e ) {
789
+ $filterRow2 = $( this ).closest( 'tr' );
790
+ clearTimeout( timer );
701
791
  var event = e;
702
- timer = setTimeout(function() {
792
+ timer = setTimeout( function() {
703
793
  // don't hide row if any filter has a value
704
- if (ts.getFilters(c.$table).join('') === '') {
705
- $filterRow2[ event.type === 'focus' ? 'removeClass' : 'addClass'](tscss.filterRowHide);
794
+ if ( ts.getFilters( c.$table ).join( '' ) === '' ) {
795
+ $filterRow2.toggleClass( tscss.filterRowHide, event.type === 'focus' );
706
796
  }
707
- }, 200);
797
+ }, 200 );
708
798
  });
709
799
  },
710
- defaultFilter: function(filter, mask){
711
- if (filter === '') { return filter; }
800
+ defaultFilter: function( filter, mask ) {
801
+ if ( filter === '' ) { return filter; }
712
802
  var regex = ts.filter.regex.iQuery,
713
803
  maskLen = mask.match( ts.filter.regex.igQuery ).length,
714
- query = maskLen > 1 ? $.trim(filter).split(/\s/) : [ $.trim(filter) ],
804
+ query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ],
715
805
  len = query.length - 1,
716
806
  indx = 0,
717
807
  val = mask;
718
808
  if ( len < 1 && maskLen > 1 ) {
719
- // only one "word" in query but mask has >1 slots
809
+ // only one 'word' in query but mask has >1 slots
720
810
  query[1] = query[0];
721
811
  }
722
812
  // replace all {query} with query words...
723
- // if query = "Bob", then convert mask from "!{query}" to "!Bob"
724
- // if query = "Bob Joe Frank", then convert mask "{q} OR {q}" to "Bob OR Joe OR Frank"
725
- while (regex.test(val)) {
726
- val = val.replace(regex, query[indx++] || '');
727
- if (regex.test(val) && indx < len && (query[indx] || '') !== '') {
728
- val = mask.replace(regex, val);
813
+ // if query = 'Bob', then convert mask from '!{query}' to '!Bob'
814
+ // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank'
815
+ while ( regex.test( val ) ) {
816
+ val = val.replace( regex, query[indx++] || '' );
817
+ if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) {
818
+ val = mask.replace( regex, val );
729
819
  }
730
820
  }
731
821
  return val;
732
822
  },
733
823
  getLatestSearch: function( $input ) {
734
- if ($input) {
735
- return $input.sort(function(a, b) {
736
- return $(b).attr('data-lastSearchTime') - $(a).attr('data-lastSearchTime');
824
+ if ( $input ) {
825
+ return $input.sort( function( a, b ) {
826
+ return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' );
737
827
  });
738
828
  }
739
829
  return $();
740
830
  },
741
831
  multipleColumns: function( c, $input ) {
742
- // look for multiple columns "1-3,4-6,8" in data-column
832
+ // look for multiple columns '1-3,4-6,8' in data-column
743
833
  var temp, ranges, range, start, end, singles, i, indx, len,
744
834
  wo = c.widgetOptions,
745
- // only target "all" column inputs on initialization
746
- // & don't target "all" column inputs if they don't exist
747
- targets = wo.filter_initialized || !$input.filter(wo.filter_anyColumnSelector).length,
835
+ // only target 'all' column inputs on initialization
836
+ // & don't target 'all' column inputs if they don't exist
837
+ targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length,
748
838
  columns = [],
749
- val = $.trim( ts.filter.getLatestSearch( $input ).attr('data-column') || '' );
839
+ val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' );
750
840
  // process column range
751
841
  if ( targets && /-/.test( val ) ) {
752
842
  ranges = val.match( /(\d+)\s*-\s*(\d+)/g );
753
843
  len = ranges.length;
754
- for (indx = 0; indx < len; indx++) {
844
+ for ( indx = 0; indx < len; indx++ ) {
755
845
  range = ranges[indx].split( /\s*-\s*/ );
756
846
  start = parseInt( range[0], 10 ) || 0;
757
847
  end = parseInt( range[1], 10 ) || ( c.columns - 1 );
758
- if ( start > end ) { temp = start; start = end; end = temp; } // swap
759
- if ( end >= c.columns ) { end = c.columns - 1; }
848
+ if ( start > end ) {
849
+ temp = start; start = end; end = temp; // swap
850
+ }
851
+ if ( end >= c.columns ) {
852
+ end = c.columns - 1;
853
+ }
760
854
  for ( ; start <= end; start++ ) {
761
- columns.push(start);
855
+ columns.push( start );
762
856
  }
763
857
  // remove processed range from val
764
- val = val.replace( ranges[indx], '' );
858
+ val = val.replace( ranges[ indx ], '' );
765
859
  }
766
860
  }
767
861
  // process single columns
768
862
  if ( targets && /,/.test( val ) ) {
769
863
  singles = val.split( /\s*,\s*/ );
770
864
  len = singles.length;
771
- for (i = 0; i < len; i++) {
772
- if (singles[i] !== '') {
773
- indx = parseInt( singles[i], 10 );
865
+ for ( i = 0; i < len; i++ ) {
866
+ if ( singles[ i ] !== '' ) {
867
+ indx = parseInt( singles[ i ], 10 );
774
868
  if ( indx < c.columns ) {
775
869
  columns.push( indx );
776
870
  }
@@ -778,382 +872,472 @@ ts.filter = {
778
872
  }
779
873
  }
780
874
  // return all columns
781
- if (!columns.length) {
875
+ if ( !columns.length ) {
782
876
  for ( indx = 0; indx < c.columns; indx++ ) {
783
877
  columns.push( indx );
784
878
  }
785
879
  }
786
880
  return columns;
787
881
  },
788
- findRows: function(table, filters, combinedFilters) {
789
- if (table.config.lastCombinedFilter === combinedFilters || !table.config.widgetOptions.filter_initialized) { return; }
790
- var len, norm_rows, $rows, rowIndex, tbodyIndex, $tbody, $cells, $cell, columnIndex,
791
- childRow, lastSearch, hasSelect, matches, result, showRow, time, val, indx,
792
- notFiltered, searchFiltered, filterMatched, excludeMatch, fxn, ffxn,
793
- query, injected, res, id,
882
+ processRow: function( c, data, vars ) {
883
+ var $cell, columnIndex, hasSelect, matches, result, val, filterMatched, excludeMatch,
884
+ fxn, ffxn, txt,
885
+ regex = ts.filter.regex,
886
+ wo = c.widgetOptions,
887
+ showRow = true;
888
+ data.$cells = data.$row.children();
889
+
890
+ if ( data.anyMatchFlag ) {
891
+ // look for multiple columns '1-3,4-6,8'
892
+ columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
893
+ data.anyMatch = true;
894
+ data.rowArray = data.$cells.map( function( i ) {
895
+ if ( $.inArray( i, columnIndex ) > -1 ) {
896
+ if ( data.parsed[ i ] ) {
897
+ txt = data.cacheArray[ i ];
898
+ } else {
899
+ txt = data.rawArray[ i ];
900
+ txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt );
901
+ if ( c.sortLocaleCompare ) {
902
+ txt = ts.replaceAccents( txt );
903
+ }
904
+ }
905
+ return txt;
906
+ }
907
+ }).get();
908
+ data.filter = data.anyMatchFilter;
909
+ data.iFilter = data.iAnyMatchFilter;
910
+ data.exact = data.rowArray.join( ' ' );
911
+ data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
912
+ data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' );
913
+ filterMatched = null;
914
+ matches = null;
915
+ for ( ffxn in ts.filter.types ) {
916
+ if ( $.inArray( ffxn, vars.noAnyMatch ) < 0 && matches === null ) {
917
+ matches = ts.filter.types[ffxn]( c, data );
918
+ if ( matches !== null ) {
919
+ filterMatched = matches;
920
+ }
921
+ }
922
+ }
923
+ if ( filterMatched !== null ) {
924
+ showRow = filterMatched;
925
+ } else {
926
+ if ( wo.filter_startsWith ) {
927
+ showRow = false;
928
+ columnIndex = c.columns;
929
+ while ( !showRow && columnIndex > 0 ) {
930
+ columnIndex--;
931
+ showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0;
932
+ }
933
+ } else {
934
+ showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0;
935
+ }
936
+ }
937
+ data.anyMatch = false;
938
+ // no other filters to process
939
+ if ( data.filters.join( '' ) === data.filter ) {
940
+ return showRow;
941
+ }
942
+ }
943
+
944
+ for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) {
945
+ data.filter = data.filters[ columnIndex ];
946
+ data.index = columnIndex;
947
+
948
+ // filter types to exclude, per column
949
+ excludeMatch = vars.excludeFilter[ columnIndex ];
950
+
951
+ // ignore if filter is empty or disabled
952
+ if ( data.filter ) {
953
+ data.cache = data.cacheArray[ columnIndex ];
954
+ // check if column data should be from the cell or from parsed data
955
+ if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) {
956
+ data.exact = data.cache;
957
+ } else {
958
+ result = data.rawArray[ columnIndex ] || '';
959
+ data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405
960
+ }
961
+ data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ?
962
+ data.exact.toLowerCase() : data.exact;
963
+ result = showRow; // if showRow is true, show that row
964
+
965
+ // in case select filter option has a different value vs text 'a - z|A through Z'
966
+ ffxn = wo.filter_columnFilters ?
967
+ c.$filters.add( c.$externalFilters )
968
+ .filter( '[data-column="'+ columnIndex + '"]' )
969
+ .find( 'select option:selected' )
970
+ .attr( 'data-function-name' ) || '' : '';
971
+ // replace accents - see #357
972
+ if ( c.sortLocaleCompare ) {
973
+ data.filter = ts.replaceAccents( data.filter );
974
+ }
975
+
976
+ val = true;
977
+ if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) {
978
+ data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] );
979
+ // val is used to indicate that a filter select is using a default filter;
980
+ // so we override the exact & partial matches
981
+ val = false;
982
+ }
983
+ // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ),
984
+ // data.filter = case sensitive
985
+ data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter;
986
+ fxn = vars.functions[ columnIndex ];
987
+ $cell = c.$headerIndexed[ columnIndex ];
988
+ hasSelect = $cell.hasClass( 'filter-select' );
989
+ filterMatched = null;
990
+ if ( fxn || ( hasSelect && val ) ) {
991
+ if ( fxn === true || hasSelect ) {
992
+ // default selector uses exact match unless 'filter-match' class is found
993
+ filterMatched = $cell.hasClass( 'filter-match' ) ?
994
+ data.iExact.search( data.iFilter ) >= 0 :
995
+ data.filter === data.exact;
996
+ } else if ( typeof fxn === 'function' ) {
997
+ // filter callback( exact cell content, parser normalized content,
998
+ // filter input value, column index, jQuery row object )
999
+ filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data );
1000
+ } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) {
1001
+ // selector option function
1002
+ txt = ffxn || data.filter;
1003
+ filterMatched =
1004
+ fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data );
1005
+ }
1006
+ }
1007
+ if ( filterMatched === null ) {
1008
+ // cycle through the different filters
1009
+ // filters return a boolean or null if nothing matches
1010
+ matches = null;
1011
+ for ( ffxn in ts.filter.types ) {
1012
+ if ( $.inArray( ffxn, excludeMatch ) < 0 && matches === null ) {
1013
+ matches = ts.filter.types[ ffxn ]( c, data );
1014
+ if ( matches !== null ) {
1015
+ filterMatched = matches;
1016
+ }
1017
+ }
1018
+ }
1019
+ if ( filterMatched !== null ) {
1020
+ result = filterMatched;
1021
+ // Look for match, and add child row data for matching
1022
+ } else {
1023
+ txt = ( data.iExact + data.childRowText )
1024
+ .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) );
1025
+ result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) );
1026
+ }
1027
+ } else {
1028
+ result = filterMatched;
1029
+ }
1030
+ showRow = ( result ) ? showRow : false;
1031
+ }
1032
+ }
1033
+ return showRow;
1034
+ },
1035
+ findRows: function( table, filters, combinedFilters ) {
1036
+ if ( table.config.lastCombinedFilter === combinedFilters ||
1037
+ !table.config.widgetOptions.filter_initialized ) {
1038
+ return;
1039
+ }
1040
+ var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex,
1041
+ isChild, childRow, lastSearch, showRow, time, val, indx,
1042
+ notFiltered, searchFiltered, query, injected, res, id, txt,
1043
+ storedFilters = $.extend( [], filters ),
794
1044
  regex = ts.filter.regex,
795
1045
  c = table.config,
796
1046
  wo = c.widgetOptions,
797
1047
  // data object passed to filters; anyMatch is a flag for the filters
798
- data = { anyMatch: false },
799
- // anyMatch really screws up with these types of filters
800
- noAnyMatch = [ 'range', 'notMatch', 'operators' ];
1048
+ data = {
1049
+ anyMatch: false,
1050
+ filters: filters,
1051
+ // regex filter type cache
1052
+ filter_regexCache : [],
1053
+ },
1054
+ vars = {
1055
+ // anyMatch really screws up with these types of filters
1056
+ noAnyMatch: [ 'range', 'notMatch', 'operators' ],
1057
+ // cache filter variables that use ts.getColumnData in the main loop
1058
+ functions : [],
1059
+ excludeFilter : [],
1060
+ defaultColFilter : [],
1061
+ defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || ''
1062
+ };
801
1063
 
802
1064
  // parse columns after formatter, in case the class is added at that point
803
- data.parsed = c.$headers.map(function(columnIndex) {
804
- return c.parsers && c.parsers[columnIndex] && c.parsers[columnIndex].parsed ||
805
- // getData won't return "parsed" if other "filter-" class names exist (e.g. <th class="filter-select filter-parsed">)
806
- ts.getData && ts.getData(c.$headerIndexed[columnIndex], ts.getColumnData( table, c.headers, columnIndex ), 'filter') === 'parsed' ||
807
- $(this).hasClass('filter-parsed');
1065
+ data.parsed = c.$headers.map( function( columnIndex ) {
1066
+ return c.parsers && c.parsers[ columnIndex ] &&
1067
+ // force parsing if parser type is numeric
1068
+ ( c.parsers[ columnIndex ].parsed || c.parsers[ columnIndex ].type === 'numeric' ) ||
1069
+ // getData won't return 'parsed' if other 'filter-' class names exist
1070
+ // ( e.g. <th class="filter-select filter-parsed"> )
1071
+ ts.getData && ts.getData( c.$headerIndexed[ columnIndex ],
1072
+ ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' ||
1073
+ $( this ).hasClass( 'filter-parsed' );
808
1074
  }).get();
809
1075
 
810
- // cache filter variables that use ts.getColumnData in the main loop
811
- wo.filter_indexed = {
812
- functions : [],
813
- excludeFilter : [],
814
- defaultColFilter : [],
815
- defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || ''
816
- };
817
1076
  for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) {
818
- wo.filter_indexed.functions[ columnIndex ] = ts.getColumnData( table, wo.filter_functions, columnIndex );
819
- wo.filter_indexed.defaultColFilter[ columnIndex ] = ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || '';
820
- wo.filter_indexed.excludeFilter[ columnIndex ] = ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split(/\s+/);
1077
+ vars.functions[ columnIndex ] =
1078
+ ts.getColumnData( table, wo.filter_functions, columnIndex );
1079
+ vars.defaultColFilter[ columnIndex ] =
1080
+ ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || '';
1081
+ vars.excludeFilter[ columnIndex ] =
1082
+ ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ );
821
1083
  }
822
1084
 
823
- if (c.debug) {
824
- ts.log('Filter: Starting filter widget search', filters);
1085
+ if ( c.debug ) {
1086
+ ts.log( 'Filter: Starting filter widget search', filters );
825
1087
  time = new Date();
826
1088
  }
827
1089
  // filtered rows count
828
1090
  c.filteredRows = 0;
829
1091
  c.totalRows = 0;
830
1092
  // combindedFilters are undefined on init
831
- combinedFilters = (filters || []).join('');
1093
+ combinedFilters = ( storedFilters || [] ).join( '' );
832
1094
 
833
- for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
834
- $tbody = ts.processTbody(table, c.$tbodies.eq(tbodyIndex), true);
835
- // skip child rows & widget added (removable) rows - fixes #448 thanks to @hempel!
836
- // $rows = $tbody.children('tr').not(c.selectorRemove);
1095
+ for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
1096
+ $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true );
1097
+ // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel!
1098
+ // $rows = $tbody.children( 'tr' ).not( c.selectorRemove );
837
1099
  columnIndex = c.columns;
838
1100
  // convert stored rows into a jQuery object
839
- norm_rows = c.cache[tbodyIndex].normalized;
840
- $rows = $( $.map(norm_rows, function(el){ return el[columnIndex].$row.get(); }) );
1101
+ norm_rows = c.cache[ tbodyIndex ].normalized;
1102
+ $rows = $( $.map( norm_rows, function( el ) {
1103
+ return el[ columnIndex ].$row.get();
1104
+ }) );
841
1105
 
842
- if (combinedFilters === '' || wo.filter_serversideFiltering) {
843
- $rows.removeClass(wo.filter_filteredRow).not('.' + c.cssChildRow).css('display', '');
1106
+ if ( combinedFilters === '' || wo.filter_serversideFiltering ) {
1107
+ $rows
1108
+ .removeClass( wo.filter_filteredRow )
1109
+ .not( '.' + c.cssChildRow )
1110
+ .css( 'display', '' );
844
1111
  } else {
845
1112
  // filter out child rows
846
- $rows = $rows.not('.' + c.cssChildRow);
1113
+ $rows = $rows.not( '.' + c.cssChildRow );
847
1114
  len = $rows.length;
848
1115
 
849
- if ( (wo.filter_$anyMatch && wo.filter_$anyMatch.length) || typeof filters[c.columns] !== 'undefined' ) {
1116
+ if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) ||
1117
+ typeof filters[c.columns] !== 'undefined' ) {
850
1118
  data.anyMatchFlag = true;
851
- data.anyMatchFilter = wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || ( '' + filters[c.columns] ) || '';
852
- if (wo.filter_columnAnyMatch) {
1119
+ data.anyMatchFilter = '' + (
1120
+ filters[ c.columns ] ||
1121
+ wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() ||
1122
+ ''
1123
+ );
1124
+ if ( wo.filter_columnAnyMatch ) {
853
1125
  // specific columns search
854
- query = data.anyMatchFilter.split( ts.filter.regex.andSplit );
1126
+ query = data.anyMatchFilter.split( regex.andSplit );
855
1127
  injected = false;
856
- for (indx = 0; indx < query.length; indx++) {
857
- res = query[indx].split(':');
1128
+ for ( indx = 0; indx < query.length; indx++ ) {
1129
+ res = query[ indx ].split( ':' );
858
1130
  if ( res.length > 1 ) {
859
1131
  // make the column a one-based index ( non-developers start counting from one :P )
860
1132
  id = parseInt( res[0], 10 ) - 1;
861
1133
  if ( id >= 0 && id < c.columns ) { // if id is an integer
862
- filters[id] = res[1];
863
- query.splice(indx, 1);
1134
+ filters[ id ] = res[1];
1135
+ query.splice( indx, 1 );
864
1136
  indx--;
865
1137
  injected = true;
866
1138
  }
867
1139
  }
868
1140
  }
869
- if (injected) {
870
- data.anyMatchFilter = query.join(' && ');
1141
+ if ( injected ) {
1142
+ data.anyMatchFilter = query.join( ' && ' );
871
1143
  }
872
1144
  }
873
1145
  }
874
1146
 
875
1147
  // optimize searching only through already filtered rows - see #313
876
1148
  searchFiltered = wo.filter_searchFiltered;
877
- lastSearch = c.lastSearch || c.$table.data('lastSearch') || [];
878
- if (searchFiltered) {
879
- // cycle through all filters; include last (columnIndex + 1 = match any column). Fixes #669
880
- for (indx = 0; indx < columnIndex + 1; indx++) {
1149
+ lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || [];
1150
+ if ( searchFiltered ) {
1151
+ // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669
1152
+ for ( indx = 0; indx < columnIndex + 1; indx++ ) {
881
1153
  val = filters[indx] || '';
882
1154
  // break out of loop if we've already determined not to search filtered rows
883
- if (!searchFiltered) { indx = columnIndex; }
1155
+ if ( !searchFiltered ) { indx = columnIndex; }
884
1156
  // search already filtered rows if...
885
1157
  searchFiltered = searchFiltered && lastSearch.length &&
886
1158
  // there are no changes from beginning of filter
887
- val.indexOf(lastSearch[indx] || '') === 0 &&
888
- // if there is NOT a logical "or", or range ("to" or "-") in the string
889
- !regex.alreadyFiltered.test(val) &&
890
- // if we are not doing exact matches, using "|" (logical or) or not "!"
891
- !/[=\"\|!]/.test(val) &&
892
- // don't search only filtered if the value is negative ('> -10' => '> -100' will ignore hidden rows)
893
- !(/(>=?\s*-\d)/.test(val) || /(<=?\s*\d)/.test(val)) &&
894
- // if filtering using a select without a "filter-match" class (exact match) - fixes #593
895
- !( val !== '' && c.$filters && c.$filters.eq(indx).find('select').length && !c.$headerIndexed[indx].hasClass('filter-match') );
1159
+ val.indexOf( lastSearch[indx] || '' ) === 0 &&
1160
+ // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string
1161
+ !regex.alreadyFiltered.test( val ) &&
1162
+ // if we are not doing exact matches, using '|' ( logical or ) or not '!'
1163
+ !/[=\"\|!]/.test( val ) &&
1164
+ // don't search only filtered if the value is negative
1165
+ // ( '> -10' => '> -100' will ignore hidden rows )
1166
+ !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) &&
1167
+ // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593
1168
+ !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length &&
1169
+ !c.$headerIndexed[indx].hasClass( 'filter-match' ) );
896
1170
  }
897
1171
  }
898
- notFiltered = $rows.not('.' + wo.filter_filteredRow).length;
1172
+ notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length;
899
1173
  // can't search when all rows are hidden - this happens when looking for exact matches
900
- if (searchFiltered && notFiltered === 0) { searchFiltered = false; }
901
- if (c.debug) {
902
- ts.log( 'Filter: Searching through ' + ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' );
1174
+ if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; }
1175
+ if ( c.debug ) {
1176
+ ts.log( 'Filter: Searching through ' +
1177
+ ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' );
903
1178
  }
904
- if (data.anyMatchFlag) {
905
- if (c.sortLocaleCompare) {
1179
+ if ( data.anyMatchFlag ) {
1180
+ if ( c.sortLocaleCompare ) {
906
1181
  // replace accents
907
- data.anyMatchFilter = ts.replaceAccents(data.anyMatchFilter);
1182
+ data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter );
908
1183
  }
909
- if ( wo.filter_defaultFilter && regex.iQuery.test( wo.filter_indexed.defaultAnyFilter ) ) {
910
- data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, wo.filter_indexed.defaultAnyFilter );
1184
+ if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) {
1185
+ data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter );
911
1186
  // clear search filtered flag because default filters are not saved to the last search
912
1187
  searchFiltered = false;
913
1188
  }
914
1189
  // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true
915
1190
  // when c.ignoreCase is true, the cache contains all lower case data
916
- data.iAnyMatchFilter = !(wo.filter_ignoreCase && c.ignoreCase) ? data.anyMatchFilter : data.anyMatchFilter.toLocaleLowerCase();
1191
+ data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ?
1192
+ data.anyMatchFilter :
1193
+ data.anyMatchFilter.toLowerCase();
917
1194
  }
918
1195
 
919
1196
  // loop through the rows
920
- for (rowIndex = 0; rowIndex < len; rowIndex++) {
1197
+ for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
921
1198
 
922
- data.cacheArray = norm_rows[rowIndex];
923
-
924
- childRow = $rows[rowIndex].className;
1199
+ txt = $rows[ rowIndex ].className;
1200
+ // the first row can never be a child row
1201
+ isChild = rowIndex && regex.child.test( txt );
925
1202
  // skip child rows & already filtered rows
926
- if ( regex.child.test(childRow) || (searchFiltered && regex.filtered.test(childRow)) ) { continue; }
927
- showRow = true;
928
- // *** nextAll/nextUntil not supported by Zepto! ***
929
- childRow = $rows.eq(rowIndex).nextUntil('tr:not(.' + c.cssChildRow + ')');
930
- // so, if "table.config.widgetOptions.filter_childRows" is true and there is
931
- // a match anywhere in the child row, then it will make the row visible
932
- // checked here so the option can be changed dynamically
933
- data.childRowText = (childRow.length && wo.filter_childRows) ? childRow.text() : '';
934
- data.childRowText = wo.filter_ignoreCase ? data.childRowText.toLocaleLowerCase() : data.childRowText;
935
- $cells = $rows.eq(rowIndex).children();
936
- if (data.anyMatchFlag) {
937
- // look for multiple columns "1-3,4-6,8"
938
- columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
939
- data.anyMatch = true;
940
- data.rowArray = $cells.map(function(i){
941
- if ( $.inArray(i, columnIndex) > -1 ) {
942
- var txt;
943
- if (data.parsed[i]) {
944
- txt = data.cacheArray[i];
945
- } else {
946
- txt = this ? this.getAttribute( c.textAttribute ) || this.textContent || $(this).text() : '';
947
- txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt );
948
- if (c.sortLocaleCompare) {
949
- txt = ts.replaceAccents(txt);
950
- }
951
- }
952
- return txt;
953
- }
954
- }).get();
955
- data.filter = data.anyMatchFilter;
956
- data.iFilter = data.iAnyMatchFilter;
957
- data.exact = data.rowArray.join(' ');
958
- data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
959
- data.cache = data.cacheArray.slice(0,-1).join(' ');
960
- filterMatched = null;
961
- $.each(ts.filter.types, function(type, typeFunction) {
962
- if ($.inArray(type, noAnyMatch) < 0) {
963
- matches = typeFunction( c, data );
964
- if (matches !== null) {
965
- filterMatched = matches;
966
- return false;
967
- }
968
- }
969
- });
970
- if (filterMatched !== null) {
971
- showRow = filterMatched;
972
- } else {
973
- if (wo.filter_startsWith) {
974
- showRow = false;
975
- columnIndex = c.columns;
976
- while (!showRow && columnIndex > 0) {
977
- columnIndex--;
978
- showRow = showRow || data.rowArray[columnIndex].indexOf(data.iFilter) === 0;
979
- }
980
- } else {
981
- showRow = (data.iExact + data.childRowText).indexOf(data.iFilter) >= 0;
982
- }
983
- }
984
- data.anyMatch = false;
1203
+ if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) {
1204
+ continue;
985
1205
  }
986
1206
 
987
- for (columnIndex = 0; columnIndex < c.columns; columnIndex++) {
988
- data.filter = filters[columnIndex];
989
- data.index = columnIndex;
990
-
991
- // filter types to exclude, per column
992
- excludeMatch = wo.filter_indexed.excludeFilter[ columnIndex ];
1207
+ data.$row = $rows.eq( rowIndex );
1208
+ data.cacheArray = norm_rows[ rowIndex ];
1209
+ rowData = data.cacheArray[ c.columns ];
1210
+ data.rawArray = rowData.raw;
1211
+ data.childRowText = '';
993
1212
 
994
- // ignore if filter is empty or disabled
995
- if (data.filter) {
996
- data.cache = data.cacheArray[columnIndex];
997
- // check if column data should be from the cell or from parsed data
998
- if (wo.filter_useParsedData || data.parsed[columnIndex]) {
999
- data.exact = data.cache;
1000
- } else {
1001
- val = $cells[columnIndex];
1002
- result = val ? $.trim( val.getAttribute( c.textAttribute ) || val.textContent || $cells.eq(columnIndex).text() ) : '';
1003
- data.exact = c.sortLocaleCompare ? ts.replaceAccents(result) : result; // issue #405
1004
- }
1005
- data.iExact = !regex.type.test(typeof data.exact) && wo.filter_ignoreCase ? data.exact.toLocaleLowerCase() : data.exact;
1006
- result = showRow; // if showRow is true, show that row
1213
+ if ( !wo.filter_childByColumn ) {
1214
+ txt = '';
1215
+ // child row cached text
1216
+ childRow = rowData.child;
1217
+ // so, if 'table.config.widgetOptions.filter_childRows' is true and there is
1218
+ // a match anywhere in the child row, then it will make the row visible
1219
+ // checked here so the option can be changed dynamically
1220
+ for ( indx = 0; indx < childRow.length; indx++ ) {
1221
+ txt += ' ' + childRow[indx].join( '' ) || '';
1222
+ }
1223
+ data.childRowText = wo.filter_childRows ?
1224
+ ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) :
1225
+ '';
1226
+ }
1007
1227
 
1008
- // in case select filter option has a different value vs text "a - z|A through Z"
1009
- ffxn = wo.filter_columnFilters ?
1010
- c.$filters.add(c.$externalFilters).filter('[data-column="'+ columnIndex + '"]').find('select option:selected').attr('data-function-name') || '' : '';
1011
- // replace accents - see #357
1012
- if (c.sortLocaleCompare) {
1013
- data.filter = ts.replaceAccents(data.filter);
1014
- }
1228
+ showRow = ts.filter.processRow( c, data, vars );
1229
+ childRow = rowData.$row.filter( ':gt( 0 )' );
1015
1230
 
1016
- val = true;
1017
- if (wo.filter_defaultFilter && regex.iQuery.test( wo.filter_indexed.defaultColFilter[ columnIndex ] )) {
1018
- data.filter = ts.filter.defaultFilter( data.filter, wo.filter_indexed.defaultColFilter[ columnIndex ] );
1019
- // val is used to indicate that a filter select is using a default filter; so we override the exact & partial matches
1020
- val = false;
1021
- }
1022
- // data.iFilter = case insensitive (if wo.filter_ignoreCase is true), data.filter = case sensitive
1023
- data.iFilter = wo.filter_ignoreCase ? (data.filter || '').toLocaleLowerCase() : data.filter;
1024
- fxn = wo.filter_indexed.functions[ columnIndex ];
1025
- $cell = c.$headerIndexed[columnIndex];
1026
- hasSelect = $cell.hasClass('filter-select');
1027
- filterMatched = null;
1028
- if ( fxn || ( hasSelect && val ) ) {
1029
- if (fxn === true || hasSelect) {
1030
- // default selector uses exact match unless "filter-match" class is found
1031
- filterMatched = ($cell.hasClass('filter-match')) ? data.iExact.search(data.iFilter) >= 0 : data.filter === data.exact;
1032
- } else if (typeof fxn === 'function') {
1033
- // filter callback( exact cell content, parser normalized content, filter input value, column index, jQuery row object )
1034
- filterMatched = fxn(data.exact, data.cache, data.filter, columnIndex, $rows.eq(rowIndex), c);
1035
- } else if (typeof fxn[ffxn || data.filter] === 'function') {
1036
- // selector option function
1037
- filterMatched = fxn[ffxn || data.filter](data.exact, data.cache, data.filter, columnIndex, $rows.eq(rowIndex), c);
1038
- }
1231
+ if ( wo.filter_childRows && childRow.length ) {
1232
+ if ( wo.filter_childByColumn ) {
1233
+ // cycle through each child row
1234
+ for ( indx = 0; indx < childRow.length; indx++ ) {
1235
+ data.$row = childRow.eq( indx );
1236
+ data.cacheArray = rowData.child[ indx ];
1237
+ data.rawArray = data.cacheArray;
1238
+ // use OR comparison on child rows
1239
+ showRow = showRow || ts.filter.processRow( c, data, vars );
1039
1240
  }
1040
- if (filterMatched === null) {
1041
- // cycle through the different filters
1042
- // filters return a boolean or null if nothing matches
1043
- $.each(ts.filter.types, function(type, typeFunction) {
1044
- if ($.inArray(type, excludeMatch) < 0) {
1045
- matches = typeFunction( c, data );
1046
- if (matches !== null) {
1047
- filterMatched = matches;
1048
- return false;
1049
- }
1050
- }
1051
- });
1052
- if (filterMatched !== null) {
1053
- result = filterMatched;
1054
- // Look for match, and add child row data for matching
1055
- } else {
1056
- data.exact = (data.iExact + data.childRowText).indexOf( ts.filter.parseFilter(c, data.iFilter, columnIndex, data.parsed[columnIndex]) );
1057
- result = ( (!wo.filter_startsWith && data.exact >= 0) || (wo.filter_startsWith && data.exact === 0) );
1058
- }
1059
- } else {
1060
- result = filterMatched;
1061
- }
1062
- showRow = (result) ? showRow : false;
1063
1241
  }
1242
+ childRow.toggleClass( wo.filter_filteredRow, !showRow );
1064
1243
  }
1065
- $rows.eq(rowIndex)
1066
- .toggleClass(wo.filter_filteredRow, !showRow)[0]
1244
+
1245
+ rowData.$row
1246
+ .toggleClass( wo.filter_filteredRow, !showRow )[0]
1067
1247
  .display = showRow ? '' : 'none';
1068
- if (childRow.length) {
1069
- childRow.toggleClass(wo.filter_filteredRow, !showRow);
1070
- }
1071
1248
  }
1072
1249
  }
1073
- c.filteredRows += $rows.not('.' + wo.filter_filteredRow).length;
1250
+ c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length;
1074
1251
  c.totalRows += $rows.length;
1075
- ts.processTbody(table, $tbody, false);
1252
+ ts.processTbody( table, $tbody, false );
1076
1253
  }
1077
1254
  c.lastCombinedFilter = combinedFilters; // save last search
1078
- c.lastSearch = filters;
1079
- c.$table.data('lastSearch', filters);
1080
- if (wo.filter_saveFilters && ts.storage) {
1081
- ts.storage( table, 'tablesorter-filters', filters );
1255
+ // don't save 'filters' directly since it may have altered ( AnyMatch column searches )
1256
+ c.lastSearch = storedFilters;
1257
+ c.$table.data( 'lastSearch', storedFilters );
1258
+ if ( wo.filter_saveFilters && ts.storage ) {
1259
+ ts.storage( table, 'tablesorter-filters', storedFilters );
1260
+ }
1261
+ if ( c.debug ) {
1262
+ ts.benchmark( 'Completed filter widget search', time );
1082
1263
  }
1083
- if (c.debug) {
1084
- ts.benchmark("Completed filter widget search", time);
1264
+ if ( wo.filter_initialized ) {
1265
+ c.$table.trigger( 'filterEnd', c );
1085
1266
  }
1086
- if (wo.filter_initialized) { c.$table.trigger('filterEnd', c ); }
1087
- setTimeout(function(){
1088
- c.$table.trigger('applyWidgets'); // make sure zebra widget is applied
1089
- }, 0);
1267
+ setTimeout( function() {
1268
+ c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied
1269
+ }, 0 );
1090
1270
  },
1091
- getOptionSource: function(table, column, onlyAvail) {
1092
- table = $(table)[0];
1271
+ getOptionSource: function( table, column, onlyAvail ) {
1272
+ table = $( table )[0];
1093
1273
  var cts, indx, len,
1094
1274
  c = table.config,
1095
1275
  wo = c.widgetOptions,
1096
1276
  parsed = [],
1097
1277
  arry = false,
1098
1278
  source = wo.filter_selectSource,
1099
- last = c.$table.data('lastSearch') || [],
1100
- fxn = $.isFunction(source) ? true : ts.getColumnData( table, source, column );
1279
+ last = c.$table.data( 'lastSearch' ) || [],
1280
+ fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column );
1101
1281
 
1102
- if (onlyAvail && last[column] !== '') {
1282
+ if ( onlyAvail && last[column] !== '' ) {
1103
1283
  onlyAvail = false;
1104
1284
  }
1105
1285
 
1106
1286
  // filter select source option
1107
- if (fxn === true) {
1287
+ if ( fxn === true ) {
1108
1288
  // OVERALL source
1109
- arry = source(table, column, onlyAvail);
1110
- } else if ( fxn instanceof $ || ($.type(fxn) === 'string' && fxn.indexOf('</option>') >= 0) ) {
1289
+ arry = source( table, column, onlyAvail );
1290
+ } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '</option>' ) >= 0 ) ) {
1111
1291
  // selectSource is a jQuery object or string of options
1112
1292
  return fxn;
1113
- } else if ($.isArray(fxn)) {
1293
+ } else if ( $.isArray( fxn ) ) {
1114
1294
  arry = fxn;
1115
- } else if ($.type(source) === 'object' && fxn) {
1295
+ } else if ( $.type( source ) === 'object' && fxn ) {
1116
1296
  // custom select source function for a SPECIFIC COLUMN
1117
- arry = fxn(table, column, onlyAvail);
1297
+ arry = fxn( table, column, onlyAvail );
1118
1298
  }
1119
- if (arry === false) {
1299
+ if ( arry === false ) {
1120
1300
  // fall back to original method
1121
- arry = ts.filter.getOptions(table, column, onlyAvail);
1301
+ arry = ts.filter.getOptions( table, column, onlyAvail );
1122
1302
  }
1123
1303
 
1124
1304
  // get unique elements and sort the list
1125
- // if $.tablesorter.sortText exists (not in the original tablesorter),
1305
+ // if $.tablesorter.sortText exists ( not in the original tablesorter ),
1126
1306
  // then natural sort the list otherwise use a basic sort
1127
- arry = $.grep(arry, function(value, indx) {
1128
- return $.inArray(value, arry) === indx;
1307
+ arry = $.grep( arry, function( value, indx ) {
1308
+ return $.inArray( value, arry ) === indx;
1129
1309
  });
1130
1310
 
1131
- if (c.$headerIndexed[column].hasClass('filter-select-nosort')) {
1311
+ if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) {
1132
1312
  // unsorted select options
1133
1313
  return arry;
1134
1314
  } else {
1135
1315
  len = arry.length;
1136
1316
  // parse select option values
1137
- for (indx = 0; indx < len; indx++) {
1317
+ for ( indx = 0; indx < len; indx++ ) {
1138
1318
  // parse array data using set column parser; this DOES NOT pass the original
1139
1319
  // table cell to the parser format function
1140
- parsed.push({ t : arry[indx], p : c.parsers && c.parsers[column].format( arry[indx], table, [], column ) });
1320
+ parsed.push({
1321
+ t : arry[ indx ],
1322
+ p : c.parsers && c.parsers[ column ].format( arry[ indx ], table, [], column )
1323
+ });
1141
1324
  }
1142
1325
 
1143
1326
  // sort parsed select options
1144
1327
  cts = c.textSorter || '';
1145
- parsed.sort(function(a, b){
1328
+ parsed.sort( function( a, b ) {
1146
1329
  // sortNatural breaks if you don't pass it strings
1147
- var x = a.p.toString(), y = b.p.toString();
1148
- if ($.isFunction(cts)) {
1330
+ var x = a.p.toString(),
1331
+ y = b.p.toString();
1332
+ if ( $.isFunction( cts ) ) {
1149
1333
  // custom OVERALL text sorter
1150
- return cts(x, y, true, column, table);
1151
- } else if (typeof(cts) === 'object' && cts.hasOwnProperty(column)) {
1334
+ return cts( x, y, true, column, table );
1335
+ } else if ( typeof( cts ) === 'object' && cts.hasOwnProperty( column ) ) {
1152
1336
  // custom text sorter for a SPECIFIC COLUMN
1153
- return cts[column](x, y, true, column, table);
1154
- } else if (ts.sortNatural) {
1337
+ return cts[column]( x, y, true, column, table );
1338
+ } else if ( ts.sortNatural ) {
1155
1339
  // fall back to natural sort
1156
- return ts.sortNatural(x, y);
1340
+ return ts.sortNatural( x, y );
1157
1341
  }
1158
1342
  // using an older version! do a basic sort
1159
1343
  return true;
@@ -1161,184 +1345,221 @@ ts.filter = {
1161
1345
  // rebuild arry from sorted parsed data
1162
1346
  arry = [];
1163
1347
  len = parsed.length;
1164
- for (indx = 0; indx < len; indx++) {
1348
+ for ( indx = 0; indx < len; indx++ ) {
1165
1349
  arry.push( parsed[indx].t );
1166
1350
  }
1167
1351
  return arry;
1168
1352
  }
1169
1353
  },
1170
- getOptions: function(table, column, onlyAvail) {
1171
- table = $(table)[0];
1172
- var rowIndex, tbodyIndex, len, row, cache, cell,
1354
+ getOptions: function( table, column, onlyAvail ) {
1355
+ table = $( table )[0];
1356
+ var rowIndex, tbodyIndex, len, row, cache,
1173
1357
  c = table.config,
1174
1358
  wo = c.widgetOptions,
1175
1359
  arry = [];
1176
- for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
1360
+ for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
1177
1361
  cache = c.cache[tbodyIndex];
1178
1362
  len = c.cache[tbodyIndex].normalized.length;
1179
1363
  // loop through the rows
1180
- for (rowIndex = 0; rowIndex < len; rowIndex++) {
1181
- // get cached row from cache.row (old) or row data object (new; last item in normalized array)
1182
- row = cache.row ? cache.row[rowIndex] : cache.normalized[rowIndex][c.columns].$row[0];
1364
+ for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
1365
+ // get cached row from cache.row ( old ) or row data object
1366
+ // ( new; last item in normalized array )
1367
+ row = cache.row ?
1368
+ cache.row[ rowIndex ] :
1369
+ cache.normalized[ rowIndex ][ c.columns ].$row[0];
1183
1370
  // check if has class filtered
1184
- if (onlyAvail && row.className.match(wo.filter_filteredRow)) { continue; }
1371
+ if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) {
1372
+ continue;
1373
+ }
1185
1374
  // get non-normalized cell content
1186
- if (wo.filter_useParsedData || c.parsers[column].parsed || c.$headerIndexed[column].hasClass('filter-parsed')) {
1187
- arry.push( '' + cache.normalized[rowIndex][column] );
1375
+ if ( wo.filter_useParsedData ||
1376
+ c.parsers[column].parsed ||
1377
+ c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) {
1378
+ arry.push( '' + cache.normalized[ rowIndex ][ column ] );
1188
1379
  } else {
1189
- cell = row.cells[column];
1190
- if (cell) {
1191
- arry.push( $.trim( cell.getAttribute( c.textAttribute ) || cell.textContent || $(cell).text() ) );
1192
- }
1380
+ // get raw cached data instead of content directly from the cells
1381
+ arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] );
1193
1382
  }
1194
1383
  }
1195
1384
  }
1196
1385
  return arry;
1197
1386
  },
1198
- buildSelect: function(table, column, arry, updating, onlyAvail) {
1199
- table = $(table)[0];
1200
- column = parseInt(column, 10);
1201
- if (!table.config.cache || $.isEmptyObject(table.config.cache)) { return; }
1387
+ buildSelect: function( table, column, arry, updating, onlyAvail ) {
1388
+ table = $( table )[0];
1389
+ column = parseInt( column, 10 );
1390
+ if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) {
1391
+ return;
1392
+ }
1202
1393
  var indx, val, txt, t, $filters, $filter,
1203
1394
  c = table.config,
1204
1395
  wo = c.widgetOptions,
1205
- node = c.$headerIndexed[column],
1206
- // t.data('placeholder') won't work in jQuery older than 1.4.3
1207
- options = '<option value="">' + ( node.data('placeholder') || node.attr('data-placeholder') || wo.filter_placeholder.select || '' ) + '</option>',
1396
+ node = c.$headerIndexed[ column ],
1397
+ // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3
1398
+ options = '<option value="">' +
1399
+ ( node.data( 'placeholder' ) ||
1400
+ node.attr( 'data-placeholder' ) ||
1401
+ wo.filter_placeholder.select || ''
1402
+ ) + '</option>',
1208
1403
  // Get curent filter value
1209
- currentValue = c.$table.find('thead').find('select.' + tscss.filter + '[data-column="' + column + '"]').val();
1210
- // nothing included in arry (external source), so get the options from filter_selectSource or column data
1211
- if (typeof arry === 'undefined' || arry === '') {
1212
- arry = ts.filter.getOptionSource(table, column, onlyAvail);
1404
+ currentValue = c.$table
1405
+ .find( 'thead' )
1406
+ .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' )
1407
+ .val();
1408
+ // nothing included in arry ( external source ), so get the options from
1409
+ // filter_selectSource or column data
1410
+ if ( typeof arry === 'undefined' || arry === '' ) {
1411
+ arry = ts.filter.getOptionSource( table, column, onlyAvail );
1213
1412
  }
1214
1413
 
1215
- if ($.isArray(arry)) {
1414
+ if ( $.isArray( arry ) ) {
1216
1415
  // build option list
1217
- for (indx = 0; indx < arry.length; indx++) {
1218
- txt = arry[indx] = ('' + arry[indx]).replace(/\"/g, "&quot;");
1416
+ for ( indx = 0; indx < arry.length; indx++ ) {
1417
+ txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '&quot;' );
1219
1418
  val = txt;
1220
1419
  // allow including a symbol in the selectSource array
1221
- // "a-z|A through Z" so that "a-z" becomes the option value
1222
- // and "A through Z" becomes the option text
1223
- if (txt.indexOf(wo.filter_selectSourceSeparator) >= 0) {
1224
- t = txt.split(wo.filter_selectSourceSeparator);
1420
+ // 'a-z|A through Z' so that 'a-z' becomes the option value
1421
+ // and 'A through Z' becomes the option text
1422
+ if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
1423
+ t = txt.split( wo.filter_selectSourceSeparator );
1225
1424
  val = t[0];
1226
1425
  txt = t[1];
1227
1426
  }
1228
- // replace quotes - fixes #242 & ignore empty strings - see http://stackoverflow.com/q/14990971/145346
1229
- options += arry[indx] !== '' ? '<option ' + (val === txt ? '' : 'data-function-name="' + arry[indx] + '" ') + 'value="' + val + '">' + txt + '</option>' : '';
1427
+ // replace quotes - fixes #242 & ignore empty strings
1428
+ // see http://stackoverflow.com/q/14990971/145346
1429
+ options += arry[indx] !== '' ?
1430
+ '<option ' +
1431
+ ( val === txt ? '' : 'data-function-name="' + arry[indx] + '" ' ) +
1432
+ 'value="' + val + '">' + txt +
1433
+ '</option>' : '';
1230
1434
  }
1231
1435
  // clear arry so it doesn't get appended twice
1232
1436
  arry = [];
1233
1437
  }
1234
1438
 
1235
- // update all selects in the same column (clone thead in sticky headers & any external selects) - fixes 473
1236
- $filters = ( c.$filters ? c.$filters : c.$table.children('thead') ).find('.' + tscss.filter);
1237
- if (wo.filter_$externalFilters) {
1238
- $filters = $filters && $filters.length ? $filters.add(wo.filter_$externalFilters) : wo.filter_$externalFilters;
1439
+ // update all selects in the same column ( clone thead in sticky headers &
1440
+ // any external selects ) - fixes 473
1441
+ $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) )
1442
+ .find( '.' + tscss.filter );
1443
+ if ( wo.filter_$externalFilters ) {
1444
+ $filters = $filters && $filters.length ?
1445
+ $filters.add( wo.filter_$externalFilters ) :
1446
+ wo.filter_$externalFilters;
1239
1447
  }
1240
- $filter = $filters.filter('select[data-column="' + column + '"]');
1448
+ $filter = $filters.filter( 'select[data-column="' + column + '"]' );
1241
1449
 
1242
1450
  // make sure there is a select there!
1243
- if ($filter.length) {
1244
- $filter[ updating ? 'html' : 'append' ](options);
1245
- if (!$.isArray(arry)) {
1451
+ if ( $filter.length ) {
1452
+ $filter[ updating ? 'html' : 'append' ]( options );
1453
+ if ( !$.isArray( arry ) ) {
1246
1454
  // append options if arry is provided externally as a string or jQuery object
1247
- // options (default value) was already added
1248
- $filter.append(arry).val(currentValue);
1455
+ // options ( default value ) was already added
1456
+ $filter.append( arry ).val( currentValue );
1249
1457
  }
1250
- $filter.val(currentValue);
1458
+ $filter.val( currentValue );
1251
1459
  }
1252
1460
  },
1253
- buildDefault: function(table, updating) {
1461
+ buildDefault: function( table, updating ) {
1254
1462
  var columnIndex, $header, noSelect,
1255
1463
  c = table.config,
1256
1464
  wo = c.widgetOptions,
1257
1465
  columns = c.columns;
1258
1466
  // build default select dropdown
1259
- for (columnIndex = 0; columnIndex < columns; columnIndex++) {
1467
+ for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) {
1260
1468
  $header = c.$headerIndexed[columnIndex];
1261
- noSelect = !($header.hasClass('filter-false') || $header.hasClass('parser-false'));
1469
+ noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
1262
1470
  // look for the filter-select class; build/update it if found
1263
- if (($header.hasClass('filter-select') || ts.getColumnData( table, wo.filter_functions, columnIndex ) === true) && noSelect) {
1264
- ts.filter.buildSelect(table, columnIndex, '', updating, $header.hasClass(wo.filter_onlyAvail));
1471
+ if ( ( $header.hasClass( 'filter-select' ) ||
1472
+ ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) {
1473
+ ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) );
1265
1474
  }
1266
1475
  }
1267
1476
  }
1268
1477
  };
1269
1478
 
1270
- ts.getFilters = function(table, getRaw, setFilters, skipFirst) {
1479
+ ts.getFilters = function( table, getRaw, setFilters, skipFirst ) {
1271
1480
  var i, $filters, $column, cols,
1272
1481
  filters = false,
1273
- c = table ? $(table)[0].config : '',
1482
+ c = table ? $( table )[0].config : '',
1274
1483
  wo = c ? c.widgetOptions : '';
1275
- if (getRaw !== true && wo && !wo.filter_columnFilters) {
1276
- return $(table).data('lastSearch');
1484
+ if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) ||
1485
+ // setFilters called, but last search is exactly the same as the current
1486
+ // fixes issue #733 & #903 where calling update causes the input values to reset
1487
+ ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) {
1488
+ return $( table ).data( 'lastSearch' );
1277
1489
  }
1278
- if (c) {
1279
- if (c.$filters) {
1280
- $filters = c.$filters.find('.' + tscss.filter);
1490
+ if ( c ) {
1491
+ if ( c.$filters ) {
1492
+ $filters = c.$filters.find( '.' + tscss.filter );
1281
1493
  }
1282
- if (wo.filter_$externalFilters) {
1283
- $filters = $filters && $filters.length ? $filters.add(wo.filter_$externalFilters) : wo.filter_$externalFilters;
1494
+ if ( wo.filter_$externalFilters ) {
1495
+ $filters = $filters && $filters.length ?
1496
+ $filters.add( wo.filter_$externalFilters ) :
1497
+ wo.filter_$externalFilters;
1284
1498
  }
1285
- if ($filters && $filters.length) {
1499
+ if ( $filters && $filters.length ) {
1286
1500
  filters = setFilters || [];
1287
- for (i = 0; i < c.columns + 1; i++) {
1501
+ for ( i = 0; i < c.columns + 1; i++ ) {
1288
1502
  cols = ( i === c.columns ?
1289
- // "all" columns can now include a range or set of columms (data-column="0-2,4,6-7")
1503
+ // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' )
1290
1504
  wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector :
1291
1505
  '[data-column="' + i + '"]' );
1292
- $column = $filters.filter(cols);
1293
- if ($column.length) {
1506
+ $column = $filters.filter( cols );
1507
+ if ( $column.length ) {
1294
1508
  // move the latest search to the first slot in the array
1295
1509
  $column = ts.filter.getLatestSearch( $column );
1296
- if ($.isArray(setFilters)) {
1297
- // skip first (latest input) to maintain cursor position while typing
1298
- if (skipFirst) { $column.slice(1); }
1299
- if (i === c.columns) {
1300
- // prevent data-column="all" from filling data-column="0,1" (etc)
1301
- cols = $column.filter(wo.filter_anyColumnSelector);
1510
+ if ( $.isArray( setFilters ) ) {
1511
+ // skip first ( latest input ) to maintain cursor position while typing
1512
+ if ( skipFirst ) {
1513
+ $column.slice( 1 );
1514
+ }
1515
+ if ( i === c.columns ) {
1516
+ // prevent data-column='all' from filling data-column='0,1' ( etc )
1517
+ cols = $column.filter( wo.filter_anyColumnSelector );
1302
1518
  $column = cols.length ? cols : $column;
1303
1519
  }
1304
1520
  $column
1305
- .val( setFilters[i] )
1306
- .trigger('change.tsfilter');
1521
+ .val( setFilters[ i ] )
1522
+ .trigger( 'change.tsfilter' );
1307
1523
  } else {
1308
1524
  filters[i] = $column.val() || '';
1309
1525
  // don't change the first... it will move the cursor
1310
- if (i === c.columns) {
1311
- // don't update range columns from "all" setting
1312
- $column.slice(1).filter('[data-column*="' + $column.attr('data-column') + '"]').val( filters[i] );
1526
+ if ( i === c.columns ) {
1527
+ // don't update range columns from 'all' setting
1528
+ $column
1529
+ .slice( 1 )
1530
+ .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' )
1531
+ .val( filters[ i ] );
1313
1532
  } else {
1314
- $column.slice(1).val( filters[i] );
1533
+ $column
1534
+ .slice( 1 )
1535
+ .val( filters[ i ] );
1315
1536
  }
1316
1537
  }
1317
1538
  // save any match input dynamically
1318
- if (i === c.columns && $column.length) {
1539
+ if ( i === c.columns && $column.length ) {
1319
1540
  wo.filter_$anyMatch = $column;
1320
1541
  }
1321
1542
  }
1322
1543
  }
1323
1544
  }
1324
1545
  }
1325
- if (filters.length === 0) {
1546
+ if ( filters.length === 0 ) {
1326
1547
  filters = false;
1327
1548
  }
1328
1549
  return filters;
1329
1550
  };
1330
1551
 
1331
- ts.setFilters = function(table, filter, apply, skipFirst) {
1332
- var c = table ? $(table)[0].config : '',
1333
- valid = ts.getFilters(table, true, filter, skipFirst);
1334
- if (c && apply) {
1552
+ ts.setFilters = function( table, filter, apply, skipFirst ) {
1553
+ var c = table ? $( table )[0].config : '',
1554
+ valid = ts.getFilters( table, true, filter, skipFirst );
1555
+ if ( c && apply ) {
1335
1556
  // ensure new set filters are applied, even if the search is the same
1336
1557
  c.lastCombinedFilter = null;
1337
1558
  c.lastSearch = [];
1338
- ts.filter.searching(c.table, filter, skipFirst);
1339
- c.$table.trigger('filterFomatterUpdate');
1559
+ ts.filter.searching( c.table, filter, skipFirst );
1560
+ c.$table.trigger( 'filterFomatterUpdate' );
1340
1561
  }
1341
1562
  return !!valid;
1342
1563
  };
1343
1564
 
1344
- })(jQuery);
1565
+ })( jQuery );