jquery-tablesorter 1.12.8 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/jquery-tablesorter/version.rb +1 -1
  4. data/vendor/assets/javascripts/jquery-tablesorter/addons/pager/jquery.tablesorter.pager.js +115 -67
  5. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.js +62 -40
  6. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.widgets.js +251 -118
  7. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-extract.js +48 -24
  8. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-iso8601.js +5 -4
  9. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-month.js +16 -10
  10. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-two-digit-year.js +26 -19
  11. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-weekday.js +16 -10
  12. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date.js +7 -4
  13. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-named-numbers.js +121 -0
  14. data/vendor/assets/javascripts/jquery-tablesorter/parsers/{parser-ipv6.js → parser-network.js} +66 -17
  15. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-columnSelector.js +10 -7
  16. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-cssStickyHeaders.js +62 -35
  17. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-editable.js +197 -174
  18. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-grouping.js +3 -6
  19. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-pager.js +145 -62
  20. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-repeatheaders.js +1 -1
  21. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-scroller.js +16 -14
  22. data/vendor/assets/stylesheets/jquery-tablesorter/theme.black-ice.css +2 -2
  23. data/vendor/assets/stylesheets/jquery-tablesorter/theme.blue.css +2 -2
  24. data/vendor/assets/stylesheets/jquery-tablesorter/theme.bootstrap_2.css +2 -2
  25. data/vendor/assets/stylesheets/jquery-tablesorter/theme.dark.css +2 -2
  26. data/vendor/assets/stylesheets/jquery-tablesorter/theme.default.css +2 -2
  27. data/vendor/assets/stylesheets/jquery-tablesorter/theme.dropbox.css +2 -2
  28. data/vendor/assets/stylesheets/jquery-tablesorter/theme.green.css +2 -2
  29. data/vendor/assets/stylesheets/jquery-tablesorter/theme.grey.css +2 -2
  30. data/vendor/assets/stylesheets/jquery-tablesorter/theme.ice.css +2 -2
  31. data/vendor/assets/stylesheets/jquery-tablesorter/theme.jui.css +4 -1
  32. data/vendor/assets/stylesheets/jquery-tablesorter/theme.metro-dark.css +4 -4
  33. metadata +4 -3
@@ -1,4 +1,4 @@
1
- /*! tableSorter 2.16+ widgets - updated 9/15/2014 (v2.17.8)
1
+ /*! tableSorter 2.16+ widgets - updated 10/26/2014 (v2.18.0)
2
2
  *
3
3
  * Column Styles
4
4
  * Column Filters
@@ -9,8 +9,8 @@
9
9
  * [ "columns", "filter", "resizable", "stickyHeaders", "uitheme", "saveSort" ]
10
10
  */
11
11
  /*jshint browser:true, jquery:true, unused:false, loopfunc:true */
