jquery-tablesorter 1.12.8 → 1.13.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 (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);