jquery-tablesorter 1.8.1 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e6f685058fd350d114a2f272290ad8a7e5c3f7ed
4
- data.tar.gz: e9de584dc9a659b30b9bd79dec2a863cadf04749
3
+ metadata.gz: af7c049ad22e3ce61522a2bf73e2b73526a3350d
4
+ data.tar.gz: 6d6a5a150b0f3888edc4111c795a2f8deb07c297
5
5
  SHA512:
6
- metadata.gz: 49d6f67cce6923d1ac1fb24d651673dc3162608e41dfb70349fb3ec37c61b815f5cd4462887d2f891cdf1a05a90603d09bd4d45289f6e605270590f2cb744958
7
- data.tar.gz: d8c7d5b3890bea49ed049a17600ef2695a205450e432fac80d72b1835e69eb3c8edb7fddfd4517bef9d47207107186696c1fdee1b95992a10562871d357d21a2
6
+ metadata.gz: 2cd353a474b4912ef182752de68191dc376a4cd899ac4b08e621943469f1bdfec026a7827b0abcdb9232cee10ff7462217c1032bd3ade5389a66ac94d96b811b
7
+ data.tar.gz: c9f19227c27c964a19ea663ef944150aa22b8d0ecfc9ccb1f1f74d731366379672a32651042ae0330b1b2a440e457da7d9d87669ae5226a84699d4b01bf06e93
@@ -4,7 +4,7 @@
4
4
 
5
5
  Simple integration of jquery-tablesorter into the asset pipeline.
6
6
 
7
- Current tablesorter version: 2.13.3 (11/9/2013), [documentation]
7
+ Current tablesorter version: 2.14.0 (11/19/2013), [documentation]
8
8
 
9
9
  Any issue associate with the js/css files, please report to [Mottie's fork].
10
10
 
@@ -1,3 +1,3 @@
1
1
  module JqueryTablesorter
2
- VERSION = "1.8.1"
2
+ VERSION = "1.9.0"
3
3
  end
@@ -1,5 +1,5 @@
1
1
  /**!
2
- * TableSorter 2.13.3 - Client-side table sorting with ease!
2
+ * TableSorter 2.14.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.13.3";
27
+ ts.version = "2.14.0";
28
28
 
29
29
  ts.parsers = [];
30
30
  ts.widgets = [];
@@ -41,6 +41,7 @@
41
41
 
42
42
  // *** functionality
43
43
  cancelSelection : true, // prevent text selection in the header
44
+ tabIndex : true, // add tabindex to header for keyboard accessibility
44
45
  dateFormat : 'mmddyyyy', // other options: "ddmmyyy" or "yyyymmdd"
45
46
  sortMultiSortKey : 'shiftKey', // key used to select additional columns
46
47
  sortResetKey : 'ctrlKey', // key used to remove sorting on a column
@@ -54,6 +55,7 @@
54
55
  sortForce : null, // column(s) first sorted; always applied
55
56
  sortList : [], // Initial sort order; applied initially; updated when manually sorted
56
57
  sortAppend : null, // column(s) sorted last; always applied
58
+ sortStable : false, // when sorting two rows with exactly the same content, the original sort order is maintained
57
59
 
58
60
  sortInitialOrder : 'asc', // sort direction on first click
59
61
  sortLocaleCompare: false, // replace equivalent character (accented characters)
@@ -413,7 +415,8 @@
413
415
  if (c.debug) {
414
416
  time = new Date();
415
417
  }
416
- i = c.cssIcon ? '<i class="' + c.cssIcon + ' ' + ts.css.icon + '"></i>' : ''; // add icon if cssIcon option exists
418
+ // add icon if cssIcon option exists
419
+ i = c.cssIcon ? '<i class="' + ( c.cssIcon === ts.css.icon ? ts.css.icon : c.cssIcon + ' ' + ts.css.icon ) + '"></i>' : '';
417
420
  c.$headers = $(table).find(c.selectorHeaders).each(function(index) {
418
421
  $t = $(this);
419
422
  ch = c.headers[index];
@@ -442,7 +445,7 @@
442
445
  // add to parent in case there are multiple rows
443
446
  $t.parent().addClass(ts.css.headerRow + ' ' + c.cssHeaderRow);
444
447
  // allow keyboard cursor to focus on element
445
- $t.attr("tabindex", 0);
448
+ if (c.tabIndex) { $t.attr("tabindex", 0); }
446
449
  });
447
450
  // enable/disable sorting
448
451
  updateHeader(table);
@@ -638,7 +641,7 @@
638
641
 
639
642
  // sort multiple columns
640
643
  function multisort(table) { /*jshint loopfunc:true */
641
- var i, k, e, num, col, colMax, cache, lc,
644
+ var i, k, num, col, colMax, cache, lc,
642
645
  order, orgOrderCol, sortTime, sort, x, y,
643
646
  dir = 0,
644
647
  c = table.config,
@@ -663,14 +666,9 @@
663
666
  // sort direction, true = asc, false = desc
664
667
  dir = order === 0;
665
668
 
666
- // set a & b depending on sort direction
667
- x = dir ? a : b;
668
- y = dir ? b : a;
669
-
670
- // determine how to sort empty cells
671
- e = c.string[ (c.empties[col] || c.emptyTo ) ];
672
- if (x[col] === '' && e !== 0) { return ((typeof(e) === 'boolean') ? (e ? -1 : 1) : (e || 1)) * (dir ? 1 : -1); }
673
- if (y[col] === '' && e !== 0) { return ((typeof(e) === 'boolean') ? (e ? 1 : -1) : (-e || -1)) * (dir ? 1 : -1); }
669
+ if (c.sortStable && a[col] === b[col] && l === 1) {
670
+ return a[orgOrderCol] - b[orgOrderCol];
671
+ }
674
672
 
675
673
  // fallback to natural sort since it is more robust
676
674
  num = /n/i.test(getCachedSortType(c.parsers, col));
@@ -683,8 +681,12 @@
683
681
  }
684
682
  // fall back to built-in numeric sort
685
683
  // var sort = $.tablesorter["sort" + s](table, a[c], b[c], c, colMax[c], dir);
686
- sort = c.numberSorter ? c.numberSorter(x[col], y[col], dir, colMax[col], table) : ts.sortNumeric(x[col], y[col], num, colMax[col]);
684
+ sort = c.numberSorter ? c.numberSorter(x[col], y[col], dir, colMax[col], table) :
685
+ ts[ 'sortNumeric' + (dir ? 'Asc' : 'Desc') ](a[col], b[col], num, colMax[col], col, table);
687
686
  } else {
687
+ // set a & b depending on sort direction
688
+ x = dir ? a : b;
689
+ y = dir ? b : a;
688
690
  // text sort function
689
691
  if (typeof(cts) === 'function') {
690
692
  // custom OVERALL text sorter
@@ -694,7 +696,7 @@
694
696
  sort = cts[col](x[col], y[col], dir, col, table);
695
697
  } else {
696
698
  // fall back to natural sort
697
- sort = ts.sortNatural(x[col], y[col]);
699
+ sort = ts[ 'sortNatural' + (dir ? 'Asc' : 'Desc') ](a[col], b[col], col, table, c);
698
700
  }
699
701
  }
700
702
  if (sort) { return sort; }
@@ -1080,6 +1082,7 @@
1080
1082
  if ( xD < yD ) { return -1; }
1081
1083
  if ( xD > yD ) { return 1; }
1082
1084
  }
1085
+
1083
1086
  // chunk/tokenize
1084
1087
  xN = a.replace(r.chunk, '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0');
1085
1088
  yN = b.replace(r.chunk, '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0');
@@ -1102,6 +1105,22 @@
1102
1105
  return 0;
1103
1106
  };
1104
1107
 
1108
+ ts.sortNaturalAsc = function(a, b, col, table, c) {
1109
+ if (a === b) { return 0; }
1110
+ var e = c.string[ (c.empties[col] || c.emptyTo ) ];
1111
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
1112
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
1113
+ return ts.sortNatural(a, b);
1114
+ };
1115
+
1116
+ ts.sortNaturalDesc = function(a, b, col, table, c) {
1117
+ if (a === b) { return 0; }
1118
+ var e = c.string[ (c.empties[col] || c.emptyTo ) ];
1119
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
1120
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
1121
+ return ts.sortNatural(b, a);
1122
+ };
1123
+
1105
1124
  // basic alphabetical sort