12
- /*global jQuery: false, localStorage: false, navigator: false */
13
- ;(function($) {
12
+ /*global jQuery: false, localStorage: false */
13
+ ;(function ($, window) {
14
14
  "use strict";
15
15
  var ts = $.tablesorter = $.tablesorter || {};
16
16
 
@@ -33,7 +33,7 @@ ts.themes = {
33
33
  },
34
34
  "jui" : {
35
35
  table : 'ui-widget ui-widget-content ui-corner-all', // table classes
36
- caption : 'ui-widget-content ui-corner-all',
36
+ caption : 'ui-widget-content',
37
37
  header : 'ui-widget-header ui-corner-all ui-state-default', // header classes
38
38
  footerRow : '',
39
39
  footerCells: '',
@@ -55,7 +55,8 @@ $.extend(ts.css, {
55
55
  wrapper : 'tablesorter-wrapper', // ui theme & resizable
56
56
  resizer : 'tablesorter-resizer', // resizable
57
57
  sticky : 'tablesorter-stickyHeader', // stickyHeader
58
- stickyVis : 'tablesorter-sticky-visible'
58
+ stickyVis : 'tablesorter-sticky-visible',
59
+ stickyWrap: 'tablesorter-sticky-wrapper'
59
60
  });
60
61
 
61
62
  // *** Store data in local storage, with a cookie fallback ***
@@ -176,34 +177,42 @@ ts.addWidget({
176
177
  id: "uitheme",
177
178
  priority: 10,
178
179
  format: function(table, c, wo) {
179
- var i, time, classes, $header, $icon, $tfoot, $h,
180
+ var i, time, classes, $header, $icon, $tfoot, $h, oldtheme, oldremove,
180
181
  themesAll = ts.themes,
181
182
  $table = c.$table,
182
183
  $headers = c.$headers,
183
184
  theme = c.theme || 'jui',
184
185
  themes = themesAll[theme] || themesAll.jui,
185
- remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc;
186
+ remove = [ themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active ].join( ' ' );
186
187
  if (c.debug) { time = new Date(); }
187
188
  // initialization code - run once
188
- if (!$table.hasClass('tablesorter-' + theme) || c.theme === theme || !table.hasInitialized) {
189
+ if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !table.hasInitialized) {
190
+ oldtheme = themes[c.appliedTheme] || {};
191
+ oldremove = oldtheme ? [ oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active ].join( ' ' ) : '';
192
+ if (oldtheme) {
193
+ wo.zebra[0] = wo.zebra[0].replace(' ' + oldtheme.even, '');
194
+ wo.zebra[1] = wo.zebra[1].replace(' ' + oldtheme.odd, '');
195
+ }
189
196
  // update zebra stripes
190
197
  if (themes.even !== '') { wo.zebra[0] += ' ' + themes.even; }
191
198
  if (themes.odd !== '') { wo.zebra[1] += ' ' + themes.odd; }
192
199
  // add caption style
193
- $table.find('caption').addClass(themes.caption);
200
+ $table.children('caption').removeClass(oldtheme.caption).addClass(themes.caption);
194
201
  // add table/footer class names
195
202
  $tfoot = $table
196
203
  // remove other selected themes
197
- .removeClass( c.theme === '' ? '' : 'tablesorter-' + c.theme )
204
+ .removeClass( c.appliedTheme ? 'tablesorter-' + ( c.appliedTheme || '' ) : '' )
198
205
  .addClass('tablesorter-' + theme + ' ' + themes.table) // add theme widget class name
199
- .find('tfoot');
206
+ .children('tfoot');
200
207
  if ($tfoot.length) {
201
208
  $tfoot
202
- .find('tr').addClass(themes.footerRow)
203
- .children('th, td').addClass(themes.footerCells);
209
+ .children('tr').removeClass(oldtheme.footerRow).addClass(themes.footerRow)
210
+ .children('th, td').removeClass(oldtheme.footerCells).addClass(themes.footerCells);
204
211
  }
205
212
  // update header classes
206
213
  $headers
214
+ .add(c.$extraHeaders)
215
+ .removeClass(oldtheme.header + ' ' + oldtheme.hover + ' ' + oldremove)
207
216
  .addClass(themes.header)
208
217
  .not('.sorter-false')
209
218
  .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) {
@@ -216,16 +225,17 @@ ts.addWidget({
216
225
  }
217
226
  if (c.cssIcon) {
218
227
  // if c.cssIcon is '', then no <i> is added to the header
219
- $headers.find('.' + ts.css.icon).addClass(themes.icons);
228
+ $headers.find('.' + ts.css.icon).removeClass(oldtheme.icons + ' ' + oldremove).addClass(themes.icons);
220
229
  }
221
230
  if ($table.hasClass('hasFilters')) {
222
- $headers.find('.' + ts.css.filterRow).addClass(themes.filterRow);
231
+ $table.children('thead').children('.' + ts.css.filterRow).removeClass(oldtheme.filterRow).addClass(themes.filterRow);
223
232
  }
233
+ c.appliedTheme = c.theme;
224
234
  }
225
235
  for (i = 0; i < c.columns; i++) {
226
- $header = c.$headers.add(c.$extraHeaders).filter('[data-column="' + i + '"]');
236
+ $header = c.$headers.add(c.$extraHeaders).not('.sorter-false').filter('[data-column="' + i + '"]');
227
237
  $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $header;
228
- $h = c.$headers.filter('[data-column="' + i + '"]:last');
238
+ $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last');
229
239
  if ($h.length) {
230
240
  if ($h[0].sortDisabled) {
231
241
  // no sort arrows for disabled columns!
@@ -274,7 +284,7 @@ ts.addWidget({
274
284
  columns : [ "primary", "secondary", "tertiary" ]
275
285
  },
276
286
  format: function(table, c, wo) {
277
- var time, $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx,
287
+ var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx,
278
288
  $table = c.$table,
279
289
  $tbodies = c.$tbodies,
280
290
  sortList = c.sortList,
@@ -283,9 +293,6 @@ ts.addWidget({
283
293
  css = wo && wo.columns || [ "primary", "secondary", "tertiary" ],
284
294
  last = css.length - 1;
285
295
  remove = css.join(' ');
286
- if (c.debug) {
287
- time = new Date();
288
- }
289
296
  // check if there is a sort (on initialization there may not be one)
290
297
  for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
291
298
  $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody
@@ -325,9 +332,6 @@ ts.addWidget({
325
332
  }
326
333
  }
327
334
  }
328
- if (c.debug) {
329
- ts.benchmark("Applying Columns widget", time);
330
- }
331
335
  },
332
336
  remove: function(table, c, wo) {
333
337
  var tbodyIndex, $tbody,
@@ -353,6 +357,7 @@ ts.addWidget({
353
357
  options : {
354
358
  filter_childRows : false, // if true, filter includes child row content in the search
355
359
  filter_columnFilters : true, // if true, a filter will be added to the top of each table column
360
+ filter_cellFilter : '', // css class name added to the filter cell (string or array)
356
361
  filter_cssFilter : '', // css class name added to the filter row & each input in the row (tablesorter-filter is ALWAYS added)
357
362
  filter_defaultFilter : {}, // add a default column filter type "~{query}" to make fuzzy searches default; "{q1} AND {q2}" to make all searches use a logical AND.
358
363
  filter_excludeFilter : {}, // filters to exclude, per column
@@ -410,17 +415,18 @@ ts.filter = {
410
415
  child : /tablesorter-childRow/, // child row class name; this gets updated in the script
411
416
  filtered : /filtered/, // filtered (hidden) row class name; updated in the script
412
417
  type : /undefined|number/, // check type
413
- exact : /(^[\"|\'|=]+)|([\"|\'|=]+$)/g, // exact match (allow '==')
418
+ exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==')
414
419
  nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser)
415
420
  operators : /[<>=]/g, // replace operators
416
421
  query : '(q|query)' // replace filter queries
417
422
  },
418
423
  // function( c, data ) { }
419
424
  // c = table.config
420
- // data.filter = array of filter input values; data.iFilter = same array, except lowercase
425
+ // data.filter = array of filter input values;
426
+ // data.iFilter = same array, except lowercase (if wo.filter_ignoreCase is true)
421
427
  // data.exact = table cell text (or parsed data if column parser enabled)
422
- // data.iExact = same as data.exact, except lowercase
423
- // data.cache = table cell text from cache, so it has been parsed
428
+ // data.iExact = same as data.exact, except lowercase (if wo.filter_ignoreCase is true)
429
+ // data.cache = table cell text from cache, so it has been parsed (& in all lower case if config.ignoreCase is true)
424
430
  // data.index = column index; table = table element (DOM)
425
431
  // data.parsed = array (by column) of boolean values (from filter_useParsedData or "filter-parsed" class)
426
432
  types: {
@@ -537,12 +543,14 @@ ts.filter = {
537
543
  },
538
544
  // Look for wild card: ? = single, * = multiple, or | = logical OR
539
545
  wild : function( c, data ) {
540
- if ( /[\?|\*]/.test(data.iFilter) || ts.filter.regex.orReplace.test(data.filter) ) {
546
+ if ( /[\?\*\|]/.test(data.iFilter) || ts.filter.regex.orReplace.test(data.filter) ) {
541
547
  var index = data.index,
542
548
  parsed = data.parsed[index],
543
549
  query = ts.filter.parseFilter(c, data.iFilter.replace(ts.filter.regex.orReplace, "|"), index, parsed);
544
550
  // look for an exact match with the "or" unless the "filter-match" class is found
545
551
  if (!c.$headers.filter('[data-column="' + index + '"]:last').hasClass('filter-match') && /\|/.test(query)) {
552
+ // show all results while using filter match. Fixes #727
553
+ if (query[ query.length - 1 ] === '|') { query += '*'; }
546
554
  query = data.anyMatch && $.isArray(data.rowArray) ? '(' + query + ')' : '^(' + query + ')$';
547
555
  }
548
556
  // parsing the filter may not work properly when using wildcards =/
@@ -578,11 +586,8 @@ ts.filter = {
578
586
  and : 'and'
579
587
  }, ts.language);
580
588
 
581
- var options, string, txt, $header, column, filters, val, time, fxn, noSelect,
589
+ var options, string, txt, $header, column, filters, val, fxn, noSelect,
582
590
  regex = ts.filter.regex;
583
- if (c.debug) {
584
- time = new Date();
585
- }
586
591
  c.$table.addClass('hasFilters');
587
592
 
588
593
  // define timers so using clearTimeout won't cause an undefined error
@@ -591,6 +596,8 @@ ts.filter = {
591
596
  wo.filter_formatterCount = 0;
592
597
  wo.filter_formatterInit = [];
593
598
  wo.filter_initializing = true;
599
+ wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]';
600
+ wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]';
594
601
 
595
602
  txt = '\\{' + ts.filter.regex.query + '\\}';
596
603
  $.extend( regex, {
@@ -714,9 +721,6 @@ ts.filter = {
714
721
  // set filtered rows count (intially unfiltered)
715
722
  c.filteredRows = c.totalRows;
716
723
 
717
- if (c.debug) {
718
- ts.benchmark("Applying Filter widget", time);
719
- }
720
724
  // add default values
721
725
  c.$table.bind('tablesorter-initialized pagerInitialized', function() {
722
726
  // redefine "wo" as it does not update properly inside this callback
@@ -758,9 +762,10 @@ ts.filter = {
758
762
  var wo = c.widgetOptions,
759
763
  count = 0,
760
764
  completed = function(){
761
- wo.filter_initialized = true;
765
+ // set initializing false first so findRows will process
762
766
  wo.filter_initializing = false;
763
767
  ts.filter.findRows(c.table, c.$table.data('lastSearch'), null);
768
+ wo.filter_initialized = true;
764
769
  c.$table.trigger('filterInit', c);
765
770
  };
766
771
  $.each( wo.filter_formatterInit, function(i, val) {
@@ -780,7 +785,7 @@ ts.filter = {
780
785
  }, 500);
781
786
  }
782
787
  },
783
-
788
+
784
789
  setDefaults: function(table, c, wo) {
785
790
  var isArray, saved, indx,
786
791
  // get current (default) filters
@@ -809,9 +814,14 @@ ts.filter = {
809
814
  var col, column, $header, buildSelect, disabled, name, ffxn,
810
815
  // c.columns defined in computeThIndexes()
811
816
  columns = c.columns,
817
+ arry = $.isArray(wo.filter_cellFilter),
812
818
  buildFilter = '<tr role="row" class="' + ts.css.filterRow + '">';
813
819
  for (column = 0; column < columns; column++) {
814
- buildFilter += '<td></td>';
820
+ if (arry) {
821
+ buildFilter += '<td' + ( wo.filter_cellFilter[column] ? ' class="' + wo.filter_cellFilter[column] + '"' : '' ) + '></td>';
822
+ } else {
823
+ buildFilter += '<td' + ( wo.filter_cellFilter !== '' ? ' class="' + wo.filter_cellFilter + '"' : '' ) + '></td>';
824
+ }
815
825
  }
816
826
  c.$filters = $(buildFilter += '</tr>').appendTo( c.$table.children('thead').eq(0) ).find('td');
817
827
  // build each filter input
@@ -870,7 +880,7 @@ ts.filter = {
870
880
  $ext = wo.filter_$externalFilters;
871
881
  if (internal !== true) {
872
882
  // save anyMatch element
873
- wo.filter_$anyMatch = $el.filter('[data-column="all"]');
883
+ wo.filter_$anyMatch = $el.filter(wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector);
874
884
  if ($ext && $ext.length) {
875
885
  wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el );
876
886
  } else {
@@ -1033,6 +1043,57 @@ ts.filter = {
1033
1043
  }
1034
1044
  return val;
1035
1045
  },
1046
+ getLatestSearch: function( $input ) {
1047
+ return $input.sort(function(a, b) {
1048
+ return $(b).attr('data-lastSearchTime') - $(a).attr('data-lastSearchTime');
1049
+ });
1050
+ },
1051
+ multipleColumns: function( c, $input ) {
1052
+ // look for multiple columns "1-3,4-6,8" in data-column
1053
+ var ranges, singles, indx,
1054
+ wo = c.widgetOptions,
1055
+ // only target "all" column inputs on initialization
1056
+ // & don't target "all" column inputs if they don't exist
1057
+ targets = wo.filter_initialized || !$input.filter(wo.filter_anyColumnSelector).length,
1058
+ columns = [],
1059
+ val = $.trim( ts.filter.getLatestSearch( $input ).attr('data-column') );
1060
+ // process column range
1061
+ if ( targets && /-/.test( val ) ) {
1062
+ ranges = val.match( /(\d+)\s*-\s*(\d+)/g );
1063
+ $.each(ranges, function(i,v){
1064
+ var t,
1065
+ range = v.split( /\s*-\s*/ ),
1066
+ start = parseInt( range[0], 10 ) || 0,
1067
+ end = parseInt( range[1], 10 ) || ( c.columns - 1 );
1068
+ if ( start > end ) { t = start; start = end; end = t; } // swap
1069
+ if ( end >= c.columns ) { end = c.columns - 1; }
1070
+ for ( ; start <= end; start++ ) {
1071
+ columns.push(start);
1072
+ }
1073
+ // remove processed range from val
1074
+ val = val.replace( v, '' );
1075
+ });
1076
+ }
1077
+ // process single columns
1078
+ if ( targets && /,/.test( val ) ) {
1079
+ singles = val.split( /\s*,\s*/ );
1080
+ $.each( singles, function(i,v) {
1081
+ if (v !== '') {
1082
+ indx = parseInt( v, 10 );
1083
+ if ( indx < c.columns ) {
1084
+ columns.push( indx );
1085
+ }
1086
+ }
1087
+ });
1088
+ }
1089
+ // return all columns
1090
+ if (!columns.length) {
1091
+ for ( indx = 0; indx < c.columns; indx++ ) {
1092
+ columns.push( indx );
1093
+ }
1094
+ }
1095
+ return columns;
1096
+ },
1036
1097
  findRows: function(table, filters, combinedFilters) {
1037
1098
  if (table.config.lastCombinedFilter === combinedFilters || table.config.widgetOptions.filter_initializing) { return; }
1038
1099
  var len, $rows, rowIndex, tbodyIndex, $tbody, $cells, $cell, columnIndex,
@@ -1092,7 +1153,7 @@ ts.filter = {
1092
1153
  // if we are not doing exact matches, using "|" (logical or) or not "!"
1093
1154
  !/[=\"\|!]/.test(val) &&
1094
1155
  // don't search only filtered if the value is negative ('> -10' => '> -100' will ignore hidden rows)
1095
- !(/(>=?\s*-\d)/.test(val) || /(<=?\s*\d)/.test(val)) &&
1156
+ !(/(>=?\s*-\d)/.test(val) || /(<=?\s*\d)/.test(val)) &&
1096
1157
  // if filtering using a select without a "filter-match" class (exact match) - fixes #593
1097
1158
  !( val !== '' && c.$filters && c.$filters.eq(indx).find('select').length && !c.$headers.filter('[data-column="' + indx + '"]:last').hasClass('filter-match') );
1098
1159
  }
@@ -1105,7 +1166,7 @@ ts.filter = {
1105
1166
  }
1106
1167
  if ((wo.filter_$anyMatch && wo.filter_$anyMatch.length) || filters[c.columns]) {
1107
1168
  data.anyMatchFlag = true;
1108
- data.anyMatchFilter = wo.filter_$anyMatch && wo.filter_$anyMatch.val() || filters[c.columns] || '';
1169
+ data.anyMatchFilter = wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || filters[c.columns] || '';
1109
1170
  if (c.sortLocaleCompare) {
1110
1171
  // replace accents
1111
1172
  data.anyMatchFilter = ts.replaceAccents(data.anyMatchFilter);
@@ -1115,7 +1176,9 @@ ts.filter = {
1115
1176
  // clear search filtered flag because default filters are not saved to the last search
1116
1177
  searchFiltered = false;
1117
1178
  }
1118
- data.iAnyMatchFilter = data.anyMatchFilter;
1179
+ // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true
1180
+ // when c.ignoreCase is true, the cache contains all lower case data
1181
+ data.iAnyMatchFilter = !(wo.filter_ignoreCase && c.ignoreCase) ? data.anyMatchFilter : data.anyMatchFilter.toLocaleLowerCase();
1119
1182
  }
1120
1183
 
1121
1184
  // loop through the rows
@@ -1135,25 +1198,28 @@ ts.filter = {
1135
1198
  data.childRowText = (childRow.length && wo.filter_childRows) ? childRow.text() : '';
1136
1199
  data.childRowText = wo.filter_ignoreCase ? data.childRowText.toLocaleLowerCase() : data.childRowText;
1137
1200
  $cells = $rows.eq(rowIndex).children();
1138
-
1139
1201
  if (data.anyMatchFlag) {
1202
+ // look for multiple columns "1-3,4-6,8"
1203
+ columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
1140
1204
  data.anyMatch = true;
1141
1205
  data.rowArray = $cells.map(function(i){
1142
- var txt;
1143
- if (data.parsed[i]) {
1144
- txt = data.cacheArray[i];
1145
- } else {
1146
- txt = wo.filter_ignoreCase ? $(this).text().toLowerCase() : $(this).text();
1147
- if (c.sortLocaleCompare) {
1148
- txt = ts.replaceAccents(txt);
1206
+ if ( $.inArray(i, columnIndex) > -1 ) {
1207
+ var txt;
1208
+ if (data.parsed[i]) {
1209
+ txt = data.cacheArray[i];
1210
+ } else {
1211
+ txt = wo.filter_ignoreCase ? $(this).text().toLowerCase() : $(this).text();
1212
+ if (c.sortLocaleCompare) {
1213
+ txt = ts.replaceAccents(txt);
1214
+ }
1149
1215
  }
1216
+ return txt;
1150
1217
  }
1151
- return txt;
1152
1218
  }).get();
1153
1219
  data.filter = data.anyMatchFilter;
1154
1220
  data.iFilter = data.iAnyMatchFilter;
1155
1221
  data.exact = data.rowArray.join(' ');
1156
- data.iExact = data.exact.toLowerCase();
1222
+ data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
1157
1223
  data.cache = data.cacheArray.slice(0,-1).join(' ');
1158
1224
  filterMatched = null;
1159
1225
  $.each(ts.filter.types, function(type, typeFunction) {
@@ -1204,7 +1270,7 @@ ts.filter = {
1204
1270
  result = showRow; // if showRow is true, show that row
1205
1271
 
1206
1272
  // in case select filter option has a different value vs text "a - z|A through Z"
1207
- ffxn = wo.filter_columnFilters ?
1273
+ ffxn = wo.filter_columnFilters ?
1208
1274
  c.$filters.add(c.$externalFilters).filter('[data-column="'+ columnIndex + '"]').find('select option:selected').attr('data-function-name') || '' : '';
1209
1275
 
1210
1276
  // replace accents - see #357
@@ -1216,7 +1282,7 @@ ts.filter = {
1216
1282
  // val is used to indicate that a filter select is using a default filter; so we override the exact & partial matches
1217
1283
  val = false;
1218
1284
  }
1219
- // data.iFilter = case insensitive, data.filter = case sensitive
1285
+ // data.iFilter = case insensitive (if wo.filter_ignoreCase is true), data.filter = case sensitive
1220
1286
  data.iFilter = wo.filter_ignoreCase ? (data.filter || '').toLocaleLowerCase() : data.filter;
1221
1287
  fxn = ts.getColumnData( table, wo.filter_functions, columnIndex );
1222
1288
  $cell = c.$headers.filter('[data-column="' + columnIndex + '"]:last');
@@ -1461,7 +1527,7 @@ ts.filter = {
1461
1527
  };
1462
1528
 
1463
1529
  ts.getFilters = function(table, getRaw, setFilters, skipFirst) {
1464
- var i, $filters, $column,
1530
+ var i, f, $filters, $column, cols,
1465
1531
  filters = false,
1466
1532
  c = table ? $(table)[0].config : '',
1467
1533
  wo = c ? c.widgetOptions : '';
@@ -1478,19 +1544,34 @@ ts.getFilters = function(table, getRaw, setFilters, skipFirst) {
1478
1544
  if ($filters && $filters.length) {
1479
1545
  filters = setFilters || [];
1480
1546
  for (i = 0; i < c.columns + 1; i++) {
1481
- $column = $filters.filter('[data-column="' + (i === c.columns ? 'all' : i) + '"]');
1547
+ cols = ( i === c.columns ?
1548
+ // "all" columns can now include a range or set of columms (data-column="0-2,4,6-7")
1549
+ wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector :
1550
+ '[data-column="' + i + '"]' );
1551
+ $column = $filters.filter(cols);
1482
1552
  if ($column.length) {
1483
1553
  // move the latest search to the first slot in the array
1484
- $column = $column.sort(function(a, b){
1485
- return $(b).attr('data-lastSearchTime') - $(a).attr('data-lastSearchTime');
1486
- });
1554
+ $column = ts.filter.getLatestSearch( $column );
1487
1555
  if ($.isArray(setFilters)) {
1488
1556
  // skip first (latest input) to maintain cursor position while typing
1489
- (skipFirst ? $column.slice(1) : $column).val( setFilters[i] ).trigger('change.tsfilter');
1557
+ if (skipFirst) { $column.slice(1); }
1558
+ if (i === c.columns) {
1559
+ // prevent data-column="all" from filling data-column="0,1" (etc)
1560
+ cols = $column.filter(wo.filter_anyColumnSelector);
1561
+ $column = cols.length ? cols : $column;
1562
+ }
1563
+ $column
1564
+ .val( setFilters[i] )
1565
+ .trigger('change.tsfilter');
1490
1566
  } else {
1491
1567
  filters[i] = $column.val() || '';
1492
1568
  // don't change the first... it will move the cursor
1493
- $column.slice(1).val( filters[i] );
1569
+ if (i === c.columns) {
1570
+ // don't update range columns from "all" setting
1571
+ $column.slice(1).filter('[data-column*="' + $column.attr('data-column') + '"]').val( filters[i] );
1572
+ } else {
1573
+ $column.slice(1).val( filters[i] );
1574
+ }
1494
1575
  }
1495
1576
  // save any match input dynamically
1496
1577
  if (i === c.columns && $column.length) {
@@ -1530,6 +1611,8 @@ ts.addWidget({
1530
1611
  options: {
1531
1612
  stickyHeaders : '', // extra class name added to the sticky header row
1532
1613
  stickyHeaders_attachTo : null, // jQuery selector or object to attach sticky header to
1614
+ stickyHeaders_xScroll : null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window)
1615
+ stickyHeaders_yScroll : null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window)
1533
1616
  stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element
1534
1617
  stickyHeaders_filteredToTop: true, // scroll table top into view after filtering
1535
1618
  stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists
@@ -1543,54 +1626,74 @@ ts.addWidget({
1543
1626
  return;
1544
1627
  }
1545
1628
  var $table = c.$table,
1546
- $attach = $(wo.stickyHeaders_attachTo || 'window'),
1629
+ $attach = $(wo.stickyHeaders_attachTo),
1630
+ namespace = c.namespace + 'stickyheaders ',
1631
+ // element to watch for the scroll event
1632
+ $yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window),
1633
+ $xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window),
1547
1634
  $thead = $table.children('thead:first'),
1548
- $win = $attach.length ? $attach : $(window),
1549
1635
  $header = $thead.children('tr').not('.sticky-false').children(),
1550
- innerHeader = '.' + ts.css.headerIn,
1551
- $tfoot = $table.find('tfoot'),
1636
+ $tfoot = $table.children('tfoot'),
1552
1637
  $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '',
1553
1638
  stickyOffset = $attach.length ? 0 : $stickyOffset.length ?
1554
1639
  $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0,
1640
+ // is this table nested? If so, find parent sticky header wrapper (div, not table)
1641
+ $nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ?
1642
+ $table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [],
1643
+ nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0,
1644
+ // clone table, then wrap to make sticky header
1555
1645
  $stickyTable = wo.$sticky = $table.clone()
1556
- .addClass('containsStickyHeaders')
1557
- .css({
1558
- position : $attach.length ? 'absolute' : 'fixed',
1559
- margin : 0,
1560
- top : stickyOffset,
1561
- left : 0,
1562
- visibility : 'hidden',
1563
- zIndex : wo.stickyHeaders_zIndex ? wo.stickyHeaders_zIndex : 2
1564
- }),
1565
- $stickyThead = $stickyTable.children('thead:first').addClass(ts.css.sticky + ' ' + wo.stickyHeaders),
1646
+ .addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders)
1647
+ .wrap('<div class="' + ts.css.stickyWrap + '">'),
1648
+ $stickyWrap = $stickyTable.parent().css({
1649
+ position : $attach.length ? 'absolute' : 'fixed',
1650
+ margin : 0,
1651
+ top : stickyOffset + nestedStickyTop,
1652
+ left : 0,
1653
+ visibility : 'hidden',
1654
+ zIndex : wo.stickyHeaders_zIndex || 2
1655
+ }),
1656
+ $stickyThead = $stickyTable.children('thead:first'),
1566
1657
  $stickyCells,
1567
1658
  laststate = '',
1568
1659
  spacing = 0,
1569
- nonwkie = $table.css('border-collapse') !== 'collapse' && !/(webkit|msie)/i.test(navigator.userAgent),
1660
+ setWidth = function($orig, $clone){
1661
+ $orig.filter(':visible').each(function(i) {
1662
+ var width, border,
1663
+ $cell = $clone.filter(':visible').eq(i),
1664
+ $this = $(this);
1665
+ // code from https://github.com/jmosbech/StickyTableHeaders
1666
+ if ($this.css('box-sizing') === 'border-box') {
1667
+ width = $this.outerWidth();
1668
+ } else {
1669
+ if ($cell.css('border-collapse') === 'collapse') {
1670
+ if (window.getComputedStyle) {
1671
+ width = parseFloat( window.getComputedStyle(this, null).width );
1672
+ } else {
1673
+ // ie8 only
1674
+ border = parseFloat( $this.css('border-width') );
1675
+ width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border;
1676
+ }
1677
+ } else {
1678
+ width = $this.width();
1679
+ }
1680
+ }
1681
+ $cell.css({
1682
+ 'min-width': width,
1683
+ 'max-width': width
1684
+ });
1685
+ });
1686
+ },
1570
1687
  resizeHeader = function() {
1571
1688
  stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0;
1572
1689
  spacing = 0;
1573
- // yes, I dislike browser sniffing, but it really is needed here :(
1574
- // webkit automatically compensates for border spacing
1575
- if (nonwkie) {
1576
- // Firefox & Opera use the border-spacing
1577
- // update border-spacing here because of demos that switch themes
1578
- spacing = parseInt($header.eq(0).css('border-left-width'), 10) * 2;
1579
- }
1580
- $stickyTable.css({
1581
- left : $attach.length ? (parseInt($attach.css('padding-left'), 10) || 0) + parseInt(c.$table.css('padding-left'), 10) +
1582
- parseInt(c.$table.css('margin-left'), 10) + parseInt($table.css('border-left-width'), 10) :
1583
- $thead.offset().left - $win.scrollLeft() - spacing,
1584
- width: $table.width()
1585
- });
1586
- $stickyCells.filter(':visible').each(function(i) {
1587
- var $cell = $header.filter(':visible').eq(i),
1588
- // some wibbly-wobbly... timey-wimey... stuff, to make columns line up in Firefox
1589
- offset = nonwkie && $(this).attr('data-column') === ( '' + parseInt(c.columns/2, 10) ) ? 1 : 0;
1590
- $(this)
1591
- .css({ width: $cell.width() - spacing })
1592
- .find(innerHeader).width( $cell.find(innerHeader).width() - offset );
1690
+ $stickyWrap.css({
1691
+ left : $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 :
1692
+ $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing,
1693
+ width: $table.outerWidth()
1593
1694
  });
1695
+ setWidth( $table, $stickyTable );
1696
+ setWidth( $header, $stickyCells );
1594
1697
  };
1595
1698
  // fix clone ID, if it exists - fixes #271
1596
1699
  if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; }
@@ -1600,42 +1703,60 @@ ts.addWidget({
1600
1703
  $stickyTable.find('tbody, tfoot').remove();
1601
1704
  if (!wo.stickyHeaders_includeCaption) {
1602
1705
  $stickyTable.find('caption').remove();
1603
- } else {
1604
- $stickyTable.find('caption').css( 'margin-left', '-1px' );
1605
1706
  }
1606
1707
  // issue #172 - find td/th in sticky header
1607
1708
  $stickyCells = $stickyThead.children().children();
1608
- $stickyTable.css({ height:0, width:0, padding:0, margin:0, border:0 });
1709
+ $stickyTable.css({ height:0, width:0, margin: 0 });
1609
1710
  // remove resizable block
1610
1711
  $stickyCells.find('.' + ts.css.resizer).remove();
1611
1712
  // update sticky header class names to match real header after sorting
1612
1713
  $table
1613
1714
  .addClass('hasStickyHeaders')
1614
- .bind('pagerComplete.tsSticky', function() {
1715
+ .bind('pagerComplete' + namespace, function() {
1615
1716
  resizeHeader();
1616
1717
  });
1617
1718
 
1618
1719
  ts.bindEvents(table, $stickyThead.children().children('.tablesorter-header'));
1619
1720
 
1620
1721
  // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned.
1621
- $table.after( $stickyTable );
1722
+ $table.after( $stickyWrap );
1723
+
1724
+ // onRenderHeader is defined, we need to do something about it (fixes #641)
1725
+ if (c.onRenderHeader) {
1726
+ $stickyThead.children('tr').children().each(function(index){
1727
+ // send second parameter
1728
+ c.onRenderHeader.apply( $(this), [ index, c, $stickyTable ] );
1729
+ });
1730
+ }
1731
+
1622
1732
  // make it sticky!
1623
- $win.bind('scroll.tsSticky resize.tsSticky', function(event) {
1733
+ $xScroll.add($yScroll)
1734
+ .unbind('scroll resize '.split(' ').join( namespace ) )
1735
+ .bind('scroll resize '.split(' ').join( namespace ), function(event) {
1624
1736
  if (!$table.is(':visible')) { return; } // fixes #278
1737
+ // Detect nested tables - fixes #724
1738
+ nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0;
1625
1739
  var prefix = 'tablesorter-sticky-',
1626
1740
  offset = $table.offset(),
1627
- captionHeight = (wo.stickyHeaders_includeCaption ? 0 : $table.find('caption').outerHeight(true)),
1628
- scrollTop = ($attach.length ? $attach.offset().top : $win.scrollTop()) + stickyOffset - captionHeight,
1629
- tableHeight = $table.height() - ($stickyTable.height() + ($tfoot.height() || 0)),
1630
- isVisible = (scrollTop > offset.top) && (scrollTop < offset.top + tableHeight) ? 'visible' : 'hidden',
1741
+ yWindow = $.isWindow( $yScroll[0] ),
1742
+ xWindow = $.isWindow( $xScroll[0] ),
1743
+ // scrollTop = ( $attach.length ? $attach.offset().top : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop,
1744
+ scrollTop = ( $attach.length ? ( yWindow ? $yScroll.scrollTop() : $yScroll.offset().top ) : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop,
1745
+ tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)),
1746
+ isVisible = ( scrollTop > offset.top) && (scrollTop < offset.top + tableHeight) ? 'visible' : 'hidden',
1631
1747
  cssSettings = { visibility : isVisible };
1748
+
1632
1749
  if ($attach.length) {
1633
- cssSettings.top = $attach.scrollTop();
1634
- } else {
1750
+ cssSettings.top = yWindow ? scrollTop : $attach.scrollTop();
1751
+ }
1752
+ if (xWindow) {
1635
1753
  // adjust when scrolling horizontally - fixes issue #143
1636
- cssSettings.left = $thead.offset().left - $win.scrollLeft() - spacing;
1754
+ cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing;
1637
1755
  }
1638
- $stickyTable
1756
+ if ($nestedSticky.length) {
1757
+ cssSettings.top = ( cssSettings.top || 0 ) + stickyOffset + nestedStickyTop;
1758
+ }
1759
+ $stickyWrap
1639
1760
  .removeClass(prefix + 'visible ' + prefix + 'hidden')
1640
1761
  .addClass(prefix + isVisible)
1641
1762
  .css(cssSettings);
@@ -1650,14 +1771,14 @@ ts.addWidget({
1650
1771
  }
1651
1772
 
1652
1773
  // look for filter widget
1653
- if ($table.hasClass('hasFilters')) {
1774
+ if ($table.hasClass('hasFilters') && wo.filter_columnFilters) {
1654
1775
  // scroll table into view after filtering, if sticky header is active - #482
1655
- $table.bind('filterEnd', function() {
1776
+ $table.bind('filterEnd' + namespace, function() {
1656
1777
  // $(':focus') needs jQuery 1.6+
1657
1778
  var $td = $(document.activeElement).closest('td'),
1658
1779
  column = $td.parent().children().index($td);
1659
1780
  // only scroll if sticky header is active
1660
- if ($stickyTable.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) {
1781
+ if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) {
1661
1782
  // scroll to original table (not sticky clone)
1662
1783
  window.scrollTo(0, $table.position().top);
1663
1784
  // give same input/select focus; check if c.$filters exists; fixes #594
@@ -1677,14 +1798,16 @@ ts.addWidget({
1677
1798
 
1678
1799
  },
1679
1800
  remove: function(table, c, wo) {
1801
+ var namespace = c.namespace + 'stickyheaders ';
1680
1802
  c.$table
1681
1803
  .removeClass('hasStickyHeaders')
1682
- .unbind('pagerComplete.tsSticky')
1683
- .find('.' + ts.css.sticky).remove();
1804
+ .unbind( 'pagerComplete filterEnd '.split(' ').join(namespace) )
1805
+ .next('.' + ts.css.stickyWrap).remove();
1684
1806
  if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table
1685
1807
  // don't unbind if any table on the page still has stickyheaders applied
1686
1808
  if (!$('.hasStickyHeaders').length) {
1687
- $(window).unbind('scroll.tsSticky resize.tsSticky');
1809
+ $(window).add(wo.stickyHeaders_xScroll).add(wo.stickyHeaders_yScroll).add(wo.stickyHeaders_attachTo)
1810
+ .unbind( 'scroll resize '.split(' ').join(namespace) );
1688
1811
  }
1689
1812
  ts.addHeaderResizeEvent(table, false);
1690
1813
  }
@@ -1710,6 +1833,8 @@ ts.addWidget({
1710
1833
  var $rows, $columns, $column, column, timer,
1711
1834
  storedSizes = {},
1712
1835
  $table = c.$table,
1836
+ $wrap = $table.parent(),
1837
+ overflow = $table.parent().css('overflow') === 'auto',
1713
1838
  mouseXPosition = 0,
1714
1839
  $target = null,
1715
1840
  $next = null,
@@ -1722,6 +1847,14 @@ ts.addWidget({
1722
1847
  $target.width( targetWidth + leftEdge );
1723
1848
  if ($target.width() !== targetWidth && fullWidth) {
1724
1849
  $next.width( $next.width() - leftEdge );
1850
+ } else if (overflow) {
1851
+ $table.width(function(i, w){
1852
+ return w + leftEdge;
1853
+ });
1854
+ if (!$next.length) {
1855
+ // if expanding right-most column, scroll the wrapper
1856
+ $wrap[0].scrollLeft = $table.width();
1857
+ }
1725
1858
  }
1726
1859
  mouseXPosition = event.pageX;
1727
1860
  },
@@ -1909,4 +2042,4 @@ ts.addWidget({
1909
2042
  }
1910
2043
  });
1911
2044
 
1912
- })(jQuery);
2045
+ })(jQuery, window);