jquery-tablesorter 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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