jquery-tablesorter 1.11.2 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) 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/images/jquery-tablesorter/bootstrap-black-unsorted.png +0 -0
  5. data/vendor/assets/images/jquery-tablesorter/bootstrap-white-unsorted.png +0 -0
  6. data/vendor/assets/images/jquery-tablesorter/metro-black-asc.png +0 -0
  7. data/vendor/assets/images/jquery-tablesorter/metro-black-desc.png +0 -0
  8. data/vendor/assets/images/jquery-tablesorter/metro-loading.gif +0 -0
  9. data/vendor/assets/images/jquery-tablesorter/metro-unsorted.png +0 -0
  10. data/vendor/assets/images/jquery-tablesorter/metro-white-asc.png +0 -0
  11. data/vendor/assets/images/jquery-tablesorter/metro-white-desc.png +0 -0
  12. data/vendor/assets/javascripts/jquery-tablesorter/addons/pager/jquery.tablesorter.pager.js +8 -3
  13. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.js +94 -29
  14. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.widgets.js +103 -67
  15. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-columnSelector.js +18 -7
  16. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-cssStickyHeaders.js +10 -7
  17. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-math.js +64 -25
  18. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-output.js +45 -21
  19. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-pager.js +9 -4
  20. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-print.js +122 -0
  21. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-scroller.js +3 -3
  22. data/vendor/assets/stylesheets/jquery-tablesorter/theme.bootstrap.css +9 -3
  23. data/vendor/assets/stylesheets/jquery-tablesorter/theme.green.css +10 -10
  24. data/vendor/assets/stylesheets/jquery-tablesorter/theme.metro-dark.css +192 -0
  25. metadata +12 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f67ead0cc7ee473fc13eba000fc6fa881e1f5055
4
- data.tar.gz: a35b216de9c3d6374d1e23fbcf902ed14ecc3530
3
+ metadata.gz: 65ae3cb21c44ee0f6bfee2c97b57b137ae72b4eb
4
+ data.tar.gz: 8497edb9c98963bd4645d158c8b161467646d23a
5
5
  SHA512:
6
- metadata.gz: 3ad77cc7413bd4780c15d580097503bc60844b9ab05cebe4a27ced3f8d4f387c27efcd09ef4160b8c818eda24a3729fee1d0dbe9fce28173c0fecd7fd322a5d0
7
- data.tar.gz: 41a29044a40b804f5c2c18950a7855e3cb7f353df1333739f4d964dba51e1a26a67366b49b67c10a4b2d050557e3775d5db03817333acc97684e7525b1bcbd16
6
+ metadata.gz: 297953c27301fe09470785ac076576f262ed73f4c8847a2b8da5d0485e9f4bd629755b586021d5ca11a859972a950825cf0f16306de247ddedd0e30e09f1e51a
7
+ data.tar.gz: 9c2d2ee68462a3c05b1a35c9294bd0fd4c2d22daf20949aed52dc1d37e133ea4f4b541f0cb7fa4d6503d6fa7132605de69597b798dc65865c212b0376c8c0234
data/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  Simple integration of jquery-tablesorter into the asset pipeline.
6
6
 
7
- Current tablesorter version: 2.16.3 (4/30/2014), [documentation]
7
+ Current tablesorter version: 2.17.0 (5/22/2014), [documentation]
8
8
 
