jquery-tablesorter 1.0.5 → 1.1.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 (50) hide show
  1. data/README.markdown +48 -31
  2. data/Rakefile +56 -1
  3. data/lib/jquery-tablesorter/version.rb +1 -1
  4. data/vendor/assets/images/jquery-tablesorter/addons/{icons → pager/icons}/first.png +0 -0
  5. data/vendor/assets/images/jquery-tablesorter/addons/{icons → pager/icons}/last.png +0 -0
  6. data/vendor/assets/images/jquery-tablesorter/addons/pager/icons/loading.gif +0 -0
  7. data/vendor/assets/images/jquery-tablesorter/addons/{icons → pager/icons}/next.png +0 -0
  8. data/vendor/assets/images/jquery-tablesorter/addons/{icons → pager/icons}/prev.png +0 -0
  9. data/vendor/assets/images/jquery-tablesorter/black-asc.gif +0 -0
  10. data/vendor/assets/images/jquery-tablesorter/black-desc.gif +0 -0
  11. data/vendor/assets/images/jquery-tablesorter/black-unsorted.gif +0 -0
  12. data/vendor/assets/images/jquery-tablesorter/dropbox-asc1.png +0 -0
  13. data/vendor/assets/images/jquery-tablesorter/dropbox-asc2.png +0 -0
  14. data/vendor/assets/images/jquery-tablesorter/dropbox-desc1.png +0 -0
  15. data/vendor/assets/images/jquery-tablesorter/dropbox-desc2.png +0 -0
  16. data/vendor/assets/images/jquery-tablesorter/green-asc.png +0 -0
  17. data/vendor/assets/images/jquery-tablesorter/green-desc.png +0 -0
  18. data/vendor/assets/images/jquery-tablesorter/green-header.png +0 -0
  19. data/vendor/assets/images/jquery-tablesorter/green-unsorted.png +0 -0
  20. data/vendor/assets/images/jquery-tablesorter/ice-asc.gif +0 -0
  21. data/vendor/assets/images/jquery-tablesorter/ice-desc.gif +0 -0
  22. data/vendor/assets/images/jquery-tablesorter/ice-unsorted.gif +0 -0
  23. data/vendor/assets/images/jquery-tablesorter/white-asc.gif +0 -0
  24. data/vendor/assets/images/jquery-tablesorter/white-desc.gif +0 -0
  25. data/vendor/assets/images/jquery-tablesorter/white-unsorted.gif +0 -0
  26. data/vendor/assets/javascripts/jquery-tablesorter.js +3 -1
  27. data/vendor/assets/javascripts/jquery-tablesorter/addons/{jquery.tablesorter.pager.js → pager/jquery.tablesorter.pager.js} +507 -419
  28. data/vendor/assets/javascripts/jquery-tablesorter/jquery.metadata.js +53 -85
  29. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.js +465 -314
  30. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.min.js +6 -0
  31. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.widgets.js +618 -267
  32. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.widgets.min.js +12 -0
  33. data/vendor/assets/stylesheets/jquery-tablesorter/addons/{jquery.tablesorter.pager.css → pager/jquery.tablesorter.pager.css} +15 -14
  34. data/vendor/assets/stylesheets/jquery-tablesorter/theme.black-ice.css +161 -0
  35. data/vendor/assets/stylesheets/jquery-tablesorter/theme.blue.css +196 -0
  36. data/vendor/assets/stylesheets/jquery-tablesorter/theme.bootstrap.css +118 -0
  37. data/vendor/assets/stylesheets/jquery-tablesorter/theme.dark.css +162 -0
  38. data/vendor/assets/stylesheets/jquery-tablesorter/theme.default.css +164 -0
  39. data/vendor/assets/stylesheets/jquery-tablesorter/theme.dropbox.css +175 -0
  40. data/vendor/assets/stylesheets/jquery-tablesorter/theme.green.css +174 -0
  41. data/vendor/assets/stylesheets/jquery-tablesorter/theme.grey.css +217 -0
  42. data/vendor/assets/stylesheets/jquery-tablesorter/theme.ice.css +164 -0
  43. data/vendor/assets/stylesheets/jquery-tablesorter/theme.jui.css +128 -0
  44. metadata +44 -14
  45. data/lib/tasks/jquery-tablesorter_tasks.rake +0 -39
  46. data/vendor/assets/images/jquery-tablesorter/addons/icons/loading.gif +0 -0
  47. data/vendor/assets/stylesheets/jquery-tablesorter/blue.css +0 -3
  48. data/vendor/assets/stylesheets/jquery-tablesorter/blue/style.css +0 -128
  49. data/vendor/assets/stylesheets/jquery-tablesorter/ui.css +0 -4
  50. data/vendor/assets/stylesheets/jquery-tablesorter/ui/style.css +0 -67
@@ -1,56 +1,48 @@
1
- /*
1
+ /*
2
2
  * Metadata - jQuery plugin for parsing metadata from elements
3
3
  *
4
- * Copyright (c) 2006 John Resig, Yehuda Katz, J�örn Zaefferer, Paul McLanahan
4
+ * Copyright (c) 2006 John Resig, Yehuda Katz, Jörn Zaefferer, Paul McLanahan
5
5
  *
6
6
  * Dual licensed under the MIT and GPL licenses:
7
7
  * http://www.opensource.org/licenses/mit-license.php
8
8
  * http://www.gnu.org/licenses/gpl.html
9
9
  *
10
- * Revision: $Id: jquery.metadata.js 3640 2007-10-11 18:34:38Z pmclanahan $
11
- *
12
10
  */
13
11
 
14
12
  /**
15
13
  * Sets the type of metadata to use. Metadata is encoded in JSON, and each property
16
14
  * in the JSON will become a property of the element itself.
17
15
  *
18
- * There are four supported types of metadata storage:
16
+ * There are three supported types of metadata storage:
19
17
  *
20
18
  * attr: Inside an attribute. The name parameter indicates *which* attribute.
21
- *
19
+ *
22
20
  * class: Inside the class attribute, wrapped in curly braces: { }
23
- *
21
+ *
24
22
  * elem: Inside a child element (e.g. a script tag). The
25
23
  * name parameter indicates *which* element.
26
- * html5: Values are stored in data-* attributes.
27
- *
24
+ *
28
25
  * The metadata for an element is loaded the first time the element is accessed via jQuery.
29
26
  *
30
27
  * As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
31
28
  * matched by expr, then redefine the metadata type and run another $(expr) for other elements.
32
- *
29
+ *
33
30
  * @name $.metadata.setType
34
31
  *
35
32
  * @example <p id="one" class="some_class {item_id: 1, item_label: 'Label'}">This is a p</p>
36
33
  * @before $.metadata.setType("class")
37
34
  * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
38
35
  * @desc Reads metadata from the class attribute
39
- *
36
+ *
40
37
  * @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
41
38
  * @before $.metadata.setType("attr", "data")
42
39
  * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
43
40
  * @desc Reads metadata from a "data" attribute
44
- *
41
+ *
45
42
  * @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
46
43
  * @before $.metadata.setType("elem", "script")
47
44
  * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
48
45
  * @desc Reads metadata from a nested script element
49
- *
50
- * @example <p id="one" class="some_class" data-item_id="1" data-item_label="Label">This is a p</p>
51
- * @before $.metadata.setType("html5")
52
- * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
53
- * @desc Reads metadata from a series of data-* attributes
54
46
  *
55
47
  * @param String type The encoding type
56
48
  * @param String name The name of the attribute to be used to get metadata (optional)
@@ -63,73 +55,49 @@
63
55
  (function($) {
64
56
 
65
57
  $.extend({
66
- metadata : {
67
- defaults : {
68
- type: 'class',
69
- name: 'metadata',
70
- cre: /({.*})/,
71
- single: 'metadata'
72
- },
73
- setType: function( type, name ){
74
- this.defaults.type = type;
75
- this.defaults.name = name;
76
- },
77
- get: function( elem, opts ){
78
- var settings = $.extend({},this.defaults,opts);
79
- // check for empty string in single property
80
- if ( !settings.single.length ) settings.single = 'metadata';
81
-
82
- var data = $.data(elem, settings.single);
83
- // returned cached data if it already exists
84
- if ( data ) return data;
85
-
86
- data = "{}";
87
-
88
- var getData = function(data) {
89
- if(typeof data != "string") return data;
90
-
91
- if( data.indexOf('{') < 0 ) {
92
- data = eval("(" + data + ")");
93
- }
94
- }
95
-
96
- var getObject = function(data) {
97
- if(typeof data != "string") return data;
98
-
99
- data = eval("(" + data + ")");
100
- return data;
101
- }
102
-
103
- if ( settings.type == "html5" ) {
104
- var object = {};
105
- $( elem.attributes ).each(function() {
106
- var name = this.nodeName;
107
- if(name.match(/^data-/)) name = name.replace(/^data-/, '');
108
- else return true;
109
- object[name] = getObject(this.nodeValue);
110
- });
111
- } else {
112
- if ( settings.type == "class" ) {
113
- var m = settings.cre.exec( elem.className );
114
- if ( m )
115
- data = m[1];
116
- } else if ( settings.type == "elem" ) {
117
- if( !elem.getElementsByTagName ) return;
118
- var e = elem.getElementsByTagName(settings.name);
119
- if ( e.length )
120
- data = $.trim(e[0].innerHTML);
121
- } else if ( elem.getAttribute != undefined ) {
122
- var attr = elem.getAttribute( settings.name );
123
- if ( attr )
124
- data = attr;
125
- }
126
- object = getObject(data.indexOf("{") < 0 ? "{" + data + "}" : data);
127
- }
128
-
129
- $.data( elem, settings.single, object );
130
- return object;
131
- }
132
- }
58
+ metadata : {
59
+ defaults : {
60
+ type: 'class',
61
+ name: 'metadata',
62
+ cre: /(\{.*\})/,
63
+ single: 'metadata'
64
+ },
65
+ setType: function( type, name ){
66
+ this.defaults.type = type;
67
+ this.defaults.name = name;
68
+ },
69
+ get: function( elem, opts ){
70
+ var data, m, e, attr,
71
+ settings = $.extend({},this.defaults,opts);
72
+ // check for empty string in single property
73
+ if ( !settings.single.length ) { settings.single = 'metadata'; }
74
+
75
+ data = $.data(elem, settings.single);
76
+ // returned cached data if it already exists
77
+ if ( data ) { return data; }
78
+
79
+ data = "{}";
80
+
81
+ if ( settings.type === "class" ) {
82
+ m = settings.cre.exec( elem.className );
83
+ if ( m ) { data = m[1]; }
84
+ } else if ( settings.type === "elem" ) {
85
+ if( !elem.getElementsByTagName ) { return undefined; }
86
+ e = elem.getElementsByTagName(settings.name);
87
+ if ( e.length ) { data = $.trim(e[0].innerHTML); }
88
+ } else if ( elem.getAttribute !== undefined ) {
89
+ attr = elem.getAttribute( settings.name );
90
+ if ( attr ) { data = attr; }
91
+ }
92
+
93
+ if ( data.indexOf( '{' ) <0 ) { data = "{" + data + "}"; }
94
+
95
+ data = eval("(" + data + ")");
96
+
97
+ $.data( elem, settings.single, data );
98
+ return data;
99
+ }
100
+ }
133
101
  });
134
102
 
135
103
  /**
@@ -142,7 +110,7 @@ $.extend({
142
110
  * @cat Plugins/Metadata
143
111
  */
