jquery-tablesorter 0.0.5 → 1.0.0

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