1106
1125
  ts.sortText = function(a, b) {
1107
1126
  return a > b ? 1 : (a < b ? -1 : 0);
@@ -1110,22 +1129,41 @@
1110
1129
  // return text string value by adding up ascii value
1111
1130
  // so the text is somewhat sorted when using a digital sort
1112
1131
  // this is NOT an alphanumeric sort
1113
- ts.getTextValue = function(a, d, mx) {
1132
+ ts.getTextValue = function(a, num, mx) {
1114
1133
  if (mx) {
1115
1134
  // make sure the text value is greater than the max numerical value (mx)
1116
- var i, l = a ? a.length : 0, n = mx + d;
1135
+ var i, l = a ? a.length : 0, n = mx + num;
1117
1136
  for (i = 0; i < l; i++) {
1118
1137
  n += a.charCodeAt(i);
1119
1138
  }
1120
- return d * n;
1139
+ return num * n;
1121
1140
  }
1122
1141
  return 0;
1123
1142
  };
1124
1143
 
1125
- ts.sortNumeric = function(a, b, dir, mx) {
1144
+ ts.sortNumericAsc = function(a, b, num, mx, col, table) {
1126
1145
  if (a === b) { return 0; }
1127
- if (isNaN(a)) { a = ts.getTextValue(a, dir, mx); }
1128
- if (isNaN(b)) { b = ts.getTextValue(b, dir, mx); }
1146
+ var c = table.config,
1147
+ e = c.string[ (c.empties[col] || c.emptyTo ) ];
1148
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
1149
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
1150
+ if (isNaN(a)) { a = ts.getTextValue(a, num, mx); }
1151
+ if (isNaN(b)) { b = ts.getTextValue(b, num, mx); }
1152
+ return a - b;
1153
+ };
1154
+
1155
+ ts.sortNumericDesc = function(a, b, num, mx, col, table) {
1156
+ if (a === b) { return 0; }
1157
+ var c = table.config,
1158
+ e = c.string[ (c.empties[col] || c.emptyTo ) ];
1159
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
1160
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
1161
+ if (isNaN(a)) { a = ts.getTextValue(a, num, mx); }
1162
+ if (isNaN(b)) { b = ts.getTextValue(b, num, mx); }
1163
+ return b - a;
1164
+ };
1165
+
1166
+ ts.sortNumeric = function(a, b) {
1129
1167
  return a - b;
1130
1168
  };
1131
1169
 
@@ -1515,7 +1553,7 @@
1515
1553
  l = $tb.children('tr').length;
1516
1554
  if (l > 1) {
1517
1555
  row = 0;
1518
- $tv = $tb.children('tr:visible');
1556
+ $tv = $tb.children('tr:visible').not(c.selectorRemove);
1519
1557
  // revered back to using jQuery each - strangely it's the fastest method
1520
1558
  /*jshint loopfunc:true */
1521
1559
  $tv.each(function(){
@@ -1,4 +1,4 @@
1
- /*! tableSorter 2.8+ widgets - updated 11/9/2013
1
+ /*! tableSorter 2.8+ widgets - updated 11/19/2013
2
2
  *
3
3
  * Column Styles
4
4
  * Column Filters
@@ -10,7 +10,7 @@
10
10
  */
11
11
  /*jshint browser:true, jquery:true, unused:false, loopfunc:true */
12
12
  /*global jQuery: false, localStorage: false, navigator: false */
13
- ;(function($){
13
+ ;(function($) {
14
14
  "use strict";
15
15
  var ts = $.tablesorter = $.tablesorter || {};
16
16
 
@@ -68,86 +68,95 @@ ts.themes = {
68
68
  val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : '';
69
69
  alert(val); // "data1" if saved, or "" if not
70
70
  */
71
- ts.storage = function(table, key, val, options){
72
- var d, k, ls = false, v = {},
73
- c = table.config,
74
- id = options && options.id || $(table).attr(options && options.group || 'data-table-group') || table.id || $('.tablesorter').index( $(table) ),
75
- url = options && options.url || $(table).attr(options && options.page || 'data-table-page') || c && c.fixedUrl || window.location.pathname;
71
+ ts.storage = function(table, key, value, options) {
72
+ table = $(table)[0];
73
+ var cookieIndex, cookies, date,
74
+ hasLocalStorage = false,
75
+ values = {},
76
+ c = table.config,
77
+ $table = $(table),
78
+ id = options && options.id || $table.attr(options && options.group ||
79
+ 'data-table-group') || table.id || $('.tablesorter').index( $table ),
80
+ url = options && options.url || $table.attr(options && options.page ||
81
+ 'data-table-page') || c && c.fixedUrl || window.location.pathname;
76
82
  // https://gist.github.com/paulirish/5558557
77
83
  if ("localStorage" in window) {
78
84
  try {
79
85
  window.localStorage.setItem('_tmptest', 'temp');
80
- ls = true;
86
+ hasLocalStorage = true;
81
87
  window.localStorage.removeItem('_tmptest');
82
- } catch(e) {}
88
+ } catch(error) {}
83
89
  }
84
- // *** get val ***
85
- if ($.parseJSON){
86
- if (ls){
87
- v = $.parseJSON(localStorage[key] || '{}');
90
+ // *** get value ***
91
+ if ($.parseJSON) {
92
+ if (hasLocalStorage) {
93
+ values = $.parseJSON(localStorage[key] || '{}');
88
94
  } else {
89
- k = document.cookie.split(/[;\s|=]/); // cookie
90
- d = $.inArray(key, k) + 1; // add one to get from the key to the value
91
- v = (d !== 0) ? $.parseJSON(k[d] || '{}') : {};
95
+ // old browser, using cookies
96
+ cookies = document.cookie.split(/[;\s|=]/);
97
+ // add one to get from the key to the value
98
+ cookieIndex = $.inArray(key, cookies) + 1;
99
+ values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || '{}') : {};
92
100
  }
93
101
  }
94
- // allow val to be an empty string to
95
- if ((val || val === '') && window.JSON && JSON.hasOwnProperty('stringify')){
102
+ // allow value to be an empty string too
103
+ if ((value || value === '') && window.JSON && JSON.hasOwnProperty('stringify')) {
96
104
  // add unique identifiers = url pathname > table ID/index on page > data
97
- if (!v[url]) {
98
- v[url] = {};
105
+ if (!values[url]) {
106
+ values[url] = {};
99
107
  }
100
- v[url][id] = val;
101
- // *** set val ***
102
- if (ls){
103
- localStorage[key] = JSON.stringify(v);
108
+ values[url][id] = value;
109
+ // *** set value ***
110
+ if (hasLocalStorage) {
111
+ localStorage[key] = JSON.stringify(values);
104
112
  } else {
105
- d = new Date();
106
- d.setTime(d.getTime() + (31536e+6)); // 365 days
107
- document.cookie = key + '=' + (JSON.stringify(v)).replace(/\"/g,'\"') + '; expires=' + d.toGMTString() + '; path=/';
113
+ date = new Date();
114
+ date.setTime(date.getTime() + (31536e+6)); // 365 days
115
+ document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g,'\"') + '; expires=' + date.toGMTString() + '; path=/';
108
116
  }
109
117
  } else {
110
- return v && v[url] ? v[url][id] : {};
118
+ return values && values[url] ? values[url][id] : {};
111
119
  }
112
120
  };
113
121
 
114
122
  // Add a resize event to table headers
115
123
  // **************************
116
- ts.addHeaderResizeEvent = function(table, disable, options){
117
- var defaults = {
118
- timer : 250
119
- },
120
- o = $.extend({}, defaults, options),
121
- c = table.config,
122
- wo = c.widgetOptions,
123
- headers,
124
- checkSizes = function(){
125
- wo.resize_flag = true;
126
- headers = [];
127
- c.$headers.each(function(){
128
- var d = $.data(this, 'savedSizes') || [0,0], // fixes #394
129
- w = this.offsetWidth,
130
- h = this.offsetHeight;
131
- if (w !== d[0] || h !== d[1]) {
132
- $.data(this, 'savedSizes', [ w, h ]);
133
- headers.push(this);
124
+ ts.addHeaderResizeEvent = function(table, disable, settings) {
125
+ var headers,
126
+ defaults = {
127
+ timer : 250
128
+ },
129
+ options = $.extend({}, defaults, settings),
130
+ c = table.config,
131
+ wo = c.widgetOptions,
132
+ checkSizes = function(triggerEvent) {
133
+ wo.resize_flag = true;
134
+ headers = [];
135
+ c.$headers.each(function() {
136
+ var $header = $(this),
137
+ sizes = $header.data('savedSizes') || [0,0], // fixes #394
138
+ width = this.offsetWidth,
139
+ height = this.offsetHeight;
140
+ if (width !== sizes[0] || height !== sizes[1]) {
141
+ $header.data('savedSizes', [ width, height ]);
142
+ headers.push(this);
143
+ }
144
+ });
145
+ if (headers.length && triggerEvent !== false) {
146
+ c.$table.trigger('resize', [ headers ]);
134
147
  }
135
- });
136
- if (headers.length) { c.$table.trigger('resize', [ headers ]); }
137
- wo.resize_flag = false;
138
- };
139
- c.$headers.each(function(){
140
- $.data(this, 'savedSizes', [ this.offsetWidth, this.offsetHeight ]);
141
- });
148
+ wo.resize_flag = false;
149
+ };
150
+ checkSizes(false);
142
151
  clearInterval(wo.resize_timer);
143
152
  if (disable) {
144
153
  wo.resize_flag = false;
145
154
  return false;
146
155
  }
147
- wo.resize_timer = setInterval(function(){
156
+ wo.resize_timer = setInterval(function() {
148
157
  if (wo.resize_flag) { return; }
149
158
  checkSizes();
150
- }, o.timer);
159
+ }, options.timer);
151
160
  };
152
161
 
153
162
  // Widget: General UI theme
@@ -156,89 +165,88 @@ ts.addHeaderResizeEvent = function(table, disable, options){
156
165
  ts.addWidget({
157
166
  id: "uitheme",
158
167
  priority: 10,
159
- options: {
160
- uitheme : 'jui'
161
- },
162
- format: function(table, c, wo){
163
- var time, klass, $el, $tar,
164
- t = ts.themes,
165
- $t = c.$table,
166
- theme = c.theme !== 'default' ? c.theme : wo.uitheme || 'jui',
167
- o = t[ t[theme] ? theme : t[wo.uitheme] ? wo.uitheme : 'jui'],
168
- $h = c.$headers,
169
- sh = 'tr.' + (wo.stickyHeaders || 'tablesorter-stickyHeader'),
170
- rmv = o.sortNone + ' ' + o.sortDesc + ' ' + o.sortAsc;
168
+ format: function(table, c, wo) {
169
+ var time, classes, $header, $icon, $tfoot,
170
+ themesAll = ts.themes,
171
+ $table = c.$table,
172
+ $headers = c.$headers,
173
+ theme = c.theme || 'jui',
174
+ themes = themesAll[theme] || themesAll.jui,
175
+ remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc;
171
176
  if (c.debug) { time = new Date(); }
172
177
  // initialization code - run once
173
- if (!$t.hasClass('tablesorter-' + theme) || c.theme === theme || !table.hasInitialized){
178
+ if (!$table.hasClass('tablesorter-' + theme) || c.theme === theme || !table.hasInitialized) {
174
179
  // update zebra stripes
175
- if (o.even !== '') { wo.zebra[0] += ' ' + o.even; }
176
- if (o.odd !== '') { wo.zebra[1] += ' ' + o.odd; }
180
+ if (themes.even !== '') { wo.zebra[0] += ' ' + themes.even; }
181
+ if (themes.odd !== '') { wo.zebra[1] += ' ' + themes.odd; }
177
182
  // add caption style
178
- $t.find('caption').addClass(o.caption);
183
+ $table.find('caption').addClass(themes.caption);
179
184
  // add table/footer class names
180
- t = $t
181
- // remove other selected themes; use widgetOptions.theme_remove
185
+ $tfoot = $table
186
+ // remove other selected themes
182
187
  .removeClass( c.theme === '' ? '' : 'tablesorter-' + c.theme )
183
- .addClass('tablesorter-' + theme + ' ' + o.table) // add theme widget class name
188
+ .addClass('tablesorter-' + theme + ' ' + themes.table) // add theme widget class name
184
189
  .find('tfoot');
185
- if (t.length) {
186
- t
187
- .find('tr').addClass(o.footerRow)
188
- .children('th, td').addClass(o.footerCells);
190
+ if ($tfoot.length) {
191
+ $tfoot
192
+ .find('tr').addClass(themes.footerRow)
193
+ .children('th, td').addClass(themes.footerCells);
189
194
  }
190
195
  // update header classes
191
- $h
192
- .addClass(o.header)
193
- .filter(':not(.sorter-false)')
194
- .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(e){
196
+ $headers
197
+ .addClass(themes.header)
198
+ .not('.sorter-false')
199
+ .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) {
195
200
  // toggleClass with switch added in jQuery 1.3
196
- $(this)[ e.type === 'mouseenter' ? 'addClass' : 'removeClass' ](o.hover);
201
+ $(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover);
197
202
  });
198
- if (!$h.find('.tablesorter-wrapper').length) {
199
- // Firefox needs this inner div to position the icon/resizer correctly
200
- $h.wrapInner('<div class="tablesorter-wrapper" style="position:relative;height:100%;width:100%"></div>');
203
+ if (!$headers.find('.tablesorter-wrapper').length) {
204
+ // Firefox needs this inner div to position the resizer correctly
205
+ $headers.wrapInner('<div class="tablesorter-wrapper" style="position:relative;height:100%;width:100%"></div>');
201
206
  }
202
- if (c.cssIcon){
207
+ if (c.cssIcon) {
203
208
  // if c.cssIcon is '', then no <i> is added to the header
204
- $h.find('.' + ts.css.icon).addClass(o.icons);
209
+ $headers.find('.' + ts.css.icon).addClass(themes.icons);
205
210
  }
206
- if ($t.hasClass('hasFilters')){
207
- $h.find('.tablesorter-filter-row').addClass(o.filterRow);
211
+ if ($table.hasClass('hasFilters')) {
212
+ $headers.find('.tablesorter-filter-row').addClass(themes.filterRow);
208
213
  }
209
214
  }
210
- $.each($h, function(i){
211
- $el = $(this);
212
- $tar = (ts.css.icon) ? $el.find('.' + ts.css.icon) : $el;
213
- if (this.sortDisabled){
215
+ $.each($headers, function() {
216
+ $header = $(this);
217
+ $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $header;
218
+ if (this.sortDisabled) {
214
219
  // no sort arrows for disabled columns!
215
- $el.removeClass(rmv);
216
- $tar.removeClass(rmv + ' tablesorter-icon ' + o.icons);
220
+ $header.removeClass(remove);
221
+ $icon.removeClass(remove + ' tablesorter-icon ' + themes.icons);
217
222
  } else {
218
- t = ($t.hasClass('hasStickyHeaders')) ? $t.find(sh).find('th').eq(i).add($el) : $el;
219
- klass = ($el.hasClass(ts.css.sortAsc)) ? o.sortAsc : ($el.hasClass(ts.css.sortDesc)) ? o.sortDesc : $el.hasClass(ts.css.header) ? o.sortNone : '';
220
- $el[klass === o.sortNone ? 'removeClass' : 'addClass'](o.active);
221
- $tar.removeClass(rmv).addClass(klass);
223
+ classes = ($header.hasClass(ts.css.sortAsc)) ?
224
+ themes.sortAsc :
225
+ ($header.hasClass(ts.css.sortDesc)) ? themes.sortDesc :
226
+ $header.hasClass(ts.css.header) ? themes.sortNone : '';
227
+ $header[classes === themes.sortNone ? 'removeClass' : 'addClass'](themes.active);
228
+ $icon.removeClass(remove).addClass(classes);
222
229
  }
223
230
  });
224
- if (c.debug){
231
+ if (c.debug) {
225
232
  ts.benchmark("Applying " + theme + " theme", time);
226
233
  }
227
234
  },
228
- remove: function(table, c, wo){
229
- var $t = c.$table,
230
- theme = typeof wo.uitheme === 'object' ? 'jui' : wo.uitheme || 'jui',
231
- o = typeof wo.uitheme === 'object' ? wo.uitheme : ts.themes[ ts.themes.hasOwnProperty(theme) ? theme : 'jui'],
232
- $h = $t.children('thead').children(),
233
- rmv = o.sortNone + ' ' + o.sortDesc + ' ' + o.sortAsc;
234
- $t
235
- .removeClass('tablesorter-' + theme + ' ' + o.table)
236
- .find(ts.css.header).removeClass(o.header);
237
- $h
235
+ remove: function(table, c, wo) {
236
+ var $table = c.$table,
237
+ theme = c.theme || 'jui',
238
+ themes = ts.themes[ theme ] || ts.themes.jui,
239
+ $headers = $table.children('thead').children(),
240
+ remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc;
241
+ $table
242
+ .removeClass('tablesorter-' + theme + ' ' + themes.table)
243
+ .find(ts.css.header).removeClass(themes.header);
244
+ $headers
238
245
  .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover
239
- .removeClass(o.hover + ' ' + rmv + ' ' + o.active)
240
- .find('.tablesorter-filter-row').removeClass(o.filterRow);
241
- $h.find('.tablesorter-icon').removeClass(o.icons);
246
+ .removeClass(themes.hover + ' ' + remove + ' ' + themes.active)
247
+ .find('.tablesorter-filter-row')
248
+ .removeClass(themes.filterRow);
249
+ $headers.find('.tablesorter-icon').removeClass(themes.icons);
242
250
  }
243
251
  });
244
252
 
@@ -252,76 +260,74 @@ ts.addWidget({
252
260
  options : {
253
261
  columns : [ "primary", "secondary", "tertiary" ]
254
262
  },
255
- format: function(table, c, wo){
256
- var $tb, $tr, $td, $t, time, last, rmv, i, k, l,
257
- $tbl = c.$table,
258
- b = c.$tbodies,
259
- list = c.sortList,
260
- len = list.length,
261
- // keep backwards compatibility, for now
262
- css = (c.widgetColumns && c.widgetColumns.hasOwnProperty('css')) ? c.widgetColumns.css || css :
263
- (wo && wo.hasOwnProperty('columns')) ? wo.columns || css : css;
264
- last = css.length-1;
265
- rmv = css.join(' ');
266
- if (c.debug){
263
+ format: function(table, c, wo) {
264
+ var time, $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx,
265
+ $table = c.$table,
266
+ $tbodies = c.$tbodies,
267
+ sortList = c.sortList,
268
+ len = sortList.length,
269
+ // removed c.widgetColumns support
270
+ css = wo && wo.columns || [ "primary", "secondary", "tertiary" ],
271
+ last = css.length - 1;
272
+ remove = css.join(' ');
273
+ if (c.debug) {
267
274
  time = new Date();
268
275
  }
269
276
  // check if there is a sort (on initialization there may not be one)
270
- for (k = 0; k < b.length; k++ ){
271
- $tb = ts.processTbody(table, b.eq(k), true); // detach tbody
272
- $tr = $tb.children('tr');
273
- l = $tr.length;
277
+ for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
278
+ $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody
279
+ $rows = $tbody.children('tr');
274
280
  // loop through the visible rows
275
- $tr.each(function(){
276
- $t = $(this);
277
- if (this.style.display !== 'none'){
281
+ $rows.each(function() {
282
+ $row = $(this);
283
+ if (this.style.display !== 'none') {
278
284
  // remove all columns class names
279
- $td = $t.children().removeClass(rmv);
285
+ $cells = $row.children().removeClass(remove);
280
286
  // add appropriate column class names
281
- if (list && list[0]){
287
+ if (sortList && sortList[0]) {
282
288
  // primary sort column class
283
- $td.eq(list[0][0]).addClass(css[0]);
284
- if (len > 1){
285
- for (i = 1; i < len; i++){
289
+ $cells.eq(sortList[0][0]).addClass(css[0]);
290
+ if (len > 1) {
291
+ for (indx = 1; indx < len; indx++) {
286
292
  // secondary, tertiary, etc sort column classes
287
- $td.eq(list[i][0]).addClass( css[i] || css[last] );
293
+ $cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] );
288
294
  }
289
295
  }
290
296
  }
291
297
  }
292
298
  });
293
- ts.processTbody(table, $tb, false);
299
+ ts.processTbody(table, $tbody, false);
294
300
  }
295
301
  // add classes to thead and tfoot
296
- $tr = wo.columns_thead !== false ? ['thead tr'] : [];
302
+ rows = wo.columns_thead !== false ? ['thead tr'] : [];
297
303
  if (wo.columns_tfoot !== false) {
298
- $tr.push('tfoot tr');
304
+ rows.push('tfoot tr');
299
305
  }
300
- if ($tr.length) {
301
- $t = $tbl.find($tr.join(',')).children().removeClass(rmv);
302
- if (len){
303
- for (i = 0; i < len; i++){
306
+ if (rows.length) {
307
+ $rows = $table.find( rows.join(',') ).children().removeClass(remove);
308
+ if (len) {
309
+ for (indx = 0; indx < len; indx++) {
304
310
  // add primary. secondary, tertiary, etc sort column classes
305
- $t.filter('[data-column="' + list[i][0] + '"]').addClass(css[i] || css[last]);
311
+ $rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]);
306
312
  }
307
313
  }
308
314
  }
309
- if (c.debug){
315
+ if (c.debug) {
310
316
  ts.benchmark("Applying Columns widget", time);
311
317
  }
312
318
  },
313
- remove: function(table, c, wo){
314
- var k, $tb,
315
- b = c.$tbodies,
316
- rmv = (wo.columns || [ "primary", "secondary", "tertiary" ]).join(' ');
317
- c.$headers.removeClass(rmv);
318
- c.$table.children('tfoot').children('tr').children('th, td').removeClass(rmv);
319
- for (k = 0; k < b.length; k++ ){
320
- $tb = ts.processTbody(table, b.eq(k), true); // remove tbody
321
- $tb.children('tr').each(function(){
322
- $(this).children().removeClass(rmv);
319
+ remove: function(table, c, wo) {
320
+ var tbodyIndex, $tbody,
321
+ $tbodies = c.$tbodies,
322
+ remove = (wo.columns || [ "primary", "secondary", "tertiary" ]).join(' ');
323
+ c.$headers.removeClass(remove);
324
+ c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove);
325
+ for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
326
+ $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody
327
+ $tbody.children('tr').each(function() {
328
+ $(this).children().removeClass(remove);
323
329
  });
324
- ts.processTbody(table, $tb, false); // restore tbody
330
+ ts.processTbody(table, $tbody, false); // restore tbody
325
331
  }
326
332
  }
327
333
  });
@@ -344,6 +350,7 @@ ts.addWidget({
344
350
  filter_liveSearch : true, // if true, search column content while the user types (with a delay)
345
351
  filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available (visible) options within the drop down
346
352
  filter_reset : null, // jQuery selector string of an element used to reset the filters
353
+ filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters
347
354
  filter_searchDelay : 300, // typing delay in milliseconds before starting a search
348
355
  filter_startsWith : false, // if true, filter start from the beginning of the cell contents
349
356
  filter_useParsedData : false, // filter all data using parsed content
@@ -563,7 +570,8 @@ ts.filter = {
563
570
  // reset button/link
564
571
  if (wo.filter_reset) {
565
572
  $(document).delegate(wo.filter_reset, 'click.tsfilter', function() {
566
- ts.filter.searching(table, []);
573
+ // trigger a reset event, so other functions (filterFormatter) know when to reset
574
+ c.$table.trigger('filterReset');
567
575
  });
568
576
  }
569
577
  if (wo.filter_functions) {
@@ -573,7 +581,7 @@ ts.filter = {
573
581
  $header = c.$headers.filter('[data-column="' + column + '"]:last');
574
582
  options = '';
575
583
  if (wo.filter_functions[column] === true && !$header.hasClass('filter-false')) {
576
- ts.filter.buildSelect(column);
584
+ ts.filter.buildSelect(table, column);
577
585
  } else if (typeof column === 'string' && !$header.hasClass('filter-false')) {
578
586
  // add custom drop down list
579
587
  for (string in wo.filter_functions[column]) {
@@ -610,6 +618,7 @@ ts.filter = {
610
618
  ts.isProcessing(table, event.type === 'filterStart', columns ? $header : '');
611
619
  });
612
620
  }
621
+
613
622
  if (c.debug) {
614
623
  ts.benchmark("Applying Filter widget", time);
615
624
  }
@@ -619,18 +628,26 @@ ts.filter = {
619
628
  if (filters.length) {
620
629
  ts.setFilters(table, filters, true);
621
630
  }
631
+ ts.filter.checkFilters(table, filters);
622
632
  });
623
633
  // filter widget initialized
624
634
  wo.filter_Initialized = true;
625
635
  c.$table.trigger('filterInit');
626
- ts.filter.checkFilters(table);
627
636
  },
628
- setDefaults: function(table, c, wo){
637
+ setDefaults: function(table, c, wo) {
629
638
  var indx,
630
639
  filters = [],
631
640
  columns = c.columns;
632
- for (indx = 0; indx < columns; indx++) {
633
- filters[indx] = c.$headers.filter('[data-column="' + indx + '"]:last').attr(wo.filter_defaultAttrib) || filters[indx];
641
+ if (wo.filter_saveFilters && ts.storage) {
642
+ filters = ts.storage( table, 'tablesorter-filters' ) || [];
643
+ // make sure we're not just saving an empty array
644
+ if (filters.join('') === '') { filters = []; }
645
+ }
646
+ // if not filters saved, then check default settings
647
+ if (!filters.length) {
648
+ for (indx = 0; indx < columns; indx++) {
649
+ filters[indx] = c.$headers.filter('[data-column="' + indx + '"]:last').attr(wo.filter_defaultAttrib) || filters[indx];
650
+ }
634
651
  }
635
652
  $(table).data('lastSearch', filters);
636
653
  return filters;
@@ -692,7 +709,7 @@ ts.filter = {
692
709
  bindSearch: function(table, $el) {
693
710
  table = $(table)[0];
694
711
  var external, wo = table.config.widgetOptions;
695
- $el.bind('keyup search', function(event, filter) {
712
+ $el.unbind('keyup search').bind('keyup search', function(event, filter) {
696
713
  // emulate what webkit does.... escape clears the filter
697
714
  if (event.which === 27) {
698
715
  this.value = '';
@@ -703,7 +720,15 @@ ts.filter = {
703
720
  return;
704
721
  }
705
722
  // external searches won't have a filter parameter, so grab the value
706
- external = $(this).hasClass('tablesorter-filter') ? filter : [ $(this).val() ];
723
+ if ($(this).hasClass('tablesorter-filter')) {
724
+ external = filter;
725
+ } else {
726
+ external = [];
727
+ $el.each(function(){
728
+ // target the appropriate column if the external input has a data-column attribute
729
+ external[ $(this).data('column') || 0 ] = $(this).val();
730
+ });
731
+ }
707
732
  ts.filter.searching(table, filter, external);
708
733
  });
709
734
  },
@@ -782,7 +807,7 @@ ts.filter = {
782
807
  columns = c.columns,
783
808
  $tbodies = c.$tbodies,
784
809
  // anyMatch really screws up with these types of filters
785
- anyMatchNotAllowedTypes = [ 'range', 'operators' ],
810
+ anyMatchNotAllowedTypes = [ 'range', 'notMatch', 'operators' ],
786
811
  // parse columns after formatter, in case the class is added at that point
787
812
  parsed = c.$headers.map(function(columnIndex) {
788
813
  return (ts.getData) ?
@@ -793,7 +818,7 @@ ts.filter = {
793
818
  for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
794
819
  if ($tbodies.eq(tbodyIndex).hasClass(ts.css.info)) { continue; } // ignore info blocks, issue #264
795
820
  $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true);
796
- $rows = $tbody.children('tr').not('.' + c.cssChildRow);
821
+ $rows = $tbody.children('tr').not('.' + c.cssChildRow).not('.group-header');
797
822
  len = $rows.length;
798
823
  if (combinedFilters === '' || wo.filter_serversideFiltering) {
799
824
  $tbody.children().show().removeClass(wo.filter_filteredRow);
@@ -902,6 +927,9 @@ ts.filter = {
902
927
  c.lastCombinedFilter = combinedFilters; // save last search
903
928
  c.lastSearch = filters;
904
929
  c.$table.data('lastSearch', filters);
930
+ if (wo.filter_saveFilters && ts.storage) {
931
+ ts.storage( table, 'tablesorter-filters', filters );
932
+ }
905
933
  if (c.debug) {
906
934
  ts.benchmark("Completed filter widget search", time);
907
935
  }
@@ -1015,7 +1043,7 @@ ts.setFilters = function(table, filter, apply) {
1015
1043
  // **************************
1016
1044
  ts.addWidget({
1017
1045
  id: "stickyHeaders",
1018
- priority: 60,
1046
+ priority: 60, // sticky widget must be initialized after the filter widget!
1019
1047
  options: {
1020
1048
  stickyHeaders : '', // extra class name added to the sticky header row
1021
1049
  stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element
@@ -1024,55 +1052,54 @@ ts.addWidget({
1024
1052
  stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header
1025
1053
  stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs
1026
1054
  },
1027
- format: function(table, c, wo){
1055
+ format: function(table, c, wo) {
1028
1056
  if (c.$table.hasClass('hasStickyHeaders')) { return; }
1029
- var $t = c.$table,
1057
+ var $cell,
1058
+ $table = c.$table,
1030
1059
  $win = $(window),
1031
- header = $t.children('thead:first'),
1032
- hdrCells = header.children('tr:not(.sticky-false)').children(),
1033
- innr = '.tablesorter-header-inner',
1034
- tfoot = $t.find('tfoot'),
1060
+ $thead = $table.children('thead:first'),
1061
+ $header = $thead.children('tr').not('.sticky-false').children(),
1062
+ innerHeader = '.tablesorter-header-inner',
1063
+ $tfoot = $table.find('tfoot'),
1035
1064
  filterInputs = '.tablesorter-filter',
1036
1065
  $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '',
1037
1066
  stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0,
1038
- stickyzIndex = wo.stickyHeaders_zIndex ? wo.stickyHeaders_zIndex : 2,
1039
- $stickyTable = wo.$sticky = $t.clone()
1067
+ $stickyTable = wo.$sticky = $table.clone()
1040
1068
  .addClass('containsStickyHeaders')
1041
1069
  .css({
1042
1070
  position : 'fixed',
1043
1071
  margin : 0,
1044
1072
  top : stickyOffset,
1045
1073
  visibility : 'hidden',
1046
- zIndex : stickyzIndex
1074
+ zIndex : wo.stickyHeaders_zIndex ? wo.stickyHeaders_zIndex : 2
1047
1075
  }),
1048
- stkyHdr = $stickyTable.children('thead:first').addClass('tablesorter-stickyHeader ' + wo.stickyHeaders),
1049
- stkyCells,
1076
+ $stickyThead = $stickyTable.children('thead:first').addClass('tablesorter-stickyHeader ' + wo.stickyHeaders),
1077
+ $stickyCells,
1050
1078
  laststate = '',
1051
1079
  spacing = 0,
1052
- flag = false,
1053
- resizeHdr = function(){
1080
+ updatingStickyFilters = false,
1081
+ resizeHeader = function() {
1054
1082
  stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0;
1055
- var bwsr = navigator.userAgent;
1056
1083
  spacing = 0;
1057
1084
  // yes, I dislike browser sniffing, but it really is needed here :(
1058
1085
  // webkit automatically compensates for border spacing
1059
- if ($t.css('border-collapse') !== 'collapse' && !/(webkit|msie)/i.test(bwsr)) {
1086
+ if ($table.css('border-collapse') !== 'collapse' && !/(webkit|msie)/i.test(navigator.userAgent)) {
1060
1087
  // Firefox & Opera use the border-spacing
1061
1088
  // update border-spacing here because of demos that switch themes
1062
- spacing = parseInt(hdrCells.eq(0).css('border-left-width'), 10) * 2;
1089
+ spacing = parseInt($header.eq(0).css('border-left-width'), 10) * 2;
1063
1090
  }
1064
1091
  $stickyTable.css({
1065
- left : header.offset().left - $win.scrollLeft() - spacing,
1066
- width: $t.width()
1092
+ left : $thead.offset().left - $win.scrollLeft() - spacing,
1093
+ width: $table.width()
1067
1094
  });
1068
- stkyCells.filter(':visible').each(function(i){
1069
- var $h = hdrCells.filter(':visible').eq(i);
1095
+ $stickyCells.filter(':visible').each(function(i) {
1096
+ var $cell = $header.filter(':visible').eq(i);
1070
1097
  $(this)
1071
1098
  .css({
1072
- width: $h.width() - spacing,
1073
- height: $h.height()
1099
+ width: $cell.width() - spacing,
1100
+ height: $cell.height()
1074
1101
  })
1075
- .find(innr).width( $h.find(innr).width() );
1102
+ .find(innerHeader).width( $cell.find(innerHeader).width() );
1076
1103
  });
1077
1104
  };
1078
1105
  // fix clone ID, if it exists - fixes #271
@@ -1084,71 +1111,70 @@ ts.addWidget({
1084
1111
  $stickyTable.find('caption').remove();
1085
1112
  }
1086
1113
  // issue #172 - find td/th in sticky header
1087
- stkyCells = stkyHdr.children().children();
1114
+ $stickyCells = $stickyThead.children().children();
1088
1115
  $stickyTable.css({ height:0, width:0, padding:0, margin:0, border:0 });
1089
1116
  // remove resizable block
1090
- stkyCells.find('.tablesorter-resizer').remove();
1117
+ $stickyCells.find('.tablesorter-resizer').remove();
1091
1118
  // update sticky header class names to match real header after sorting
1092
- $t
1093
- .addClass('hasStickyHeaders')
1094
- .bind('sortEnd.tsSticky', function(){
1095
- hdrCells.filter(':visible').each(function(i){
1096
- var t = stkyCells.filter(':visible').eq(i);
1097
- t
1098
- .attr('class', $(this).attr('class'))
1099
- // remove processing icon
1100
- .removeClass(ts.css.processing + ' ' + c.cssProcessing);
1101
- if (c.cssIcon){
1102
- t
1103
- .find('.' + ts.css.icon)
1104
- .attr('class', $(this).find('.' + ts.css.icon).attr('class'));
1105
- }
1119
+ $table
1120
+ .addClass('hasStickyHeaders')
1121
+ .bind('sortEnd.tsSticky', function() {
1122
+ $header.filter(':visible').each(function(indx) {
1123
+ $cell = $stickyCells.filter(':visible').eq(indx)
1124
+ .attr('class', $(this).attr('class'))
1125
+ // remove processing icon
1126
+ .removeClass(ts.css.processing + ' ' + c.cssProcessing);
1127
+ if (c.cssIcon) {
1128
+ $cell
1129
+ .find('.' + ts.css.icon)
1130
+ .attr('class', $(this).find('.' + ts.css.icon).attr('class'));
1131
+ }
1132
+ });
1133
+ })
1134
+ .bind('pagerComplete.tsSticky', function() {
1135
+ resizeHeader();
1106
1136
  });
1107
- })
1108
- .bind('pagerComplete.tsSticky', function(){
1109
- resizeHdr();
1110
- });
1111
- // http://stackoverflow.com/questions/5312849/jquery-find-self;
1112
- hdrCells.find(c.selectorSort).add( c.$headers.filter(c.selectorSort) ).each(function(i){
1113
- var t = $(this),
1114
- // clicking on sticky will trigger sort
1115
- $cell = stkyHdr.children('tr.tablesorter-headerRow').children().eq(i).bind('mouseup', function(e){
1116
- t.trigger(e, true); // external mouseup flag (click timer is ignored)
1137
+ // http://stackoverflow.com/questions/5312849/jquery-find-self;
1138
+ $header.find(c.selectorSort).add( c.$headers.filter(c.selectorSort) ).each(function(indx) {
1139
+ var $header = $(this),
1140
+ // clicking on sticky will trigger sort
1141
+ $cell = $stickyThead.children('tr.tablesorter-headerRow').children().eq(indx).bind('mouseup', function(event) {
1142
+ $header.trigger(event, true); // external mouseup flag (click timer is ignored)
1143
+ });
1144
+ // prevent sticky header text selection
1145
+ if (c.cancelSelection) {
1146
+ $cell
1147
+ .attr('unselectable', 'on')
1148
+ .bind('selectstart', false)
1149
+ .css({
1150
+ 'user-select': 'none',
1151
+ 'MozUserSelect': 'none'
1152
+ });
1153
+ }
1117
1154
  });
1118
- // prevent sticky header text selection
1119
- if (c.cancelSelection) {
1120
- $cell
1121
- .attr('unselectable', 'on')
1122
- .bind('selectstart', false)
1123
- .css({
1124
- 'user-select': 'none',
1125
- 'MozUserSelect': 'none'
1126
- });
1127
- }
1128
- });
1129
1155
  // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned.
1130
- $t.after( $stickyTable );
1156
+ $table.after( $stickyTable );
1131
1157
  // make it sticky!
1132
- $win.bind('scroll.tsSticky resize.tsSticky', function(e){
1133
- if (!$t.is(':visible')) { return; } // fixes #278
1134
- var pre = 'tablesorter-sticky-',
1135
- offset = $t.offset(),
1136
- cap = (wo.stickyHeaders_includeCaption ? 0 : $t.find('caption').outerHeight(true)),
1137
- sTop = $win.scrollTop() + stickyOffset - cap,
1138
- tableHt = $t.height() - ($stickyTable.height() + (tfoot.height() || 0)),
1139
- vis = (sTop > offset.top) && (sTop < offset.top + tableHt) ? 'visible' : 'hidden';
1158
+ $win.bind('scroll.tsSticky resize.tsSticky', function(event) {
1159
+ if (!$table.is(':visible')) { return; } // fixes #278
1160
+ var prefix = 'tablesorter-sticky-',
1161
+ offset = $table.offset(),
1162
+ captionHeight = (wo.stickyHeaders_includeCaption ? 0 : $table.find('caption').outerHeight(true)),
1163
+ scrollTop = $win.scrollTop() + stickyOffset - captionHeight,
1164
+ tableHeight = $table.height() - ($stickyTable.height() + ($tfoot.height() || 0)),
1165
+ isVisible = (scrollTop > offset.top) && (scrollTop < offset.top + tableHeight) ? 'visible' : 'hidden';
1140
1166
  $stickyTable
1141
- .removeClass(pre + 'visible ' + pre + 'hidden')
1142
- .addClass(pre + vis)
1143
- .css({
1144
- // adjust when scrolling horizontally - fixes issue #143
1145
- left : header.offset().left - $win.scrollLeft() - spacing,
1146
- visibility : vis
1147
- });
1148
- if (vis !== laststate || e.type === 'resize'){
1167
+ .removeClass(prefix + 'visible ' + prefix + 'hidden')
1168
+ .addClass(prefix + isVisible)
1169
+ .css({
1170
+ // adjust when scrolling horizontally - fixes issue #143
1171
+ left : $thead.offset().left - $win.scrollLeft() - spacing,
1172
+ visibility : isVisible
1173
+ });
1174
+ if (isVisible !== laststate || event.type === 'resize') {
1149
1175
  // make sure the column widths match
1150
- resizeHdr();
1151
- laststate = vis;
1176
+ resizeHeader();
1177
+ laststate = isVisible;
1152
1178
  }
1153
1179
  });
1154
1180
  if (wo.stickyHeaders_addResizeEvent) {
@@ -1156,28 +1182,28 @@ ts.addWidget({
1156
1182
  }
1157
1183
 
1158
1184
  // look for filter widget
1159
- $t.bind('filterEnd', function(){
1160
- if (flag) { return; }
1161
- stkyHdr.find('.tablesorter-filter-row').children().each(function(i){
1162
- $(this).find(filterInputs).val( c.$filters.find(filterInputs).eq(i).val() );
1185
+ $table.bind('filterEnd', function() {
1186
+ if (updatingStickyFilters) { return; }
1187
+ $stickyThead.find('.tablesorter-filter-row').children().each(function(indx) {
1188
+ $(this).find(filterInputs).val( c.$filters.find(filterInputs).eq(indx).val() );
1163
1189
  });
1164
1190
  });
1165
- stkyCells.find(filterInputs).bind('keyup search change', function(e){
1191
+ $stickyCells.find(filterInputs).bind('keyup search change', function(event) {
1166
1192
  // ignore arrow and meta keys; allow backspace
1167
- if ((e.which < 32 && e.which !== 8) || (e.which >= 37 && e.which <=40)) { return; }
1168
- flag = true;
1169
- var $f = $(this), col = $f.attr('data-column');
1170
- c.$filters.find(filterInputs).eq(col)
1193
+ if ((event.which < 32 && event.which !== 8) || (event.which >= 37 && event.which <=40)) { return; }
1194
+ updatingStickyFilters = true;
1195
+ var $f = $(this), column = $f.attr('data-column');
1196
+ c.$filters.find(filterInputs).eq(column)
1171
1197
  .val( $f.val() )
1172
1198
  .trigger('search');
1173
- setTimeout(function(){
1174
- flag = false;
1199
+ setTimeout(function() {
1200
+ updatingStickyFilters = false;
1175
1201
  }, wo.filter_searchDelay);
1176
1202
  });
1177
- $t.trigger('stickyHeadersInit');
1203
+ $table.trigger('stickyHeadersInit');
1178
1204
 
1179
1205
  },
1180
- remove: function(table, c, wo){
1206
+ remove: function(table, c, wo) {
1181
1207
  c.$table
1182
1208
  .removeClass('hasStickyHeaders')
1183
1209
  .unbind('sortEnd.tsSticky pagerComplete.tsSticky')
@@ -1202,119 +1228,120 @@ ts.addWidget({
1202
1228
  resizable : true,
1203
1229
  resizable_addLastColumn : false
1204
1230
  },
1205
- format: function(table, c, wo){
1231
+ format: function(table, c, wo) {
1206
1232
  if (c.$table.hasClass('hasResizable')) { return; }
1207
1233
  c.$table.addClass('hasResizable');
1208
- var $t, t, i, j, s = {}, $c, $cols, w, tw,
1209
- $tbl = c.$table,
1210
- position = 0,
1234
+ var $rows, $columns, $column, column,
1235
+ storedSizes = {},
1236
+ $table = c.$table,
1237
+ mouseXPosition = 0,
1211
1238
  $target = null,
1212
1239
  $next = null,
1213
- fullWidth = Math.abs($tbl.parent().width() - $tbl.width()) < 20,
1214
- stopResize = function(){
1215
- if (ts.storage && $target){
1216
- s[$target.index()] = $target.width();
1217
- s[$next.index()] = $next.width();
1218
- $target.width( s[$target.index()] );
1219
- $next.width( s[$next.index()] );
1220
- if (wo.resizable !== false){
1221
- ts.storage(table, 'tablesorter-resizable', s);
1240
+ fullWidth = Math.abs($table.parent().width() - $table.width()) < 20,
1241
+ stopResize = function() {
1242
+ if (ts.storage && $target) {
1243
+ storedSizes[$target.index()] = $target.width();
1244
+ storedSizes[$next.index()] = $next.width();
1245
+ $target.width( storedSizes[$target.index()] );
1246
+ $next.width( storedSizes[$next.index()] );
1247
+ if (wo.resizable !== false) {
1248
+ ts.storage(table, 'tablesorter-resizable', storedSizes);
1222
1249
  }
1223
1250
  }
1224
- position = 0;
1251
+ mouseXPosition = 0;
1225
1252
  $target = $next = null;
1226
1253
  $(window).trigger('resize'); // will update stickyHeaders, just in case
1227
1254
  };
1228
- s = (ts.storage && wo.resizable !== false) ? ts.storage(table, 'tablesorter-resizable') : {};
1255
+ storedSizes = (ts.storage && wo.resizable !== false) ? ts.storage(table, 'tablesorter-resizable') : {};
1229
1256
  // process only if table ID or url match
1230
- if (s){
1231
- for (j in s){
1232
- if (!isNaN(j) && j < c.$headers.length){
1233
- c.$headers.eq(j).width(s[j]); // set saved resizable widths
1257
+ if (storedSizes) {
1258
+ for (column in storedSizes) {
1259
+ if (!isNaN(column) && column < c.$headers.length) {
1260
+ c.$headers.eq(column).width(storedSizes[column]); // set saved resizable widths
1234
1261
  }
1235
1262
  }
1236
1263
  }
1237
- $t = $tbl.children('thead:first').children('tr');
1264
+ $rows = $table.children('thead:first').children('tr');
1238
1265
  // add resizable-false class name to headers (across rows as needed)
1239
- $t.children().each(function(){
1240
- t = $(this);
1241
- i = t.attr('data-column');
1242
- j = ts.getData( t, c.headers[i], 'resizable') === "false";
1243
- $t.children().filter('[data-column="' + i + '"]').toggleClass('resizable-false', j);
1266
+ $rows.children().each(function() {
1267
+ var canResize,
1268
+ $column = $(this);
1269
+ column = $column.attr('data-column');
1270
+ canResize = ts.getData( $column, c.headers[column], 'resizable') === "false";
1271
+ $rows.children().filter('[data-column="' + column + '"]')[canResize ? 'addClass' : 'removeClass']('resizable-false');
1244
1272
  });
1245
1273
  // add wrapper inside each cell to allow for positioning of the resizable target block
1246
- $t.each(function(){
1247
- $c = $(this).children(':not(.resizable-false)');
1274
+ $rows.each(function() {
1275
+ $column = $(this).children().not('.resizable-false');
1248
1276
  if (!$(this).find('.tablesorter-wrapper').length) {
1249
1277
  // Firefox needs this inner div to position the resizer correctly
1250
- $c.wrapInner('<div class="tablesorter-wrapper" style="position:relative;height:100%;width:100%"></div>');
1278
+ $column.wrapInner('<div class="tablesorter-wrapper" style="position:relative;height:100%;width:100%"></div>');
1251
1279
  }
1252
1280
  // don't include the last column of the row
1253
- if (!wo.resizable_addLastColumn) { $c = $c.slice(0,-1); }
1254
- $cols = $cols ? $cols.add($c) : $c;
1281
+ if (!wo.resizable_addLastColumn) { $column = $column.slice(0,-1); }
1282
+ $columns = $columns ? $columns.add($column) : $column;
1255
1283
  });
1256
- $cols
1257
- .each(function(){
1258
- $t = $(this);
1259
- j = parseInt($t.css('padding-right'), 10) + 10; // 8 is 1/2 of the 16px wide resizer grip
1260
- t = '<div class="tablesorter-resizer" style="cursor:w-resize;position:absolute;z-index:1;right:-' + j +
1261
- 'px;top:0;height:100%;width:20px;"></div>';
1262
- $t
1284
+ $columns
1285
+ .each(function() {
1286
+ var $column = $(this),
1287
+ padding = parseInt($column.css('padding-right'), 10) + 10; // 10 is 1/2 of the 20px wide resizer grip
1288
+ $column
1263
1289
  .find('.tablesorter-wrapper')
1264
- .append(t);
1290
+ .append('<div class="tablesorter-resizer" style="cursor:w-resize;position:absolute;z-index:1;right:-' +
1291
+ padding + 'px;top:0;height:100%;width:20px;"></div>');
1265
1292
  })
1266
- .bind('mousemove.tsresize', function(e){
1293
+ .bind('mousemove.tsresize', function(event) {
1267
1294
  // ignore mousemove if no mousedown
1268
- if (position === 0 || !$target) { return; }
1295
+ if (mouseXPosition === 0 || !$target) { return; }
1269
1296
  // resize columns
1270
- w = e.pageX - position;
1271
- tw = $target.width();
1272
- $target.width( tw + w );
1273
- if ($target.width() !== tw && fullWidth){
1274
- $next.width( $next.width() - w );
1297
+ var leftEdge = event.pageX - mouseXPosition,
1298
+ targetWidth = $target.width();
1299
+ $target.width( targetWidth + leftEdge );
1300
+ if ($target.width() !== targetWidth && fullWidth) {
1301
+ $next.width( $next.width() - leftEdge );
1275
1302
  }
1276
- position = e.pageX;
1303
+ mouseXPosition = event.pageX;
1277
1304
  })
1278
- .bind('mouseup.tsresize', function(){
1305
+ .bind('mouseup.tsresize', function() {
1279
1306
  stopResize();
1280
1307
  })
1281
1308
  .find('.tablesorter-resizer,.tablesorter-resizer-grip')
1282
- .bind('mousedown', function(e){
1309
+ .bind('mousedown', function(event) {
1283
1310
  // save header cell and mouse position; closest() not supported by jQuery v1.2.6
1284
- $target = $(e.target).closest('th');
1285
- t = c.$headers.filter('[data-column="' + $target.attr('data-column') + '"]');
1286
- if (t.length > 1) { $target = $target.add(t); }
1311
+ $target = $(event.target).closest('th');
1312
+ var $header = c.$headers.filter('[data-column="' + $target.attr('data-column') + '"]');
1313
+ if ($header.length > 1) { $target = $target.add($header); }
1287
1314
  // if table is not as wide as it's parent, then resize the table
1288
- $next = e.shiftKey ? $target.parent().find('th:not(.resizable-false)').filter(':last') : $target.nextAll(':not(.resizable-false)').eq(0);
1289
- position = e.pageX;
1315
+ $next = event.shiftKey ? $target.parent().find('th').not('.resizable-false').filter(':last') : $target.nextAll(':not(.resizable-false)').eq(0);
1316
+ mouseXPosition = event.pageX;
1290
1317
  });
1291
- $tbl.find('thead:first')
1292
- .bind('mouseup.tsresize mouseleave.tsresize', function(){
1318
+ $table.find('thead:first')
1319
+ .bind('mouseup.tsresize mouseleave.tsresize', function() {
1293
1320
  stopResize();
1294
1321
  })
1295
1322
  // right click to reset columns to default widths
1296
- .bind('contextmenu.tsresize', function(){
1323
+ .bind('contextmenu.tsresize', function() {
1297
1324
  ts.resizableReset(table);
1298
- // $.isEmptyObject() needs jQuery 1.4+
1299
- var rtn = $.isEmptyObject ? $.isEmptyObject(s) : s === {}; // allow right click if already reset
1300
- s = {};
1301
- return rtn;
1325
+ // $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset
1326
+ var allowClick = $.isEmptyObject ? $.isEmptyObject(storedSizes) : true;
1327
+ storedSizes = {};
1328
+ return allowClick;
1302
1329
  });
1303
1330
  },
1304
- remove: function(table, c, wo){
1331
+ remove: function(table, c) {
1305
1332
  c.$table
1306
1333
  .removeClass('hasResizable')
1307
- .find('thead')
1334
+ .children('thead')
1308
1335
  .unbind('mouseup.tsresize mouseleave.tsresize contextmenu.tsresize')
1309
- .find('tr').children()
1336
+ .children('tr').children()
1310
1337
  .unbind('mousemove.tsresize mouseup.tsresize')
1311
1338
  // don't remove "tablesorter-wrapper" as uitheme uses it too
1312
1339
  .find('.tablesorter-resizer,.tablesorter-resizer-grip').remove();
1313
1340
  ts.resizableReset(table);
1314
1341
  }
1315
1342
  });
1316
- ts.resizableReset = function(table){
1317
- table.config.$headers.filter(':not(.resizable-false)').css('width','');
1343
+ ts.resizableReset = function(table) {
1344
+ table.config.$headers.not('.resizable-false').css('width','');
1318
1345
  if (ts.storage) { ts.storage(table, 'tablesorter-resizable', {}); }
1319
1346
  };
1320
1347
 
@@ -1329,52 +1356,52 @@ ts.addWidget({
1329
1356
  options: {
1330
1357
  saveSort : true
1331
1358
  },
1332
- init: function(table, thisWidget, c, wo){
1359
+ init: function(table, thisWidget, c, wo) {
1333
1360
  // run widget format before all other widgets are applied to the table
1334
1361
  thisWidget.format(table, c, wo, true);
1335
1362
  },
1336
- format: function(table, c, wo, init){
1337
- var sl, time,
1338
- $t = c.$table,
1339
- ss = wo.saveSort !== false, // make saveSort active/inactive; default to true
1363
+ format: function(table, c, wo, init) {
1364
+ var stored, time,
1365
+ $table = c.$table,
1366
+ saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true
1340
1367
  sortList = { "sortList" : c.sortList };
1341
- if (c.debug){
1368
+ if (c.debug) {
1342
1369
  time = new Date();
1343
1370
  }
1344
- if ($t.hasClass('hasSaveSort')){
1345
- if (ss && table.hasInitialized && ts.storage){
1371
+ if ($table.hasClass('hasSaveSort')) {
1372
+ if (saveSort && table.hasInitialized && ts.storage) {
1346
1373
  ts.storage( table, 'tablesorter-savesort', sortList );
1347
- if (c.debug){
1374
+ if (c.debug) {
1348
1375
  ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time);
1349
1376
  }
1350
1377
  }
1351
1378
  } else {
1352
1379
  // set table sort on initial run of the widget
1353
- $t.addClass('hasSaveSort');
1380
+ $table.addClass('hasSaveSort');
1354
1381
  sortList = '';
1355
1382
  // get data
1356
- if (ts.storage){
1357
- sl = ts.storage( table, 'tablesorter-savesort' );
1358
- sortList = (sl && sl.hasOwnProperty('sortList') && $.isArray(sl.sortList)) ? sl.sortList : '';
1359
- if (c.debug){
1383
+ if (ts.storage) {
1384
+ stored = ts.storage( table, 'tablesorter-savesort' );
1385
+ sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : '';
1386
+ if (c.debug) {
1360
1387
  ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time);
1361
1388
  }
1362
- $t.bind('saveSortReset', function(e){
1363
- e.stopPropagation();
1389
+ $table.bind('saveSortReset', function(event) {
1390
+ event.stopPropagation();
1364
1391
  ts.storage( table, 'tablesorter-savesort', '' );
1365
1392
  });
1366
1393
  }
1367
1394
  // init is true when widget init is run, this will run this widget before all other widgets have initialized
1368
1395
  // this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice.
1369
- if (init && sortList && sortList.length > 0){
1396
+ if (init && sortList && sortList.length > 0) {
1370
1397
  c.sortList = sortList;
1371
- } else if (table.hasInitialized && sortList && sortList.length > 0){
1398
+ } else if (table.hasInitialized && sortList && sortList.length > 0) {
1372
1399
  // update sort change
1373
- $t.trigger('sorton', [sortList]);
1400
+ $table.trigger('sorton', [sortList]);
1374
1401
  }
1375
1402
  }
1376
1403
  },
1377
- remove: function(table){
1404
+ remove: function(table) {
1378
1405
  // clear storage
1379
1406
  if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); }
1380
1407
  }