jquery-tablesorter-rails4 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +29 -0
  6. data/Rakefile +1 -0
  7. data/app/assets/javascripts/jquery_tablesorter/jquery.metadata.js +116 -0
  8. data/app/assets/javascripts/jquery_tablesorter/jquery.tablesorter.js +1477 -0
  9. data/app/assets/javascripts/jquery_tablesorter/jquery.tablesorter.widgets-filter-formatter.js +924 -0
  10. data/app/assets/javascripts/jquery_tablesorter/jquery.tablesorter.widgets.js +1210 -0
  11. data/app/assets/javascripts/jquery_tablesorter/parsers/parser-date-iso8601.js +34 -0
  12. data/app/assets/javascripts/jquery_tablesorter/parsers/parser-date-month.js +32 -0
  13. data/app/assets/javascripts/jquery_tablesorter/parsers/parser-date-two-digit-year.js +68 -0
  14. data/app/assets/javascripts/jquery_tablesorter/parsers/parser-date-weekday.js +32 -0
  15. data/app/assets/javascripts/jquery_tablesorter/parsers/parser-date.js +36 -0
  16. data/app/assets/javascripts/jquery_tablesorter/parsers/parser-feet-inch-fraction.js +63 -0
  17. data/app/assets/javascripts/jquery_tablesorter/parsers/parser-ignore-articles.js +47 -0
  18. data/app/assets/javascripts/jquery_tablesorter/parsers/parser-input-select.js +78 -0
  19. data/app/assets/javascripts/jquery_tablesorter/parsers/parser-metric.js +77 -0
  20. data/app/assets/javascripts/jquery_tablesorter/widgets/widget-editable.js +68 -0
  21. data/app/assets/javascripts/jquery_tablesorter/widgets/widget-grouping.js +114 -0
  22. data/app/assets/javascripts/jquery_tablesorter/widgets/widget-repeatheaders.js +50 -0
  23. data/app/assets/javascripts/jquery_tablesorter/widgets/widget-scroller.js +240 -0
  24. data/app/assets/stylesheets/jquery_tablesorter/filter.formatter.css +183 -0
  25. data/app/assets/stylesheets/jquery_tablesorter/theme.black-ice.css +180 -0
  26. data/app/assets/stylesheets/jquery_tablesorter/theme.blue.css +215 -0
  27. data/app/assets/stylesheets/jquery_tablesorter/theme.bootstrap.css +139 -0
  28. data/app/assets/stylesheets/jquery_tablesorter/theme.dark.css +181 -0
  29. data/app/assets/stylesheets/jquery_tablesorter/theme.default.css +183 -0
  30. data/app/assets/stylesheets/jquery_tablesorter/theme.dropbox.css +201 -0
  31. data/app/assets/stylesheets/jquery_tablesorter/theme.green.css +198 -0
  32. data/app/assets/stylesheets/jquery_tablesorter/theme.grey.css +234 -0
  33. data/app/assets/stylesheets/jquery_tablesorter/theme.ice.css +189 -0
  34. data/app/assets/stylesheets/jquery_tablesorter/theme.jui.css +145 -0
  35. data/jquery-tablesorter-rails4.gemspec +25 -0
  36. data/lib/jquery-tablesorter-rails4.rb +1 -0
  37. data/lib/jquery/tablesorter/rails4.rb +10 -0
  38. data/lib/jquery/tablesorter/rails4/engine.rb +8 -0
  39. data/lib/jquery/tablesorter/rails4/version.rb +7 -0
  40. metadata +124 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: be64b10df8335031ddf808f9c9f2b5e0355d82fd