144
112
  $.fn.metadata = function( opts ){
145
- return $.metadata.get( this[0], opts );
113
+ return $.metadata.get( this[0], opts );
146
114
  };
147
115
 
148
116
  })(jQuery);
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * TableSorter 2.3.11 - Client-side table sorting with ease!
2
+ * TableSorter 2.4.6 - Client-side table sorting with ease!
3
3
  * @requires jQuery v1.2.6+
4
4
  *
5
5
  * Copyright (c) 2007 Christian Bach
@@ -14,24 +14,32 @@
14
14
  * @author Christian Bach/christian.bach@polyester.se
15
15
  * @contributor Rob Garrison/https://github.com/Mottie/tablesorter
16
16
  */
17
+ /*jshint evil:true, browser:true, jquery:true, unused:false */
18
+ /*global console:false, alert:false */
17
19
  !(function($) {
20
+ "use strict";
18
21
  $.extend({
19
22
  tablesorter: new function() {
20
23
 
21
- this.version = "2.3.11";
24
+ var ts = this;
22
25
 
23
- var parsers = [], widgets = [];
24
- this.defaults = {
26
+ ts.version = "2.4.5";
27
+
28
+ ts.parsers = [];
29
+ ts.widgets = [];
30
+ ts.defaults = {
25
31
 
26
32
  // appearance
33
+ theme : 'default', // adds tablesorter-{theme} to the table for styling
27
34
  widthFixed : false, // adds colgroup to fix widths of columns
35
+ showProcessing : false, // show an indeterminate timer icon in the header when the table is sorted or filtered.
28
36
 
29
37
  // functionality
30
38
  cancelSelection : true, // prevent text selection in the header
31
- dateFormat : "mmddyyyy", // other options: "ddmmyyy" or "yyyymmdd"
32
- sortMultiSortKey : "shiftKey", // key used to select additional columns
39
+ dateFormat : 'mmddyyyy', // other options: "ddmmyyy" or "yyyymmdd"
40
+ sortMultiSortKey : 'shiftKey', // key used to select additional columns
33
41
  usNumberFormat : true, // false for German "1.234.567,89" or French "1 234 567,89"
34
- delayInit : false, // if false, the parsed table contents will not update until the first sort.
42
+ delayInit : false, // if false, the parsed table contents will not update until the first sort
35
43
 
36
44
  // sort options
37
45
  headers : {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc.
@@ -40,20 +48,20 @@
40
48
  sortList : [], // Initial sort order; applied initially; updated when manually sorted
41
49
  sortAppend : null, // column(s) sorted last; always applied
42
50
 
43
- sortInitialOrder : "asc", // sort direction on first click
51
+ sortInitialOrder : 'asc', // sort direction on first click
44
52
  sortLocaleCompare: false, // replace equivalent character (accented characters)
45
53
  sortReset : false, // third click on the header will reset column to default - unsorted
46
54
  sortRestart : false, // restart sort to "sortInitialOrder" when clicking on previously unsorted columns
47
55
 
48
- emptyTo : "bottom", // sort empty cell to bottom, top, none, zero
49
- stringTo : "max", // sort strings in numerical column as max, min, top, bottom, zero
50
- textExtraction : "simple", // text extraction method/function - function(node, table, cellIndex){}
56
+ emptyTo : 'bottom', // sort empty cell to bottom, top, none, zero
57
+ stringTo : 'max', // sort strings in numerical column as max, min, top, bottom, zero
58
+ textExtraction : 'simple', // text extraction method/function - function(node, table, cellIndex){}
51
59
  textSorter : null, // use custom text sorter - function(a,b){ return a.sort(b); } // basic sort
52
60
 
53
61
  // widget options
54
62
  widgets: [], // method to add widgets, e.g. widgets: ['zebra']
55
63
  widgetOptions : {
56
- zebra : [ "even", "odd" ] // zebra widget alternating row class names
64
+ zebra : [ 'even', 'odd' ] // zebra widget alternating row class names
57
65
  },
58
66
  initWidgets : true, // apply widgets on tablesorter initialization
59
67
 
@@ -63,15 +71,19 @@
63
71
 
64
72
  // css class names
65
73
  tableClass : 'tablesorter',
66
- cssAsc : "tablesorter-headerSortUp",
67
- cssChildRow : "expand-child",
68
- cssDesc : "tablesorter-headerSortDown",
69
- cssHeader : "tablesorter-header",
70
- cssInfoBlock : "tablesorter-infoOnly", // don't sort tbody with this class name
74
+ cssAsc : 'tablesorter-headerSortUp',
75
+ cssChildRow : 'tablesorter-childRow', // previously "expand-child"
76
+ cssDesc : 'tablesorter-headerSortDown',
77
+ cssHeader : 'tablesorter-header',
78
+ cssHeaderRow : 'tablesorter-headerRow',
79
+ cssIcon : 'tablesorter-icon', // if this class exists, a <i> will be added to the header automatically
80
+ cssInfoBlock : 'tablesorter-infoOnly', // don't sort tbody with this class name
81
+ cssProcessing : 'tablesorter-processing', // processing icon applied to header during sort/filter
71
82
 
72
83
  // selectors
73
- selectorHeaders : '> thead th',
74
- selectorRemove : "tr.remove-me",
84
+ selectorHeaders : '> thead th, > thead td',
85
+ selectorSort : 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort
86
+ selectorRemove : '.remove-me',
75
87
 
76
88
  // advanced
77
89
  debug : false,
@@ -100,8 +112,7 @@
100
112
  log(s + " (" + (new Date().getTime() - d.getTime()) + "ms)");
101
113
  }
102
114
 
103
- this.benchmark = benchmark;
104
- this.hasInitialized = false;
115
+ ts.benchmark = benchmark;
105
116
 
106
117
  function getElementText(table, node, cellIndex) {
107
118
  if (!node) { return ""; }
@@ -125,19 +136,8 @@
125
136
  return $.trim(text);
126
137
  }
127
138
 
128
- /* parsers utils */
129
- function getParserById(name) {
130
- var i, l = parsers.length;
131
- for (i = 0; i < l; i++) {
132
- if (parsers[i].id.toLowerCase() === (name.toString()).toLowerCase()) {
133
- return parsers[i];
134
- }
135
- }
136
- return false;
137
- }
138
-
139
139
  function detectParserForColumn(table, rows, rowIndex, cellIndex) {
140
- var i, l = parsers.length,
140
+ var i, l = ts.parsers.length,
141
141
  node = false,
142
142
  nodeValue = '',
143
143
  keepLooking = true;
@@ -154,18 +154,18 @@
154
154
  }
155
155
  }
156
156
  for (i = 1; i < l; i++) {
157
- if (parsers[i].is(nodeValue, table, node)) {
158
- return parsers[i];
157
+ if (ts.parsers[i].is(nodeValue, table, node)) {
158
+ return ts.parsers[i];
159
159
  }
160
160
  }
161
161
  // 0 is always the generic parser (text)
162
- return parsers[0];
162
+ return ts.parsers[0];
163
163
  }
164
164
 
165
165
  function buildParserCache(table, $headers) {
166
166
  var c = table.config,
167
167
  tb = $(table.tBodies).filter(':not(.' + c.cssInfoBlock + ')'),
168
- ts = $.tablesorter, rows, list, l, i, h, m, ch, cl, p, parsersDebug = "";
168
+ rows, list, l, i, h, ch, p, parsersDebug = "";
169
169
  if ( tb.length === 0) { return; } // In the case of empty tables
170
170
  rows = tb[0].rows;
171
171
  if (rows[0]) {
@@ -176,8 +176,8 @@
176
176
  h = $headers.filter(':not([colspan])[data-column="' + i + '"]:last,[colspan="1"][data-column="' + i + '"]:last');
177
177
  ch = c.headers[i];
178
178
  // get column parser
179
- p = getParserById( ts.getData(h, ch, 'sorter') );
180
- // empty cells behaviour - keeping emptyToBottom for backwards compatibility.
179
+ p = ts.getParserById( ts.getData(h, ch, 'sorter') );
180
+ // empty cells behaviour - keeping emptyToBottom for backwards compatibility
181
181
  c.empties[i] = ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' );
182
182
  // text strings behaviour in numerical sorts
183
183
  c.strings[i] = ts.getData(h, ch, 'string') || c.stringTo || 'max';
@@ -208,11 +208,14 @@
208
208
  if (tc.debug) {
209
209
  cacheTime = new Date();
210
210
  }
211
+ // processing icon
212
+ if (tc.showProcessing) {
213
+ ts.isProcessing(table, true);
214
+ }
211
215
  for (k = 0; k < b.length; k++) {
212
216
  tc.cache[k] = { row: [], normalized: [] };
213
217
  // ignore tbodies with class name from css.cssInfoBlock
214
218
  if (!$(b[k]).hasClass(tc.cssInfoBlock)) {
215
- $(b[k]).addClass('tablesorter-hidden');
216
219
  totalRows = (b[k] && b[k].rows.length) || 0;
217
220
  totalCells = (b[k].rows[0] && b[k].rows[0].cells.length) || 0;
218
221
  for (i = 0; i < totalRows; ++i) {
@@ -235,42 +238,13 @@
235
238
  cols.push(tc.cache[k].normalized.length); // add position for rowCache
236
239
  tc.cache[k].normalized.push(cols);
237
240
  }
238
- $(b[k]).removeClass('tablesorter-hidden');
239
241
  }
240
242
  }
241
- if (tc.debug) {
242
- benchmark("Building cache for " + totalRows + " rows", cacheTime);
243
- }
244
- }
245
-
246
- function getWidgetById(name) {
247
- var i, w, l = widgets.length;
248
- for (i = 0; i < l; i++) {
249
- w = widgets[i];
250
- if (w && w.hasOwnProperty('id') && w.id.toLowerCase() === name.toLowerCase()) {
251
- return w;
252
- }
253
- }
254
- }
255
-
256
- function applyWidget(table, init) {
257
- var tc = table.config, c = tc.widgets,
258
- time, i, w, l = c.length;
259
- if (tc.debug) {
260
- time = new Date();
261
- }
262
- for (i = 0; i < l; i++) {
263
- w = getWidgetById(c[i]);
264
- if ( w ) {
265
- if (init === true && w.hasOwnProperty('init')) {
266
- w.init(table, widgets, w);
267
- } else if (!init && w.hasOwnProperty('format')) {
268
- w.format(table);
269
- }
270
- }
243
+ if (tc.showProcessing) {
244
+ ts.isProcessing(table); // remove processing icon
271
245
  }
272
246
  if (tc.debug) {
273
- benchmark("Completed " + (init === true ? "initializing" : "applying") + " widgets", time);
247
+ benchmark("Building cache for " + totalRows + " rows", cacheTime);
274
248
  }
275
249
  }
276
250
 
@@ -279,15 +253,17 @@
279
253
  var c = table.config,
280
254
  b = table.tBodies,
281
255
  rows = [],
282
- r, n, totalRows, checkCell, c2 = c.cache,
283
- f, i, j, k, l, pos, appendTime;
256
+ c2 = c.cache,
257
+ r, n, totalRows, checkCell, $bk, $tb,
258
+ i, j, k, l, pos, appendTime;
284
259
  if (c.debug) {
285
260
  appendTime = new Date();
286
261
  }
287
262
  for (k = 0; k < b.length; k++) {
288
- if (!$(b[k]).hasClass(c.cssInfoBlock)){
289
- $(b[k]).addClass('tablesorter-hidden');
290
- f = document.createDocumentFragment();
263
+ $bk = $(b[k]);
264
+ if (!$bk.hasClass(c.cssInfoBlock)) {
265
+ // get tbody
266
+ $tb = ts.processTbody(table, $bk, true);
291
267
  r = c2[k].row;
292
268
  n = c2[k].normalized;
293
269
  totalRows = n.length;
@@ -299,12 +275,12 @@
299
275
  if (!c.appender || !c.removeRows) {
300
276
  l = r[pos].length;
301
277
  for (j = 0; j < l; j++) {
302
- f.appendChild(r[pos][j]);
278
+ $tb.append(r[pos][j]);
303
279
  }
304
280
  }
305
281
  }
306
- table.tBodies[k].appendChild(f);
307
- $(b[k]).removeClass('tablesorter-hidden');
282
+ // restore tbody
283
+ ts.processTbody(table, $tb, false);
308
284
  }
309
285
  }
310
286
  if (c.appender) {
@@ -314,7 +290,7 @@
314
290
  benchmark("Rebuilt table", appendTime);
315
291
  }
316
292
  // apply table widgets
317
- if (!init) { applyWidget(table); }
293
+ if (!init) { ts.applyWidget(table); }
318
294
  // trigger sortend
319
295
  $(table).trigger("sortEnd", table);
320
296
  }
@@ -325,7 +301,7 @@
325
301
  function computeThIndexes(t) {
326
302
  var matrix = [],
327
303
  lookup = {},
328
- trs = $(t).find('thead:eq(0) tr'),
304
+ trs = $(t).find('thead:eq(0) tr, tfoot tr'),
329
305
  i, j, k, l, c, cells, rowIndex, cellId, rowSpan, colSpan, firstAvailCol, matrixrow;
330
306
  for (i = 0; i < trs.length; i++) {
331
307
  cells = trs[i].cells;
@@ -367,36 +343,38 @@
367
343
  return (/^d/i.test(v) || v === 1);
368
344
  }
369
345
 
370
-
371
346
  function buildHeaders(table) {
372
347
  var header_index = computeThIndexes(table), ch, $t,
373
- $th, lock, time, $tableHeaders, c = table.config, ts = $.tablesorter;
348
+ t, lock, time, $tableHeaders, c = table.config;
374
349
  c.headerList = [];
375
350
  if (c.debug) {
376
351
  time = new Date();
377
352
  }
378
- $tableHeaders = $(table).find(c.selectorHeaders)
379
- .each(function(index) {
353
+ $tableHeaders = $(table).find(c.selectorHeaders).each(function(index) {
380
354
  $t = $(this);
381
355
  ch = c.headers[index];
382
- this.innerHTML = '<div class="tablesorter-header-inner">' + this.innerHTML + '</div>'; // faster than wrapInner
356
+ t = c.cssIcon ? '<i class="' + c.cssIcon + '"></i>' : ''; // add icon if cssIcon option exists
357
+ this.innerHTML = '<div class="tablesorter-header-inner">' + this.innerHTML + t + '</div>'; // faster than wrapInner
383
358
  if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index]); }
384
359
  this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex];
385
360
  this.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2];
386
361
  this.count = -1; // set to -1 because clicking on the header automatically adds one
387
- if (ts.getData($t, ch, 'sorter') === 'false') { this.sortDisabled = true; }
362
+ if (ts.getData($t, ch, 'sorter') === 'false') {
363
+ this.sortDisabled = true;
364
+ $t.addClass('sorter-false');
365
+ } else {
366
+ $t.removeClass('sorter-false');
367
+ }
388
368
  this.lockedOrder = false;
389
369
  lock = ts.getData($t, ch, 'lockedOrder') || false;
390
370
  if (typeof(lock) !== 'undefined' && lock !== false) {
391
371
  this.order = this.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0];
392
372
  }
393
- if (!this.sortDisabled) {
394
- $th = $t.addClass(c.cssHeader);
395
- }
373
+ $t.addClass( this.sortDisabled ? 'sorter-false' : c.cssHeader );
396
374
  // add cell to headerList
397
375
  c.headerList[index] = this;
398
376
  // add to parent in case there are multiple rows
399
- $t.parent().addClass(c.cssHeader);
377
+ $t.parent().addClass(c.cssHeaderRow);
400
378
  });
401
379
  if (table.config.debug) {
402
380
  benchmark("Built headers:", time);
@@ -405,39 +383,30 @@
405
383
  return $tableHeaders;
406
384
  }
407
385
 
408
- function isValueInArray(v, a) {
409
- var i, l = a.length;
410
- for (i = 0; i < l; i++) {
411
- if (a[i][0] === v) {
412
- return true;
413
- }
414
- }
415
- return false;
416
- }
417
-
418
- function setHeadersCss(table, $headers, list) {
419
- var f, h = [], i, j, l, css = [table.config.cssDesc, table.config.cssAsc];
386
+ function setHeadersCss(table, $headers) {
387
+ var f, i, j, l,
388
+ c = table.config,
389
+ list = c.sortList,
390
+ css = [c.cssDesc, c.cssAsc],
391
+ // find the footer
392
+ $t = $(table).find('tfoot tr').children().removeClass(css.join(' '));
420
393
  // remove all header information
421
- $headers
422
- .removeClass(css.join(' '))
423
- .each(function() {
424
- if (!this.sortDisabled) {
425
- h[this.column] = $(this);
426
- }
427
- });
394
+ $headers.removeClass(css.join(' '));
428
395
  l = list.length;
429
396
  for (i = 0; i < l; i++) {
430
- if (list[i][1] === 2) { continue; } // direction = 2 means reset!
431
- if (h[list[i][0]]) {
432
- // add class if cell exists - fix for issue #78
433
- h[list[i][0]].addClass(css[list[i][1]]);
434
- }
435
- // multicolumn sorting updating
436
- f = $headers.filter('[data-column="' + list[i][0] + '"]');
437
- if (l > 1 && f.length) {
438
- for (j = 0; j < f.length; j++) {
439
- if (!f[j].sortDisabled) {
440
- $(f[j]).addClass(css[list[i][1]]);
397
+ // direction = 2 means reset!
398
+ if (list[i][1] !== 2) {
399
+ // multicolumn sorting updating - choose the :last in case there are nested columns
400
+ f = $headers.not('.sorter-false').filter('[data-column="' + list[i][0] + '"]' + (l === 1 ? ':last' : '') );
401
+ if (f.length) {
402
+ for (j = 0; j < f.length; j++) {
403
+ if (!f[j].sortDisabled) {
404
+ f.eq(j).addClass(css[list[i][1]]);
405
+ // add sorted class to footer, if it exists
406
+ if ($t.length) {
407
+ $t.filter('[data-column="' + list[i][0] + '"]').eq(j).addClass(css[list[i][1]]);
408
+ }
409
+ }
441
410
  }
442
411
  }
443
412
  }
@@ -445,35 +414,41 @@
445
414
  }
446
415
 
447
416
  function fixColumnWidth(table) {
448
- if (table.config.widthFixed) {
449
- var colgroup = $('<colgroup>');
417
+ if (table.config.widthFixed && $(table).find('colgroup').length === 0) {
418
+ var colgroup = $('<colgroup>'),
419
+ overallWidth = $(table).width();
450
420
  $("tr:first td", table.tBodies[0]).each(function() {
451
- colgroup.append($('<col>').css('width', $(this).width()));
421
+ colgroup.append($('<col>').css('width', parseInt(($(this).width()/overallWidth)*1000, 10)/10 + '%'));
452
422
  });
453
423
  $(table).prepend(colgroup);
454
424
  }
455
425
  }
456
426
 
457
- function updateHeaderSortCount(table, sortList) {
458
- var i, s, o, c = table.config,
459
- l = sortList.length;
460
- for (i = 0; i < l; i++) {
461
- s = sortList[i];
427
+ function updateHeaderSortCount(table, list) {
428
+ var s, o, c = table.config,
429
+ l = c.headerList.length,
430
+ sl = list || c.sortList;
431
+ c.sortList = [];
432
+ $.each(sl, function(i,v){
433
+ // ensure all sortList values are numeric - fixes #127
434
+ s = [ parseInt(v[0], 10), parseInt(v[1], 10) ];
435
+ // make sure header exists
462
436
  o = c.headerList[s[0]];
463
437
  if (o) { // prevents error if sorton array is wrong
438
+ c.sortList.push(s);
464
439
  o.count = s[1] % (c.sortReset ? 3 : 2);
465
440
  }
466
- }
441
+ });
467
442
  }
468
443
 
469
444
  function getCachedSortType(parsers, i) {
470
445
  return (parsers && parsers[i]) ? parsers[i].type || '' : '';
471
446
  }
472
447
 
473
- /* sorting methods - reverted sorting method back to version 2.0.3 */
474
- function multisort(table, sortList) {
448
+ // sort multiple columns
449
+ function multisort(table) {
475
450
  var dynamicExp, sortWrapper, col, mx = 0, dir = 0, tc = table.config,
476
- l = sortList.length, bl = table.tBodies.length,
451
+ sortList = tc.sortList, l = sortList.length, bl = table.tBodies.length,
477
452
  sortTime, i, j, k, c, cache, lc, s, e, order, orgOrderCol;
478
453
  if (tc.debug) { sortTime = new Date(); }
479
454
  for (k = 0; k < bl; k++) {
@@ -514,48 +489,54 @@
514
489
  dynamicExp += "}; ";
515
490
  cache.normalized.sort(eval(dynamicExp)); // sort using eval expression
516
491
  }
517
- if (tc.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order+ " time", sortTime); }
492
+ if (tc.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time", sortTime); }
493
+ }
494
+
495
+ function resortComplete($table, callback){
496
+ $table.trigger('updateComplete');
497
+ if (typeof callback === "function") {
498
+ callback($table[0]);
499
+ }
518
500
  }
519
501
 
520
502
  function checkResort($table, flag, callback) {
521
- var t = $table[0];
522
503
  if (flag !== false) {
523
- $table.trigger("sorton", [t.config.sortList, function(){
524
- $table.trigger('updateComplete');
525
- if (typeof callback === "function") {
526
- callback(t);
527
- }
504
+ $table.trigger("sorton", [$table[0].config.sortList, function(){
505
+ resortComplete($table, callback);
528
506
  }]);
529
507
  } else {
530
- $table.trigger('updateComplete');
531
- if (typeof callback === "function") {
532
- callback(t);
533
- }
508
+ resortComplete($table, callback);
534
509
  }
535
510
  }
536
511
 
537
512
  /* public methods */
538
- this.construct = function(settings) {
513
+ ts.construct = function(settings) {
539
514
  return this.each(function() {
540
- // if no thead or tbody quit.
541
- if (!this.tHead || this.tBodies.length === 0) { return; }
515
+ // if no thead or tbody, or tablesorter is already present, quit
516
+ if (!this.tHead || this.tBodies.length === 0 || this.hasInitialized === true) { return; }
542
517
  // declare
543
- var $headers, $cell, $this,
544
- c, i, j, k, a, s, o, downTime,
518
+ var $headers, $cell, $this = $(this),
519
+ c, i, j, k = '', a, s, o, downTime,
545
520
  m = $.metadata;
521
+ // initialization flag
522
+ this.hasInitialized = false;
546
523
  // new blank config object
547
524
  this.config = {};
548
- // merge and extend.
549
- c = $.extend(true, this.config, $.tablesorter.defaults, settings);
550
-
551
- if (c.debug) { $.data( this, 'startoveralltimer', new Date()); }
552
- // store common expression for speed
553
- $this = $(this).addClass(c.tableClass);
525
+ // merge and extend
526
+ c = $.extend(true, this.config, ts.defaults, settings);
554
527
  // save the settings where they read
555
528
  $.data(this, "tablesorter", c);
529
+ if (c.debug) { $.data( this, 'startoveralltimer', new Date()); }
530
+ // constants
556
531
  c.supportsTextContent = $('<span>x</span>')[0].textContent === 'x';
532
+ c.supportsDataObject = parseFloat($.fn.jquery) >= 1.4;
557
533
  // digit sort text location; keeping max+/- for backwards compatibility
558
534
  c.string = { 'max': 1, 'min': -1, 'max+': 1, 'max-': -1, 'zero': 0, 'none': 0, 'null': 0, 'top': true, 'bottom': false };
535
+ // add table theme class only if there isn't already one there
536
+ if (!/tablesorter\-/.test($this.attr('class'))) {
537
+ k = (c.theme !== '' ? ' tablesorter-' + c.theme : '');
538
+ }
539
+ $this.addClass(c.tableClass + k);
559
540
  // build headers
560
541
  $headers = buildHeaders(this);
561
542
  // try to auto detect column type, and store in tables config
@@ -563,29 +544,36 @@
563
544
  // build the cache for the tbody cells
564
545
  // delayInit will delay building the cache until the user starts a sort
565
546
  if (!c.delayInit) { buildCache(this); }
566
- // fixate columns if the users supplies the fixedWidth option
567
- fixColumnWidth(this);
568
547
  // apply event handling to headers
569
548
  // this is to big, perhaps break it out?
570
- $headers.bind('mousedown.tablesorter mouseup.tablesorter', function(e, external) {
549
+ $headers
550
+ // http://stackoverflow.com/questions/5312849/jquery-find-self
551
+ .find('*').andSelf().filter(c.selectorSort)
552
+ .unbind('mousedown.tablesorter mouseup.tablesorter')
553
+ .bind('mousedown.tablesorter mouseup.tablesorter', function(e, external) {
554
+ // jQuery v1.2.6 doesn't have closest()
555
+ var $cell = this.tagName.match('TH|TD') ? $(this) : $(this).parents('th, td').filter(':last'), cell = $cell[0];
556
+ // only recognize left clicks
557
+ if ((e.which || e.button) !== 1) { return false; }
558
+ // set timer on mousedown
571
559
  if (e.type === 'mousedown') {
572
560
  downTime = new Date().getTime();
573
- return !c.cancelSelection;
561
+ return e.target.tagName === "INPUT" ? '' : !c.cancelSelection;
574
562
  }
575
- // prevent resizable widget from initializing a sort (long clicks are ignored)
576
- if (external !== true && (new Date().getTime() - downTime > 500)) { return false; }
563
+ // ignore long clicks (prevents resizable widget from initializing a sort)
564
+ if (external !== true && (new Date().getTime() - downTime > 250)) { return false; }
577
565
  if (c.delayInit && !c.cache) { buildCache($this[0]); }
578
- if (!this.sortDisabled) {
579
- // Only call sortStart if sorting is enabled.
566
+ if (!cell.sortDisabled) {
567
+ // Only call sortStart if sorting is enabled
580
568
  $this.trigger("sortStart", $this[0]);
581
569
  // store exp, for speed
582
- $cell = $(this);
570
+ // $cell = $(this);
583
571
  k = !e[c.sortMultiSortKey];
584
572
  // get current column sort order
585
- this.count = (this.count + 1) % (c.sortReset ? 3 : 2);
573
+ cell.count = (cell.count + 1) % (c.sortReset ? 3 : 2);
586
574
  // reset all sorts on non-current column - issue #30
587
575
  if (c.sortRestart) {
588
- i = this;
576
+ i = cell;
589
577
  $headers.each(function() {
590
578
  // only reset counts on columns that weren't just clicked on and if not included in a multisort
591
579
  if (this !== i && (k || !$(this).is('.' + c.cssDesc + ',.' + c.cssAsc))) {
@@ -594,7 +582,7 @@
594
582
  });
595
583
  }
596
584
  // get current column index
597
- i = this.column;
585
+ i = cell.column;
598
586
  // user only wants to sort on one column
599
587
  if (k) {
600
588
  // flush the sort list
@@ -608,21 +596,27 @@
608
596
  }
609
597
  }
610
598
  // add column to sort list
611
- o = this.order[this.count];
599
+ o = cell.order[cell.count];
612
600
  if (o < 2) {
613
601
  c.sortList.push([i, o]);
614
602
  // add other columns if header spans across multiple
615
- if (this.colSpan > 1) {
616
- for (j = 1; j < this.colSpan; j++) {
603
+ if (cell.colSpan > 1) {
604
+ for (j = 1; j < cell.colSpan; j++) {
617
605
  c.sortList.push([i + j, o]);
618
606
  }
619
607
  }
620
608
  }
621
609
  // multi column sorting
622
610
  } else {
623
- // the user has clicked on an already sorted column.
624
- if (isValueInArray(i, c.sortList)) {
625
- // reverse the sorting direction for all tables.
611
+ // get rid of the sortAppend before adding more - fixes issue #115
612
+ if (c.sortAppend && c.sortList.length > 1) {
613
+ if (ts.isValueInArray(c.sortAppend[0][0], c.sortList)) {
614
+ c.sortList.pop();
615
+ }
616
+ }
617
+ // the user has clicked on an already sorted column
618
+ if (ts.isValueInArray(i, c.sortList)) {
619
+ // reverse the sorting direction for all tables
626
620
  for (j = 0; j < c.sortList.length; j++) {
627
621
  s = c.sortList[j];
628
622
  o = c.headerList[s[0]];
@@ -636,12 +630,12 @@
636
630
  }
637
631
  } else {
638
632
  // add column to sort list array
639
- o = this.order[this.count];
633
+ o = cell.order[cell.count];
640
634
  if (o < 2) {
641
635
  c.sortList.push([i, o]);
642
636
  // add other columns if header spans across multiple
643
- if (this.colSpan > 1) {
644
- for (j = 1; j < this.colSpan; j++) {
637
+ if (cell.colSpan > 1) {
638
+ for (j = 1; j < cell.colSpan; j++) {
645
639
  c.sortList.push([i + j, o]);
646
640
  }
647
641
  }
@@ -658,10 +652,13 @@
658
652
  }
659
653
  // sortBegin event triggered immediately before the sort
660
654
  $this.trigger("sortBegin", $this[0]);
661
- // set css for headers
662
- setHeadersCss($this[0], $headers, c.sortList);
663
- multisort($this[0], c.sortList);
664
- appendToTable($this[0]);
655
+ // setTimeout needed so the processing icon shows up
656
+ setTimeout(function(){
657
+ // set css for headers
658
+ setHeadersCss($this[0], $headers);
659
+ multisort($this[0]);
660
+ appendToTable($this[0]);
661
+ }, 1);
665
662
  }
666
663
  });
667
664
  if (c.cancelSelection) {
@@ -674,24 +671,33 @@
674
671
  }
675
672
  // apply easy methods that trigger binded events
676
673
  $this
674
+ .unbind('update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave')
677
675
  .bind("update", function(e, resort, callback) {
678
676
  // remove rows/elements before update
679
677
  $(c.selectorRemove, this).remove();
680
- // rebuild parsers.
678
+ // rebuild parsers
681
679
  c.parsers = buildParserCache(this, $headers);
682
680
  // rebuild the cache map
683
681
  buildCache(this);
684
682
  checkResort($this, resort, callback);
685
683
  })
686
684
  .bind("updateCell", function(e, cell, resort, callback) {
687
- // get position from the dom.
688
- var t = this, $tb = $(this).find('tbody'), row, pos,
685
+ // get position from the dom
686
+ var l, row, icell,
687
+ t = this, $tb = $(this).find('tbody'),
689
688
  // update cache - format: function(s, table, cell, cellIndex)
690
- tbdy = $tb.index( $(cell).closest('tbody') );
691
- row = $tb.eq(tbdy).find('tr').index( $(cell).closest('tr') );
692
- pos = [ row, cell.cellIndex];
693
- t.config.cache[tbdy].normalized[pos[0]][pos[1]] = c.parsers[pos[1]].format( getElementText(t, cell, pos[1]), t, cell, pos[1] );
694
- checkResort($this, resort, callback);
689
+ // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
690
+ tbdy = $tb.index( $(cell).parents('tbody').filter(':last') ),
691
+ $row = $(cell).parents('tr').filter(':last');
692
+ // tbody may not exist if update is initialized while tbody is removed for processing
693
+ if ($tb.length && tbdy >= 0) {
694
+ row = $tb.eq(tbdy).find('tr').index( $row );
695
+ icell = cell.cellIndex;
696
+ l = t.config.cache[tbdy].normalized[row].length - 1;
697
+ t.config.cache[tbdy].row[t.config.cache[tbdy].normalized[row][l]] = $row;
698
+ t.config.cache[tbdy].normalized[row][icell] = c.parsers[icell].format( getElementText(t, cell, icell), t, cell, icell );
699
+ checkResort($this, resort, callback);
700
+ }
695
701
  })
696
702
  .bind("addRows", function(e, $row, resort, callback) {
697
703
  var i, rows = $row.filter('tr').length,
@@ -715,68 +721,158 @@
715
721
  })
716
722
  .bind("sorton", function(e, list, callback, init) {
717
723
  $(this).trigger("sortStart", this);
718
- var l = c.headerList.length;
719
- c.sortList = [];
720
- $.each(list, function(i,v){
721
- // make sure column exists
722
- if (v[0] < l) { c.sortList.push(list[i]); }
723
- });
724
724
  // update header count index
725
- updateHeaderSortCount(this, c.sortList);
725
+ updateHeaderSortCount(this, list);
726
726
  // set css for headers
727
- setHeadersCss(this, $headers, c.sortList);
727
+ setHeadersCss(this, $headers);
728
728
  // sort the table and append it to the dom
729
- multisort(this, c.sortList);
729
+ multisort(this);
730
730
  appendToTable(this, init);
731
731
  if (typeof callback === "function") {
732
732
  callback(this);
733
733
  }
734
734
  })
735
- .bind("appendCache", function(e, init) {
735
+ .bind("appendCache", function(e, callback, init) {
736
736
  appendToTable(this, init);
737
+ if (typeof callback === "function") {
738
+ callback(this);
739
+ }
737
740
  })
738
741
  .bind("applyWidgetId", function(e, id) {
739
- getWidgetById(id).format(this);
742
+ ts.getWidgetById(id).format(this, c, c.widgetOptions);
740
743
  })
741
744
  .bind("applyWidgets", function(e, init) {
742
745
  // apply widgets
743
- applyWidget(this, init);
746
+ ts.applyWidget(this, init);
744
747
  })
745
- .bind("destroy", function(e,c){
746
- $.tablesorter.destroy(this, c);
748
+ .bind("refreshWidgets", function(e, all, dontapply){
749
+ ts.refreshWidgets(this, all, dontapply);
750
+ })
751
+ .bind("destroy", function(e, c, cb){
752
+ ts.destroy(this, c, cb);
747
753
  });
748
754
 
749
755
  // get sort list from jQuery data or metadata
750
- if ($this.data() && typeof $this.data().sortlist !== 'undefined') {
756
+ // in jQuery < 1.4, an error occurs when calling $this.data()
757
+ if (c.supportsDataObject && typeof $this.data().sortlist !== 'undefined') {
751
758
  c.sortList = $this.data().sortlist;
752
759
  } else if (m && ($this.metadata() && $this.metadata().sortlist)) {
753
760
  c.sortList = $this.metadata().sortlist;
754
761
  }
755
762
  // apply widget init code
756
- applyWidget(this, true);
757
- // if user has supplied a sort list to constructor.
763
+ ts.applyWidget(this, true);
764
+ // if user has supplied a sort list to constructor
758
765
  if (c.sortList.length > 0) {
759
766
  $this.trigger("sorton", [c.sortList, {}, !c.initWidgets]);
760
767
  } else if (c.initWidgets) {
761
768
  // apply widget format
762
- applyWidget(this);
769
+ ts.applyWidget(this);
770
+ }
771
+
772
+ // fixate columns if the users supplies the fixedWidth option
773
+ // do this after theme has been applied
774
+ fixColumnWidth(this);
775
+
776
+ // show processesing icon
777
+ if (c.showProcessing) {
778
+ $this
779
+ .unbind('sortBegin sortEnd')
780
+ .bind('sortBegin sortEnd', function(e) {
781
+ ts.isProcessing($this[0], e.type === 'sortBegin');
782
+ });
763
783
  }
764
784
 
765
785
  // initialized
766
786
  this.hasInitialized = true;
767
787
  if (c.debug) {
768
- $.tablesorter.benchmark("Overall initialization time", $.data( this, 'startoveralltimer'));
788
+ ts.benchmark("Overall initialization time", $.data( this, 'startoveralltimer'));
769
789
  }
770
790
  $this.trigger('tablesorter-initialized', this);
771
791
  if (typeof c.initialized === 'function') { c.initialized(this); }
772
792
  });
773
793
  };
774
794
 
795
+ // *** Process table ***
796
+ // add processing indicator
797
+ ts.isProcessing = function(table, toggle, $ths) {
798
+ var c = table.config,
799
+ // default to all headers
800
+ $h = $ths || $(table).find('.' + c.cssHeader);
801
+ if (toggle) {
802
+ if (c.sortList.length > 0) {
803
+ // get headers from the sortList
804
+ $h = $h.filter(function(){
805
+ // get data-column from attr to keep compatibility with jQuery 1.2.6
806
+ return this.sortDisabled ? false : ts.isValueInArray( parseFloat($(this).attr('data-column')), c.sortList);
807
+ });
808
+ }
809
+ $h.addClass(c.cssProcessing);
810
+ } else {
811
+ $h.removeClass(c.cssProcessing);
812
+ }
813
+ };
814
+
815
+ // detach tbody but save the position
816
+ // don't use tbody because there are portions that look for a tbody index (updateCell)
817
+ ts.processTbody = function(table, $tb, getIt){
818
+ var t, holdr;
819
+ if (getIt) {
820
+ $tb.before('<span class="tablesorter-savemyplace"/>');
821
+ holdr = ($.fn.detach) ? $tb.detach() : $tb.remove();
822
+ return holdr;
823
+ }
824
+ holdr = $(table).find('span.tablesorter-savemyplace');
825
+ $tb.insertAfter( holdr );
826
+ holdr.remove();
827
+ };
828
+
829
+ ts.clearTableBody = function(table) {
830
+ $(table.tBodies).filter(':not(.' + table.config.cssInfoBlock + ')').empty();
831
+ };
832
+
833
+ ts.destroy = function(table, removeClasses, callback){
834
+ var $t = $(table), c = table.config,
835
+ $h = $t.find('thead:first');
836
+ // clear flag in case the plugin is initialized again
837
+ table.hasInitialized = false;
838
+ // remove widget added rows
839
+ $h.find('tr:not(.' + c.cssHeaderRow + ')').remove();
840
+ // remove resizer widget stuff
841
+ $h.find('.tablesorter-resizer').remove();
842
+ // remove all widgets
843
+ ts.refreshWidgets(table, true, true);
844
+ // disable tablesorter
845
+ $t
846
+ .removeData('tablesorter')
847
+ .unbind('update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave')
848
+ .find('.' + c.cssHeader)
849
+ .unbind('click mousedown mousemove mouseup')
850
+ .removeClass(c.cssHeader + ' ' + c.cssAsc + ' ' + c.cssDesc)
851
+ .find('.tablesorter-header-inner').each(function(){
852
+ if (c.cssIcon !== '') { $(this).find('.' + c.cssIcon).remove(); }
853
+ $(this).replaceWith( $(this).contents() );
854
+ });
855
+ if (removeClasses !== false) {
856
+ $t.removeClass(c.tableClass);
857
+ }
858
+ if (typeof callback === 'function') {
859
+ callback(table);
860
+ }
861
+ };
862
+
863
+ // *** sort functions ***
864
+ // regex used in natural sort
865
+ ts.regex = [
866
+ /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi, // chunk/tokenize numbers & letters
867
+ /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, //date
868
+ /^0x[0-9a-f]+$/i // hex
869
+ ];
870
+
775
871
  // Natural sort - https://github.com/overset/javascript-natural-sort
776
- this.sortText = function(table, a, b, col) {
872
+ ts.sortText = function(table, a, b, col) {
777
873
  if (a === b) { return 0; }
778
874
  var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ],
779
- r = $.tablesorter.regex, xN, xD, yN, yD, xF, yF, i, mx;
875
+ r = ts.regex, xN, xD, yN, yD, xF, yF, i, mx;
780
876
  if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : -e || -1; }
781
877
  if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : e || 1; }
782
878
  if (typeof c.textSorter === 'function') { return c.textSorter(a, b, table, col); }
@@ -794,9 +890,9 @@
794
890
  mx = Math.max(xN.length, yN.length);
795
891
  // natural sorting through split numeric strings and default strings
796
892
  for (i = 0; i < mx; i++) {
797
- // find floats not starting with '0', string or 0 if not defined (Clint Priest)
798
- xF = (!(xN[i] || '').match(r[3]) && parseFloat(xN[i])) || xN[i] || 0;
799
- yF = (!(yN[i] || '').match(r[3]) && parseFloat(yN[i])) || yN[i] || 0;
893
+ // find floats not starting with '0', string or 0 if not defined
894
+ xF = isNaN(xN[i]) ? xN[i] || 0 : parseFloat(xN[i]) || 0;
895
+ yF = isNaN(yN[i]) ? yN[i] || 0 : parseFloat(yN[i]) || 0;
800
896
  // handle numeric vs string comparison - number < string - (Kyle Adams)
801
897
  if (isNaN(xF) !== isNaN(yF)) { return (isNaN(xF)) ? 1 : -1; }
802
898
  // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
@@ -810,19 +906,19 @@
810
906
  return 0;
811
907
  };
812
908
 
813
- this.sortTextDesc = function(table, a, b, col) {
909
+ ts.sortTextDesc = function(table, a, b, col) {
814
910
  if (a === b) { return 0; }
815
911
  var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
816
912
  if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : e || 1; }
817
913
  if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : -e || -1; }
818
914
  if (typeof c.textSorter === 'function') { return c.textSorter(b, a, table, col); }
819
- return this.sortText(table, b, a);
915
+ return ts.sortText(table, b, a);
820
916
  };
821
917
 
822
918
  // return text string value by adding up ascii value
823
919
  // so the text is somewhat sorted when using a digital sort
824
920
  // this is NOT an alphanumeric sort
825
- this.getTextValue = function(a, mx, d) {
921
+ ts.getTextValue = function(a, mx, d) {
826
922
  if (mx) {
827
923
  // make sure the text value is greater than the max numerical value (mx)
828
924
  var i, l = a.length, n = mx + d;
@@ -834,89 +930,28 @@
834
930
  return 0;
835
931
  };
836
932
 
837
- this.sortNumeric = function(table, a, b, col, mx, d) {
933
+ ts.sortNumeric = function(table, a, b, col, mx, d) {
838
934
  if (a === b) { return 0; }
839
935
  var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
840
936
  if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : -e || -1; }
841
937
  if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : e || 1; }
842
- if (isNaN(a)) { a = this.getTextValue(a, mx, d); }
843
- if (isNaN(b)) { b = this.getTextValue(b, mx, d); }
938
+ if (isNaN(a)) { a = ts.getTextValue(a, mx, d); }
939
+ if (isNaN(b)) { b = ts.getTextValue(b, mx, d); }
844
940
  return a - b;
845
941
  };
846
942
 
847
- this.sortNumericDesc = function(table, a, b, col, mx, d) {
943
+ ts.sortNumericDesc = function(table, a, b, col, mx, d) {
848
944
  if (a === b) { return 0; }
849
945
  var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
850
946
  if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : e || 1; }
851
947
  if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : -e || -1; }
852
- if (isNaN(a)) { a = this.getTextValue(a, mx, d); }
853
- if (isNaN(b)) { b = this.getTextValue(b, mx, d); }
948
+ if (isNaN(a)) { a = ts.getTextValue(a, mx, d); }
949
+ if (isNaN(b)) { b = ts.getTextValue(b, mx, d); }
854
950
  return b - a;
855
951
  };
856
952
 
857
- this.destroy = function(table, removeClasses){
858
- var $t = $(table), c = table.config;
859
- // remove widget added rows
860
- $t.find('thead:first tr:not(.' + c.cssHeader + ')').remove();
861
- // remove resizer widget stuff
862
- $t.find('thead:first .tablesorter-resizer').remove();
863
- // disable tablesorter
864
- $t
865
- .unbind('update updateCell addRows sorton appendCache applyWidgetId applyWidgets destroy mouseup mouseleave')
866
- .find(c.selectorHeaders)
867
- .unbind('click mousedown mousemove mouseup')
868
- .removeClass(c.cssHeader + ' ' + c.cssAsc + ' ' + c.cssDesc);
869
- if (removeClasses !== false) {
870
- $t.removeClass(c.tableClass);
871
- }
872
- };
873
-
874
- this.addParser = function(parser) {
875
- var i, l = parsers.length, a = true;
876
- for (i = 0; i < l; i++) {
877
- if (parsers[i].id.toLowerCase() === parser.id.toLowerCase()) {
878
- a = false;
879
- }
880
- }
881
- if (a) {
882
- parsers.push(parser);
883
- }
884
- };
885
- this.addWidget = function(widget) {
886
- widgets.push(widget);
887
- };
888
-
889
- this.formatFloat = function(s, table) {
890
- if (typeof(s) !== 'string' || s === '') { return s; }
891
- if (table.config.usNumberFormat !== false) {
892
- // US Format - 1,234,567.89 -> 1234567.89
893
- s = s.replace(/,/g,'');
894
- } else {
895
- // German Format = 1.234.567,89 -> 1234567.89
896
- // French Format = 1 234 567,89 -> 1234567.89
897
- s = s.replace(/[\s|\.]/g,'').replace(/,/g,'.');
898
- }
899
- if(/^\s*\([.\d]+\)/.test(s)) {
900
- s = s.replace(/^\s*\(/,'-').replace(/\)/,'');
901
- }
902
- var i = parseFloat(s);
903
- // return the text instead of zero
904
- return isNaN(i) ? $.trim(s) : i;
905
- };
906
- this.isDigit = function(s) {
907
- // replace all unwanted chars and match.
908
- return (/^[\-+(]?\d+[)]?$/).test(s.replace(/[,.'\s]/g, ''));
909
- };
910
-
911
- // regex used in natural sort
912
- this.regex = [
913
- /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi, // chunk/tokenize numbers & letters
914
- /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, //date
915
- /^0x[0-9a-f]+$/i, // hex
916
- /^0/ // leading zeros
917
- ];
918
953
  // used when replacing accented characters during sorting
919
- this.characterEquivalents = {
954
+ ts.characterEquivalents = {
920
955
  "a" : "\u00e1\u00e0\u00e2\u00e3\u00e4", // áàâãä
921
956
  "A" : "\u00c1\u00c0\u00c2\u00c3\u00c4", // ÁÀÂÃÄ
922
957
  "c" : "\u00e7", // ç
@@ -931,37 +966,130 @@
931
966
  "u" : "\u00fa\u00f9\u00fb\u00fc", // úùûü
932
967
  "U" : "\u00da\u00d9\u00db\u00dc" // ÚÙÛÜ
933
968
  };
934
- this.replaceAccents = function(s) {
935
- var a, acc = '[', eq = this.characterEquivalents;
936
- if (!this.characterRegex) {
937
- this.characterRegexArray = {};
969
+ ts.replaceAccents = function(s) {
970
+ var a, acc = '[', eq = ts.characterEquivalents;
971
+ if (!ts.characterRegex) {
972
+ ts.characterRegexArray = {};
938
973
  for (a in eq) {
939
974
  if (typeof a === 'string') {
940
975
  acc += eq[a];
941
- this.characterRegexArray[a] = new RegExp('[' + eq[a] + ']', 'g');
976
+ ts.characterRegexArray[a] = new RegExp('[' + eq[a] + ']', 'g');
942
977
  }
943
978
  }
944
- this.characterRegex = new RegExp(acc + ']');
979
+ ts.characterRegex = new RegExp(acc + ']');
945
980
  }
946
- if (this.characterRegex.test(s)) {
981
+ if (ts.characterRegex.test(s)) {
947
982
  for (a in eq) {
948
983
  if (typeof a === 'string') {
949
- s = s.replace( this.characterRegexArray[a], a );
984
+ s = s.replace( ts.characterRegexArray[a], a );
950
985
  }
951
986
  }
952
987
  }
953
988
  return s;
954
989
  };
955
990
 
991
+ // *** utilities ***
992
+ ts.isValueInArray = function(v, a) {
993
+ var i, l = a.length;
994
+ for (i = 0; i < l; i++) {
995
+ if (a[i][0] === v) {
996
+ return true;
997
+ }
998
+ }
999
+ return false;
1000
+ };
1001
+
1002
+ ts.addParser = function(parser) {
1003
+ var i, l = ts.parsers.length, a = true;
1004
+ for (i = 0; i < l; i++) {
1005
+ if (ts.parsers[i].id.toLowerCase() === parser.id.toLowerCase()) {
1006
+ a = false;
1007
+ }
1008
+ }
1009
+ if (a) {
1010
+ ts.parsers.push(parser);
1011
+ }
1012
+ };
1013
+
1014
+ ts.getParserById = function(name) {
1015
+ var i, l = ts.parsers.length;
1016
+ for (i = 0; i < l; i++) {
1017
+ if (ts.parsers[i].id.toLowerCase() === (name.toString()).toLowerCase()) {
1018
+ return ts.parsers[i];
1019
+ }
1020
+ }
1021
+ return false;
1022
+ };
1023
+
1024
+ ts.addWidget = function(widget) {
1025
+ ts.widgets.push(widget);
1026
+ };
1027
+
1028
+ ts.getWidgetById = function(name) {
1029
+ var i, w, l = ts.widgets.length;
1030
+ for (i = 0; i < l; i++) {
1031
+ w = ts.widgets[i];
1032
+ if (w && w.hasOwnProperty('id') && w.id.toLowerCase() === name.toLowerCase()) {
1033
+ return w;
1034
+ }
1035
+ }
1036
+ };
1037
+
1038
+ ts.applyWidget = function(table, init) {
1039
+ var c = table.config,
1040
+ wo = c.widgetOptions,
1041
+ ws = c.widgets.sort().reverse(), // ensure that widgets are always applied in a certain order
1042
+ time, i, w, l = ws.length;
1043
+ // make zebra last
1044
+ i = $.inArray('zebra', c.widgets);
1045
+ if (i >= 0) {
1046
+ c.widgets.splice(i,1);
1047
+ c.widgets.push('zebra');
1048
+ }
1049
+ if (c.debug) {
1050
+ time = new Date();
1051
+ }
1052
+ // add selected widgets
1053
+ for (i = 0; i < l; i++) {
1054
+ w = ts.getWidgetById(ws[i]);
1055
+ if ( w ) {
1056
+ if (init === true && w.hasOwnProperty('init')) {
1057
+ w.init(table, w, c, wo);
1058
+ } else if (!init && w.hasOwnProperty('format')) {
1059
+ w.format(table, c, wo);
1060
+ }
1061
+ }
1062
+ }
1063
+ if (c.debug) {
1064
+ benchmark("Completed " + (init === true ? "initializing" : "applying") + " widgets", time);
1065
+ }
1066
+ };
1067
+
1068
+ ts.refreshWidgets = function(table, doAll, dontapply) {
1069
+ var i, c = table.config,
1070
+ cw = c.widgets,
1071
+ w = ts.widgets, l = w.length;
1072
+ // remove previous widgets
1073
+ for (i = 0; i < l; i++){
1074
+ if ( w[i] && w[i].id && (doAll || $.inArray( w[i].id, cw ) < 0) ) {
1075
+ if (c.debug) { log( 'removing ' + w[i].id ); }
1076
+ if (w[i].hasOwnProperty('remove')) { w[i].remove(table, c, c.widgetOptions); }
1077
+ }
1078
+ }
1079
+ if (dontapply !== true) {
1080
+ ts.applyWidget(table, doAll);
1081
+ }
1082
+ };
1083
+
956
1084
  // get sorter, string, empty, etc options for each column from
957
1085
  // jQuery data, metadata, header option or header class name ("sorter-false")
958
1086
  // priority = jQuery data > meta > headers option > header class name
959
- this.getData = function(h, ch, key) {
1087
+ ts.getData = function(h, ch, key) {
960
1088
  var val = '', $h = $(h), m, cl;
961
1089
  if (!$h.length) { return ''; }
962
1090
  m = $.metadata ? $h.metadata() : false;
963
1091
  cl = ' ' + ($h.attr('class') || '');
964
- if ($h.data() && ( typeof $h.data(key) !== 'undefined' || typeof $h.data(key.toLowerCase()) !== 'undefined') ){
1092
+ if (typeof $h.data(key) !== 'undefined' || typeof $h.data(key.toLowerCase()) !== 'undefined'){
965
1093
  // "data-lockedOrder" is assigned to "lockedorder"; but "data-locked-order" is assigned to "lockedOrder"
966
1094
  // "data-sort-initial-order" is assigned to "sortInitialOrder"
967
1095
  val += $h.data(key) || $h.data(key.toLowerCase());
@@ -969,19 +1097,39 @@
969
1097
  val += m[key];
970
1098
  } else if (ch && typeof ch[key] !== 'undefined') {
971
1099
  val += ch[key];
972
- } else if (cl && cl.match(' ' + key + '-')) {
1100
+ } else if (cl !== ' ' && cl.match(' ' + key + '-')) {
973
1101
  // include sorter class name "sorter-text", etc
974
1102
  val = cl.match( new RegExp(' ' + key + '-(\\w+)') )[1] || '';
975
1103
  }
976
1104
  return $.trim(val);
977
1105
  };
978
1106
 
979
- this.clearTableBody = function(table) {
980
- $(table.tBodies).filter(':not(.' + table.config.cssInfoBlock + ')').empty();
1107
+ ts.formatFloat = function(s, table) {
1108
+ if (typeof(s) !== 'string' || s === '') { return s; }
1109
+ if (table.config.usNumberFormat !== false) {
1110
+ // US Format - 1,234,567.89 -> 1234567.89
1111
+ s = s.replace(/,/g,'');
1112
+ } else {
1113
+ // German Format = 1.234.567,89 -> 1234567.89
1114
+ // French Format = 1 234 567,89 -> 1234567.89
1115
+ s = s.replace(/[\s|\.]/g,'').replace(/,/g,'.');
1116
+ }
1117
+ if(/^\s*\([.\d]+\)/.test(s)) {
1118
+ // make (#) into a negative number -> (10) = -10
1119
+ s = s.replace(/^\s*\(/,'-').replace(/\)/,'');
1120
+ }
1121
+ var i = parseFloat(s);
1122
+ // return the text instead of zero
1123
+ return isNaN(i) ? $.trim(s) : i;
981
1124
  };
982
1125
 
983
- }
984
- })();
1126
+ ts.isDigit = function(s) {
1127
+ // replace all unwanted chars and match
1128
+ return isNaN(s) ? (/^[\-+(]?\d+[)]?$/).test(s.toString().replace(/[,.'\s]/g, '')) : true;
1129
+ };
1130
+
1131
+ }()
1132
+ });
985
1133
 
986
1134
  // make shortcut
987
1135
  var ts = $.tablesorter;
@@ -1069,10 +1217,10 @@
1069
1217
  ts.addParser({
1070
1218
  id: "usLongDate",
1071
1219
  is: function(s) {
1072
- return s.match(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/);
1220
+ return (/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4}|'?\d{2})\s+(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s);
1073
1221
  },
1074
1222
  format: function(s, table) {
1075
- return ts.formatFloat( (new Date(s).getTime() || ''), table);
1223
+ return ts.formatFloat( (new Date(s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ''), table);
1076
1224
  },
1077
1225
  type: "numeric"
1078
1226
  });
@@ -1106,10 +1254,10 @@
1106
1254
  ts.addParser({
1107
1255
  id: "time",
1108
1256
  is: function(s) {
1109
- return (/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/).test(s);
1257
+ return (/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s);
1110
1258
  },
1111
1259
  format: function(s, table) {
1112
- return ts.formatFloat( (new Date("2000/01/01 " + s).getTime() || ''), table);
1260
+ return ts.formatFloat( (new Date("2000/01/01 " + s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ""), table);
1113
1261
  },
1114
1262
  type: "numeric"
1115
1263
  });
@@ -1141,15 +1289,10 @@
1141
1289
  // add default widgets
1142
1290
  ts.addWidget({
1143
1291
  id: "zebra",
1144
- format: function(table) {
1292
+ format: function(table, c, wo) {
1145
1293
  var $tb, $tv, $tr, row, even, time, k, l,
1146
- c = table.config,
1147
1294
  child = new RegExp(c.cssChildRow, 'i'),
1148
- b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')'),
1149
- css = [ "even", "odd" ];
1150
- // maintain backwards compatibility
1151
- css = c.widgetZebra && c.hasOwnProperty('css') ? c.widgetZebra.css :
1152
- (c.widgetOptions && c.widgetOptions.hasOwnProperty('zebra')) ? c.widgetOptions.zebra : css;
1295
+ b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')');
1153
1296
  if (c.debug) {
1154
1297
  time = new Date();
1155
1298
  }
@@ -1159,22 +1302,30 @@
1159
1302
  l = $tb.children('tr').length;
1160
1303
  if (l > 1) {
1161
1304
  row = 0;
1162
- $tv = $tb.find('tr:visible');
1163
- $tb.addClass('tablesorter-hidden');
1305
+ $tv = $tb.children('tr:visible');
1164
1306
  // revered back to using jQuery each - strangely it's the fastest method
1165
1307
  $tv.each(function(){
1166
1308
  $tr = $(this);
1167
1309
  // style children rows the same way the parent row was styled
1168
1310
  if (!child.test(this.className)) { row++; }
1169
1311
  even = (row % 2 === 0);
1170
- $tr.removeClass(css[even ? 1 : 0]).addClass(css[even ? 0 : 1]);
1312
+ $tr.removeClass(wo.zebra[even ? 1 : 0]).addClass(wo.zebra[even ? 0 : 1]);
1171
1313
  });
1172
- $tb.removeClass('tablesorter-hidden');
1173
1314
  }
1174
1315
  }
1175
1316
  if (c.debug) {
1176
1317
  ts.benchmark("Applying Zebra widget", time);
1177
1318
  }
1319
+ },
1320
+ remove: function(table, c, wo){
1321
+ var k, $tb,
1322
+ b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')'),
1323
+ rmv = (c.widgetOptions.zebra || [ "even", "odd" ]).join(' ');
1324
+ for (k = 0; k < b.length; k++ ){
1325
+ $tb = $.tablesorter.processTbody(table, $(b[k]), true); // remove tbody
1326
+ $tb.children().removeClass(rmv);
1327
+ $.tablesorter.processTbody(table, $tb, false); // restore tbody
1328
+ }
1178
1329
  }
1179
1330
  });
1180
1331