jquery-tablesorter 1.19.1 → 1.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
- /*! Widget: filter - updated 11/4/2015 (v2.24.3) *//*
1
+ /*! Widget: filter - updated 11/10/2015 (v2.24.4) *//*
2
2
  * Requires tablesorter v2.8+ and jQuery 1.7+
3
3
  * by Rob Garrison
4
4
  */
5
5
  ;( function ( $ ) {
6
6
  'use strict';
7
- var tsf,
7
+ var tsf, tsfRegex,
8
8
  ts = $.tablesorter || {},
9
9
  tscss = ts.css;
10
10
 
@@ -110,20 +110,21 @@
110
110
  // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
111
111
  types: {
112
112
  or : function( c, data, vars ) {
113
- if ( tsf.regex.orTest.test( data.iFilter ) || tsf.regex.orSplit.test( data.filter ) ) {
113
+ // look for "|", but not if it is inside of a regular expression
114
+ if ( ( tsfRegex.orTest.test( data.iFilter ) || tsfRegex.orSplit.test( data.filter ) ) &&
115
+ // this test for regex has potential to slow down the overall search
116
+ !tsfRegex.regex.test( data.filter ) ) {
114
117
  var indx, filterMatched, query, regex,
115
118
  // duplicate data but split filter
116
119
  data2 = $.extend( {}, data ),
117
- index = data.index,
118
- parsed = data.parsed[ index ],
119
- filter = data.filter.split( tsf.regex.orSplit ),
120
- iFilter = data.iFilter.split( tsf.regex.orSplit ),
120
+ filter = data.filter.split( tsfRegex.orSplit ),
121
+ iFilter = data.iFilter.split( tsfRegex.orSplit ),
121
122
  len = filter.length;
122
123
  for ( indx = 0; indx < len; indx++ ) {
123
124
  data2.nestedFilters = true;
124
- data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], index, parsed ) || '' );
125
- data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
126
- query = '(' + ( tsf.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')';
125
+ data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], data ) || '' );
126
+ data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], data ) || '' );
127
+ query = '(' + ( tsf.parseFilter( c, data2.filter, data ) || '' ) + ')';
127
128
  try {
128
129
  // use try/catch, because query may not be a valid regex if "|" is contained within a partial regex search,
129
130
  // e.g "/(Alex|Aar" -> Uncaught SyntaxError: Invalid regular expression: /(/(Alex)/: Unterminated group
@@ -145,22 +146,20 @@
145
146
  },
146
147
  // Look for an AND or && operator ( logical and )
147
148
  and : function( c, data, vars ) {
148
- if ( tsf.regex.andTest.test( data.filter ) ) {
149
+ if ( tsfRegex.andTest.test( data.filter ) ) {
149
150
  var indx, filterMatched, result, query, regex,
150
151
  // duplicate data but split filter
151
152
  data2 = $.extend( {}, data ),
152
- index = data.index,
153
- parsed = data.parsed[ index ],
154
- filter = data.filter.split( tsf.regex.andSplit ),
155
- iFilter = data.iFilter.split( tsf.regex.andSplit ),
153
+ filter = data.filter.split( tsfRegex.andSplit ),
154
+ iFilter = data.iFilter.split( tsfRegex.andSplit ),
156
155
  len = filter.length;
157
156
  for ( indx = 0; indx < len; indx++ ) {
158
157
  data2.nestedFilters = true;
159
- data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], index, parsed ) || '' );
160
- data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
161
- query = ( '(' + ( tsf.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' )
158
+ data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], data ) || '' );
159
+ data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], data ) || '' );
160
+ query = ( '(' + ( tsf.parseFilter( c, data2.filter, data ) || '' ) + ')' )
162
161
  // replace wild cards since /(a*)/i will match anything
163
- .replace( tsf.regex.wild01, '\\S{1}' ).replace( tsf.regex.wild0More, '\\S*' );
162
+ .replace( tsfRegex.wild01, '\\S{1}' ).replace( tsfRegex.wild0More, '\\S*' );
164
163
  try {
165
164
  // use try/catch just in case RegExp is invalid
166
165
  regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
@@ -182,10 +181,10 @@
182
181
  },
183
182
  // Look for regex