4
+ data.tar.gz: b5132280bedea60fc190bdf0d29d0faf124194a2
5
+ SHA512:
6
+ metadata.gz: 54f883e257e253ca50ba6d81f407f9dae1faa4b5be54f4897057c6bd7aa2313956282320dab97a376fc246823cb3b3d127d16c31b210b1f4718269b4477e2943
7
+ data.tar.gz: 69cfb306863bc20b67eb976416eed4553055a0f4a419eced5deb2e71d288970f7d46a75397a6dc044127442b38105ed8a4f70957bff0e4e6b447ce7741f35b50
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jquery-tablesorter-rails4.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jordan
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Jquery::Tablesorter::Rails4
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'jquery-tablesorter-rails4'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install jquery-tablesorter-rails4
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,116 @@
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
+ */
11
+
12
+ /**
13
+ * Sets the type of metadata to use. Metadata is encoded in JSON, and each property
14
+ * in the JSON will become a property of the element itself.
15
+ *
16
+ * There are three supported types of metadata storage:
17
+ *
18
+ * attr: Inside an attribute. The name parameter indicates *which* attribute.
19
+ *
20
+ * class: Inside the class attribute, wrapped in curly braces: { }
21
+ *
22
+ * elem: Inside a child element (e.g. a script tag). The
23
+ * name parameter indicates *which* element.
24
+ *
25
+ * The metadata for an element is loaded the first time the element is accessed via jQuery.
26
+ *
27
+ * As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
28
+ * matched by expr, then redefine the metadata type and run another $(expr) for other elements.
29
+ *
30
+ * @name $.metadata.setType
31
+ *
32
+ * @example <p id="one" class="some_class {item_id: 1, item_label: 'Label'}">This is a p</p>
33
+ * @before $.metadata.setType("class")
34
+ * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
35
+ * @desc Reads metadata from the class attribute
36
+ *
37
+ * @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
38
+ * @before $.metadata.setType("attr", "data")
39
+ * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
40
+ * @desc Reads metadata from a "data" attribute
41
+ *
42
+ * @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
43
+ * @before $.metadata.setType("elem", "script")
44
+ * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
45
+ * @desc Reads metadata from a nested script element
46
+ *
47
+ * @param String type The encoding type
48
+ * @param String name The name of the attribute to be used to get metadata (optional)
49
+ * @cat Plugins/Metadata
50
+ * @descr Sets the type of encoding to be used when loading metadata for the first time
51
+ * @type undefined
52
+ * @see metadata()
53
+ */
54
+
55
+ (function($) {
56
+
57
+ $.extend({
58
+ metadata : {
59
+ defaults : {
60
+ type: 'class',
61
+ name: 'metadata',
62
+ cre: /(\{.*\})/,
63
+ single: 'metadata'
64
+ },
65
+ setType: function( type, name ){
66
+ this.defaults.type = type;
67
+ this.defaults.name = name;
68
+ },
69
+ get: function( elem, opts ){
70
+ var data, m, e, attr,
71
+ settings = $.extend({},this.defaults,opts);
72
+ // check for empty string in single property
73
+ if ( !settings.single.length ) { settings.single = 'metadata'; }
74
+
75
+ data = $.data(elem, settings.single);
76
+ // returned cached data if it already exists
77
+ if ( data ) { return data; }
78
+
79
+ data = "{}";
80
+
81
+ if ( settings.type === "class" ) {
82
+ m = settings.cre.exec( elem.className );
83
+ if ( m ) { data = m[1]; }
84
+ } else if ( settings.type === "elem" ) {
85
+ if( !elem.getElementsByTagName ) { return undefined; }
86
+ e = elem.getElementsByTagName(settings.name);
87
+ if ( e.length ) { data = $.trim(e[0].innerHTML); }
88
+ } else if ( elem.getAttribute !== undefined ) {
89
+ attr = elem.getAttribute( settings.name );
90
+ if ( attr ) { data = attr; }
91
+ }
92
+
93
+ if ( data.indexOf( '{' ) <0 ) { data = "{" + data + "}"; }
94
+
95
+ data = eval("(" + data + ")");
96
+
97
+ $.data( elem, settings.single, data );
98
+ return data;
99
+ }
100
+ }
101
+ });
102
+
103
+ /**
104
+ * Returns the metadata object for the first member of the jQuery object.
105
+ *
106
+ * @name metadata
107
+ * @descr Returns element's metadata object
108
+ * @param Object opts An object contianing settings to override the defaults
109
+ * @type jQuery
110
+ * @cat Plugins/Metadata
111
+ */
112
+ $.fn.metadata = function( opts ){
113
+ return $.metadata.get( this[0], opts );
114
+ };
115
+
116
+ })(jQuery);
@@ -0,0 +1,1477 @@
1
+ /*!
2
+ * TableSorter 2.10.8 - 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
+ /*jshint browser:true, jquery:true, unused:false, expr: true */
18
+ /*global console:false, alert:false */
19
+ !(function($) {
20
+ "use strict";
21
+ $.extend({
22
+ /*jshint supernew:true */
23
+ tablesorter: new function() {
24
+
25
+ var ts = this;
26
+
27
+ ts.version = "2.10.8";
28
+
29
+ ts.parsers = [];
30
+ ts.widgets = [];
31
+ ts.defaults = {
32
+
33
+ // *** appearance
34
+ theme : 'default', // adds tablesorter-{theme} to the table for styling
35
+ widthFixed : false, // adds colgroup to fix widths of columns
36
+ showProcessing : false, // show an indeterminate timer icon in the header when the table is sorted or filtered.
37
+
38
+ headerTemplate : '{content}',// header layout template (HTML ok); {content} = innerHTML, {icon} = <i/> (class from cssIcon)
39
+ onRenderTemplate : null, // function(index, template){ return template; }, (template is a string)
40
+ onRenderHeader : null, // function(index){}, (nothing to return)
41
+
42
+ // *** functionality
43
+ cancelSelection : true, // prevent text selection in the header
44
+ dateFormat : 'mmddyyyy', // other options: "ddmmyyy" or "yyyymmdd"
45
+ sortMultiSortKey : 'shiftKey', // key used to select additional columns
46
+ sortResetKey : 'ctrlKey', // key used to remove sorting on a column
47
+ usNumberFormat : true, // false for German "1.234.567,89" or French "1 234 567,89"
48
+ delayInit : false, // if false, the parsed table contents will not update until the first sort
49
+ serverSideSorting: false, // if true, server-side sorting should be performed because client-side sorting will be disabled, but the ui and events will still be used.
50
+
51
+ // *** sort options
52
+ headers : {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc.
53
+ ignoreCase : true, // ignore case while sorting
54
+ sortForce : null, // column(s) first sorted; always applied
55
+ sortList : [], // Initial sort order; applied initially; updated when manually sorted
56
+ sortAppend : null, // column(s) sorted last; always applied
57
+
58
+ sortInitialOrder : 'asc', // sort direction on first click
59
+ sortLocaleCompare: false, // replace equivalent character (accented characters)
60
+ sortReset : false, // third click on the header will reset column to default - unsorted
61
+ sortRestart : false, // restart sort to "sortInitialOrder" when clicking on previously unsorted columns
62
+
63
+ emptyTo : 'bottom', // sort empty cell to bottom, top, none, zero
64
+ stringTo : 'max', // sort strings in numerical column as max, min, top, bottom, zero
65
+ textExtraction : 'simple', // text extraction method/function - function(node, table, cellIndex){}
66
+ textSorter : null, // use custom text sorter - function(a,b){ return a.sort(b); } // basic sort
67
+
68
+ // *** widget options
69
+ widgets: [], // method to add widgets, e.g. widgets: ['zebra']
70
+ widgetOptions : {
71
+ zebra : [ 'even', 'odd' ] // zebra widget alternating row class names
72
+ },
73
+ initWidgets : true, // apply widgets on tablesorter initialization
74
+
75
+ // *** callbacks
76
+ initialized : null, // function(table){},
77
+
78
+ // *** css class names
79
+ tableClass : 'tablesorter',
80
+ cssAsc : 'tablesorter-headerAsc',
81
+ cssChildRow : 'tablesorter-childRow', // previously "expand-child"
82
+ cssDesc : 'tablesorter-headerDesc',
83
+ cssHeader : 'tablesorter-header',
84
+ cssHeaderRow : 'tablesorter-headerRow',
85
+ cssIcon : 'tablesorter-icon', // if this class exists, a <i> will be added to the header automatically
86
+ cssInfoBlock : 'tablesorter-infoOnly', // don't sort tbody with this class name
87
+ cssProcessing : 'tablesorter-processing', // processing icon applied to header during sort/filter
88
+
89
+ // *** selectors
90
+ selectorHeaders : '> thead th, > thead td',
91
+ selectorSort : 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort
92
+ selectorRemove : '.remove-me',
93
+
94
+ // *** advanced
95
+ debug : false,
96
+
97
+ // *** Internal variables
98
+ headerList: [],
99
+ empties: {},
100
+ strings: {},
101
+ parsers: []
102
+
103
+ // deprecated; but retained for backwards compatibility
104
+ // widgetZebra: { css: ["even", "odd"] }
105
+
106
+ };
107
+
108
+ /* debuging utils */
109
+ function log(s) {
110
+ if (typeof console !== "undefined" && typeof console.log !== "undefined") {
111
+ console.log(s);
112
+ } else {
113
+ alert(s);
114
+ }
115
+ }
116
+
117
+ function benchmark(s, d) {
118
+ log(s + " (" + (new Date().getTime() - d.getTime()) + "ms)");
119
+ }
120
+
121
+ ts.log = log;
122
+ ts.benchmark = benchmark;
123
+
124
+ function getElementText(table, node, cellIndex) {
125
+ if (!node) { return ""; }
126
+ var c = table.config,
127
+ t = c.textExtraction, text = "";
128
+ if (t === "simple") {
129
+ if (c.supportsTextContent) {
130
+ text = node.textContent; // newer browsers support this
131
+ } else {
132
+ text = $(node).text();
133
+ }
134
+ } else {
135
+ if (typeof t === "function") {
136
+ text = t(node, table, cellIndex);
137
+ } else if (typeof t === "object" && t.hasOwnProperty(cellIndex)) {
138
+ text = t[cellIndex](node, table, cellIndex);
139
+ } else {
140
+ text = c.supportsTextContent ? node.textContent : $(node).text();
141
+ }
142
+ }
143
+ return $.trim(text);
144
+ }
145
+
146
+ function detectParserForColumn(table, rows, rowIndex, cellIndex) {
147
+ var cur,
148
+ i = ts.parsers.length,
149
+ node = false,
150
+ nodeValue = '',
151
+ keepLooking = true;
152
+ while (nodeValue === '' && keepLooking) {
153
+ rowIndex++;
154
+ if (rows[rowIndex]) {
155
+ node = rows[rowIndex].cells[cellIndex];
156
+ nodeValue = getElementText(table, node, cellIndex);
157
+ if (table.config.debug) {
158
+ log('Checking if value was empty on row ' + rowIndex + ', column: ' + cellIndex + ': "' + nodeValue + '"');
159
+ }
160
+ } else {
161
+ keepLooking = false;
162
+ }
163
+ }
164
+ while (--i >= 0) {
165
+ cur = ts.parsers[i];
166
+ // ignore the default text parser because it will always be true
167
+ if (cur && cur.id !== 'text' && cur.is && cur.is(nodeValue, table, node)) {
168
+ return cur;
169
+ }
170
+ }
171
+ // nothing found, return the generic parser (text)
172
+ return ts.getParserById('text');
173
+ }
174
+
175
+ function buildParserCache(table) {
176
+ var c = table.config,
177
+ // update table bodies in case we start with an empty table
178
+ tb = c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')'),
179
+ rows, list, l, i, h, ch, p, parsersDebug = "";
180
+ if ( tb.length === 0) {
181
+ return c.debug ? log('*Empty table!* Not building a parser cache') : '';
182
+ }
183
+ rows = tb[0].rows;
184
+ if (rows[0]) {
185
+ list = [];
186
+ l = rows[0].cells.length;
187
+ for (i = 0; i < l; i++) {
188
+ // tons of thanks to AnthonyM1229 for working out the following selector (issue #74) to make this work in IE8!
189
+ // More fixes to this selector to work properly in iOS and jQuery 1.8+ (issue #132 & #174)
190
+ h = c.$headers.filter(':not([colspan])');
191
+ h = h.add( c.$headers.filter('[colspan="1"]') ) // ie8 fix
192
+ .filter('[data-column="' + i + '"]:last');
193
+ ch = c.headers[i];
194
+ // get column parser
195
+ p = ts.getParserById( ts.getData(h, ch, 'sorter') );
196
+ // empty cells behaviour - keeping emptyToBottom for backwards compatibility
197
+ c.empties[i] = ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' );
198
+ // text strings behaviour in numerical sorts
199
+ c.strings[i] = ts.getData(h, ch, 'string') || c.stringTo || 'max';
200
+ if (!p) {
201
+ p = detectParserForColumn(table, rows, -1, i);
202
+ }
203
+ if (c.debug) {
204
+ parsersDebug += "column:" + i + "; parser:" + p.id + "; string:" + c.strings[i] + '; empty: ' + c.empties[i] + "\n";
205
+ }
206
+ list.push(p);
207
+ }
208
+ }
209
+ if (c.debug) {
210
+ log(parsersDebug);
211
+ }
212
+ c.parsers = list;
213
+ }
214
+
215
+ /* utils */
216
+ function buildCache(table) {
217
+ var b = table.tBodies,
218
+ tc = table.config,
219
+ totalRows,
220
+ totalCells,
221
+ parsers = tc.parsers,
222
+ t, v, i, j, k, c, cols, cacheTime, colMax = [];
223
+ tc.cache = {};
224
+ // if no parsers found, return - it's an empty table.
225
+ if (!parsers) {
226
+ return tc.debug ? log('*Empty table!* Not building a cache') : '';
227
+ }
228
+ if (tc.debug) {
229
+ cacheTime = new Date();
230
+ }
231
+ // processing icon
232
+ if (tc.showProcessing) {
233
+ ts.isProcessing(table, true);
234
+ }
235
+ for (k = 0; k < b.length; k++) {
236
+ tc.cache[k] = { row: [], normalized: [] };
237
+ // ignore tbodies with class name from css.cssInfoBlock
238
+ if (!$(b[k]).hasClass(tc.cssInfoBlock)) {
239
+ totalRows = (b[k] && b[k].rows.length) || 0;
240
+ totalCells = (b[k].rows[0] && b[k].rows[0].cells.length) || 0;
241
+ for (i = 0; i < totalRows; ++i) {
242
+ /** Add the table data to main data array */
243
+ c = $(b[k].rows[i]);
244
+ cols = [];
245
+ // if this is a child row, add it to the last row's children and continue to the next row
246
+ if (c.hasClass(tc.cssChildRow)) {
247
+ tc.cache[k].row[tc.cache[k].row.length - 1] = tc.cache[k].row[tc.cache[k].row.length - 1].add(c);
248
+ // go to the next for loop
249
+ continue;
250
+ }
251
+ tc.cache[k].row.push(c);
252
+ for (j = 0; j < totalCells; ++j) {
253
+ t = getElementText(table, c[0].cells[j], j);
254
+ // allow parsing if the string is empty, previously parsing would change it to zero,
255
+ // in case the parser needs to extract data from the table cell attributes
256
+ v = parsers[j].format(t, table, c[0].cells[j], j);
257
+ cols.push(v);
258
+ if ((parsers[j].type || '').toLowerCase() === "numeric") {
259
+ colMax[j] = Math.max(Math.abs(v) || 0, colMax[j] || 0); // determine column max value (ignore sign)
260
+ }
261
+ }
262
+ cols.push(tc.cache[k].normalized.length); // add position for rowCache
263
+ tc.cache[k].normalized.push(cols);
264
+ }
265
+ tc.cache[k].colMax = colMax;
266
+ }
267
+ }
268
+ if (tc.showProcessing) {
269
+ ts.isProcessing(table); // remove processing icon
270
+ }
271
+ if (tc.debug) {
272
+ benchmark("Building cache for " + totalRows + " rows", cacheTime);
273
+ }
274
+ }
275
+
276
+ // init flag (true) used by pager plugin to prevent widget application
277
+ function appendToTable(table, init) {
278
+ var c = table.config,
279
+ b = table.tBodies,
280
+ rows = [],
281
+ c2 = c.cache,
282
+ r, n, totalRows, checkCell, $bk, $tb,
283
+ i, j, k, l, pos, appendTime;
284
+ if (!c2[0]) { return; } // empty table - fixes #206
285
+ if (c.debug) {
286
+ appendTime = new Date();
287
+ }
288
+ for (k = 0; k < b.length; k++) {
289
+ $bk = $(b[k]);
290
+ if ($bk.length && !$bk.hasClass(c.cssInfoBlock)) {
291
+ // get tbody
292
+ $tb = ts.processTbody(table, $bk, true);
293
+ r = c2[k].row;
294
+ n = c2[k].normalized;
295
+ totalRows = n.length;
296
+ checkCell = totalRows ? (n[0].length - 1) : 0;
297
+ for (i = 0; i < totalRows; i++) {
298
+ pos = n[i][checkCell];
299
+ rows.push(r[pos]);
300
+ // removeRows used by the pager plugin
301
+ if (!c.appender || !c.removeRows) {
302
+ l = r[pos].length;
303
+ for (j = 0; j < l; j++) {
304
+ $tb.append(r[pos][j]);
305
+ }
306
+ }
307
+ }
308
+ // restore tbody
309
+ ts.processTbody(table, $tb, false);
310
+ }
311
+ }
312
+ if (c.appender) {
313
+ c.appender(table, rows);
314
+ }
315
+ if (c.debug) {
316
+ benchmark("Rebuilt table", appendTime);
317
+ }
318
+ // apply table widgets
319
+ if (!init) { ts.applyWidget(table); }
320
+ // trigger sortend
321
+ $(table).trigger("sortEnd", table);
322
+ }
323
+
324
+ // computeTableHeaderCellIndexes from:
325
+ // http://www.javascripttoolbox.com/lib/table/examples.php
326
+ // http://www.javascripttoolbox.com/temp/table_cellindex.html
327
+ function computeThIndexes(t) {
328
+ var matrix = [],
329
+ lookup = {},
330
+ cols = 0, // determine the number of columns
331
+ trs = $(t).find('thead:eq(0), tfoot').children('tr'), // children tr in tfoot - see issue #196
332
+ i, j, k, l, c, cells, rowIndex, cellId, rowSpan, colSpan, firstAvailCol, matrixrow;
333
+ for (i = 0; i < trs.length; i++) {
334
+ cells = trs[i].cells;
335
+ for (j = 0; j < cells.length; j++) {
336
+ c = cells[j];
337
+ rowIndex = c.parentNode.rowIndex;
338
+ cellId = rowIndex + "-" + c.cellIndex;
339
+ rowSpan = c.rowSpan || 1;
340
+ colSpan = c.colSpan || 1;
341
+ if (typeof(matrix[rowIndex]) === "undefined") {
342
+ matrix[rowIndex] = [];
343
+ }
344
+ // Find first available column in the first row
345
+ for (k = 0; k < matrix[rowIndex].length + 1; k++) {
346
+ if (typeof(matrix[rowIndex][k]) === "undefined") {
347
+ firstAvailCol = k;
348
+ break;
349
+ }
350
+ }
351
+ lookup[cellId] = firstAvailCol;
352
+ cols = Math.max(firstAvailCol, cols);
353
+ // add data-column
354
+ $(c).attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex
355
+ for (k = rowIndex; k < rowIndex + rowSpan; k++) {
356
+ if (typeof(matrix[k]) === "undefined") {
357
+ matrix[k] = [];
358
+ }
359
+ matrixrow = matrix[k];
360
+ for (l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
361
+ matrixrow[l] = "x";
362
+ }
363
+ }
364
+ }
365
+ }
366
+ t.config.columns = cols; // may not be accurate if # header columns !== # tbody columns
367
+ return lookup;
368
+ }
369
+
370
+ function formatSortingOrder(v) {
371
+ // look for "d" in "desc" order; return true
372
+ return (/^d/i.test(v) || v === 1);
373
+ }
374
+
375
+ function buildHeaders(table) {
376
+ var header_index = computeThIndexes(table), ch, $t,
377
+ h, i, t, lock, time, c = table.config;
378
+ c.headerList = [];
379
+ c.headerContent = [];
380
+ if (c.debug) {
381
+ time = new Date();
382
+ }
383
+ i = c.cssIcon ? '<i class="' + c.cssIcon + '"></i>' : ''; // add icon if cssIcon option exists
384
+ c.$headers = $(table).find(c.selectorHeaders).each(function(index) {
385
+ $t = $(this);
386
+ ch = c.headers[index];
387
+ c.headerContent[index] = this.innerHTML; // save original header content
388
+ // set up header template
389
+ t = c.headerTemplate.replace(/\{content\}/g, this.innerHTML).replace(/\{icon\}/g, i);
390
+ if (c.onRenderTemplate) {
391
+ h = c.onRenderTemplate.apply($t, [index, t]);
392
+ if (h && typeof h === 'string') { t = h; } // only change t if something is returned
393
+ }
394
+ this.innerHTML = '<div class="tablesorter-header-inner">' + t + '</div>'; // faster than wrapInner
395
+
396
+ if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index]); }
397
+
398
+ this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex];
399
+ this.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2];
400
+ this.count = -1; // set to -1 because clicking on the header automatically adds one
401
+ this.lockedOrder = false;
402
+ lock = ts.getData($t, ch, 'lockedOrder') || false;
403
+ if (typeof lock !== 'undefined' && lock !== false) {
404
+ this.order = this.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0];
405
+ }
406
+ $t.addClass(c.cssHeader);
407
+ // add cell to headerList
408
+ c.headerList[index] = this;
409
+ // add to parent in case there are multiple rows
410
+ $t.parent().addClass(c.cssHeaderRow);
411
+ // allow keyboard cursor to focus on element
412
+ $t.attr("tabindex", 0);
413
+ });
414
+ // enable/disable sorting
415
+ updateHeader(table);
416
+ if (c.debug) {
417
+ benchmark("Built headers:", time);
418
+ log(c.$headers);
419
+ }
420
+ }
421
+
422
+ function commonUpdate(table, resort, callback) {
423
+ var c = table.config;
424
+ // remove rows/elements before update
425
+ c.$table.find(c.selectorRemove).remove();
426
+ // rebuild parsers
427
+ buildParserCache(table);
428
+ // rebuild the cache map
429
+ buildCache(table);
430
+ checkResort(c.$table, resort, callback);
431
+ }
432
+
433
+ function updateHeader(table) {
434
+ var s, c = table.config;
435
+ c.$headers.each(function(index, th){
436
+ s = ts.getData( th, c.headers[index], 'sorter' ) === 'false';
437
+ th.sortDisabled = s;
438
+ $(th)[ s ? 'addClass' : 'removeClass' ]('sorter-false');
439
+ });
440
+ }
441
+
442
+ function setHeadersCss(table) {
443
+ var f, i, j, l,
444
+ c = table.config,
445
+ list = c.sortList,
446
+ css = [c.cssAsc, c.cssDesc],
447
+ // find the footer
448
+ $t = $(table).find('tfoot tr').children().removeClass(css.join(' '));
449
+ // remove all header information
450
+ c.$headers.removeClass(css.join(' '));
451
+ l = list.length;
452
+ for (i = 0; i < l; i++) {
453
+ // direction = 2 means reset!
454
+ if (list[i][1] !== 2) {
455
+ // multicolumn sorting updating - choose the :last in case there are nested columns
456
+ f = c.$headers.not('.sorter-false').filter('[data-column="' + list[i][0] + '"]' + (l === 1 ? ':last' : '') );
457
+ if (f.length) {
458
+ for (j = 0; j < f.length; j++) {
459
+ if (!f[j].sortDisabled) {
460
+ f.eq(j).addClass(css[list[i][1]]);
461
+ // add sorted class to footer, if it exists
462
+ if ($t.length) {
463
+ $t.filter('[data-column="' + list[i][0] + '"]').eq(j).addClass(css[list[i][1]]);
464
+ }
465
+ }
466
+ }
467
+ }
468
+ }
469
+ }
470
+ }
471
+
472
+ // automatically add col group, and column sizes if set
473
+ function fixColumnWidth(table) {
474
+ if (table.config.widthFixed && $(table).find('colgroup').length === 0) {
475
+ var colgroup = $('<colgroup>'),
476
+ overallWidth = $(table).width();
477
+ $(table.tBodies[0]).find("tr:first").children("td").each(function() {
478
+ colgroup.append($('<col>').css('width', parseInt(($(this).width()/overallWidth)*1000, 10)/10 + '%'));
479
+ });
480
+ $(table).prepend(colgroup);
481
+ }
482
+ }
483
+
484
+ function updateHeaderSortCount(table, list) {
485
+ var s, t, o, c = table.config,
486
+ sl = list || c.sortList;
487
+ c.sortList = [];
488
+ $.each(sl, function(i,v){
489
+ // ensure all sortList values are numeric - fixes #127
490
+ s = [ parseInt(v[0], 10), parseInt(v[1], 10) ];
491
+ // make sure header exists
492
+ o = c.headerList[s[0]];
493
+ if (o) { // prevents error if sorton array is wrong
494
+ c.sortList.push(s);
495
+ t = $.inArray(s[1], o.order); // fixes issue #167
496
+ o.count = t >= 0 ? t : s[1] % (c.sortReset ? 3 : 2);
497
+ }
498
+ });
499
+ }
500
+
501
+ function getCachedSortType(parsers, i) {
502
+ return (parsers && parsers[i]) ? parsers[i].type || '' : '';
503
+ }
504
+
505
+ function initSort(table, cell, e){
506
+ var a, i, j, o, s,
507
+ c = table.config,
508
+ k = !e[c.sortMultiSortKey],
509
+ $this = $(table);
510
+ // Only call sortStart if sorting is enabled
511
+ $this.trigger("sortStart", table);
512
+ // get current column sort order
513
+ cell.count = e[c.sortResetKey] ? 2 : (cell.count + 1) % (c.sortReset ? 3 : 2);
514
+ // reset all sorts on non-current column - issue #30
515
+ if (c.sortRestart) {
516
+ i = cell;
517
+ c.$headers.each(function() {
518
+ // only reset counts on columns that weren't just clicked on and if not included in a multisort
519
+ if (this !== i && (k || !$(this).is('.' + c.cssDesc + ',.' + c.cssAsc))) {
520
+ this.count = -1;
521
+ }
522
+ });
523
+ }
524
+ // get current column index
525
+ i = cell.column;
526
+ // user only wants to sort on one column
527
+ if (k) {
528
+ // flush the sort list
529
+ c.sortList = [];
530
+ if (c.sortForce !== null) {
531
+ a = c.sortForce;
532
+ for (j = 0; j < a.length; j++) {
533
+ if (a[j][0] !== i) {
534
+ c.sortList.push(a[j]);
535
+ }
536
+ }
537
+ }
538
+ // add column to sort list
539
+ o = cell.order[cell.count];
540
+ if (o < 2) {
541
+ c.sortList.push([i, o]);
542
+ // add other columns if header spans across multiple
543
+ if (cell.colSpan > 1) {
544
+ for (j = 1; j < cell.colSpan; j++) {
545
+ c.sortList.push([i + j, o]);
546
+ }
547
+ }
548
+ }
549
+ // multi column sorting
550
+ } else {
551
+ // get rid of the sortAppend before adding more - fixes issue #115
552
+ if (c.sortAppend && c.sortList.length > 1) {
553
+ if (ts.isValueInArray(c.sortAppend[0][0], c.sortList)) {
554
+ c.sortList.pop();
555
+ }
556
+ }
557
+ // the user has clicked on an already sorted column
558
+ if (ts.isValueInArray(i, c.sortList)) {
559
+ // reverse the sorting direction for all tables
560
+ for (j = 0; j < c.sortList.length; j++) {
561
+ s = c.sortList[j];
562
+ o = c.headerList[s[0]];
563
+ if (s[0] === i) {
564
+ s[1] = o.order[o.count];
565
+ if (s[1] === 2) {
566
+ c.sortList.splice(j,1);
567
+ o.count = -1;
568
+ }
569
+ }
570
+ }
571
+ } else {
572
+ // add column to sort list array
573
+ o = cell.order[cell.count];
574
+ if (o < 2) {
575
+ c.sortList.push([i, o]);
576
+ // add other columns if header spans across multiple
577
+ if (cell.colSpan > 1) {
578
+ for (j = 1; j < cell.colSpan; j++) {
579
+ c.sortList.push([i + j, o]);
580
+ }
581
+ }
582
+ }
583
+ }
584
+ }
585
+ if (c.sortAppend !== null) {
586
+ a = c.sortAppend;
587
+ for (j = 0; j < a.length; j++) {
588
+ if (a[j][0] !== i) {
589
+ c.sortList.push(a[j]);
590
+ }
591
+ }
592
+ }
593
+ // sortBegin event triggered immediately before the sort
594
+ $this.trigger("sortBegin", table);
595
+ // setTimeout needed so the processing icon shows up
596
+ setTimeout(function(){
597
+ // set css for headers
598
+ setHeadersCss(table);
599
+ multisort(table);
600
+ appendToTable(table);
601
+ }, 1);
602
+ }
603
+
604
+ // sort multiple columns
605
+ function multisort(table) { /*jshint loopfunc:true */
606
+ var dir = 0, tc = table.config,
607
+ sortList = tc.sortList, l = sortList.length, bl = table.tBodies.length,
608
+ sortTime, i, k, c, colMax, cache, lc, s, order, orgOrderCol;
609
+ if (tc.serverSideSorting || !tc.cache[0]) { // empty table - fixes #206
610
+ return;
611
+ }
612
+ if (tc.debug) { sortTime = new Date(); }
613
+ for (k = 0; k < bl; k++) {
614
+ colMax = tc.cache[k].colMax;
615
+ cache = tc.cache[k].normalized;
616
+ lc = cache.length;
617
+ orgOrderCol = (cache && cache[0]) ? cache[0].length - 1 : 0;
618
+ cache.sort(function(a, b) {
619
+ // cache is undefined here in IE, so don't use it!
620
+ for (i = 0; i < l; i++) {
621
+ c = sortList[i][0];
622
+ order = sortList[i][1];
623
+ // fallback to natural sort since it is more robust
624
+ s = /n/i.test(getCachedSortType(tc.parsers, c)) ? "Numeric" : "Text";
625
+ s += order === 0 ? "" : "Desc";
626
+ if (/Numeric/.test(s) && tc.strings[c]) {
627
+ // sort strings in numerical columns
628
+ if (typeof (tc.string[tc.strings[c]]) === 'boolean') {
629
+ dir = (order === 0 ? 1 : -1) * (tc.string[tc.strings[c]] ? -1 : 1);
630
+ } else {
631
+ dir = (tc.strings[c]) ? tc.string[tc.strings[c]] || 0 : 0;
632
+ }
633
+ }
634
+ var sort = $.tablesorter["sort" + s](table, a[c], b[c], c, colMax[c], dir);
635
+ if (sort) { return sort; }
636
+ }
637
+ return a[orgOrderCol] - b[orgOrderCol];
638
+ });
639
+ }
640
+ if (tc.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time", sortTime); }
641
+ }
642
+
643
+ function resortComplete($table, callback){
644
+ $table.trigger('updateComplete');
645
+ if (typeof callback === "function") {
646
+ callback($table[0]);
647
+ }
648
+ }
649
+
650
+ function checkResort($table, flag, callback) {
651
+ // don't try to resort if the table is still processing
652
+ // this will catch spamming of the updateCell method
653
+ if (flag !== false && !$table[0].isProcessing) {
654
+ $table.trigger("sorton", [$table[0].config.sortList, function(){
655
+ resortComplete($table, callback);
656
+ }]);
657
+ } else {
658
+ resortComplete($table, callback);
659
+ }
660
+ }
661
+
662
+ function bindEvents(table){
663
+ var c = table.config,
664
+ $this = c.$table,
665
+ j, downTime;
666
+ // apply event handling to headers
667
+ c.$headers
668
+ // http://stackoverflow.com/questions/5312849/jquery-find-self;
669
+ .find(c.selectorSort).add( c.$headers.filter(c.selectorSort) )
670
+ .unbind('mousedown.tablesorter mouseup.tablesorter sort.tablesorter keypress.tablesorter')
671
+ .bind('mousedown.tablesorter mouseup.tablesorter sort.tablesorter keypress.tablesorter', function(e, external) {
672
+ // only recognize left clicks or enter
673
+ if ( ((e.which || e.button) !== 1 && !/sort|keypress/.test(e.type)) || (e.type === 'keypress' && e.which !== 13) ) {
674
+ return false;
675
+ }
676
+ // ignore long clicks (prevents resizable widget from initializing a sort)
677
+ if (e.type === 'mouseup' && external !== true && (new Date().getTime() - downTime > 250)) { return false; }
678
+ // set timer on mousedown
679
+ if (e.type === 'mousedown') {
680
+ downTime = new Date().getTime();
681
+ return e.target.tagName === "INPUT" ? '' : !c.cancelSelection;
682
+ }
683
+ if (c.delayInit && !c.cache) { buildCache(table); }
684
+ // jQuery v1.2.6 doesn't have closest()
685
+ var $cell = /TH|TD/.test(this.tagName) ? $(this) : $(this).parents('th, td').filter(':first'), cell = $cell[0];
686
+ if (!cell.sortDisabled) {
687
+ initSort(table, cell, e);
688
+ }
689
+ });
690
+ if (c.cancelSelection) {
691
+ // cancel selection
692
+ c.$headers
693
+ .attr('unselectable', 'on')
694
+ .bind('selectstart', false)
695
+ .css({
696
+ 'user-select': 'none',
697
+ 'MozUserSelect': 'none' // not needed for jQuery 1.8+
698
+ });
699
+ }
700
+ // apply easy methods that trigger bound events
701
+ $this
702
+ .unbind('sortReset update updateRows updateCell updateAll addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave '.split(' ').join('.tablesorter '))
703
+ .bind("sortReset.tablesorter", function(e){
704
+ e.stopPropagation();
705
+ c.sortList = [];
706
+ setHeadersCss(table);
707
+ multisort(table);
708
+ appendToTable(table);
709
+ })
710
+ .bind("updateAll.tablesorter", function(e, resort, callback){
711
+ e.stopPropagation();
712
+ ts.refreshWidgets(table, true, true);
713
+ ts.restoreHeaders(table);
714
+ buildHeaders(table);
715
+ bindEvents(table);
716
+ commonUpdate(table, resort, callback);
717
+ })
718
+ .bind("update.tablesorter updateRows.tablesorter", function(e, resort, callback) {
719
+ e.stopPropagation();
720
+ // update sorting (if enabled/disabled)
721
+ updateHeader(table);
722
+ commonUpdate(table, resort, callback);
723
+ })
724
+ .bind("updateCell.tablesorter", function(e, cell, resort, callback) {
725
+ e.stopPropagation();
726
+ $this.find(c.selectorRemove).remove();
727
+ // get position from the dom
728
+ var l, row, icell,
729
+ $tb = $this.find('tbody'),
730
+ // update cache - format: function(s, table, cell, cellIndex)
731
+ // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
732
+ tbdy = $tb.index( $(cell).parents('tbody').filter(':first') ),
733
+ $row = $(cell).parents('tr').filter(':first');
734
+ cell = $(cell)[0]; // in case cell is a jQuery object
735
+ // tbody may not exist if update is initialized while tbody is removed for processing
736
+ if ($tb.length && tbdy >= 0) {
737
+ row = $tb.eq(tbdy).find('tr').index( $row );
738
+ icell = cell.cellIndex;
739
+ l = c.cache[tbdy].normalized[row].length - 1;
740
+ c.cache[tbdy].row[table.config.cache[tbdy].normalized[row][l]] = $row;
741
+ c.cache[tbdy].normalized[row][icell] = c.parsers[icell].format( getElementText(table, cell, icell), table, cell, icell );
742
+ checkResort($this, resort, callback);
743
+ }
744
+ })
745
+ .bind("addRows.tablesorter", function(e, $row, resort, callback) {
746
+ e.stopPropagation();
747
+ var i, rows = $row.filter('tr').length,
748
+ dat = [], l = $row[0].cells.length,
749
+ tbdy = $this.find('tbody').index( $row.parents('tbody').filter(':first') );
750
+ // fixes adding rows to an empty table - see issue #179
751
+ if (!c.parsers) {
752
+ buildParserCache(table);
753
+ }
754
+ // add each row
755
+ for (i = 0; i < rows; i++) {
756
+ // add each cell
757
+ for (j = 0; j < l; j++) {
758
+ dat[j] = c.parsers[j].format( getElementText(table, $row[i].cells[j], j), table, $row[i].cells[j], j );
759
+ }
760
+ // add the row index to the end
761
+ dat.push(c.cache[tbdy].row.length);
762
+ // update cache
763
+ c.cache[tbdy].row.push([$row[i]]);
764
+ c.cache[tbdy].normalized.push(dat);
765
+ dat = [];
766
+ }
767
+ // resort using current settings
768
+ checkResort($this, resort, callback);
769
+ })
770
+ .bind("sorton.tablesorter", function(e, list, callback, init) {
771
+ e.stopPropagation();
772
+ $this.trigger("sortStart", this);
773
+ // update header count index
774
+ updateHeaderSortCount(table, list);
775
+ // set css for headers
776
+ setHeadersCss(table);
777
+ $this.trigger("sortBegin", this);
778
+ // sort the table and append it to the dom
779
+ multisort(table);
780
+ appendToTable(table, init);
781
+ if (typeof callback === "function") {
782
+ callback(table);
783
+ }
784
+ })
785
+ .bind("appendCache.tablesorter", function(e, callback, init) {
786
+ e.stopPropagation();
787
+ appendToTable(table, init);
788
+ if (typeof callback === "function") {
789
+ callback(table);
790
+ }
791
+ })
792
+ .bind("applyWidgetId.tablesorter", function(e, id) {
793
+ e.stopPropagation();
794
+ ts.getWidgetById(id).format(table, c, c.widgetOptions);
795
+ })
796
+ .bind("applyWidgets.tablesorter", function(e, init) {
797
+ e.stopPropagation();
798
+ // apply widgets
799
+ ts.applyWidget(table, init);
800
+ })
801
+ .bind("refreshWidgets.tablesorter", function(e, all, dontapply){
802
+ e.stopPropagation();
803
+ ts.refreshWidgets(table, all, dontapply);
804
+ })
805
+ .bind("destroy.tablesorter", function(e, c, cb){
806
+ e.stopPropagation();
807
+ ts.destroy(table, c, cb);
808
+ });
809
+ }
810
+
811
+ /* public methods */
812
+ ts.construct = function(settings) {
813
+ return this.each(function() {
814
+ // if no thead or tbody, or tablesorter is already present, quit
815
+ if (!this.tHead || this.tBodies.length === 0 || this.hasInitialized === true) {
816
+ return (this.config && this.config.debug) ? log('stopping initialization! No thead, tbody or tablesorter has already been initialized') : '';
817
+ }
818
+ // declare
819
+ var $this = $(this), table = this,
820
+ c, k = '',
821
+ m = $.metadata;
822
+ // initialization flag
823
+ table.hasInitialized = false;
824
+ // table is being processed flag
825
+ table.isProcessing = true;
826
+ // new blank config object
827
+ table.config = {};
828
+ // merge and extend
829
+ c = $.extend(true, table.config, ts.defaults, settings);
830
+ // save the settings where they read
831
+ $.data(table, "tablesorter", c);
832
+ if (c.debug) { $.data( table, 'startoveralltimer', new Date()); }
833
+ // constants
834
+ c.supportsTextContent = $('<span>x</span>')[0].textContent === 'x';
835
+ c.supportsDataObject = parseFloat($.fn.jquery) >= 1.4;
836
+ // digit sort text location; keeping max+/- for backwards compatibility
837
+ c.string = { 'max': 1, 'min': -1, 'max+': 1, 'max-': -1, 'zero': 0, 'none': 0, 'null': 0, 'top': true, 'bottom': false };
838
+ // add table theme class only if there isn't already one there
839
+ if (!/tablesorter\-/.test($this.attr('class'))) {
840
+ k = (c.theme !== '' ? ' tablesorter-' + c.theme : '');
841
+ }
842
+ c.$table = $this.addClass(c.tableClass + k);
843
+ c.$tbodies = $this.children('tbody:not(.' + c.cssInfoBlock + ')');
844
+ // build headers
845
+ buildHeaders(table);
846
+ // fixate columns if the users supplies the fixedWidth option
847
+ // do this after theme has been applied
848
+ fixColumnWidth(table);
849
+ // try to auto detect column type, and store in tables config
850
+ buildParserCache(table);
851
+ // build the cache for the tbody cells
852
+ // delayInit will delay building the cache until the user starts a sort
853
+ if (!c.delayInit) { buildCache(table); }
854
+ // bind all header events and methods
855
+ bindEvents(table);
856
+ // get sort list from jQuery data or metadata
857
+ // in jQuery < 1.4, an error occurs when calling $this.data()
858
+ if (c.supportsDataObject && typeof $this.data().sortlist !== 'undefined') {
859
+ c.sortList = $this.data().sortlist;
860
+ } else if (m && ($this.metadata() && $this.metadata().sortlist)) {
861
+ c.sortList = $this.metadata().sortlist;
862
+ }
863
+ // apply widget init code
864
+ ts.applyWidget(table, true);
865
+ // if user has supplied a sort list to constructor
866
+ if (c.sortList.length > 0) {
867
+ $this.trigger("sorton", [c.sortList, {}, !c.initWidgets]);
868
+ } else if (c.initWidgets) {
869
+ // apply widget format
870
+ ts.applyWidget(table);
871
+ }
872
+
873
+ // show processesing icon
874
+ if (c.showProcessing) {
875
+ $this
876
+ .unbind('sortBegin.tablesorter sortEnd.tablesorter')
877
+ .bind('sortBegin.tablesorter sortEnd.tablesorter', function(e) {
878
+ ts.isProcessing(table, e.type === 'sortBegin');
879
+ });
880
+ }
881
+
882
+ // initialized
883
+ table.hasInitialized = true;
884
+ table.isProcessing = false;
885
+ if (c.debug) {
886
+ ts.benchmark("Overall initialization time", $.data( table, 'startoveralltimer'));
887
+ }
888
+ $this.trigger('tablesorter-initialized', table);
889
+ if (typeof c.initialized === 'function') { c.initialized(table); }
890
+ });
891
+ };
892
+
893
+ // *** Process table ***
894
+ // add processing indicator
895
+ ts.isProcessing = function(table, toggle, $ths) {
896
+ table = $(table);
897
+ var c = table[0].config,
898
+ // default to all headers
899
+ $h = $ths || table.find('.' + c.cssHeader);
900
+ if (toggle) {
901
+ if (c.sortList.length > 0) {
902
+ // get headers from the sortList
903
+ $h = $h.filter(function(){
904
+ // get data-column from attr to keep compatibility with jQuery 1.2.6
905
+ return this.sortDisabled ? false : ts.isValueInArray( parseFloat($(this).attr('data-column')), c.sortList);
906
+ });
907
+ }
908
+ $h.addClass(c.cssProcessing);
909
+ } else {
910
+ $h.removeClass(c.cssProcessing);
911
+ }
912
+ };
913
+
914
+ // detach tbody but save the position
915
+ // don't use tbody because there are portions that look for a tbody index (updateCell)
916
+ ts.processTbody = function(table, $tb, getIt){
917
+ var holdr;
918
+ if (getIt) {
919
+ table.isProcessing = true;
920
+ $tb.before('<span class="tablesorter-savemyplace"/>');
921
+ holdr = ($.fn.detach) ? $tb.detach() : $tb.remove();
922
+ return holdr;
923
+ }
924
+ holdr = $(table).find('span.tablesorter-savemyplace');
925
+ $tb.insertAfter( holdr );
926
+ holdr.remove();
927
+ table.isProcessing = false;
928
+ };
929
+
930
+ ts.clearTableBody = function(table) {
931
+ $(table)[0].config.$tbodies.empty();
932
+ };
933
+
934
+ // restore headers
935
+ ts.restoreHeaders = function(table){
936
+ var c = table.config;
937
+ // don't use c.$headers here in case header cells were swapped
938
+ c.$table.find(c.selectorHeaders).each(function(i){
939
+ // only restore header cells if it is wrapped
940
+ // because this is also used by the updateAll method
941
+ if ($(this).find('.tablesorter-header-inner').length){
942
+ $(this).html( c.headerContent[i] );
943
+ }
944
+ });
945
+ };
946
+
947
+ ts.destroy = function(table, removeClasses, callback){
948
+ table = $(table)[0];
949
+ if (!table.hasInitialized) { return; }
950
+ // remove all widgets
951
+ ts.refreshWidgets(table, true, true);
952
+ var $t = $(table), c = table.config,
953
+ $h = $t.find('thead:first'),
954
+ $r = $h.find('tr.' + c.cssHeaderRow).removeClass(c.cssHeaderRow),
955
+ $f = $t.find('tfoot:first > tr').children('th, td');
956
+ // remove widget added rows, just in case
957
+ $h.find('tr').not($r).remove();
958
+ // disable tablesorter
959
+ $t
960
+ .removeData('tablesorter')
961
+ .unbind('sortReset update updateAll updateRows updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd '.split(' ').join('.tablesorter '));
962
+ c.$headers.add($f)
963
+ .removeClass(c.cssHeader + ' ' + c.cssAsc + ' ' + c.cssDesc)
964
+ .removeAttr('data-column');
965
+ $r.find(c.selectorSort).unbind('mousedown.tablesorter mouseup.tablesorter keypress.tablesorter');
966
+ ts.restoreHeaders(table);
967
+ if (removeClasses !== false) {
968
+ $t.removeClass(c.tableClass + ' tablesorter-' + c.theme);
969
+ }
970
+ // clear flag in case the plugin is initialized again
971
+ table.hasInitialized = false;
972
+ if (typeof callback === 'function') {
973
+ callback(table);
974
+ }
975
+ };
976
+
977
+ // *** sort functions ***
978
+ // regex used in natural sort
979
+ ts.regex = [
980
+ /(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi, // chunk/tokenize numbers & letters
981
+ /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, //date
982
+ /^0x[0-9a-f]+$/i // hex
983
+ ];
984
+
985
+ // Natural sort - https://github.com/overset/javascript-natural-sort
986
+ ts.sortText = function(table, a, b, col) {
987
+ if (a === b) { return 0; }
988
+ var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ],
989
+ r = ts.regex, xN, xD, yN, yD, xF, yF, i, mx;
990
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
991
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
992
+ if (typeof c.textSorter === 'function') { return c.textSorter(a, b, table, col); }
993
+ // chunk/tokenize
994
+ xN = a.replace(r[0], '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0');
995
+ yN = b.replace(r[0], '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0');
996
+ // numeric, hex or date detection
997
+ xD = parseInt(a.match(r[2]),16) || (xN.length !== 1 && a.match(r[1]) && Date.parse(a));
998
+ yD = parseInt(b.match(r[2]),16) || (xD && b.match(r[1]) && Date.parse(b)) || null;
999
+ // first try and sort Hex codes or Dates
1000
+ if (yD) {
1001
+ if ( xD < yD ) { return -1; }
1002
+ if ( xD > yD ) { return 1; }
1003
+ }
1004
+ mx = Math.max(xN.length, yN.length);
1005
+ // natural sorting through split numeric strings and default strings
1006
+ for (i = 0; i < mx; i++) {
1007
+ // find floats not starting with '0', string or 0 if not defined
1008
+ xF = isNaN(xN[i]) ? xN[i] || 0 : parseFloat(xN[i]) || 0;
1009
+ yF = isNaN(yN[i]) ? yN[i] || 0 : parseFloat(yN[i]) || 0;
1010
+ // handle numeric vs string comparison - number < string - (Kyle Adams)
1011
+ if (isNaN(xF) !== isNaN(yF)) { return (isNaN(xF)) ? 1 : -1; }
1012
+ // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
1013
+ if (typeof xF !== typeof yF) {
1014
+ xF += '';
1015
+ yF += '';
1016
+ }
1017
+ if (xF < yF) { return -1; }
1018
+ if (xF > yF) { return 1; }
1019
+ }
1020
+ return 0;
1021
+ };
1022
+
1023
+ ts.sortTextDesc = function(table, a, b, col) {
1024
+ if (a === b) { return 0; }
1025
+ var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
1026
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
1027
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
1028
+ if (typeof c.textSorter === 'function') { return c.textSorter(b, a, table, col); }
1029
+ return ts.sortText(table, b, a);
1030
+ };
1031
+
1032
+ // return text string value by adding up ascii value
1033
+ // so the text is somewhat sorted when using a digital sort
1034
+ // this is NOT an alphanumeric sort
1035
+ ts.getTextValue = function(a, mx, d) {
1036
+ if (mx) {
1037
+ // make sure the text value is greater than the max numerical value (mx)
1038
+ var i, l = a ? a.length : 0, n = mx + d;
1039
+ for (i = 0; i < l; i++) {
1040
+ n += a.charCodeAt(i);
1041
+ }
1042
+ return d * n;
1043
+ }
1044
+ return 0;
1045
+ };
1046
+
1047
+ ts.sortNumeric = function(table, a, b, col, mx, d) {
1048
+ if (a === b) { return 0; }
1049
+ var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
1050
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
1051
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
1052
+ if (isNaN(a)) { a = ts.getTextValue(a, mx, d); }
1053
+ if (isNaN(b)) { b = ts.getTextValue(b, mx, d); }
1054
+ return a - b;
1055
+ };
1056
+
1057
+ ts.sortNumericDesc = function(table, a, b, col, mx, d) {
1058
+ if (a === b) { return 0; }
1059
+ var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
1060
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
1061
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
1062
+ if (isNaN(a)) { a = ts.getTextValue(a, mx, d); }
1063
+ if (isNaN(b)) { b = ts.getTextValue(b, mx, d); }
1064
+ return b - a;
1065
+ };
1066
+
1067
+ // used when replacing accented characters during sorting
1068
+ ts.characterEquivalents = {
1069
+ "a" : "\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5", // áàâãäąå
1070
+ "A" : "\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5", // ÁÀÂÃÄĄÅ
1071
+ "c" : "\u00e7\u0107\u010d", // çćč
1072
+ "C" : "\u00c7\u0106\u010c", // ÇĆČ
1073
+ "e" : "\u00e9\u00e8\u00ea\u00eb\u011b\u0119", // éèêëěę
1074
+ "E" : "\u00c9\u00c8\u00ca\u00cb\u011a\u0118", // ÉÈÊËĚĘ
1075
+ "i" : "\u00ed\u00ec\u0130\u00ee\u00ef\u0131", // íìİîïı
1076
+ "I" : "\u00cd\u00cc\u0130\u00ce\u00cf", // ÍÌİÎÏ
1077
+ "o" : "\u00f3\u00f2\u00f4\u00f5\u00f6", // óòôõö
1078
+ "O" : "\u00d3\u00d2\u00d4\u00d5\u00d6", // ÓÒÔÕÖ
1079
+ "ss": "\u00df", // ß (s sharp)
1080
+ "SS": "\u1e9e", // ẞ (Capital sharp s)
1081
+ "u" : "\u00fa\u00f9\u00fb\u00fc\u016f", // úùûüů
1082
+ "U" : "\u00da\u00d9\u00db\u00dc\u016e" // ÚÙÛÜŮ
1083
+ };
1084
+ ts.replaceAccents = function(s) {
1085
+ var a, acc = '[', eq = ts.characterEquivalents;
1086
+ if (!ts.characterRegex) {
1087
+ ts.characterRegexArray = {};
1088
+ for (a in eq) {
1089
+ if (typeof a === 'string') {
1090
+ acc += eq[a];
1091
+ ts.characterRegexArray[a] = new RegExp('[' + eq[a] + ']', 'g');
1092
+ }
1093
+ }
1094
+ ts.characterRegex = new RegExp(acc + ']');
1095
+ }
1096
+ if (ts.characterRegex.test(s)) {
1097
+ for (a in eq) {
1098
+ if (typeof a === 'string') {
1099
+ s = s.replace( ts.characterRegexArray[a], a );
1100
+ }
1101
+ }
1102
+ }
1103
+ return s;
1104
+ };
1105
+
1106
+ // *** utilities ***
1107
+ ts.isValueInArray = function(v, a) {
1108
+ var i, l = a.length;
1109
+ for (i = 0; i < l; i++) {
1110
+ if (a[i][0] === v) {
1111
+ return true;
1112
+ }
1113
+ }
1114
+ return false;
1115
+ };
1116
+
1117
+ ts.addParser = function(parser) {
1118
+ var i, l = ts.parsers.length, a = true;
1119
+ for (i = 0; i < l; i++) {
1120
+ if (ts.parsers[i].id.toLowerCase() === parser.id.toLowerCase()) {
1121
+ a = false;
1122
+ }
1123
+ }
1124
+ if (a) {
1125
+ ts.parsers.push(parser);
1126
+ }
1127
+ };
1128
+
1129
+ ts.getParserById = function(name) {
1130
+ var i, l = ts.parsers.length;
1131
+ for (i = 0; i < l; i++) {
1132
+ if (ts.parsers[i].id.toLowerCase() === (name.toString()).toLowerCase()) {
1133
+ return ts.parsers[i];
1134
+ }
1135
+ }
1136
+ return false;
1137
+ };
1138
+
1139
+ ts.addWidget = function(widget) {
1140
+ ts.widgets.push(widget);
1141
+ };
1142
+
1143
+ ts.getWidgetById = function(name) {
1144
+ var i, w, l = ts.widgets.length;
1145
+ for (i = 0; i < l; i++) {
1146
+ w = ts.widgets[i];
1147
+ if (w && w.hasOwnProperty('id') && w.id.toLowerCase() === name.toLowerCase()) {
1148
+ return w;
1149
+ }
1150
+ }
1151
+ };
1152
+
1153
+ ts.applyWidget = function(table, init) {
1154
+ table = $(table)[0]; // in case this is called externally
1155
+ var c = table.config,
1156
+ wo = c.widgetOptions,
1157
+ widgets = [],
1158
+ time, i, w, wd;
1159
+ if (c.debug) { time = new Date(); }
1160
+ if (c.widgets.length) {
1161
+ // ensure unique widget ids
1162
+ c.widgets = $.grep(c.widgets, function(v, k){
1163
+ return $.inArray(v, c.widgets) === k;
1164
+ });
1165
+ // build widget array & add priority as needed
1166
+ $.each(c.widgets || [], function(i,n){
1167
+ wd = ts.getWidgetById(n);
1168
+ if (wd && wd.id) {
1169
+ // set priority to 10 if not defined
1170
+ if (!wd.priority) { wd.priority = 10; }
1171
+ widgets[i] = wd;
1172
+ }
1173
+ });
1174
+ // sort widgets by priority
1175
+ widgets.sort(function(a, b){
1176
+ return a.priority < b.priority ? -1 : a.priority === b.priority ? 0 : 1;
1177
+ });
1178
+
1179
+ // add/update selected widgets
1180
+ $.each(widgets, function(i,w){
1181
+ if (w) {
1182
+ if (init) {
1183
+ if (w.hasOwnProperty('options')) {
1184
+ wo = table.config.widgetOptions = $.extend( true, {}, w.options, wo );
1185
+ }
1186
+ if (w.hasOwnProperty('init')) {
1187
+ w.init(table, w, c, wo);
1188
+ }
1189
+ } else if (!init && w.hasOwnProperty('format')) {
1190
+ w.format(table, c, wo, false);
1191
+ }
1192
+ }
1193
+ });
1194
+ }
1195
+ if (c.debug) {
1196
+ w = c.widgets.length;
1197
+ benchmark("Completed " + (init === true ? "initializing " : "applying ") + w + " widget" + (w !== 1 ? "s" : ""), time);
1198
+ }
1199
+ };
1200
+
1201
+ ts.refreshWidgets = function(table, doAll, dontapply) {
1202
+ table = $(table)[0]; // see issue #243
1203
+ var i, c = table.config,
1204
+ cw = c.widgets,
1205
+ w = ts.widgets, l = w.length;
1206
+ // remove previous widgets
1207
+ for (i = 0; i < l; i++){
1208
+ if ( w[i] && w[i].id && (doAll || $.inArray( w[i].id, cw ) < 0) ) {
1209
+ if (c.debug) { log( 'Refeshing widgets: Removing ' + w[i].id ); }
1210
+ if (w[i].hasOwnProperty('remove')) { w[i].remove(table, c, c.widgetOptions); }
1211
+ }
1212
+ }
1213
+ if (dontapply !== true) {
1214
+ ts.applyWidget(table, doAll);
1215
+ }
1216
+ };
1217
+
1218
+ // get sorter, string, empty, etc options for each column from
1219
+ // jQuery data, metadata, header option or header class name ("sorter-false")
1220
+ // priority = jQuery data > meta > headers option > header class name
1221
+ ts.getData = function(h, ch, key) {
1222
+ var val = '', $h = $(h), m, cl;
1223
+ if (!$h.length) { return ''; }
1224
+ m = $.metadata ? $h.metadata() : false;
1225
+ cl = ' ' + ($h.attr('class') || '');
1226
+ if (typeof $h.data(key) !== 'undefined' || typeof $h.data(key.toLowerCase()) !== 'undefined'){
1227
+ // "data-lockedOrder" is assigned to "lockedorder"; but "data-locked-order" is assigned to "lockedOrder"
1228
+ // "data-sort-initial-order" is assigned to "sortInitialOrder"
1229
+ val += $h.data(key) || $h.data(key.toLowerCase());
1230
+ } else if (m && typeof m[key] !== 'undefined') {
1231
+ val += m[key];
1232
+ } else if (ch && typeof ch[key] !== 'undefined') {
1233
+ val += ch[key];
1234
+ } else if (cl !== ' ' && cl.match(' ' + key + '-')) {
1235
+ // include sorter class name "sorter-text", etc; now works with "sorter-my-custom-parser"
1236
+ val = cl.match( new RegExp('\\s' + key + '-([\\w-]+)') )[1] || '';
1237
+ }
1238
+ return $.trim(val);
1239
+ };
1240
+
1241
+ ts.formatFloat = function(s, table) {
1242
+ if (typeof s !== 'string' || s === '') { return s; }
1243
+ // allow using formatFloat without a table; defaults to US number format
1244
+ var i,
1245
+ t = table && table.config ? table.config.usNumberFormat !== false :
1246
+ typeof table !== "undefined" ? table : true;
1247
+ if (t) {
1248
+ // US Format - 1,234,567.89 -> 1234567.89
1249
+ s = s.replace(/,/g,'');
1250
+ } else {
1251
+ // German Format = 1.234.567,89 -> 1234567.89
1252
+ // French Format = 1 234 567,89 -> 1234567.89
1253
+ s = s.replace(/[\s|\.]/g,'').replace(/,/g,'.');
1254
+ }
1255
+ if(/^\s*\([.\d]+\)/.test(s)) {
1256
+ // make (#) into a negative number -> (10) = -10
1257
+ s = s.replace(/^\s*\(/,'-').replace(/\)/,'');
1258
+ }
1259
+ i = parseFloat(s);
1260
+ // return the text instead of zero
1261
+ return isNaN(i) ? $.trim(s) : i;
1262
+ };
1263
+
1264
+ ts.isDigit = function(s) {
1265
+ // replace all unwanted chars and match
1266
+ return isNaN(s) ? (/^[\-+(]?\d+[)]?$/).test(s.toString().replace(/[,.'"\s]/g, '')) : true;
1267
+ };
1268
+
1269
+ }()
1270
+ });
1271
+
1272
+ // make shortcut
1273
+ var ts = $.tablesorter;
1274
+
1275
+ // extend plugin scope
1276
+ $.fn.extend({
1277
+ tablesorter: ts.construct
1278
+ });
1279
+
1280
+ // add default parsers
1281
+ ts.addParser({
1282
+ id: "text",
1283
+ is: function() {
1284
+ return true;
1285
+ },
1286
+ format: function(s, table) {
1287
+ var c = table.config;
1288
+ if (s) {
1289
+ s = $.trim( c.ignoreCase ? s.toLocaleLowerCase() : s );
1290
+ s = c.sortLocaleCompare ? ts.replaceAccents(s) : s;
1291
+ }
1292
+ return s;
1293
+ },
1294
+ type: "text"
1295
+ });
1296
+
1297
+ ts.addParser({
1298
+ id: "digit",
1299
+ is: function(s) {
1300
+ return ts.isDigit(s);
1301
+ },
1302
+ format: function(s, table) {
1303
+ var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ""), table);
1304
+ return s && typeof n === 'number' ? n : s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
1305
+ },
1306
+ type: "numeric"
1307
+ });
1308
+
1309
+ ts.addParser({
1310
+ id: "currency",
1311
+ is: function(s) {
1312
+ return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[,. ]/g,'')); // £$€¤¥¢
1313
+ },
1314
+ format: function(s, table) {
1315
+ var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ""), table);
1316
+ return s && typeof n === 'number' ? n : s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
1317
+ },
1318
+ type: "numeric"
1319
+ });
1320
+
1321
+ ts.addParser({
1322
+ id: "ipAddress",
1323
+ is: function(s) {
1324
+ return (/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/).test(s);
1325
+ },
1326
+ format: function(s, table) {
1327
+ var i, a = s ? s.split(".") : '',
1328
+ r = "",
1329
+ l = a.length;
1330
+ for (i = 0; i < l; i++) {
1331
+ r += ("00" + a[i]).slice(-3);
1332
+ }
1333
+ return s ? ts.formatFloat(r, table) : s;
1334
+ },
1335
+ type: "numeric"
1336
+ });
1337
+
1338
+ ts.addParser({
1339
+ id: "url",
1340
+ is: function(s) {
1341
+ return (/^(https?|ftp|file):\/\//).test(s);
1342
+ },
1343
+ format: function(s) {
1344
+ return s ? $.trim(s.replace(/(https?|ftp|file):\/\//, '')) : s;
1345
+ },
1346
+ type: "text"
1347
+ });
1348
+
1349
+ ts.addParser({
1350
+ id: "isoDate",
1351
+ is: function(s) {
1352
+ return (/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/).test(s);
1353
+ },
1354
+ format: function(s, table) {
1355
+ return s ? ts.formatFloat((s !== "") ? (new Date(s.replace(/-/g, "/")).getTime() || "") : "", table) : s;
1356
+ },
1357
+ type: "numeric"
1358
+ });
1359
+
1360
+ ts.addParser({
1361
+ id: "percent",
1362
+ is: function(s) {
1363
+ return (/(\d\s*?%|%\s*?\d)/).test(s) && s.length < 15;
1364
+ },
1365
+ format: function(s, table) {
1366
+ return s ? ts.formatFloat(s.replace(/%/g, ""), table) : s;
1367
+ },
1368
+ type: "numeric"
1369
+ });
1370
+
1371
+ ts.addParser({
1372
+ id: "usLongDate",
1373
+ is: function(s) {
1374
+ // two digit years are not allowed cross-browser
1375
+ // Jan 01, 2013 12:34:56 PM or 01 Jan 2013
1376
+ return (/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i).test(s) || (/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i).test(s);
1377
+ },
1378
+ format: function(s, table) {
1379
+ return s ? ts.formatFloat( (new Date(s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ''), table) : s;
1380
+ },
1381
+ type: "numeric"
1382
+ });
1383
+
1384
+ ts.addParser({
1385
+ id: "shortDate", // "mmddyyyy", "ddmmyyyy" or "yyyymmdd"
1386
+ is: function(s) {
1387
+ // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
1388
+ return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g," ").replace(/[\-.,]/g, "/"));
1389
+ },
1390
+ format: function(s, table, cell, cellIndex) {
1391
+ if (s) {
1392
+ var c = table.config, ci = c.headerList[cellIndex],
1393
+ format = ci.dateFormat || ts.getData( ci, c.headers[cellIndex], 'dateFormat') || c.dateFormat;
1394
+ s = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/"); // escaped - because JSHint in Firefox was showing it as an error
1395
+ if (format === "mmddyyyy") {
1396
+ s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2");
1397
+ } else if (format === "ddmmyyyy") {
1398
+ s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$2/$1");
1399
+ } else if (format === "yyyymmdd") {
1400
+ s = s.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$1/$2/$3");
1401
+ }
1402
+ }
1403
+ return s ? ts.formatFloat( (new Date(s).getTime() || ''), table) : s;
1404
+ },
1405
+ type: "numeric"
1406
+ });
1407
+
1408
+ ts.addParser({
1409
+ id: "time",
1410
+ is: function(s) {
1411
+ return (/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s);
1412
+ },
1413
+ format: function(s, table) {
1414
+ return s ? ts.formatFloat( (new Date("2000/01/01 " + s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ""), table) : s;
1415
+ },
1416
+ type: "numeric"
1417
+ });
1418
+
1419
+ ts.addParser({
1420
+ id: "metadata",
1421
+ is: function() {
1422
+ return false;
1423
+ },
1424
+ format: function(s, table, cell) {
1425
+ var c = table.config,
1426
+ p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
1427
+ return $(cell).metadata()[p];
1428
+ },
1429
+ type: "numeric"
1430
+ });
1431
+
1432
+ // add default widgets
1433
+ ts.addWidget({
1434
+ id: "zebra",
1435
+ priority: 90,
1436
+ format: function(table, c, wo) {
1437
+ var $tb, $tv, $tr, row, even, time, k, l,
1438
+ child = new RegExp(c.cssChildRow, 'i'),
1439
+ b = c.$tbodies;
1440
+ if (c.debug) {
1441
+ time = new Date();
1442
+ }
1443
+ for (k = 0; k < b.length; k++ ) {
1444
+ // loop through the visible rows
1445
+ $tb = b.eq(k);
1446
+ l = $tb.children('tr').length;
1447
+ if (l > 1) {
1448
+ row = 0;
1449
+ $tv = $tb.children('tr:visible');
1450
+ // revered back to using jQuery each - strangely it's the fastest method
1451
+ /*jshint loopfunc:true */
1452
+ $tv.each(function(){
1453
+ $tr = $(this);
1454
+ // style children rows the same way the parent row was styled
1455
+ if (!child.test(this.className)) { row++; }
1456
+ even = (row % 2 === 0);
1457
+ $tr.removeClass(wo.zebra[even ? 1 : 0]).addClass(wo.zebra[even ? 0 : 1]);
1458
+ });
1459
+ }
1460
+ }
1461
+ if (c.debug) {
1462
+ ts.benchmark("Applying Zebra widget", time);
1463
+ }
1464
+ },
1465
+ remove: function(table, c, wo){
1466
+ var k, $tb,
1467
+ b = c.$tbodies,
1468
+ rmv = (wo.zebra || [ "even", "odd" ]).join(' ');
1469
+ for (k = 0; k < b.length; k++ ){
1470
+ $tb = $.tablesorter.processTbody(table, b.eq(k), true); // remove tbody
1471
+ $tb.children().removeClass(rmv);
1472
+ $.tablesorter.processTbody(table, $tb, false); // restore tbody
1473
+ }
1474
+ }
1475
+ });
1476
+
1477
+ })(jQuery);