jquery-tablesorter 0.0.5 → 1.0.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.
data/README.markdown CHANGED
@@ -3,7 +3,13 @@ jQuery Table Sorter plugin for Rails
3
3
 
4
4
  Simple integration of jquery-tablesorter into the asset pipeline.
5
5
 
6
- The original jQuery plugin is [http://tablesorter.com/](http://tablesorter.com/)
6
+ Current version: 2.3.4
7
+
8
+ NOTICE:
9
+ ---
10
+
11
+ v0.0.5 users, from v1.0.0, the tablesorter's version will change
12
+ to [Mottie's fork] :smile:.
7
13
 
8
14
  Install
9
15
  ---
@@ -21,52 +27,40 @@ Rails 3.1 and higher
21
27
  Usage
22
28
  ---
23
29
 
24
- In your javascript file include the following
30
+ ### In your `application.js`
25
31
 
26
32
  ```
27
33
  require jquery-tablesorter
28
34
  ```
29
35
 
30
- And stylesheet
36
+ this will require `jquery.metadata`, `jquery.tablesorter`,
37
+ `jquery.tablesorter.widgets`
38
+
39
+ Or you can include single file with:
31
40
 
32
41
  ```
33
- require jquery-tablesorter/<theme name>
42
+ require jquery-tablesorter/jquery.metadata
43
+ require jquery-tablesorter/jquery.tablesorter
44
+ require jquery-tablesorter/jquery.tablesorter.widgets
34
45
  ```
35
46
 
36
- Avaliable theme names:
37
-
38
- * green
39
- * blue
40
47
 
41
- In CoffeeScript file:
48
+ ### In your `application.css`
42
49
 
43
50
  ```
44
- $('table').tablesorter()
51
+ require jquery-tablesorter/<theme name>
45
52
  ```
46
53
 
47
- Changelog
48
- ---
49
-
50
- #### v0.0.5
51
-
52
- * FIX: now require pager plugin as default.
53
- * FIX: move assets files from app to vendor & cleanup.
54
- * FIX: remove dependency on `jquery-rails`
55
- * FIX: remove development dependency on `sqlite3`
56
-
57
- #### v0.0.4
58
-
59
- * FIX: update gemspec to be compatible with Rails 3.2, Thanks to [derekprior](https://github.com/derekprior).
60
-
61
- #### v0.0.3
54
+ Avaliable theme names:
62
55
 
63
- * NEW: added pagenation plugin, use `require jquery-tablesorter/pager` to require
64
- * FIX: use `require jquery-tablesorter` instead of `require jquery-tablesorter/jquery-tablesorter`, the old way to require will still works, but will be removed in future.
56
+ * blue
57
+ * ui
65
58
 
66
- #### v0.0.2
59
+ ### In CoffeeScript file:
67
60
 
68
- * FIX: test issues.
61
+ ```
62
+ $('table.tablesorter').tablesorter()
63
+ ```
69
64
 
70
- #### v0.0.1
65
+ [Mottie's fork]: https://github.com/Mottie/tablesorter
71
66
 
72
- * NEW: added jquery-tablesorter plugin, use `require jquery-tablesorter/jquery-tablesorter` to require javascript and `require jquery-tablesorter/<theme name>` to require stylesheet.
data/Rakefile CHANGED
@@ -1,39 +1,6 @@
1
1
  #!/usr/bin/env rake
2
- begin
3
- require 'bundler/setup'
4
- rescue LoadError
5
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
- end
7
- begin
8
- require 'rdoc/task'
9
- rescue LoadError
10
- require 'rdoc/rdoc'
11
- require 'rake/rdoctask'
12
- RDoc::Task = Rake::RDocTask
13
- end
14
-
15
- RDoc::Task.new(:rdoc) do |rdoc|
16
- rdoc.rdoc_dir = 'rdoc'
17
- rdoc.title = 'JqueryTablesorter'
18
- rdoc.options << '--line-numbers'
19
- rdoc.rdoc_files.include('README.rdoc')
20
- rdoc.rdoc_files.include('lib/**/*.rb')
21
- end
22
-
23
- APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
- load 'rails/tasks/engine.rake'
25
-
26
2
 
3
+ require 'bundler'
27
4
  Bundler::GemHelper.install_tasks
28
5
 
29
- require 'rake/testtask'
30
-
31
- Rake::TestTask.new(:test) do |t|
32
- t.libs << 'lib'
33
- t.libs << 'test'
34
- t.pattern = 'test/**/*_test.rb'
35
- t.verbose = false
36
- end
37
-
38
-
39
- task :default => :test
6
+ load 'lib/tasks/jquery-tablesorter_tasks.rake'
@@ -1,3 +1,3 @@
1
1
  module JqueryTablesorter
2
- VERSION = "0.0.5"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -2,3 +2,27 @@
2
2
  # task :jquery-tablesorter do
3
3
  # # Task goes here
4
4
  # end
5
+ #
6
+ namespace :jquery_tablesorter do
7
+ desc 'update tablesorter'
8
+ task :update do
9
+ # js files
10
+ javascript_files = [
11
+ 'tablesorter/js/jquery.metadata.js',
12
+ 'tablesorter/js/jquery.tablesorter.js',
13
+ 'tablesorter/js/jquery.tablesorter.widgets.js'
14
+ ]
15
+ FileUtils.cp javascript_files,
16
+ 'vendor/assets/javascripts/jquery-tablesorter/',
17
+ :verbose => true
18
+
19
+ # stylesheets
20
+ FileUtils.cp 'tablesorter/css/blue/style.css',
21
+ 'vendor/assets/stylesheets/jquery-tablesorter/blue',
22
+ :verbose => true
23
+ FileUtils.cp 'tablesorter/css/ui/style.css',
24
+ 'vendor/assets/stylesheets/jquery-tablesorter/ui',
25
+ :verbose => true
26
+
27
+ end
28
+ end
@@ -1,2 +1 @@
1
- //= require jquery-tablesorter/jquery-tablesorter
2
- //= require jquery-tablesorter/pager
1
+ //= require_tree ./jquery-tablesorter
@@ -0,0 +1,148 @@
1
+ /*
2
+ * Metadata - jQuery plugin for parsing metadata from elements
3
+ *
4
+ * Copyright (c) 2006 John Resig, Yehuda Katz, J�örn Zaefferer, Paul McLanahan
5
+ *
6
+ * Dual licensed under the MIT and GPL licenses:
7
+ * http://www.opensource.org/licenses/mit-license.php
8
+ * http://www.gnu.org/licenses/gpl.html
9
+ *
10
+ * Revision: $Id: jquery.metadata.js 3640 2007-10-11 18:34:38Z pmclanahan $
11
+ *
12
+ */
13
+
14
+ /**
15
+ * Sets the type of metadata to use. Metadata is encoded in JSON, and each property
16
+ * in the JSON will become a property of the element itself.
17
+ *
18
+ * There are four supported types of metadata storage:
19
+ *
20
+ * attr: Inside an attribute. The name parameter indicates *which* attribute.
21
+ *
22
+ * class: Inside the class attribute, wrapped in curly braces: { }
23
+ *
24
+ * elem: Inside a child element (e.g. a script tag). The
25
+ * name parameter indicates *which* element.
26
+ * html5: Values are stored in data-* attributes.
27
+ *
28
+ * The metadata for an element is loaded the first time the element is accessed via jQuery.
29
+ *
30
+ * As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
31
+ * matched by expr, then redefine the metadata type and run another $(expr) for other elements.
32
+ *
33
+ * @name $.metadata.setType
34
+ *
35
+ * @example <p id="one" class="some_class {item_id: 1, item_label: 'Label'}">This is a p</p>
36
+ * @before $.metadata.setType("class")
37
+ * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
38
+ * @desc Reads metadata from the class attribute
39
+ *
40
+ * @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
41
+ * @before $.metadata.setType("attr", "data")
42
+ * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
43
+ * @desc Reads metadata from a "data" attribute
44
+ *
45
+ * @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
46
+ * @before $.metadata.setType("elem", "script")
47
+ * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
48
+ * @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
+ *
55
+ * @param String type The encoding type
56
+ * @param String name The name of the attribute to be used to get metadata (optional)
57
+ * @cat Plugins/Metadata
58
+ * @descr Sets the type of encoding to be used when loading metadata for the first time
59
+ * @type undefined
60
+ * @see metadata()
61
+ */
62
+
63
+ (function($) {
64
+
65
+ $.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
+ }
133
+ });
134
+
135
+ /**
136
+ * Returns the metadata object for the first member of the jQuery object.
137
+ *
138
+ * @name metadata
139
+ * @descr Returns element's metadata object
140
+ * @param Object opts An object contianing settings to override the defaults
141
+ * @type jQuery
142
+ * @cat Plugins/Metadata
143
+ */
144
+ $.fn.metadata = function( opts ){
145
+ return $.metadata.get( this[0], opts );
146
+ };
147
+
148
+ })(jQuery);
@@ -0,0 +1,1138 @@
1
+ /*!
2
+ * TableSorter 2.3.4 - Client-side table sorting with ease!
3
+ * @requires jQuery v1.2.6+
4
+ *
5
+ * Copyright (c) 2007 Christian Bach
6
+ * Examples and docs at: http://tablesorter.com
7
+ * Dual licensed under the MIT and GPL licenses:
8
+ * http://www.opensource.org/licenses/mit-license.php
9
+ * http://www.gnu.org/licenses/gpl.html
10
+ *
11
+ * @type jQuery
12
+ * @name tablesorter
13
+ * @cat Plugins/Tablesorter
14
+ * @author Christian Bach/christian.bach@polyester.se
15
+ * @contributor Rob Garrison/https://github.com/Mottie/tablesorter
16
+ */
17
+ !(function($) {
18
+ $.extend({
19
+ tablesorter: new function() {
20
+
21
+ this.version = "2.3.4";
22
+
23
+ var parsers = [], widgets = [];
24
+ this.defaults = {
25
+
26
+ // appearance
27
+ widthFixed : false,
28
+
29
+ // functionality
30
+ 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
33
+ 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.
35
+
36
+ // sort options
37
+ headers : {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc.
38
+ ignoreCase : true, // ignore case while sorting
39
+ sortForce : null, // column(s) first sorted; always applied
40
+ sortList : [], // Initial sort order; applied initially; updated when manually sorted
41
+ sortAppend : null, // column(s) sorted last; always applied
42
+
43
+ sortInitialOrder : "asc", // sort direction on first click
44
+ sortLocaleCompare: false, // replace equivalent character (accented characters)
45
+ sortReset : false, // third click on the header will reset column to default - unsorted
46
+ sortRestart : false, // restart sort to "sortInitialOrder" when clicking on previously unsorted columns
47
+
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){}
51
+ textSorter : null, // use custom text sorter - function(a,b){ return a.sort(b); } // basic sort
52
+
53
+ // widget options
54
+ widgetOptions : {
55
+ zebra : [ "even", "odd" ] // zebra widget alternating row class names
56
+ },
57
+
58
+ // callbacks
59
+ initialized : null, // function(table){},
60
+ onRenderHeader : null, // function(index){},
61
+
62
+ // css class names
63
+ tableClass : 'tablesorter',
64
+ cssAsc : "tablesorter-headerSortUp",
65
+ cssChildRow : "expand-child",
66
+ cssDesc : "tablesorter-headerSortDown",
67
+ cssHeader : "tablesorter-header",
68
+ cssInfoBlock : "tablesorter-infoOnly", // don't sort tbody with this class name
69
+
70
+ // selectors
71
+ selectorHeaders : '> thead th',
72
+ selectorRemove : "tr.remove-me",
73
+
74
+ // advanced
75
+ debug : false,
76
+
77
+ // Internal variables
78
+ headerList: [],
79
+ empties: {},
80
+ strings: {},
81
+ parsers: [],
82
+ widgets: []
83
+
84
+ // deprecated; but retained for backwards compatibility
85
+ // widgetZebra: { css: ["even", "odd"] }
86
+
87
+ };
88
+
89
+ /* debuging utils */
90
+ function log(s) {
91
+ if (typeof console !== "undefined" && typeof console.log !== "undefined") {
92
+ console.log(s);
93
+ } else {
94
+ alert(s);
95
+ }
96
+ }
97
+
98
+ function benchmark(s, d) {
99
+ log(s + " (" + (new Date().getTime() - d.getTime()) + "ms)");
100
+ }
101
+
102
+ this.benchmark = benchmark;
103
+ this.hasInitialized = false;
104
+
105
+ function getElementText(table, node, cellIndex) {
106
+ var text = "", t = table.config.textExtraction;
107
+ if (!node) { return ""; }
108
+ if (t === "simple") {
109
+ text = $(node).text();
110
+ } else {
111
+ if (typeof(t) === "function") {
112
+ text = t(node, table, cellIndex);
113
+ } else if (typeof(t) === "object" && t.hasOwnProperty(cellIndex)) {
114
+ text = t[cellIndex](node, table, cellIndex);
115
+ } else {
116
+ text = $(node).text();
117
+ }
118
+ }
119
+ return $.trim(text);
120
+ }
121
+
122
+ /* parsers utils */
123
+ function getParserById(name) {
124
+ var i, l = parsers.length;
125
+ for (i = 0; i < l; i++) {
126
+ if (parsers[i].id.toLowerCase() === (name.toString()).toLowerCase()) {
127
+ return parsers[i];
128
+ }
129
+ }
130
+ return false;
131
+ }
132
+
133
+ function detectParserForColumn(table, rows, rowIndex, cellIndex) {
134
+ var i, l = parsers.length,
135
+ node = false,
136
+ nodeValue = '',
137
+ keepLooking = true;
138
+ while (nodeValue === '' && keepLooking) {
139
+ rowIndex++;
140
+ if (rows[rowIndex]) {
141
+ node = rows[rowIndex].cells[cellIndex];
142
+ nodeValue = getElementText(table, node, cellIndex);
143
+ if (table.config.debug) {
144
+ log('Checking if value was empty on row ' + rowIndex + ', column:' + cellIndex + ": " + nodeValue);
145
+ }
146
+ } else {
147
+ keepLooking = false;
148
+ }
149
+ }
150
+ for (i = 1; i < l; i++) {
151
+ if (parsers[i].is(nodeValue, table, node)) {
152
+ return parsers[i];
153
+ }
154
+ }
155
+ // 0 is always the generic parser (text)
156
+ return parsers[0];
157
+ }
158
+
159
+ function buildParserCache(table, $headers) {
160
+ var c = table.config, tb = $(table.tBodies).filter(':not(.' + c.cssInfoBlock + ')'),
161
+ ts = $.tablesorter, rows, list, l, i, h, m, ch, cl, p, parsersDebug = "";
162
+ if ( tb.length === 0) { return; } // In the case of empty tables
163
+ rows = tb[0].rows;
164
+ if (rows[0]) {
165
+ list = [];
166
+ l = rows[0].cells.length;
167
+ for (i = 0; i < l; i++) {
168
+ // tons of thanks to AnthonyM1229 for working out the following selector (issue #74) to make this work in IE8!
169
+ h = $headers.filter(':not([colspan])[data-column="'+i+'"]:last,[colspan="1"][data-column="'+i+'"]:last');
170
+ ch = c.headers[i];
171
+ // get column parser
172
+ p = getParserById( ts.getData(h, ch, 'sorter') );
173
+ // empty cells behaviour - keeping emptyToBottom for backwards compatibility.
174
+ c.empties[i] = ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' );
175
+ // text strings behaviour in numerical sorts
176
+ c.strings[i] = ts.getData(h, ch, 'string') || c.stringTo || 'max';
177
+ if (!p) {
178
+ p = detectParserForColumn(table, rows, -1, i);
179
+ }
180
+ if (c.debug) {
181
+ parsersDebug += "column:" + i + "; parser:" + p.id + "; string:" + c.strings[i] + '; empty: ' + c.empties[i] + "\n";
182
+ }
183
+ list.push(p);
184
+ }
185
+ }
186
+ if (c.debug) {
187
+ log(parsersDebug);
188
+ }
189
+ return list;
190
+ }
191
+
192
+ /* utils */
193
+ function buildRegex(){
194
+ var a, acc = '[', t = $.tablesorter,
195
+ reg = t.characterEquivalents;
196
+ t.characterRegexArray = {};
197
+ for (a in reg) {
198
+ if (typeof a === 'string') {
199
+ acc += reg[a];
200
+ t.characterRegexArray[a] = new RegExp('[' + reg[a] + ']', 'g');
201
+ }
202
+ }
203
+ t.characterRegex = new RegExp(acc + ']');
204
+ }
205
+
206
+ function buildCache(table) {
207
+ var b = table.tBodies,
208
+ tc = table.config,
209
+ totalRows,
210
+ totalCells,
211
+ parsers = tc.parsers,
212
+ t, i, j, k, c, cols, cacheTime;
213
+ tc.cache = {};
214
+ if (tc.debug) {
215
+ cacheTime = new Date();
216
+ }
217
+ for (k = 0; k < b.length; k++) {
218
+ tc.cache[k] = { row: [], normalized: [] };
219
+ // ignore tbodies with class name from css.cssInfoBlock
220
+ if (!$(b[k]).hasClass(tc.cssInfoBlock)) {
221
+ totalRows = (b[k] && b[k].rows.length) || 0;
222
+ totalCells = (b[k].rows[0] && b[k].rows[0].cells.length) || 0;
223
+ for (i = 0; i < totalRows; ++i) {
224
+ /** Add the table data to main data array */
225
+ c = $(b[k].rows[i]);
226
+ cols = [];
227
+ // if this is a child row, add it to the last row's children and continue to the next row
228
+ if (c.hasClass(tc.cssChildRow)) {
229
+ tc.cache[k].row[tc.cache[k].row.length - 1] = tc.cache[k].row[tc.cache[k].row.length - 1].add(c);
230
+ // go to the next for loop
231
+ continue;
232
+ }
233
+ tc.cache[k].row.push(c);
234
+ for (j = 0; j < totalCells; ++j) {
235
+ t = getElementText(table, c[0].cells[j], j);
236
+ // allow parsing if the string is empty, previously parsing would change it to zero,
237
+ // in case the parser needs to extract data from the table cell attributes
238
+ cols.push( parsers[j].format(t, table, c[0].cells[j], j) );
239
+ }
240
+ cols.push(tc.cache[k].normalized.length); // add position for rowCache
241
+ tc.cache[k].normalized.push(cols);
242
+ }
243
+ }
244
+ }
245
+ if (tc.debug) {
246
+ benchmark("Building cache for " + totalRows + " rows", cacheTime);
247
+ }
248
+ }
249
+
250
+ function getWidgetById(name) {
251
+ var i, w, l = widgets.length;
252
+ for (i = 0; i < l; i++) {
253
+ w = widgets[i];
254
+ if (w && w.hasOwnProperty('id') && w.id.toLowerCase() === name.toLowerCase()) {
255
+ return w;
256
+ }
257
+ }
258
+ }
259
+
260
+ function applyWidget(table, init) {
261
+ var c = table.config.widgets,
262
+ i, w, l = c.length;
263
+ for (i = 0; i < l; i++) {
264
+ w = getWidgetById(c[i]);
265
+ if ( w ) {
266
+ if (init && w.hasOwnProperty('init')) {
267
+ w.init(table, widgets, w);
268
+ } else if (!init && w.hasOwnProperty('format')) {
269
+ w.format(table);
270
+ }
271
+ }
272
+ }
273
+ }
274
+
275
+ function appendToTable(table) {
276
+ var c = table.config,
277
+ b = table.tBodies,
278
+ rows = [],
279
+ r, n, totalRows, checkCell, c2 = c.cache,
280
+ f, i, j, k, l, pos, appendTime;
281
+ if (c.debug) {
282
+ appendTime = new Date();
283
+ }
284
+ for (k = 0; k < b.length; k++) {
285
+ if (!$(b[k]).hasClass(c.cssInfoBlock)){
286
+ f = document.createDocumentFragment();
287
+ r = c2[k].row;
288
+ n = c2[k].normalized;
289
+ totalRows = n.length;
290
+ checkCell = totalRows ? (n[0].length - 1) : 0;
291
+ for (i = 0; i < totalRows; i++) {
292
+ pos = n[i][checkCell];
293
+ rows.push(r[pos]);
294
+ // removeRows used by the pager plugin
295
+ if (!c.appender || !c.removeRows) {
296
+ l = r[pos].length;
297
+ for (j = 0; j < l; j++) {
298
+ f.appendChild(r[pos][j]);
299
+ }
300
+ }
301
+ }
302
+ table.tBodies[k].appendChild(f);
303
+ }
304
+ }
305
+ if (c.appender) {
306
+ c.appender(table, rows);
307
+ }
308
+ if (c.debug) {
309
+ benchmark("Rebuilt table", appendTime);
310
+ }
311
+ // apply table widgets
312
+ applyWidget(table);
313
+ // trigger sortend
314
+ $(table).trigger("sortEnd", table);
315
+ }
316
+
317
+ // from:
318
+ // http://www.javascripttoolbox.com/lib/table/examples.php
319
+ // http://www.javascripttoolbox.com/temp/table_cellindex.html
320
+ function computeTableHeaderCellIndexes(t) {
321
+ var matrix = [],
322
+ lookup = {},
323
+ trs = $(t).find('thead:eq(0) tr'),
324
+ i, j, k, l, c, cells, rowIndex, cellId, rowSpan, colSpan, firstAvailCol, matrixrow;
325
+ for (i = 0; i < trs.length; i++) {
326
+ cells = trs[i].cells;
327
+ for (j = 0; j < cells.length; j++) {
328
+ c = cells[j];
329
+ rowIndex = c.parentNode.rowIndex;
330
+ cellId = rowIndex + "-" + c.cellIndex;
331
+ rowSpan = c.rowSpan || 1;
332
+ colSpan = c.colSpan || 1;
333
+ if (typeof(matrix[rowIndex]) === "undefined") {
334
+ matrix[rowIndex] = [];
335
+ }
336
+ // Find first available column in the first row
337
+ for (k = 0; k < matrix[rowIndex].length + 1; k++) {
338
+ if (typeof(matrix[rowIndex][k]) === "undefined") {
339
+ firstAvailCol = k;
340
+ break;
341
+ }
342
+ }
343
+ lookup[cellId] = firstAvailCol;
344
+ // add data-column
345
+ $(c).attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex
346
+ for (k = rowIndex; k < rowIndex + rowSpan; k++) {
347
+ if (typeof(matrix[k]) === "undefined") {
348
+ matrix[k] = [];
349
+ }
350
+ matrixrow = matrix[k];
351
+ for (l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
352
+ matrixrow[l] = "x";
353
+ }
354
+ }
355
+ }
356
+ }
357
+ return lookup;
358
+ }
359
+
360
+ function formatSortingOrder(v) {
361
+ // look for "d" in "desc" order; return true
362
+ return (/^d/i.test(v) || v === 1);
363
+ }
364
+
365
+ function buildHeaders(table) {
366
+ var meta = ($.metadata) ? true : false,
367
+ header_index = computeTableHeaderCellIndexes(table), ch, $t,
368
+ $th, lock, time, $tableHeaders, c = table.config, ts = $.tablesorter;
369
+ c.headerList = [];
370
+ if (c.debug) {
371
+ time = new Date();
372
+ }
373
+ $tableHeaders = $(c.selectorHeaders, table)
374
+ .wrapInner("<div class='tablesorter-header-inner' />")
375
+ .each(function(index) {
376
+ $t = $(this);
377
+ ch = c.headers[index];
378
+ this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex];
379
+ this.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2];
380
+ this.count = -1; // set to -1 because clicking on the header automatically adds one
381
+ if (ts.getData($t, ch, 'sorter') === 'false') { this.sortDisabled = true; }
382
+ this.lockedOrder = false;
383
+ lock = ts.getData($t, ch, 'lockedOrder') || false;
384
+ if (typeof(lock) !== 'undefined' && lock !== false) {
385
+ this.order = this.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0];
386
+ }
387
+ if (!this.sortDisabled) {
388
+ $th = $t.addClass(c.cssHeader);
389
+ if (c.onRenderHeader) { c.onRenderHeader.apply($th, [index]); }
390
+ }
391
+ // add cell to headerList
392
+ c.headerList[index] = this;
393
+ // add to parent in case there are multiple rows
394
+ $t.parent().addClass(c.cssHeader);
395
+ });
396
+ if (c.debug) {
397
+ benchmark("Built headers", time);
398
+ log($tableHeaders);
399
+ }
400
+ return $tableHeaders;
401
+ }
402
+
403
+ function isValueInArray(v, a) {
404
+ var i, l = a.length;
405
+ for (i = 0; i < l; i++) {
406
+ if (a[i][0] === v) {
407
+ return true;
408
+ }
409
+ }
410
+ return false;
411
+ }
412
+
413
+ function setHeadersCss(table, $headers, list) {
414
+ var f, h = [], i, j, l, css = [table.config.cssDesc, table.config.cssAsc];
415
+ // remove all header information
416
+ $headers
417
+ .removeClass(css.join(' '))
418
+ .each(function() {
419
+ if (!this.sortDisabled) {
420
+ h[this.column] = $(this);
421
+ }
422
+ });
423
+ l = list.length;
424
+ for (i = 0; i < l; i++) {
425
+ if (list[i][1] === 2) { continue; } // direction = 2 means reset!
426
+ h[list[i][0]].addClass(css[list[i][1]]);
427
+ // multicolumn sorting updating
428
+ f = $headers.filter('[data-column="' + list[i][0] + '"]');
429
+ if (l > 1 && f.length) {
430
+ for (j = 0; j < f.length; j++) {
431
+ if (!f[j].sortDisabled) {
432
+ $(f[j]).addClass(css[list[i][1]]);
433
+ }
434
+ }
435
+ }
436
+ }
437
+ }
438
+
439
+ function fixColumnWidth(table) {
440
+ if (table.config.widthFixed) {
441
+ var colgroup = $('<colgroup>');
442
+ $("tr:first td", table.tBodies[0]).each(function() {
443
+ colgroup.append($('<col>').css('width', $(this).width()));
444
+ });
445
+ $(table).prepend(colgroup);
446
+ }
447
+ }
448
+
449
+ function updateHeaderSortCount(table, sortList) {
450
+ var i, s, o, c = table.config,
451
+ l = sortList.length;
452
+ for (i = 0; i < l; i++) {
453
+ s = sortList[i];
454
+ o = c.headerList[s[0]];
455
+ o.count = s[1] % (c.sortReset ? 3 : 2);
456
+ }
457
+ }
458
+
459
+ function getCachedSortType(parsers, i) {
460
+ return (parsers) ? parsers[i].type : '';
461
+ }
462
+
463
+ /* sorting methods - reverted sorting method back to version 2.0.3 */
464
+ function multisort(table, sortList) {
465
+ var dynamicExp, col, mx = 0, dir = 0, tc = table.config,
466
+ l = sortList.length, bl = table.tBodies.length,
467
+ sortTime, i, j, k, c, cache, lc, s, e, order, orgOrderCol;
468
+ if (tc.debug) { sortTime = new Date(); }
469
+ for (k = 0; k < bl; k++) {
470
+ dynamicExp = "var sortWrapper = function(a,b) {";
471
+ cache = tc.cache[k];
472
+ lc = cache.normalized.length;
473
+ for (i = 0; i < l; i++) {
474
+ c = sortList[i][0];
475
+ order = sortList[i][1];
476
+ // fallback to natural sort since it is more robust
477
+ s = /n/i.test(getCachedSortType(tc.parsers, c)) ? "Numeric" : "Text";
478
+ s += order === 0 ? "" : "Desc";
479
+ e = "e" + i;
480
+ // get max column value (ignore sign)
481
+ if (/Numeric/.test(s) && tc.strings[c]) {
482
+ for (j = 0; j < lc; j++) {
483
+ col = Math.abs(parseFloat(cache.normalized[j][c]));
484
+ mx = Math.max( mx, isNaN(col) ? 0 : col );
485
+ }
486
+ // sort strings in numerical columns
487
+ if (typeof(tc.string[tc.strings[c]]) === 'boolean') {
488
+ dir = (order === 0 ? 1 : -1) * (tc.string[tc.strings[c]] ? -1 : 1);
489
+ } else {
490
+ dir = (tc.strings[c]) ? tc.string[tc.strings[c]] || 0 : 0;
491
+ }
492
+ }
493
+ dynamicExp += "var " + e + " = sort" + s + "(table, a[" + c + "],b[" + c + "]," + c + "," + mx + "," + dir + "); ";
494
+ dynamicExp += "if (" + e + ") { return " + e + "; } ";
495
+ dynamicExp += "else { ";
496
+ }
497
+ // if value is the same keep orignal order
498
+ orgOrderCol = (cache.normalized && cache.normalized[0]) ? cache.normalized[0].length - 1 : 0;
499
+ dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];";
500
+ for (i=0; i < l; i++) {
501
+ dynamicExp += "}; ";
502
+ }
503
+ dynamicExp += "return 0; ";
504
+ dynamicExp += "}; ";
505
+ eval(dynamicExp);
506
+ cache.normalized.sort(sortWrapper); // sort using eval expression
507
+ }
508
+ if (tc.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order+ " time", sortTime); }
509
+ }
510
+
511
+ // Natural sort - https://github.com/overset/javascript-natural-sort
512
+ function sortText(table, a, b, col) {
513
+ if (a === b) { return 0; }
514
+ var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ],
515
+ r = $.tablesorter.regex, xN, xD, yN, yD, xF, yF, i, mx;
516
+ if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : -e || -1; }
517
+ if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : e || 1; }
518
+ if (typeof c.textSorter === 'function') { return c.textSorter(a, b, table, col); }
519
+ // chunk/tokenize
520
+ xN = a.replace(r[0], '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0');
521
+ yN = b.replace(r[0], '\0$1\0').replace(/\0$/, '').replace(/^\0/, '').split('\0');
522
+ // numeric, hex or date detection
523
+ xD = parseInt(a.match(r[2])) || (xN.length !== 1 && a.match(r[1]) && Date.parse(a));
524
+ yD = parseInt(b.match(r[2])) || (xD && b.match(r[1]) && Date.parse(b)) || null;
525
+ // first try and sort Hex codes or Dates
526
+ if (yD) {
527
+ if ( xD < yD ) { return -1; }
528
+ if ( xD > yD ) { return 1; }
529
+ }
530
+ mx = Math.max(xN.length, yN.length);
531
+ // natural sorting through split numeric strings and default strings
532
+ for (i = 0; i < mx; i++) {
533
+ // find floats not starting with '0', string or 0 if not defined (Clint Priest)
534
+ xF = (!(xN[i] || '').match(r[3]) && parseFloat(xN[i])) || xN[i] || 0;
535
+ yF = (!(yN[i] || '').match(r[3]) && parseFloat(yN[i])) || yN[i] || 0;
536
+ // handle numeric vs string comparison - number < string - (Kyle Adams)
537
+ if (isNaN(xF) !== isNaN(yF)) { return (isNaN(xF)) ? 1 : -1; }
538
+ // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
539
+ if (typeof xF !== typeof yF) {
540
+ xF += '';
541
+ yF += '';
542
+ }
543
+ if (xF < yF) { return -1; }
544
+ if (xF > yF) { return 1; }
545
+ }
546
+ return 0;
547
+ }
548
+
549
+ function sortTextDesc(table, a, b, col) {
550
+ if (a === b) { return 0; }
551
+ var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
552
+ if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : e || 1; }
553
+ if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : -e || -1; }
554
+ if (typeof c.textSorter === 'function') { return c.textSorter(b, a, table, col); }
555
+ return sortText(table, b, a);
556
+ }
557
+
558
+ // return text string value by adding up ascii value
559
+ // so the text is somewhat sorted when using a digital sort
560
+ // this is NOT an alphanumeric sort
561
+ function getTextValue(a, mx, d) {
562
+ if (mx) {
563
+ // make sure the text value is greater than the max numerical value (mx)
564
+ var i, l = a.length, n = mx + d;
565
+ for (i = 0; i < l; i++) {
566
+ n += a.charCodeAt(i);
567
+ }
568
+ return d * n;
569
+ }
570
+ return 0;
571
+ }
572
+
573
+ function sortNumeric(table, a, b, col, mx, d) {
574
+ if (a === b) { return 0; }
575
+ var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
576
+ if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : -e || -1; }
577
+ if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : e || 1; }
578
+ if (isNaN(a)) { a = getTextValue(a, mx, d); }
579
+ if (isNaN(b)) { b = getTextValue(b, mx, d); }
580
+ return a - b;
581
+ }
582
+
583
+ function sortNumericDesc(table, a, b, col, mx, d) {
584
+ if (a === b) { return 0; }
585
+ var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
586
+ if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : e || 1; }
587
+ if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : -e || -1; }
588
+ if (isNaN(a)) { a = getTextValue(a, mx, d); }
589
+ if (isNaN(b)) { b = getTextValue(b, mx, d); }
590
+ return b - a;
591
+ }
592
+
593
+ /* public methods */
594
+ this.construct = function(settings) {
595
+ return this.each(function() {
596
+ // if no thead or tbody quit.
597
+ if (!this.tHead || this.tBodies.length === 0) { return; }
598
+ // declare
599
+ var $headers, $cell, totalRows, $this,
600
+ config, c, i, j, k, a, s, o,
601
+ m = $.metadata;
602
+ // new blank config object
603
+ this.config = {};
604
+ // merge and extend.
605
+ c = config = $.extend(true, this.config, $.tablesorter.defaults, settings);
606
+
607
+ if (c.debug) { $.data( this, 'startoveralltimer', new Date()); }
608
+ // store common expression for speed
609
+ $this = $(this).addClass(c.tableClass);
610
+ // save the settings where they read
611
+ $.data(this, "tablesorter", c);
612
+ // build up character equivalent cross-reference
613
+ buildRegex();
614
+ // digit sort text location; keeping max+/- for backwards compatibility
615
+ c.string = { 'max': 1, 'min': -1, 'max+': 1, 'max-': -1, 'zero': 0, 'none': 0, 'null': 0, 'top': true, 'bottom': false };
616
+ // build headers
617
+ $headers = buildHeaders(this);
618
+ // try to auto detect column type, and store in tables config
619
+ c.parsers = buildParserCache(this, $headers);
620
+ // build the cache for the tbody cells
621
+ // delayInit will delay building the cache until the user starts a sort
622
+ if (!c.delayInit) { buildCache(this); }
623
+ // fixate columns if the users supplies the fixedWidth option
624
+ fixColumnWidth(this);
625
+ // apply event handling to headers
626
+ // this is to big, perhaps break it out?
627
+ $headers
628
+ .click(function(e) {
629
+ if (c.delayInit && !c.cache) { buildCache($this[0]); }
630
+ if (!this.sortDisabled) {
631
+ // Only call sortStart if sorting is enabled.
632
+ $this.trigger("sortStart", $this[0]);
633
+ // store exp, for speed
634
+ $cell = $(this);
635
+ k = !e[c.sortMultiSortKey];
636
+ // get current column sort order
637
+ this.count = (this.count + 1) % (c.sortReset ? 3 : 2);
638
+ // reset all sorts on non-current column - issue #30
639
+ if (c.sortRestart) {
640
+ i = this;
641
+ $headers.each(function() {
642
+ // only reset counts on columns that weren't just clicked on and if not included in a multisort
643
+ if (this !== i && (k || !$(this).is('.' + c.cssDesc + ',.' + c.cssAsc))) {
644
+ this.count = -1;
645
+ }
646
+ });
647
+ }
648
+ // get current column index
649
+ i = this.column;
650
+ // user only wants to sort on one column
651
+ if (k) {
652
+ // flush the sort list
653
+ c.sortList = [];
654
+ if (c.sortForce !== null) {
655
+ a = c.sortForce;
656
+ for (j = 0; j < a.length; j++) {
657
+ if (a[j][0] !== i) {
658
+ c.sortList.push(a[j]);
659
+ }
660
+ }
661
+ }
662
+ // add column to sort list
663
+ o = this.order[this.count];
664
+ if (o < 2) {
665
+ c.sortList.push([i, o]);
666
+ // add other columns if header spans across multiple
667
+ if (this.colSpan > 1) {
668
+ for (j = 1; j < this.colSpan; j++) {
669
+ c.sortList.push([i+j, o]);
670
+ }
671
+ }
672
+ }
673
+ // multi column sorting
674
+ } else {
675
+ // the user has clicked on an already sorted column.
676
+ if (isValueInArray(i, c.sortList)) {
677
+ // reverse the sorting direction for all tables.
678
+ for (j = 0; j < c.sortList.length; j++) {
679
+ s = c.sortList[j];
680
+ o = c.headerList[s[0]];
681
+ if (s[0] === i) {
682
+ s[1] = o.order[o.count];
683
+ if (s[1] === 2) {
684
+ c.sortList.splice(j,1);
685
+ o.count = -1;
686
+ }
687
+ }
688
+ }
689
+ } else {
690
+ // add column to sort list array
691
+ o = this.order[this.count];
692
+ if (o < 2) {
693
+ c.sortList.push([i, o]);
694
+ // add other columns if header spans across multiple
695
+ if (this.colSpan > 1) {
696
+ for (j = 1; j < this.colSpan; j++) {
697
+ c.sortList.push([i+j, o]);
698
+ }
699
+ }
700
+ }
701
+ }
702
+ }
703
+ if (c.sortAppend !== null) {
704
+ a = c.sortAppend;
705
+ for (j = 0; j < a.length; j++) {
706
+ if (a[j][0] !== i) {
707
+ c.sortList.push(a[j]);
708
+ }
709
+ }
710
+ }
711
+ // sortBegin event triggered immediately before the sort
712
+ $this.trigger("sortBegin", $this[0]);
713
+ // set css for headers
714
+ setHeadersCss($this[0], $headers, c.sortList);
715
+ appendToTable($this[0], multisort($this[0], c.sortList));
716
+ // stop normal event by returning false
717
+ return false;
718
+ }
719
+ // cancel selection
720
+ })
721
+ .mousedown(function() {
722
+ if (c.cancelSelection) {
723
+ this.onselectstart = function() {
724
+ return false;
725
+ };
726
+ return false;
727
+ }
728
+ });
729
+ // apply easy methods that trigger binded events
730
+ $this
731
+ .bind("update", function(e, resort) {
732
+ // remove rows/elements before update
733
+ $(c.selectorRemove, this).remove();
734
+ // rebuild parsers.
735
+ c.parsers = buildParserCache(this, $headers);
736
+ // rebuild the cache map
737
+ buildCache(this);
738
+ if (resort !== false) { $(this).trigger("sorton", [c.sortList]); }
739
+ })
740
+ .bind("updateCell", function(e, cell, resort) {
741
+ // get position from the dom.
742
+ var t = this, pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex],
743
+ // update cache - format: function(s, table, cell, cellIndex)
744
+ tbdy = $(this).find('tbody').index( $(cell).closest('tbody') );
745
+ t.config.cache[tbdy].normalized[pos[0]][pos[1]] = c.parsers[pos[1]].format( getElementText(t, cell, pos[1]), t, cell, pos[1] );
746
+ if (resort !== false) { $(this).trigger("sorton", [c.sortList]); }
747
+ })
748
+ .bind("addRows", function(e, $row, resort) {
749
+ var i, rows = $row.filter('tr').length,
750
+ dat = [], l = $row[0].cells.length, t = this,
751
+ tbdy = $(this).find('tbody').index( $row.closest('tbody') );
752
+ // add each row
753
+ for (i = 0; i < rows; i++) {
754
+ // add each cell
755
+ for (j = 0; j < l; j++) {
756
+ dat[j] = c.parsers[j].format( getElementText(t, $row[i].cells[j], j), t, $row[i].cells[j], j );
757
+ }
758
+ // add the row index to the end
759
+ dat.push(c.cache[tbdy].row.length);
760
+ // update cache
761
+ c.cache[tbdy].row.push([$row[i]]);
762
+ c.cache[tbdy].normalized.push(dat);
763
+ dat = [];
764
+ }
765
+ // resort using current settings
766
+ if (resort !== false) { $(this).trigger("sorton", [c.sortList]); }
767
+ })
768
+ .bind("sorton", function(e, list) {
769
+ $(this).trigger("sortStart", this);
770
+ // update and store the sortlist
771
+ c.sortList = list;
772
+ // update header count index
773
+ updateHeaderSortCount(this, c.sortList);
774
+ // set css for headers
775
+ setHeadersCss(this, $headers, c.sortList);
776
+ // sort the table and append it to the dom
777
+ appendToTable(this, multisort(this, c.sortList));
778
+ })
779
+ .bind("appendCache", function() {
780
+ appendToTable(this);
781
+ })
782
+ .bind("applyWidgetId", function(e, id) {
783
+ getWidgetById(id).format(this);
784
+ })
785
+ .bind("applyWidgets", function() {
786
+ // apply widgets
787
+ applyWidget(this);
788
+ })
789
+ .bind("destroy", function(e,c){
790
+ $.tablesorter.destroy(this, c);
791
+ });
792
+
793
+ // get sort list from jQuery data or metadata
794
+ if ($this.data() && typeof $this.data().sortlist !== 'undefined') {
795
+ c.sortList = $this.data().sortlist;
796
+ } else if (m && ($this.metadata() && $this.metadata().sortlist)) {
797
+ c.sortList = $this.metadata().sortlist;
798
+ }
799
+ // apply widget init code
800
+ applyWidget(this, true);
801
+ // if user has supplied a sort list to constructor.
802
+ if (c.sortList.length > 0) {
803
+ $this.trigger("sorton", [c.sortList]);
804
+ } else {
805
+ // apply widget format
806
+ applyWidget(this);
807
+ }
808
+
809
+ // initialized
810
+ this.hasInitialized = true;
811
+ if (c.debug) {
812
+ $.tablesorter.benchmark("Overall initialization time", $.data( this, 'startoveralltimer'));
813
+ }
814
+ $this.trigger('tablesorter-initialized', this);
815
+ if (typeof c.initialized === 'function') { c.initialized(this); }
816
+ });
817
+ };
818
+
819
+ this.destroy = function(table, removeClasses){
820
+ var $t = $(table), c = table.config;
821
+ // remove widget added rows
822
+ $t.find('thead:first tr:not(.' + c.cssHeader + ')').remove();
823
+ // remove resizer widget stuff
824
+ $t.find('thead:first .tablesorter-resizer').remove();
825
+ // disable tablesorter
826
+ $t
827
+ .unbind('update updateCell addRows sorton appendCache applyWidgetId applyWidgets destroy mouseup mouseleave')
828
+ .find(c.selectorHeaders)
829
+ .unbind('click mousedown mousemove mouseup')
830
+ .removeClass(c.cssHeader + ' ' + c.cssAsc + ' ' + c.cssDesc);
831
+ if (removeClasses !== false) {
832
+ $t.removeClass(c.tableClass);
833
+ }
834
+ };
835
+
836
+ this.addParser = function(parser) {
837
+ var i, l = parsers.length, a = true;
838
+ for (i = 0; i < l; i++) {
839
+ if (parsers[i].id.toLowerCase() === parser.id.toLowerCase()) {
840
+ a = false;
841
+ }
842
+ }
843
+ if (a) {
844
+ parsers.push(parser);
845
+ }
846
+ };
847
+ this.addWidget = function(widget) {
848
+ widgets.push(widget);
849
+ };
850
+
851
+ this.formatFloat = function(s, table) {
852
+ if (typeof(s) !== 'string' || s === '') { return s; }
853
+ if (table.config.usNumberFormat !== false) {
854
+ // US Format - 1,234,567.89 -> 1234567.89
855
+ s = s.replace(/,/g,'');
856
+ } else {
857
+ // German Format = 1.234.567,89 -> 1234567.89
858
+ // French Format = 1 234 567,89 -> 1234567.89
859
+ s = s.replace(/[\s|\.]/g,'').replace(/,/g,'.');
860
+ }
861
+ if(/^\s*\([.\d]+\)/.test(s)) {
862
+ s = s.replace(/^\s*\(/,'-').replace(/\)/,'');
863
+ }
864
+ var i = parseFloat(s);
865
+ // return the text instead of zero
866
+ return isNaN(i) ? $.trim(s) : i;
867
+ };
868
+ this.isDigit = function(s) {
869
+ // replace all unwanted chars and match.
870
+ return (/^[\-+(]?\d*[)]?$/).test(s.replace(/[,.'\s]/g, ''));
871
+ };
872
+
873
+ // regex used in natural sort
874
+ this.regex = [
875
+ /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi, // chunk/tokenize numbers & letters
876
+ /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, //date
877
+ /^0x[0-9a-f]+$/i, // hex
878
+ /^0/ // leading zeros
879
+ ];
880
+ // used when replacing accented characters during sorting
881
+ this.characterEquivalents = {
882
+ "a" : "\u00e1\u00e0\u00e2\u00e3\u00e4", // áàâãä
883
+ "A" : "\u00c1\u00c0\u00c2\u00c3\u00c4", // ÁÀÂÃÄ
884
+ "c" : "\u00e7", // ç
885
+ "C" : "\u00c7", // Ç
886
+ "e" : "\u00e9\u00e8\u00ea\u00eb", // éèêë
887
+ "E" : "\u00c9\u00c8\u00ca\u00cb", // ÉÈÊË
888
+ "i" : "\u00ed\u00ec\u0130\u00ee\u00ef", // íìİîï
889
+ "I" : "\u00cd\u00cc\u0130\u00ce\u00cf", // ÍÌİÎÏ
890
+ "o" : "\u00f3\u00f2\u00f4\u00f5\u00f6", // óòôõö
891
+ "O" : "\u00d3\u00d2\u00d4\u00d5\u00d6", // ÓÒÔÕÖ
892
+ "S" : "\u00df", // ß
893
+ "u" : "\u00fa\u00f9\u00fb\u00fc", // úùûü
894
+ "U" : "\u00da\u00d9\u00db\u00dc" // ÚÙÛÜ
895
+ };
896
+ this.replaceAccents = function(s) {
897
+ if (this.characterRegex.test(s)) {
898
+ var a, eq = this.characterEquivalents;
899
+ for (a in eq) {
900
+ if (typeof a === 'string') {
901
+ s = s.replace( this.characterRegexArray[a], a );
902
+ }
903
+ }
904
+ }
905
+ return s;
906
+ };
907
+
908
+ // get sorter, string, empty, etc options for each column from
909
+ // metadata, header option or header class name ("sorter-false")
910
+ // priority = jQuery data > meta > headers option > header class name
911
+ this.getData = function(h, ch, key) {
912
+ var val = '', $h = $(h),
913
+ m = $.metadata ? $h.metadata() : false,
914
+ cl = ' ' + ($h.length ? $h.attr('class') || '' : '');
915
+ if ($h.length && $h.data() && ( typeof $h.data(key) !== 'undefined' || typeof $h.data(key.toLowerCase()) !== 'undefined') ){
916
+ // "data-lockedOrder" is assigned to "lockedorder"; but "data-locked-order" is assigned to "lockedOrder"
917
+ // "data-sort-initial-order" is assigned to "sortInitialOrder"
918
+ val += $h.data(key) || $h.data(key.toLowerCase());
919
+ } else if (m && typeof m[key] !== 'undefined') {
920
+ val += m[key];
921
+ } else if (ch && typeof ch[key] !== 'undefined') {
922
+ val += ch[key];
923
+ } else if (cl && cl.match(' ' + key + '-')) {
924
+ // include sorter class name "sorter-text", etc
925
+ val = cl.match( new RegExp(' ' + key + '-(\\w+)') )[1] || '';
926
+ }
927
+ return $.trim(val);
928
+ };
929
+
930
+ this.clearTableBody = function(table) {
931
+ $(table.tBodies).filter(':not(.' + table.config.cssInfoBlock + ')').empty();
932
+ };
933
+
934
+ }
935
+ })();
936
+
937
+ // make shortcut
938
+ var ts = $.tablesorter;
939
+
940
+ // extend plugin scope
941
+ $.fn.extend({
942
+ tablesorter: ts.construct
943
+ });
944
+
945
+ // add default parsers
946
+ ts.addParser({
947
+ id: "text",
948
+ is: function(s, table, node) {
949
+ return true;
950
+ },
951
+ format: function(s, table, cell, cellIndex) {
952
+ var c = table.config;
953
+ s = $.trim( c.ignoreCase ? s.toLocaleLowerCase() : s );
954
+ return c.sortLocaleCompare ? ts.replaceAccents(s) : s;
955
+ },
956
+ type: "text"
957
+ });
958
+
959
+ ts.addParser({
960
+ id: "currency",
961
+ is: function(s) {
962
+ return (/^\(?[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+/).test(s); // £$€¤¥¢
963
+ },
964
+ format: function(s, table) {
965
+ return ts.formatFloat(s.replace(/[^\w,. \-()]/g, ""), table);
966
+ },
967
+ type: "numeric"
968
+ });
969
+
970
+ ts.addParser({
971
+ id: "ipAddress",
972
+ is: function(s) {
973
+ return (/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/).test(s);
974
+ },
975
+ format: function(s, table) {
976
+ var i, item, a = s.split("."),
977
+ r = "",
978
+ l = a.length;
979
+ for (i = 0; i < l; i++) {
980
+ item = a[i];
981
+ if (item.length === 2) {
982
+ r += "0" + item;
983
+ } else {
984
+ r += item;
985
+ }
986
+ }
987
+ return ts.formatFloat(r, table);
988
+ },
989
+ type: "numeric"
990
+ });
991
+
992
+ ts.addParser({
993
+ id: "url",
994
+ is: function(s) {
995
+ return (/^(https?|ftp|file):\/\/$/).test(s);
996
+ },
997
+ format: function(s) {
998
+ return $.trim(s.replace(/(https?|ftp|file):\/\//, ''));
999
+ },
1000
+ type: "text"
1001
+ });
1002
+
1003
+ ts.addParser({
1004
+ id: "isoDate",
1005
+ is: function(s) {
1006
+ return (/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/).test(s);
1007
+ },
1008
+ format: function(s, table) {
1009
+ return ts.formatFloat((s !== "") ? (new Date(s.replace(/-/g, "/")).getTime() || "") : "", table);
1010
+ },
1011
+ type: "numeric"
1012
+ });
1013
+
1014
+ ts.addParser({
1015
+ id: "percent",
1016
+ is: function(s) {
1017
+ return (/\d%\)?$/).test(s);
1018
+ },
1019
+ format: function(s, table) {
1020
+ return ts.formatFloat(s.replace(/%/g, ""), table);
1021
+ },
1022
+ type: "numeric"
1023
+ });
1024
+
1025
+ ts.addParser({
1026
+ id: "usLongDate",
1027
+ is: function(s) {
1028
+ 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)))$/);
1029
+ },
1030
+ format: function(s, table) {
1031
+ return ts.formatFloat( (new Date(s).getTime() || ''), table);
1032
+ },
1033
+ type: "numeric"
1034
+ });
1035
+
1036
+ ts.addParser({
1037
+ id: "shortDate", // "mmddyyyy", "ddmmyyyy" or "yyyymmdd"
1038
+ is: function(s) {
1039
+ // testing for ####-####-#### - so it's not perfect
1040
+ return (/\d{1,4}[\/\-\,\.\s+]\d{1,4}[\/\-\.\,\s+]\d{1,4}/).test(s);
1041
+ },
1042
+ format: function(s, table, cell, cellIndex) {
1043
+ var c = table.config, ci = c.headerList[cellIndex],
1044
+ format = ci.shortDateFormat;
1045
+ if (typeof format === 'undefined') {
1046
+ // cache header formatting so it doesn't getData for every cell in the column
1047
+ format = ci.shortDateFormat = ts.getData( ci, c.headers[cellIndex], 'dateFormat') || c.dateFormat;
1048
+ }
1049
+ s = s.replace(/\s+/g," ").replace(/[\-|\.|\,]/g, "/");
1050
+ if (format === "mmddyyyy") {
1051
+ s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2");
1052
+ } else if (format === "ddmmyyyy") {
1053
+ s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$2/$1");
1054
+ } else if (format === "yyyymmdd") {
1055
+ s = s.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$1/$2/$3");
1056
+ }
1057
+ return ts.formatFloat( (new Date(s).getTime() || ''), table);
1058
+ },
1059
+ type: "numeric"
1060
+ });
1061
+
1062
+ ts.addParser({
1063
+ id: "time",
1064
+ is: function(s) {
1065
+ return (/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/).test(s);
1066
+ },
1067
+ format: function(s, table) {
1068
+ return ts.formatFloat( (new Date("2000/01/01 " + s).getTime() || ''), table);
1069
+ },
1070
+ type: "numeric"
1071
+ });
1072
+
1073
+ ts.addParser({
1074
+ id: "digit",
1075
+ is: function(s) {
1076
+ return ts.isDigit(s);
1077
+ },
1078
+ format: function(s, table) {
1079
+ return ts.formatFloat(s.replace(/[^\w,. \-()]/g, ""), table);
1080
+ },
1081
+ type: "numeric"
1082
+ });
1083
+
1084
+ ts.addParser({
1085
+ id: "metadata",
1086
+ is: function(s) {
1087
+ return false;
1088
+ },
1089
+ format: function(s, table, cell) {
1090
+ var c = table.config,
1091
+ p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
1092
+ return $(cell).metadata()[p];
1093
+ },
1094
+ type: "numeric"
1095
+ });
1096
+
1097
+ // add default widgets
1098
+ ts.addWidget({
1099
+ id: "zebra",
1100
+ format: function(table) {
1101
+ var $tb, $tr, $f, row, even, time, k, j, l, n,
1102
+ c = table.config,
1103
+ child = new RegExp(c.cssChildRow, 'i'),
1104
+ b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')'),
1105
+ css = [ "even", "odd" ];
1106
+ // maintain backwards compatibility
1107
+ css = c.widgetZebra && c.hasOwnProperty('css') ? c.widgetZebra.css :
1108
+ (c.widgetOptions && c.widgetOptions.hasOwnProperty('zebra')) ? c.widgetOptions.zebra : css;
1109
+ if (c.debug) {
1110
+ time = new Date();
1111
+ }
1112
+ for (k = 0; k < b.length; k++ ) {
1113
+ row = 0;
1114
+ // loop through the visible rows
1115
+ $tb = $(b[k]);
1116
+ l = $tb.children('tr').length;
1117
+ if (l > 1) {
1118
+ $f = $(document.createDocumentFragment());
1119
+ $tr = $tb.children('tr').appendTo($f);
1120
+ for (j = 0; j < l; j++) {
1121
+ if ($tr[j].style.display !== 'none') {
1122
+ n = $tr[j].className;
1123
+ // style children rows the same way the parent row was styled
1124
+ if (!child.test(n)) { row++; }
1125
+ even = (row % 2 === 0);
1126
+ $tr[j].className = n.replace(/\s+/g,'').replace(css[0],'').replace(css[1],'') + ' ' + css[even ? 0 : 1];
1127
+ }
1128
+ }
1129
+ }
1130
+ $tb.append($tr);
1131
+ }
1132
+ if (c.debug) {
1133
+ ts.benchmark("Applying Zebra widget", time);
1134
+ }
1135
+ }
1136
+ });
1137
+
1138
+ })(jQuery);