jquery-tablesorter 1.11.2 → 1.12.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 (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