jquery-tablesorter 1.16.5 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 );