184
183
  regex: function( c, data ) {
185
- if ( tsf.regex.regex.test( data.filter ) ) {
184
+ if ( tsfRegex.regex.test( data.filter ) ) {
186
185
  var matches,
187
186
  // cache regex per column for optimal speed
188
- regex = data.filter_regexCache[ data.index ] || tsf.regex.regex.exec( data.filter ),
187
+ regex = data.filter_regexCache[ data.index ] || tsfRegex.regex.exec( data.filter ),
189
188
  isRegex = regex instanceof RegExp;
190
189
  try {
191
190
  if ( !isRegex ) {
@@ -204,18 +203,17 @@
204
203
  // Look for operators >, >=, < or <=
205
204
  operators: function( c, data ) {
206
205
  // ignore empty strings... because '' < 10 is true
207
- if ( tsf.regex.operTest.test( data.iFilter ) && data.iExact !== '' ) {
206
+ if ( tsfRegex.operTest.test( data.iFilter ) && data.iExact !== '' ) {
208
207
  var cachedValue, result, txt,
209
208
  table = c.table,
210
- index = data.index,
211
- parsed = data.parsed[index],
212
- query = ts.formatFloat( data.iFilter.replace( tsf.regex.operators, '' ), table ),
213
- parser = c.parsers[index],
209
+ parsed = data.parsed[ data.index ],
210
+ query = ts.formatFloat( data.iFilter.replace( tsfRegex.operators, '' ), table ),
211
+ parser = c.parsers[ data.index ],
214
212
  savedSearch = query;
215
213
  // parse filter value in case we're comparing numbers ( dates )
216
214
  if ( parsed || parser.type === 'numeric' ) {
217
- txt = $.trim( '' + data.iFilter.replace( tsf.regex.operators, '' ) );
218
- result = tsf.parseFilter( c, txt, index, true );
215
+ txt = $.trim( '' + data.iFilter.replace( tsfRegex.operators, '' ) );
216
+ result = tsf.parseFilter( c, txt, data, true );
219
217
  query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query;
220
218
  }
221
219
  // iExact may be numeric - see issue #149;
@@ -227,10 +225,10 @@
227
225
  txt = isNaN( data.iExact ) ? data.iExact.replace( ts.regex.nondigit, '' ) : data.iExact;
228
226
  cachedValue = ts.formatFloat( txt, table );
229
227
  }
230
- if ( tsf.regex.gtTest.test( data.iFilter ) ) {
231
- result = tsf.regex.gteTest.test( data.iFilter ) ? cachedValue >= query : cachedValue > query;
232
- } else if ( tsf.regex.ltTest.test( data.iFilter ) ) {
233
- result = tsf.regex.lteTest.test( data.iFilter ) ? cachedValue <= query : cachedValue < query;
228
+ if ( tsfRegex.gtTest.test( data.iFilter ) ) {
229
+ result = tsfRegex.gteTest.test( data.iFilter ) ? cachedValue >= query : cachedValue > query;
230
+ } else if ( tsfRegex.ltTest.test( data.iFilter ) ) {
231
+ result = tsfRegex.lteTest.test( data.iFilter ) ? cachedValue <= query : cachedValue < query;
234
232
  }
235
233
  // keep showing all rows if nothing follows the operator
236
234
  if ( !result && savedSearch === '' ) {
@@ -242,13 +240,13 @@
242
240
  },
243
241
  // Look for a not match
244
242
  notMatch: function( c, data ) {
245
- if ( tsf.regex.notTest.test( data.iFilter ) ) {
243
+ if ( tsfRegex.notTest.test( data.iFilter ) ) {
246
244
  var indx,
247
245
  txt = data.iFilter.replace( '!', '' ),
248
- filter = tsf.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
249
- if ( tsf.regex.exact.test( filter ) ) {
246
+ filter = tsf.parseFilter( c, txt, data ) || '';
247
+ if ( tsfRegex.exact.test( filter ) ) {
250
248
  // look for exact not matches - see #628
251
- filter = filter.replace( tsf.regex.exact, '' );
249
+ filter = filter.replace( tsfRegex.exact, '' );
252
250
  return filter === '' ? true : $.trim( filter ) !== data.iExact;
253
251
  } else {
254
252
  indx = data.iExact.search( $.trim( filter ) );
@@ -260,29 +258,29 @@
260
258
  // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
261
259
  exact: function( c, data ) {
262
260
  /*jshint eqeqeq:false */
263
- if ( tsf.regex.exact.test( data.iFilter ) ) {
264
- var txt = data.iFilter.replace( tsf.regex.exact, '' ),
265
- filter = tsf.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
261
+ if ( tsfRegex.exact.test( data.iFilter ) ) {
262
+ var txt = data.iFilter.replace( tsfRegex.exact, '' ),
263
+ filter = tsf.parseFilter( c, txt, data ) || '';
266
264
  return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact;
267
265
  }
268
266
  return null;
269
267
  },
270
268
  // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
271
269
  range : function( c, data ) {
272
- if ( tsf.regex.toTest.test( data.iFilter ) ) {
270
+ if ( tsfRegex.toTest.test( data.iFilter ) ) {
273
271
  var result, tmp, range1, range2,
274
272
  table = c.table,
275
273
  index = data.index,
276
274
  parsed = data.parsed[index],
277
275
  // make sure the dash is for a range and not indicating a negative number
278
- query = data.iFilter.split( tsf.regex.toSplit );
276
+ query = data.iFilter.split( tsfRegex.toSplit );
279
277
 
280
278
  tmp = query[0].replace( ts.regex.nondigit, '' ) || '';
281
- range1 = ts.formatFloat( tsf.parseFilter( c, tmp, index, parsed ), table );
279
+ range1 = ts.formatFloat( tsf.parseFilter( c, tmp, data ), table );
282
280
  tmp = query[1].replace( ts.regex.nondigit, '' ) || '';
283
- range2 = ts.formatFloat( tsf.parseFilter( c, tmp, index, parsed ), table );
281
+ range2 = ts.formatFloat( tsf.parseFilter( c, tmp, data ), table );
284
282
  // parse filter value in case we're comparing numbers ( dates )
285
- if ( parsed || c.parsers[index].type === 'numeric' ) {
283
+ if ( parsed || c.parsers[ index ].type === 'numeric' ) {
286
284
  result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index );
287
285
  range1 = ( result !== '' && !isNaN( result ) ) ? result : range1;
288
286
  result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index );
@@ -303,18 +301,16 @@
303
301
  },
304
302
  // Look for wild card: ? = single, * = multiple, or | = logical OR
305
303
  wild : function( c, data ) {
306
- if ( tsf.regex.wildOrTest.test( data.iFilter ) ) {
307
- var index = data.index,
308
- parsed = data.parsed[ index ],
309
- query = '' + ( tsf.parseFilter( c, data.iFilter, index, parsed ) || '' );
304
+ if ( tsfRegex.wildOrTest.test( data.iFilter ) ) {
305
+ var query = '' + ( tsf.parseFilter( c, data.iFilter, data ) || '' );
310
306
  // look for an exact match with the 'or' unless the 'filter-match' class is found
311
- if ( !tsf.regex.wildTest.test( query ) && data.nestedFilters ) {
307
+ if ( !tsfRegex.wildTest.test( query ) && data.nestedFilters ) {
312
308
  query = data.isMatch ? query : '^(' + query + ')$';
313
309
  }
314
310
  // parsing the filter may not work properly when using wildcards =/
315
311
  try {
316
312
  return new RegExp(
317
- query.replace( tsf.regex.wild01, '\\S{1}' ).replace( tsf.regex.wild0More, '\\S*' ),
313
+ query.replace( tsfRegex.wild01, '\\S{1}' ).replace( tsfRegex.wild0More, '\\S*' ),
318
314
  c.widgetOptions.filter_ignoreCase ? 'i' : ''
319
315
  )
320
316
  .test( data.exact );
@@ -326,21 +322,18 @@
326
322
  },
327
323
  // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license )
328
324
  fuzzy: function( c, data ) {
329
- if ( tsf.regex.fuzzyTest.test( data.iFilter ) ) {
325
+ if ( tsfRegex.fuzzyTest.test( data.iFilter ) ) {
330
326
  var indx,
331
327
  patternIndx = 0,
332
328
  len = data.iExact.length,
333
329
  txt = data.iFilter.slice( 1 ),
334
- pattern = tsf.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
330
+ pattern = tsf.parseFilter( c, txt, data ) || '';
335
331
  for ( indx = 0; indx < len; indx++ ) {
336
332
  if ( data.iExact[ indx ] === pattern[ patternIndx ] ) {
337
333
  patternIndx += 1;
338
334
  }
339
335
  }
340
- if ( patternIndx === pattern.length ) {
341
- return true;
342
- }
343
- return false;
336
+ return patternIndx === pattern.length;
344
337
  }
345
338
  return null;
346
339
  }
@@ -353,8 +346,7 @@
353
346
  and : 'and'
354
347
  }, ts.language );
355
348
 
356
- var options, string, txt, $header, column, filters, val, fxn, noSelect,
357
- regex = tsf.regex;
349
+ var options, string, txt, $header, column, filters, val, fxn, noSelect;
358
350
  c.$table.addClass( 'hasFilters' );
359
351
 
360
352
  // define timers so using clearTimeout won't cause an undefined error
@@ -365,8 +357,8 @@
365
357
  wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]';
366
358
  wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]';
367
359
 
368
- val = '\\{' + tsf.regex.query + '\\}';
369
- $.extend( regex, {
360
+ val = '\\{' + tsfRegex.query + '\\}';
361
+ $.extend( tsfRegex, {
370
362
  child : new RegExp( c.cssChildRow ),
371
363
  filtered : new RegExp( wo.filter_filteredRow ),
372
364
  alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ),
@@ -644,8 +636,10 @@
644
636
  c.$table.data( 'lastSearch', filters );
645
637
  return filters;
646
638
  },
647
- parseFilter: function( c, filter, column, parsed ) {
648
- return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter;
639
+ parseFilter: function( c, filter, data, parsed ) {
640
+ return parsed || data.parsed[ data.index ] ?
641
+ c.parsers[ data.index ].format( filter, c.table, [], data.index ) :
642
+ filter;
649
643
  },
650
644
  buildRow: function( table, c, wo ) {
651
645
  var $filter, col, column, $header, makeSelect, disabled, name, ffxn, tmp,
@@ -838,8 +832,12 @@
838
832
  c.lastCombinedFilter = null;
839
833
  c.lastSearch = [];
840
834
  }
841
- // convert filters to strings (maybe not the best method)- see #1070
842
- filters = filters.join( '\u0000' ).split( '\u0000' );
835
+ // convert filters to strings - see #1070
836
+ filters = Array.prototype.map ?
837
+ filters.map( String ) :
838
+ // for IE8 & older browsers - maybe not the best method
839
+ filters.join( '\u0000' ).split( '\u0000' );
840
+
843
841
  if ( wo.filter_initialized ) {
844
842
  c.$table.trigger( 'filterStart', [ filters ] );
845
843
  }
@@ -893,8 +891,8 @@
893
891
  },
894
892
  defaultFilter: function( filter, mask ) {
895
893
  if ( filter === '' ) { return filter; }
896
- var regex = tsf.regex.iQuery,
897
- maskLen = mask.match( tsf.regex.igQuery ).length,
894
+ var regex = tsfRegex.iQuery,
895
+ maskLen = mask.match( tsfRegex.igQuery ).length,
898
896
  query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ],
899
897
  len = query.length - 1,
900
898
  indx = 0,
@@ -991,9 +989,8 @@
991
989
  return filterMatched;
992
990
  },
993
991
  processRow: function( c, data, vars ) {
994
- var hasSelect, result, val, filterMatched,
992
+ var result, filterMatched,
995
993
  fxn, ffxn, txt,
996
- regex = tsf.regex,
997
994
  wo = c.widgetOptions,
998
995
  showRow = true,
999
996
 
@@ -1072,7 +1069,7 @@
1072
1069
  result = data.rawArray[ columnIndex ] || '';
1073
1070
  data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405
1074
1071
  }
1075
- data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ?
1072
+ data.iExact = !tsfRegex.type.test( typeof data.exact ) && wo.filter_ignoreCase ?
1076
1073
  data.exact.toLowerCase() : data.exact;
1077
1074
 
1078
1075
  data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' );
@@ -1090,21 +1087,13 @@
1090
1087
  data.filter = ts.replaceAccents( data.filter );
1091
1088
  }
1092
1089
 
1093
- val = true;
1094
- if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) {
1095
- data.filter = tsf.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] );
1096
- // val is used to indicate that a filter select is using a default filter;
1097
- // so we override the exact & partial matches
1098
- val = false;
1099
- }
1100
1090
  // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ),
1101
1091
  // data.filter = case sensitive
1102
1092
  data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter;
1103
1093
  fxn = vars.functions[ columnIndex ];
1104
- hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' );
1105
1094
  filterMatched = null;
1106
- if ( fxn || ( hasSelect && val ) ) {
1107
- if ( fxn === true || hasSelect ) {
1095
+ if ( fxn ) {
1096
+ if ( fxn === true ) {
1108
1097
  // default selector uses exact match unless 'filter-match' class is found
1109
1098
  filterMatched = data.isMatch ?
1110
1099
  // data.iExact may be a number
@@ -1130,7 +1119,7 @@
1130
1119
  // Look for match, and add child row data for matching
1131
1120
  } else {
1132
1121
  txt = ( data.iExact + data.childRowText )
1133
- .indexOf( tsf.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) );
1122
+ .indexOf( tsf.parseFilter( c, data.iFilter, data ) );
1134
1123
  result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) );
1135
1124
  }
1136
1125
  } else {
@@ -1150,7 +1139,6 @@
1150
1139
  isChild, childRow, lastSearch, showRow, showParent, time, val, indx,
1151
1140
  notFiltered, searchFiltered, query, injected, res, id, txt,
1152
1141
  storedFilters = $.extend( [], filters ),
1153
- regex = tsf.regex,
1154
1142
  c = table.config,
1155
1143
  wo = c.widgetOptions,
1156
1144
  // data object passed to filters; anyMatch is a flag for the filters
@@ -1232,7 +1220,7 @@
1232
1220
  );
1233
1221
  if ( wo.filter_columnAnyMatch ) {
1234
1222
  // specific columns search
1235
- query = data.anyMatchFilter.split( regex.andSplit );
1223
+ query = data.anyMatchFilter.split( tsfRegex.andSplit );
1236
1224
  injected = false;
1237
1225
  for ( indx = 0; indx < query.length; indx++ ) {
1238
1226
  res = query[ indx ].split( ':' );
@@ -1267,12 +1255,12 @@
1267
1255
  // there are no changes from beginning of filter
1268
1256
  val.indexOf( lastSearch[indx] || '' ) === 0 &&
1269
1257
  // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string
1270
- !regex.alreadyFiltered.test( val ) &&
1258
+ !tsfRegex.alreadyFiltered.test( val ) &&
1271
1259
  // if we are not doing exact matches, using '|' ( logical or ) or not '!'
1272
- !regex.exactTest.test( val ) &&
1260
+ !tsfRegex.exactTest.test( val ) &&
1273
1261
  // don't search only filtered if the value is negative
1274
1262
  // ( '> -10' => '> -100' will ignore hidden rows )
1275
- !( regex.isNeg1.test( val ) || regex.isNeg2.test( val ) ) &&
1263
+ !( tsfRegex.isNeg1.test( val ) || tsfRegex.isNeg2.test( val ) ) &&
1276
1264
  // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593
1277
1265
  !( val !== '' && c.$filters && c.$filters.filter( '[data-column="' + indx + '"]' ).find( 'select' ).length &&
1278
1266
  !c.$headerIndexed[indx].hasClass( 'filter-match' ) );
@@ -1290,7 +1278,7 @@
1290
1278
  // replace accents
1291
1279
  data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter );
1292
1280
  }
1293
- if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) {
1281
+ if ( wo.filter_defaultFilter && tsfRegex.iQuery.test( vars.defaultAnyFilter ) ) {
1294
1282
  data.anyMatchFilter = tsf.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter );
1295
1283
  // clear search filtered flag because default filters are not saved to the last search
1296
1284
  searchFiltered = false;
@@ -1307,9 +1295,9 @@
1307
1295
 
1308
1296
  txt = $rows[ rowIndex ].className;
1309
1297
  // the first row can never be a child row
1310
- isChild = rowIndex && regex.child.test( txt );
1298
+ isChild = rowIndex && tsfRegex.child.test( txt );
1311
1299
  // skip child rows & already filtered rows
1312
- if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) {
1300
+ if ( isChild || ( searchFiltered && tsfRegex.filtered.test( txt ) ) ) {
1313
1301
  continue;
1314
1302
  }
1315
1303
 
@@ -1419,7 +1407,6 @@
1419
1407
  // custom select source function for a SPECIFIC COLUMN
1420
1408
  arry = fxn( table, column, onlyAvail );
1421
1409
  }
1422
-
1423
1410
  if ( arry === false ) {
1424
1411
  // fall back to original method
1425
1412
  arry = tsf.getOptions( table, column, onlyAvail );
@@ -1433,18 +1420,19 @@
1433
1420
  return false;
1434
1421
  }
1435
1422
  table = $( table )[0];
1436
- var cts, txt, indx, len,
1423
+ var cts, txt, indx, len, parsedTxt, str,
1437
1424
  c = table.config,
1438
1425
  validColumn = typeof column !== 'undefined' && column !== null && column >= 0 && column < c.columns,
1439
1426
  parsed = [];
1440
-
1441
1427
  // get unique elements and sort the list
1442
1428
  // if $.tablesorter.sortText exists ( not in the original tablesorter ),
1443
1429
  // then natural sort the list otherwise use a basic sort
1444
1430
  arry = $.grep( arry, function( value, indx ) {
1431
+ if ( value.text ) {
1432
+ return true;
1433
+ }
1445
1434
  return $.inArray( value, arry ) === indx;
1446
1435
  });
1447
-
1448
1436
  if ( validColumn && c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) {
1449
1437
  // unsorted select options
1450
1438
  return arry;
@@ -1453,22 +1441,30 @@
1453
1441
  // parse select option values
1454
1442
  for ( indx = 0; indx < len; indx++ ) {
1455
1443
  txt = arry[ indx ];
1444
+ // check for object
1445
+ str = txt.text ? txt.text : txt;
1446
+ // sortNatural breaks if you don't pass it strings
1447
+ parsedTxt = ( validColumn && c.parsers && c.parsers.length &&
1448
+ c.parsers[ column ].format( str, table, [], column ) || str ).toString();
1449
+ parsedTxt = c.widgetOptions.filter_ignoreCase ? parsedTxt.toLowerCase() : parsedTxt;
1456
1450
  // parse array data using set column parser; this DOES NOT pass the original
1457
1451
  // table cell to the parser format function
1458
- parsed.push({
1459
- t : txt,
1460
- // check parser length - fixes #934
1461
- p : validColumn && c.parsers && c.parsers.length &&
1462
- c.parsers[ column ].format( txt, table, [], column ) || txt
1463
- });
1452
+ if ( txt.text ) {
1453
+ txt.parsed = parsedTxt;
1454
+ parsed.push( txt );
1455
+ } else {
1456
+ parsed.push({
1457
+ text : txt,
1458
+ // check parser length - fixes #934
1459
+ parsed : parsedTxt
1460
+ });
1461
+ }
1464
1462
  }
1465
-
1466
1463
  // sort parsed select options
1467
1464
  cts = c.textSorter || '';
1468
1465
  parsed.sort( function( a, b ) {
1469
- // sortNatural breaks if you don't pass it strings
1470
- var x = a.p.toString(),
1471
- y = b.p.toString();
1466
+ var x = a.parsed,
1467
+ y = b.parsed;
1472
1468
  if ( validColumn && typeof cts === 'function' ) {
1473
1469
  // custom OVERALL text sorter
1474
1470
  return cts( x, y, true, column, table );
@@ -1486,7 +1482,7 @@
1486
1482
  arry = [];
1487
1483
  len = parsed.length;
1488
1484
  for ( indx = 0; indx < len; indx++ ) {
1489
- arry.push( parsed[indx].t );
1485
+ arry.push( parsed[indx] );
1490
1486
  }
1491
1487
  return arry;
1492
1488
  }
@@ -1546,7 +1542,7 @@
1546
1542
  return;
1547
1543
  }
1548
1544
 
1549
- var indx, val, txt, t, $filters, $filter,
1545
+ var indx, val, txt, t, $filters, $filter, option,
1550
1546
  c = table.config,
1551
1547
  wo = c.widgetOptions,
1552
1548
  node = c.$headerIndexed[ column ],
@@ -1571,23 +1567,45 @@
1571
1567
  if ( $.isArray( arry ) ) {
1572
1568
  // build option list
1573
1569
  for ( indx = 0; indx < arry.length; indx++ ) {
1574
- txt = arry[indx] = ( '' + arry[indx] ).replace( tsf.regex.quote, '&quot;' );
1575
- val = txt;
1576
- // allow including a symbol in the selectSource array
1577
- // 'a-z|A through Z' so that 'a-z' becomes the option value
1578
- // and 'A through Z' becomes the option text
1579
- if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
1580
- t = txt.split( wo.filter_selectSourceSeparator );
1581
- val = t[0];
1582
- txt = t[1];
1570
+ option = arry[ indx ];
1571
+ if ( option.text ) {
1572
+ // OBJECT!! add data-function-name in case the value is set in filter_functions
1573
+ option['data-function-name'] = typeof option.value === 'undefined' ? option.text : option.value;
1574
+
1575
+ // support jQuery < v1.8, otherwise the below code could be shortened to
1576
+ // options += $( '<option>', option )[ 0 ].outerHTML;
1577
+ options += '<option';
1578
+ for ( val in option ) {
1579
+ if ( option.hasOwnProperty( val ) && val !== 'text' ) {
1580
+ options += ' ' + val + '="' + option[ val ] + '"';
1581
+ }
1582
+ }
1583
+ if ( !option.value ) {
1584
+ options += ' value="' + option.text + '"';
1585
+ }
1586
+ options += '>' + option.text + '</option>';
1587
+ // above code is needed in jQuery < v1.8
1588
+
1589
+ // make sure we don't turn an object into a string (objects without a "text" property)
1590
+ } else if ( '' + option !== '[object Object]' ) {
1591
+ txt = option = ( '' + option ).replace( tsfRegex.quote, '&quot;' );
1592
+ val = txt;
1593
+ // allow including a symbol in the selectSource array
1594
+ // 'a-z|A through Z' so that 'a-z' becomes the option value
1595
+ // and 'A through Z' becomes the option text
1596
+ if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
1597
+ t = txt.split( wo.filter_selectSourceSeparator );
1598
+ val = t[0];
1599
+ txt = t[1];
1600
+ }
1601
+ // replace quotes - fixes #242 & ignore empty strings
1602
+ // see http://stackoverflow.com/q/14990971/145346
1603
+ options += option !== '' ?
1604
+ '<option ' +
1605
+ ( val === txt ? '' : 'data-function-name="' + option + '" ' ) +
1606
+ 'value="' + val + '">' + txt +
1607
+ '</option>' : '';
1583
1608
  }
1584
- // replace quotes - fixes #242 & ignore empty strings
1585
- // see http://stackoverflow.com/q/14990971/145346
1586
- options += arry[indx] !== '' ?
1587
- '<option ' +
1588
- ( val === txt ? '' : 'data-function-name="' + arry[indx] + '" ' ) +
1589
- 'value="' + val + '">' + txt +
1590
- '</option>' : '';
1591
1609
  }
1592
1610
  // clear arry so it doesn't get appended twice
1593
1611
  arry = [];
@@ -1633,6 +1651,9 @@
1633
1651
  }
1634
1652
  };
1635
1653
 
1654
+ // filter regex variable
1655
+ tsfRegex = tsf.regex;
1656
+
1636
1657
  ts.getFilters = function( table, getRaw, setFilters, skipFirst ) {
1637
1658
  var i, $filters, $column, cols,
1638
1659
  filters = false,
@@ -1,4 +1,4 @@
1
- /*! Widget: grouping - updated 11/2/2015 (v2.24.1) *//*
1
+ /*! Widget: grouping - updated 11/10/2015 (v2.24.4) *//*
2
2
  * Requires tablesorter v2.8+ and jQuery 1.7+
3
3
  * by Rob Garrison
4
4
  */
@@ -223,7 +223,7 @@
223
223
  if ( data.group !== data.currentGroup ) {
224
224
  data.group = data.currentGroup;
225
225
  if ( $.isFunction( wo.group_formatter ) ) {
226
- data.currentGroup = wo.group_formatter( ( data.group || '' ).toString(), data.column, c.table, c, wo ) || data.group;
226
+ data.currentGroup = wo.group_formatter( ( data.group || '' ).toString(), data.column, c.table, c, wo, data ) || data.group;
227
227
  }
228
228
  data.$row.before( tsg.groupHeaderHTML( c, wo, data ) );
229
229
  if ( wo.group_saveGroups && !data.savedGroup && wo.group_collapsed && wo.group_collapsible ) {
@@ -1,4 +1,4 @@
1
- /*! Widget: headerTitles - updated 10/31/2015 (v2.24.0) *//*
1
+ /*! Widget: headerTitles - updated 11/10/2015 (v2.24.4) *//*
2
2
  * Requires tablesorter v2.8+ and jQuery 1.7+
3
3
  * by Rob Garrison
4
4
  */
@@ -59,7 +59,7 @@
59
59
  sortDirection = $this.hasClass(ts.css.sortAsc) ? 0 : $this.hasClass(ts.css.sortDesc) ? 1 : 2,
60
60
  sortNext = c.sortVars[ col ].order[ ( c.sortVars[ col ].count + 1 ) % ( c.sortReset ? 3 : 2 ) ];
61
61
  if (wo.headerTitle_useAria) {
62
- txt = $this.hasClass('sorter-false') ? wo.headerTitle_output_nosort : $this.attr('aria-label') || '';
62
+ txt = $this.attr('aria-label') || wo.headerTitle_output_nosort || '';
63
63
  } else {
64
64
  txt = (wo.headerTitle_prefix || '') + // now deprecated
65
65
  ($this.hasClass('sorter-false') ? wo.headerTitle_output_nosort :
@@ -1,4 +1,4 @@
1
- /*! Widget: math - updated 10/31/2015 (v2.24.0) *//*
1
+ /*! Widget: math - updated 11/10/2015 (v2.24.4) *//*
2
2
  * Requires tablesorter v2.16+ and jQuery 1.7+
3
3
  * by Rob Garrison
4
4
  */
@@ -172,17 +172,18 @@
172
172
 
173
173
  // all non-info tbody cells
174
174
  mathAttr = wo.math_dataAttrib;
175
- $mathCells = c.$tbodies.find( '[' + mathAttr + ']' );
175
+ $mathCells = c.$tbodies.children( 'tr' ).children( '[' + mathAttr + ']' );
176
176
  math.mathType( c, $mathCells, wo.math_priority );
177
177
 
178
178
  // only info tbody cells
179
179
  $mathCells = c.$table
180
180
  .children( '.' + c.cssInfoBlock + ', tfoot' )
181
- .find( '[' + mathAttr + ']' );
181
+ .children( 'tr' )
182
+ .children( '[' + mathAttr + ']' );
182
183
  math.mathType( c, $mathCells, wo.math_priority );
183
184
 
184
185
  // find the 'all' total
185
- $mathCells = c.$table.find( '[' + mathAttr + '^=all]' );
186
+ $mathCells = c.$table.children().children( 'tr' ).children( '[' + mathAttr + '^=all]' );
186
187
  math.mathType( c, $mathCells, [ 'all' ] );
187
188
 
188
189
  wo.math_isUpdating = true;
@@ -522,7 +523,7 @@
522
523
  if ( refreshing ) { return; }
523
524
  c.$table
524
525
  .off( ( math.events + ' updateComplete.tsmath ' + wo.math_event ).replace( /\s+/g, ' ' ) )
525
- .find( '[data-' + wo.math_data + ']' ).empty();
526
+ .children().children( 'tr' ).children( '[data-' + wo.math_data + ']' ).empty();
526
527
  }
527
528
  });
528
529
 
@@ -1,4 +1,4 @@
1
- /*! Widget: Pager - updated 10/31/2015 (v2.24.0) */
1
+ /*! Widget: Pager - updated 11/10/2015 (v2.24.4) */
2
2
  /* Requires tablesorter v2.8+ and jQuery 1.7+
3
3
  * by Rob Garrison
4
4
  */
@@ -13,7 +13,7 @@
13
13
  priority: 55, // load pager after filter widget
14
14
  options : {
15
15
  // output default: '{page}/{totalPages}'
16
- // possible variables: {page}, {totalPages}, {filteredPages}, {startRow}, {endRow}, {filteredRows} and {totalRows}
16
+ // possible variables: {size}, {page}, {totalPages}, {filteredPages}, {startRow}, {endRow}, {filteredRows} and {totalRows}
17
17
  pager_output: '{startRow} to {endRow} of {totalRows} rows', // '{page}/{totalPages}'
18
18
 
19
19
  // apply disabled classname to the pager arrows when the rows at either extreme is visible
@@ -654,6 +654,12 @@
654
654
  wo = c.widgetOptions;
655
655
  // process data
656
656
  if ( $.isFunction(wo.pager_ajaxProcessing) ) {
657
+
658
+ // in case nothing is returned by ajax, empty out the table; see #1032
659
+ // but do it before calling pager_ajaxProcessing because that function may add content
660
+ // directly to the table
661
+ c.$tbodies.eq(0).empty();
662
+
657
663
  // ajaxProcessing result: [ total, rows, headers ]
658
664
  var i, j, t, hsh, $f, $sh, $headers, $h, icon, th, d, l, rr_count, len,
659
665
  $table = c.$table,
@@ -712,9 +718,6 @@
712
718
  if (wo.pager_processAjaxOnInit) {
713
719
  c.$tbodies.eq(0).html( tds );
714
720
  }
715
- } else {
716
- // nothing returned by ajax, empty out the table; see #1032
717
- c.$tbodies.eq(0).empty();
718
721
  }
719
722
  wo.pager_processAjaxOnInit = true;
720
723
  // only add new header text if the length matches