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