9
- Any issue associate with the js/css files, please report to [Mottie's fork].
9
+ Any issue associated with the js/css files, please report to [Mottie's fork].
10
10
 
11
11
  ## Installation
12
12
 
@@ -1,3 +1,3 @@
1
1
  module JqueryTablesorter
2
- VERSION = '1.11.2'
2
+ VERSION = '1.12.0'
3
3
  end
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * tablesorter pager plugin
3
- * updated 4/23/2014 (v2.16.0)
3
+ * updated 5/22/2014 (v2.17.0)
4
4
  */
5
5
  /*jshint browser:true, jquery:true, unused:false */
6
6
  ;(function($) {
@@ -289,7 +289,7 @@
289
289
  exception === 'timeout' ? 'Time out error' :
290
290
  exception === 'abort' ? 'Ajax Request aborted' :
291
291
  'Uncaught error: ' + xhr.statusText + ' [' + xhr.status + ']' );
292
- c.$tbodies.eq(0).empty();
292
+ c.$tbodies.eq(0).detach();
293
293
  p.totalRows = 0;
294
294
  } else {
295
295
  // process ajax object
@@ -311,7 +311,7 @@
311
311
  if (d instanceof jQuery) {
312
312
  if (p.processAjaxOnInit) {
313
313
  // append jQuery object
314
- c.$tbodies.eq(0).empty().append(d);
314
+ c.$tbodies.eq(0).detach().append(d);
315
315
  }
316
316
  } else if (l) {
317
317
  // build table from array
@@ -726,6 +726,11 @@
726
726
  })
727
727
  .bind('update updateRows updateAll addRows '.split(' ').join('.pager '), function(e){
728
728
  e.stopPropagation();
729
+ fixHeight(table, p);
730
+ var $rows = c.$tbodies.eq(0).children();
731
+ p.totalRows = $rows.length - ( p.countChildRows ? 0 : $rows.filter('.' + c.cssChildRow).length );
732
+ p.totalPages = Math.ceil( p.totalRows / p.size );
733
+ updatePageDisplay(table, p);
729
734
  hideRows(table, p);
730
735
  })
731
736
  .bind('pageSize.pager', function(e,v){
@@ -1,5 +1,5 @@
1
1
  /**!
2
- * TableSorter 2.16.3 - Client-side table sorting with ease!
2
+ * TableSorter 2.17.0 - Client-side table sorting with ease!
3
3
  * @requires jQuery v1.2.6+
4
4
  *
5
5
  * Copyright (c) 2007 Christian Bach
@@ -24,7 +24,7 @@
24
24
 
25
25
  var ts = this;
26
26
 
27
- ts.version = "2.16.3";
27
+ ts.version = "2.17.0";
28
28
 
29
29
  ts.parsers = [];
30
30
  ts.widgets = [];
@@ -167,7 +167,7 @@
167
167
 
168
168
  function getElementText(table, node, cellIndex) {
169
169
  if (!node) { return ""; }
170
- var c = table.config,
170
+ var te, c = table.config,
171
171
  t = c.textExtraction || '',
172
172
  text = "";
173
173
  if (t === "basic") {
@@ -176,8 +176,8 @@
176
176
  } else {
177
177
  if (typeof(t) === "function") {
178
178
  text = t(node, table, cellIndex);
179
- } else if (typeof(t) === "object" && t.hasOwnProperty(cellIndex)) {
180
- text = t[cellIndex](node, table, cellIndex);
179
+ } else if (typeof (te = ts.getColumnData( table, t, cellIndex )) === 'function') {
180
+ text = te(node, table, cellIndex);
181
181
  } else {
182
182
  // previous "simple" method
183
183
  text = node.textContent || node.innerText || $(node).text() || "";
@@ -219,7 +219,7 @@
219
219
  var c = table.config,
220
220
  // update table bodies in case we start with an empty table
221
221
  tb = c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')'),
222
- rows, list, l, i, h, ch, p, time, indx,
222
+ rows, list, l, i, h, ch, p, time,
223
223
  j = 0,
224
224
  parsersDebug = "",
225
225
  len = tb.length;
@@ -233,16 +233,11 @@
233
233
  while (j < len) {
234
234
  rows = tb[j].rows;
235
235
  if (rows[j]) {
236
- l = rows[j].cells.length;
236
+ l = c.columns; // rows[j].cells.length;
237
237
  for (i = 0; i < l; i++) {
238
- // tons of thanks to AnthonyM1229 for working out the following selector (issue #74) to make this work in IE8!
239
- // More fixes to this selector to work properly in iOS and jQuery 1.8+ (issue #132 & #174)
240
- h = c.$headers.filter(':not([colspan])');
241
- h = h.add( c.$headers.filter('[colspan="1"]') ) // ie8 fix
242
- .filter('[data-column="' + i + '"]:last');
243
- // get headers option corrected index
244
- indx = c.$headers.index(h);
245
- ch = c.headers[indx];
238
+ h = c.$headers.filter('[data-column="' + i + '"]:last');
239
+ // get column indexed table cell
240
+ ch = ts.getColumnData( table, c.headers, i );
246
241
  // get column parser
247
242
  p = ts.getParserById( ts.getData(h, ch, 'sorter') );
248
243
  // empty cells behaviour - keeping emptyToBottom for backwards compatibility
@@ -421,10 +416,12 @@
421
416
  c.columns = ts.computeColumnIndex( c.$table.children('thead, tfoot').children('tr') );
422
417
  // add icon if cssIcon option exists
423
418
  i = c.cssIcon ? '<i class="' + ( c.cssIcon === ts.css.icon ? ts.css.icon : c.cssIcon + ' ' + ts.css.icon ) + '"></i>' : '';
424
- c.$headers = $(table).find(c.selectorHeaders).each(function(index) {
419
+ c.$headers.each(function(index) {
425
420
  $t = $(this);
426
- ch = c.headers[index];
427
- c.headerContent[index] = $(this).html(); // save original header content
421
+ // make sure to get header cell & not column indexed cell
422
+ ch = ts.getColumnData( table, c.headers, index, true );
423
+ // save original header content
424
+ c.headerContent[index] = $(this).html();
428
425
  // set up header template
429
426
  t = c.headerTemplate.replace(/\{content\}/g, $(this).html()).replace(/\{icon\}/g, i);
430
427
  if (c.onRenderTemplate) {
@@ -473,10 +470,11 @@
473
470
  }
474
471
 
475
472
  function updateHeader(table) {
476
- var s, $th, c = table.config;
473
+ var s, $th,
474
+ c = table.config;
477
475
  c.$headers.each(function(index, th){
478
476
  $th = $(th);
479
- s = ts.getData( th, c.headers[index], 'sorter' ) === 'false';
477
+ s = ts.getData( th, ts.getColumnData( table, c.headers, index, true ), 'sorter' ) === 'false';
480
478
  th.sortDisabled = s;
481
479
  $th[ s ? 'addClass' : 'removeClass' ]('sorter-false').attr('aria-disabled', '' + s);
482
480
  // aria-controls - requires table ID
@@ -546,19 +544,46 @@
546
544
  }
547
545
  }
548
546
 
549
- function updateHeaderSortCount(table, list, triggered) {
550
- var s, t, o, c = table.config,
547
+ function updateHeaderSortCount(table, list) {
548
+ var s, t, o, col, primary,
549
+ c = table.config,
551
550
  sl = list || c.sortList;
552
551
  c.sortList = [];
553
552
  $.each(sl, function(i,v){
554
553
  // ensure all sortList values are numeric - fixes #127
555
- s = [ parseInt(v[0], 10), parseInt(v[1], 10) ];
554
+ col = parseInt(v[0], 10);
556
555
  // make sure header exists
557
- o = c.$headers.filter('[data-column="' + s[0] + '"]:last')[0];
556
+ o = c.$headers.filter('[data-column="' + col + '"]:last')[0];
558
557
  if (o) { // prevents error if sorton array is wrong
558
+ // o.count = o.count + 1;
559
+ t = ('' + v[1]).match(/^(1|d|s|o|n)/);
560
+ t = t ? t[0] : '';
561
+ // 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext
562
+ switch(t) {
563
+ case '1': case 'd': // descending
564
+ t = 1;
565
+ break;
566
+ case 's': // same direction (as primary column)
567
+ // if primary sort is set to "s", make it ascending
568
+ t = primary || 0;
569
+ break;
570
+ case 'o':
571
+ s = o.order[(primary || 0) % (c.sortReset ? 3 : 2)];
572
+ // opposite of primary column; but resets if primary resets
573
+ t = s === 0 ? 1 : s === 1 ? 0 : 2;
574
+ break;
575
+ case 'n':
576
+ o.count = o.count + 1;
577
+ t = o.order[(o.count) % (c.sortReset ? 3 : 2)];
578
+ break;
579
+ default: // ascending
580
+ t = 0;
581
+ break;
582
+ }
583
+ primary = i === 0 ? t : primary;
584
+ s = [ col, parseInt(t, 10) || 0 ];
559
585
  c.sortList.push(s);
560
586
  t = $.inArray(s[1], o.order); // fixes issue #167
561
- if (triggered) { o.count = o.count + 1; }
562
587
  o.count = t >= 0 ? t : s[1] % (c.sortReset ? 3 : 2);
563
588
  }
564
589
  });
@@ -872,7 +897,7 @@
872
897
  e.stopPropagation();
873
898
  $table.trigger("sortStart", this);
874
899
  // update header count index
875
- updateHeaderSortCount(table, list, true);
900
+ updateHeaderSortCount(table, list);
876
901
  // set css for headers
877
902
  setHeadersCss(table);
878
903
  // fixes #346
@@ -921,6 +946,16 @@
921
946
  .bind("destroy" + c.namespace, function(e, c, cb){
922
947
  e.stopPropagation();
923
948
  ts.destroy(table, c, cb);
949
+ })
950
+ .bind("resetToLoadState" + c.namespace, function(e){
951
+ // remove all widgets
952
+ ts.refreshWidgets(table, true, true);
953
+ // restore original settings; this clears out current settings, but does not clear
954
+ // values saved to storage.
955
+ c = $.extend(true, ts.defaults, c.originalSettings);
956
+ table.hasInitialized = false;
957
+ // setup the entire table again
958
+ ts.setup( table, c );
924
959
  });
925
960
  }
926
961
 
@@ -930,6 +965,8 @@
930
965
  var table = this,
931
966
  // merge & extend config options
932
967
  c = $.extend(true, {}, ts.defaults, settings);
968
+ // save initial settings
969
+ c.originalSettings = settings;
933
970
  // create a table from data (build table widget)
934
971
  if (!table.hasInitialized && ts.buildTable && this.tagName !== 'TABLE') {
935
972
  // return the table (in case the original target is the table's container)
@@ -973,6 +1010,7 @@
973
1010
  c.$table = $table
974
1011
  .addClass(ts.css.table + ' ' + c.tableClass + k)
975
1012
  .attr({ role : 'grid'});
1013
+ c.$headers = $(table).find(c.selectorHeaders);
976
1014
 
977
1015
  // give the table a unique id, which will be used in namespace binding
978
1016
  if (!c.namespace) {
@@ -1050,6 +1088,31 @@
1050
1088
  if (typeof c.initialized === 'function') { c.initialized(table); }
1051
1089
  };
1052
1090
 
1091
+ ts.getColumnData = function(table, obj, indx, getCell){
1092
+ if (typeof obj === 'undefined' || obj === null) { return; }
1093
+ table = $(table)[0];
1094
+ var result, $h, k,
1095
+ c = table.config;
1096
+ if (obj[indx]) {
1097
+ return getCell ? obj[indx] : obj[c.$headers.index( c.$headers.filter('[data-column="' + indx + '"]:last') )];
1098
+ }
1099
+ for (k in obj) {
1100
+ if (typeof k === 'string') {
1101
+ if (getCell) {
1102
+ // get header cell
1103
+ $h = c.$headers.eq(indx).filter(k);
1104
+ } else {
1105
+ // get column indexed cell
1106
+ $h = c.$headers.filter('[data-column="' + indx + '"]:last').filter(k);
1107
+ }
1108
+ if ($h.length) {
1109
+ return obj[k];
1110
+ }
1111
+ }
1112
+ }
1113
+ return result;
1114
+ };
1115
+
1053
1116
  // computeTableHeaderCellIndexes from:
1054
1117
  // http://www.javascripttoolbox.com/lib/table/examples.php
1055
1118
  // http://www.javascripttoolbox.com/temp/table_cellindex.html
@@ -1136,7 +1199,7 @@
1136
1199
  };
1137
1200
 
1138
1201
  ts.clearTableBody = function(table) {
1139
- $(table)[0].config.$tbodies.empty();
1202
+ $(table)[0].config.$tbodies.detach();
1140
1203
  };
1141
1204
 
1142
1205
  ts.bindEvents = function(table, $headers, core){
@@ -1217,7 +1280,7 @@
1217
1280
  // disable tablesorter
1218
1281
  $t
1219
1282
  .removeData('tablesorter')
1220
- .unbind('sortReset update updateAll updateRows updateCell addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd '.split(' ').join(c.namespace + ' '));
1283
+ .unbind('sortReset update updateAll updateRows updateCell addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd resetToLoadState '.split(' ').join(c.namespace + ' '));
1221
1284
  c.$headers.add($f)
1222
1285
  .removeClass( [ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone].join(' ') )
1223
1286
  .removeAttr('data-column')
@@ -1403,6 +1466,8 @@
1403
1466
  };
1404
1467
 
1405
1468
  ts.getParserById = function(name) {
1469
+ /*jshint eqeqeq:false */
1470
+ if (name == 'false') { return false; }
1406
1471
  var i, l = ts.parsers.length;
1407
1472
  for (i = 0; i < l; i++) {
1408
1473
  if (ts.parsers[i].id.toLowerCase() === (name.toString()).toLowerCase()) {
@@ -1678,7 +1743,7 @@
1678
1743
  if (s) {
1679
1744
  var c = table.config,
1680
1745
  ci = c.$headers.filter('[data-column=' + cellIndex + ']:last'),
1681
- format = ci.length && ci[0].dateFormat || ts.getData( ci, c.headers[cellIndex], 'dateFormat') || c.dateFormat;
1746
+ format = ci.length && ci[0].dateFormat || ts.getData( ci, ts.getColumnData( table, c.headers, cellIndex ), 'dateFormat') || c.dateFormat;
1682
1747
  s = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/"); // escaped - because JSHint in Firefox was showing it as an error
1683
1748
  if (format === "mmddyyyy") {
1684
1749
  s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2");
@@ -1,4 +1,4 @@
1
- /*! tableSorter 2.16+ widgets - updated 4/30/2014 (v2.16.3)
1
+ /*! tableSorter 2.16+ widgets - updated 5/22/2014 (v2.17.0)
2
2
  *
3
3
  * Column Styles
4
4
  * Column Filters
@@ -438,16 +438,18 @@ ts.filter = {
438
438
  query = ts.formatFloat( iFilter.replace(ts.filter.regex.operators, ''), table ),
439
439
  parser = c.parsers[index],
440
440
  savedSearch = query;
441
- // parse filter value in case we're comparing numbers (dates)
441
+ // parse filter value in case we're comparing numbers (dates)
442
442
  if (parsed[index] || parser.type === 'numeric') {
443
- cachedValue = parser.format( '' + iFilter.replace(ts.filter.regex.operators, ''), table, c.$headers.eq(index), index );
444
- query = ( typeof query === "number" && cachedValue !== '' && !isNaN(cachedValue) ) ? cachedValue : query;
443
+ result = parser.format( $.trim('' + iFilter.replace(ts.filter.regex.operators, '')), table, [], index );
444
+ query = ( typeof result === "number" && result !== '' && !isNaN(result) ) ? result : query;
445
445
  }
446
+
446
447
  // iExact may be numeric - see issue #149;
447
448
  // check if cached is defined, because sometimes j goes out of range? (numeric columns)
448
- cachedValue = ( parsed[index] || parser.type === 'numeric' ) && !isNaN(query) && cached ? cached :
449
+ cachedValue = ( parsed[index] || parser.type === 'numeric' ) && !isNaN(query) && typeof cached !== 'undefined' ? cached :
449
450
  isNaN(iExact) ? ts.formatFloat( iExact.replace(ts.filter.regex.nondigit, ''), table) :
450
451
  ts.formatFloat( iExact, table );
452
+
451
453
  if ( />/.test(iFilter) ) { result = />=/.test(iFilter) ? cachedValue >= query : cachedValue > query; }
452
454
  if ( /</.test(iFilter) ) { result = /<=/.test(iFilter) ? cachedValue <= query : cachedValue < query; }
453
455
  // keep showing all rows if nothing follows the operator
@@ -476,8 +478,8 @@ ts.filter = {
476
478
  },
477
479
  // Look for an AND or && operator (logical and)
478
480
  and : function( filter, iFilter, exact, iExact ) {
479
- if ( /\s+(AND|&&)\s+/g.test(filter) ) {
480
- var query = iFilter.split( /(?:\s+(?:and|&&)\s+)/g ),
481
+ if ( ts.filter.regex.andTest.test(filter) ) {
482
+ var query = iFilter.split( ts.filter.regex.andSplit ),
481
483
  result = iExact.search( $.trim(query[0]) ) >= 0,
482
484
  indx = query.length - 1;
483
485
  while (result && indx) {
@@ -490,10 +492,11 @@ ts.filter = {
490
492
  },
491
493
  // Look for a range (using " to " or " - ") - see issue #166; thanks matzhu!
492
494
  range : function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed ) {
493
- if ( /\s+(-|to)\s+/.test(iFilter) ) {
495
+ if ( ts.filter.regex.toTest.test(iFilter) ) {
494
496
  var result, tmp,
495
497
  c = table.config,
496
- query = iFilter.split(/(?: - | to )/), // make sure the dash is for a range and not indicating a negative number
498
+ // make sure the dash is for a range and not indicating a negative number
499
+ query = iFilter.split( ts.filter.regex.toSplit ),
497
500
  range1 = ts.formatFloat(query[0].replace(ts.filter.regex.nondigit, ''), table),
498
501
  range2 = ts.formatFloat(query[1].replace(ts.filter.regex.nondigit, ''), table);
499
502
  // parse filter value in case we're comparing numbers (dates)
@@ -513,9 +516,9 @@ ts.filter = {
513
516
  },
514
517
  // Look for wild card: ? = single, * = multiple, or | = logical OR
515
518
  wild : function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed, rowArray ) {
516
- if ( /[\?|\*]/.test(iFilter) || /\s+OR\s+/i.test(filter) ) {
519
+ if ( /[\?|\*]/.test(iFilter) || ts.filter.regex.orReplace.test(filter) ) {
517
520
  var c = table.config,
518
- query = iFilter.replace(/\s+OR\s+/gi,"|");
521
+ query = iFilter.replace(ts.filter.regex.orReplace, "|");
519
522
  // look for an exact match with the "or" unless the "filter-match" class is found
520
523
  if (!c.$headers.filter('[data-column="' + index + '"]:last').hasClass('filter-match') && /\|/.test(query)) {
521
524
  query = $.isArray(rowArray) ? '(' + query + ')' : '^(' + query + ')$';
@@ -545,14 +548,30 @@ ts.filter = {
545
548
  }
546
549
  },
547
550
  init: function(table, c, wo) {
548
- var options, string, $header, column, filters, time;
551
+ // filter language options
552
+ ts.language = $.extend(true, {}, {
553
+ to : 'to',
554
+ or : 'or',
555
+ and : 'and'
556
+ }, ts.language);
557
+
558
+ var options, string, $header, column, filters, time, fxn,
559
+ regex = ts.filter.regex;
549
560
  if (c.debug) {
550
561
  time = new Date();
551
562
  }
552
563
  c.$table.addClass('hasFilters');
553
564
 
554
- ts.filter.regex.child = new RegExp(c.cssChildRow);
555
- ts.filter.regex.filtered = new RegExp(wo.filter_filteredRow);
565
+ $.extend( regex, {
566
+ child : new RegExp(c.cssChildRow),
567
+ filtered : new RegExp(wo.filter_filteredRow),
568
+ alreadyFiltered : new RegExp('(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i'),
569
+ toTest : new RegExp('\\s+(-|' + ts.language.to + ')\\s+', 'i'),
570
+ toSplit : new RegExp('(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi'),
571
+ andTest : new RegExp('\\s+(' + ts.language.and + '|&&)\\s+', 'i'),
572
+ andSplit : new RegExp('(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi'),
573
+ orReplace : new RegExp('\\s+(' + ts.language.or + ')\\s+', 'gi')
574
+ });
556
575
 
557
576
  // don't build filter row if columnFilters is false or all columns are set to "filter-false" - issue #156
558
577
  if (wo.filter_columnFilters !== false && c.$headers.filter('.filter-false').length !== c.$headers.length) {
@@ -576,6 +595,7 @@ ts.filter = {
576
595
  if (/(update|add)/.test(event.type) && event.type !== "updateComplete") {
577
596
  // force a new search since content has changed
578
597
  c.lastCombinedFilter = null;
598
+ c.lastSearch = [];
579
599
  }
580
600
  // pass true (skipFirst) to prevent the tablesorter.setFilters function from skipping the first input
581
601
  // ensures all inputs are updated when a search is triggered on the table $('table').trigger('search', [...]);
@@ -602,16 +622,16 @@ ts.filter = {
602
622
  }
603
623
  }
604
624
  if (wo.filter_functions) {
605
- // column = column # (string)
606
- for (column in wo.filter_functions) {
607
- if (wo.filter_functions.hasOwnProperty(column) && typeof column === 'string') {
625
+ for (column = 0; column < c.columns; column++) {
626
+ fxn = ts.getColumnData( table, wo.filter_functions, column );
627
+ if (fxn) {
608
628
  $header = c.$headers.filter('[data-column="' + column + '"]:last');
609
629
  options = '';
610
- if (wo.filter_functions[column] === true && !$header.hasClass('filter-false')) {
630
+ if (fxn === true && !$header.hasClass('filter-false')) {
611
631
  ts.filter.buildSelect(table, column);
612
- } else if (typeof column === 'string' && !$header.hasClass('filter-false')) {
632
+ } else if (typeof fxn === 'object' && !$header.hasClass('filter-false')) {
613
633
  // add custom drop down list
614
- for (string in wo.filter_functions[column]) {
634
+ for (string in fxn) {
615
635
  if (typeof string === 'string') {
616
636
  options += options === '' ?
617
637
  '<option value="">' + ($header.data('placeholder') || $header.attr('data-placeholder') || wo.filter_placeholder.select || '') + '</option>' : '';
@@ -683,7 +703,7 @@ ts.filter = {
683
703
  return filters;
684
704
  },
685
705
  buildRow: function(table, c, wo) {
686
- var column, $header, buildSelect, disabled, name,
706
+ var column, $header, buildSelect, disabled, name, ffxn,
687
707
  // c.columns defined in computeThIndexes()
688
708
  columns = c.columns,
689
709
  buildFilter = '<tr class="' + ts.css.filterRow + '">';
@@ -696,22 +716,18 @@ ts.filter = {
696
716
  disabled = false;
697
717
  // assuming last cell of a column is the main column
698
718
  $header = c.$headers.filter('[data-column="' + column + '"]:last');
699
- buildSelect = (wo.filter_functions && wo.filter_functions[column] && typeof wo.filter_functions[column] !== 'function') ||
719
+ ffxn = ts.getColumnData( table, wo.filter_functions, column );
720
+ buildSelect = (wo.filter_functions && ffxn && typeof ffxn !== "function" ) ||
700
721
  $header.hasClass('filter-select');
701
722
  // get data from jQuery data, metadata, headers option or header class name
702
- if (ts.getData) {
703
- // get data from jQuery data, metadata, headers option or header class name
704
- disabled = ts.getData($header[0], c.headers[column], 'filter') === 'false';
705
- } else {
706
- // only class names and header options - keep this for compatibility with tablesorter v2.0.5
707
- disabled = (c.headers[column] && c.headers[column].hasOwnProperty('filter') && c.headers[column].filter === false) ||
708
- $header.hasClass('filter-false');
709
- }
723
+ disabled = ts.getData($header[0], ts.getColumnData( table, c.headers, column ), 'filter') === 'false';
724
+
710
725
  if (buildSelect) {
711
726
  buildFilter = $('<select>').appendTo( c.$filters.eq(column) );
712
727
  } else {
713
- if (wo.filter_formatter && $.isFunction(wo.filter_formatter[column])) {
714
- buildFilter = wo.filter_formatter[column]( c.$filters.eq(column), column );
728
+ ffxn = ts.getColumnData( table, wo.filter_formatter, column );
729
+ if (ffxn) {
730
+ buildFilter = ffxn( c.$filters.eq(column), column );
715
731
  // no element returned, so lets go find it
716
732
  if (buildFilter && buildFilter.length === 0) {
717
733
  buildFilter = c.$filters.eq(column).children('input');
@@ -761,7 +777,7 @@ ts.filter = {
761
777
  $el
762
778
  // use data attribute instead of jQuery data since the head is cloned without including the data/binding
763
779
  .attr('data-lastSearchTime', new Date().getTime())
764
- .unbind('keyup search change '.split(' ').join(c.namespace + 'filter '))
780
+ .unbind('keypress keyup search change '.split(' ').join(c.namespace + 'filter '))
765
781
  // include change for select - fixes #473
766
782
  .bind('keyup search change '.split(' ').join(c.namespace + 'filter '), function(event) {
767
783
  $(this).attr('data-lastSearchTime', new Date().getTime());
@@ -774,8 +790,14 @@ ts.filter = {
774
790
  ( event.which >= 37 && event.which <= 40 ) || (event.which !== 13 && wo.filter_liveSearch === false) ) ) ) {
775
791
  return;
776
792
  }
777
- // true flag tells getFilters to skip newest timed input
778
- ts.filter.searching( table, true, true );
793
+ // change event = no delay; last true flag tells getFilters to skip newest timed input
794
+ ts.filter.searching( table, event.type !== 'change', true );
795
+ })
796
+ .bind('keypress.' + c.namespace + 'filter', function(event){
797
+ if (event.which === 13) {
798
+ event.preventDefault();
799
+ $(this).blur();
800
+ }
779
801
  });
780
802
  c.$table.bind('filterReset', function(){
781
803
  $el.val('');
@@ -787,6 +809,8 @@ ts.filter = {
787
809
  filterArray = $.isArray(filter),
788
810
  filters = (filterArray) ? filter : ts.getFilters(table, true),
789
811
  combinedFilters = (filters || []).join(''); // combined filter values
812
+ // prevent errors if delay init is set
813
+ if ($.isEmptyObject(c.cache)) { return; }
790
814
  // add filter array back into inputs
791
815
  if (filterArray) {
792
816
  ts.setFilters( table, filters, false, skipFirst !== true );
@@ -802,6 +826,7 @@ ts.filter = {
802
826
  } else if (filter === false) {
803
827
  // force filter refresh
804
828
  c.lastCombinedFilter = null;
829
+ c.lastSearch = [];
805
830
  }
806
831
  c.$table.trigger('filterStart', [filters]);
807
832
  if (c.showProcessing) {
@@ -856,8 +881,9 @@ ts.filter = {
856
881
  if (table.config.lastCombinedFilter === combinedFilters) { return; }
857
882
  var cached, len, $rows, rowIndex, tbodyIndex, $tbody, $cells, columnIndex,
858
883
  childRow, childRowText, exact, iExact, iFilter, lastSearch, matches, result,
859
- notFiltered, searchFiltered, filterMatched, showRow, time,
860
- anyMatch, iAnyMatch, rowArray, rowText, iRowText, rowCache,
884
+ notFiltered, searchFiltered, filterMatched, showRow, time, val, indx,
885
+ anyMatch, iAnyMatch, rowArray, rowText, iRowText, rowCache, fxn,
886
+ regex = ts.filter.regex,
861
887
  c = table.config,
862
888
  wo = c.widgetOptions,
863
889
  columns = c.columns,
@@ -868,7 +894,7 @@ ts.filter = {
868
894
  parsed = c.$headers.map(function(columnIndex) {
869
895
  return c.parsers && c.parsers[columnIndex] && c.parsers[columnIndex].parsed ||
870
896
  // getData won't return "parsed" if other "filter-" class names exist (e.g. <th class="filter-select filter-parsed">)
871
- ts.getData && ts.getData(c.$headers.filter('[data-column="' + columnIndex + '"]:last'), c.headers[columnIndex], 'filter') === 'parsed' ||
897
+ ts.getData && ts.getData(c.$headers.filter('[data-column="' + columnIndex + '"]:last'), ts.getColumnData( table, c.headers, columnIndex ), 'filter') === 'parsed' ||
872
898
  $(this).hasClass('filter-parsed');
873
899
  }).get();
874
900
  if (c.debug) { time = new Date(); }
@@ -879,26 +905,34 @@ ts.filter = {
879
905
  // $rows = $tbody.children('tr').not(c.selectorRemove);
880
906
  columnIndex = c.columns;
881
907
  // convert stored rows into a jQuery object
882
- $rows = true ? $( $.map(c.cache[tbodyIndex].normalized, function(el){ return el[columnIndex].$row.get(); }) ) : $tbody.children('tr').not(c.selectorRemove);
883
- len = $rows.length;
908
+ $rows = $( $.map(c.cache[tbodyIndex].normalized, function(el){ return el[columnIndex].$row.get(); }) );
909
+
884
910
  if (combinedFilters === '' || wo.filter_serversideFiltering) {
885
911
  $rows.removeClass(wo.filter_filteredRow).not('.' + c.cssChildRow).show();
886
912
  } else {
913
+ // filter out child rows
914
+ $rows = $rows.not('.' + c.cssChildRow);
915
+ len = $rows.length;
887
916
  // optimize searching only through already filtered rows - see #313
888
917
  searchFiltered = true;
889
918
  lastSearch = c.lastSearch || c.$table.data('lastSearch') || [];
890
- $.each(filters, function(indx, val) {
919
+ for (indx = 0; indx < columnIndex; indx++) {
920
+ val = filters[indx] || '';
921
+ // break out of loop if we've already determined not to search filtered rows
922
+ if (!searchFiltered) { indx = columnIndex; }
891
923
  // search already filtered rows if...
892
- searchFiltered = searchFiltered &&
893
- // there are changes from beginning of filter
894
- (val || '').indexOf(lastSearch[indx]) === 0 &&
895
- // if there is not a logical "or" in the string
896
- !/(\s+or\s+|\|)/g.test(val || '') &&
897
- // if we are not doing exact matches
898
- !/[=\"]/.test(lastSearch[indx]) &&
924
+ searchFiltered = searchFiltered && lastSearch.length &&
925
+ // there are no changes from beginning of filter
926
+ val.indexOf(lastSearch[indx] || '') === 0 &&
927
+ // if there is NOT a logical "or", or range ("to" or "-") in the string
928
+ !regex.alreadyFiltered.test(val) &&
929
+ // if we are not doing exact matches, using "|" (logical or) or not "!"
930
+ !/[=\"\|!]/.test(val) &&
931
+ // don't search only filtered if the value is negative ('> -10' => '> -100' will ignore hidden rows)
932
+ !(/(>=?\s*-\d)/.test(val) || /(<=?\s*\d)/.test(val)) &&
899
933
  // if filtering using a select without a "filter-match" class (exact match) - fixes #593
900
- !( val !== '' && wo.filter_functions && wo.filter_functions[indx] === true && !c.$headers.filter('[data-column="' + indx + '"]:last').hasClass('filter-match') );
901
- });
934
+ !( val !== '' && c.$filters && c.$filters.eq(indx).find('select').length && !c.$headers.filter('[data-column="' + indx + '"]:last').hasClass('filter-match') );
935
+ }
902
936
  notFiltered = $rows.not('.' + wo.filter_filteredRow).length;
903
937
  // can't search when all rows are hidden - this happens when looking for exact matches
904
938
  if (searchFiltered && notFiltered === 0) { searchFiltered = false; }
@@ -917,7 +951,7 @@ ts.filter = {
917
951
  for (rowIndex = 0; rowIndex < len; rowIndex++) {
918
952
  childRow = $rows[rowIndex].className;
919
953
  // skip child rows & already filtered rows
920
- if ( ts.filter.regex.child.test(childRow) || (searchFiltered && ts.filter.regex.filtered.test(childRow)) ) { continue; }
954
+ if ( regex.child.test(childRow) || (searchFiltered && regex.filtered.test(childRow)) ) { continue; }
921
955
  showRow = true;
922
956
  // *** nextAll/nextUntil not supported by Zepto! ***
923
957
  childRow = $rows.eq(rowIndex).nextUntil('tr:not(.' + c.cssChildRow + ')');
@@ -973,24 +1007,25 @@ ts.filter = {
973
1007
  exact = $.trim($cells.eq(columnIndex).text());
974
1008
  exact = c.sortLocaleCompare ? ts.replaceAccents(exact) : exact; // issue #405
975
1009
  }
976
- iExact = !ts.filter.regex.type.test(typeof exact) && wo.filter_ignoreCase ? exact.toLocaleLowerCase() : exact;
1010
+ iExact = !regex.type.test(typeof exact) && wo.filter_ignoreCase ? exact.toLocaleLowerCase() : exact;
977
1011
  result = showRow; // if showRow is true, show that row
978
1012
 
979
1013
  // replace accents - see #357
980
1014
  filters[columnIndex] = c.sortLocaleCompare ? ts.replaceAccents(filters[columnIndex]) : filters[columnIndex];
981
1015
  // val = case insensitive, filters[columnIndex] = case sensitive
982
1016
  iFilter = wo.filter_ignoreCase ? (filters[columnIndex] || '').toLocaleLowerCase() : filters[columnIndex];
983
- if (wo.filter_functions && wo.filter_functions[columnIndex]) {
984
- if (wo.filter_functions[columnIndex] === true) {
1017
+ fxn = ts.getColumnData( table, wo.filter_functions, columnIndex );
1018
+ if (fxn) {
1019
+ if (fxn === true) {
985
1020
  // default selector; no "filter-select" class
986
1021
  result = (c.$headers.filter('[data-column="' + columnIndex + '"]:last').hasClass('filter-match')) ?
987
1022
  iExact.search(iFilter) >= 0 : filters[columnIndex] === exact;
988
- } else if (typeof wo.filter_functions[columnIndex] === 'function') {
1023
+ } else if (typeof fxn === 'function') {
989
1024
  // filter callback( exact cell content, parser normalized content, filter input value, column index, jQuery row object )
990
- result = wo.filter_functions[columnIndex](exact, cached, filters[columnIndex], columnIndex, $rows.eq(rowIndex));
991
- } else if (typeof wo.filter_functions[columnIndex][filters[columnIndex]] === 'function') {
1025
+ result = fxn(exact, cached, filters[columnIndex], columnIndex, $rows.eq(rowIndex));
1026
+ } else if (typeof fxn[filters[columnIndex]] === 'function') {
992
1027
  // selector option function
993
- result = wo.filter_functions[columnIndex][filters[columnIndex]](exact, cached, filters[columnIndex], columnIndex, $rows.eq(rowIndex));
1028
+ result = fxn[filters[columnIndex]](exact, cached, filters[columnIndex], columnIndex, $rows.eq(rowIndex));
994
1029
  }
995
1030
  } else {
996
1031
  filterMatched = null;
@@ -1044,15 +1079,16 @@ ts.filter = {
1044
1079
  wo = c.widgetOptions,
1045
1080
  parsed = [],
1046
1081
  arry = false,
1047
- source = wo.filter_selectSource;
1082
+ source = wo.filter_selectSource,
1083
+ fxn = $.isFunction(source) ? true : ts.getColumnData( table, source, column );
1048
1084
 
1049
1085
  // filter select source option
1050
- if ($.isFunction(source)) {
1086
+ if (fxn === true) {
1051
1087
  // OVERALL source
1052
1088
  arry = source(table, column, onlyAvail);
1053
- } else if ($.type(source) === 'object' && source.hasOwnProperty(column)) {
1089
+ } else if ($.type(source) === 'object' && fxn) {
1054
1090
  // custom select source function for a SPECIFIC COLUMN
1055
- arry = source[column](table, column, onlyAvail);
1091
+ arry = fxn(table, column, onlyAvail);
1056
1092
  }
1057
1093
  if (arry === false) {
1058
1094
  // fall back to original method
@@ -1159,6 +1195,8 @@ ts.filter = {
1159
1195
  $filters = $filters && $filters.length ? $filters.add(wo.filter_$externalFilters) : wo.filter_$externalFilters;
1160
1196
  }
1161
1197
  $filters.filter('select[data-column="' + column + '"]')[ updating ? 'html' : 'append' ](options);
1198
+ if (!wo.filter_functions) { wo.filter_functions = {}; }
1199
+ wo.filter_functions[column] = true;
1162
1200
  },
1163
1201
  buildDefault: function(table, updating) {
1164
1202
  var columnIndex, $header,
@@ -1169,10 +1207,7 @@ ts.filter = {
1169
1207
  for (columnIndex = 0; columnIndex < columns; columnIndex++) {
1170
1208
  $header = c.$headers.filter('[data-column="' + columnIndex + '"]:last');
1171
1209
  // look for the filter-select class; build/update it if found
1172
- if (($header.hasClass('filter-select') || wo.filter_functions && wo.filter_functions[columnIndex] === true) &&
1173
- !$header.hasClass('filter-false')) {
1174
- if (!wo.filter_functions) { wo.filter_functions = {}; }
1175
- wo.filter_functions[columnIndex] = true; // make sure this select gets processed by filter_functions
1210
+ if (($header.hasClass('filter-select') || ts.getColumnData( table, wo.filter_functions, columnIndex ) === true) && !$header.hasClass('filter-false')) {
1176
1211
  ts.filter.buildSelect(table, columnIndex, updating, $header.hasClass(wo.filter_onlyAvail));
1177
1212
  }
1178
1213
  }
@@ -1244,6 +1279,7 @@ ts.setFilters = function(table, filter, apply, skipFirst) {
1244
1279
  if (c && apply) {
1245
1280
  // ensure new set filters are applied, even if the search is the same
1246
1281
  c.lastCombinedFilter = null;
1282
+ c.lastSearch = [];
1247
1283
  ts.filter.searching(c.$table[0], filter, skipFirst);
1248
1284
  c.$table.trigger('filterFomatterUpdate');
1249
1285
  }
@@ -1475,7 +1511,7 @@ ts.addWidget({
1475
1511
  var canResize,
1476
1512
  $column = $(this);
1477
1513
  column = $column.attr('data-column');
1478
- canResize = ts.getData( $column, c.headers[column], 'resizable') === "false";
1514
+ canResize = ts.getData( $column, ts.getColumnData( table, c.headers, column ), 'resizable') === "false";
1479
1515
  $rows.children().filter('[data-column="' + column + '"]')[canResize ? 'addClass' : 'removeClass']('resizable-false');
1480
1516
  });
1481
1517
  // add wrapper inside each cell to allow for positioning of the resizable target block