|
1 /* |
|
2 * |
|
3 * TableSorter 2.0 - Client-side table sorting with ease! |
|
4 * Version 2.0.5b |
|
5 * @requires jQuery v1.2.3 |
|
6 * |
|
7 * Copyright (c) 2007 Christian Bach |
|
8 * Examples and docs at: http://tablesorter.com |
|
9 * Dual licensed under the MIT and GPL licenses: |
|
10 * http://www.opensource.org/licenses/mit-license.php |
|
11 * http://www.gnu.org/licenses/gpl.html |
|
12 * |
|
13 */ |
|
14 /** |
|
15 * |
|
16 * @description Create a sortable table with multi-column sorting capabilitys |
|
17 * |
|
18 * @example $('table').tablesorter(); |
|
19 * @desc Create a simple tablesorter interface. |
|
20 * |
|
21 * @example $('table').tablesorter({ sortList:[[0,0],[1,0]] }); |
|
22 * @desc Create a tablesorter interface and sort on the first and secound column column headers. |
|
23 * |
|
24 * @example $('table').tablesorter({ headers: { 0: { sorter: false}, 1: {sorter: false} } }); |
|
25 * |
|
26 * @desc Create a tablesorter interface and disableing the first and second column headers. |
|
27 * |
|
28 * |
|
29 * @example $('table').tablesorter({ headers: { 0: {sorter:"integer"}, 1: {sorter:"currency"} } }); |
|
30 * |
|
31 * @desc Create a tablesorter interface and set a column parser for the first |
|
32 * and second column. |
|
33 * |
|
34 * |
|
35 * @param Object |
|
36 * settings An object literal containing key/value pairs to provide |
|
37 * optional settings. |
|
38 * |
|
39 * |
|
40 * @option String cssHeader (optional) A string of the class name to be appended |
|
41 * to sortable tr elements in the thead of the table. Default value: |
|
42 * "header" |
|
43 * |
|
44 * @option String cssAsc (optional) A string of the class name to be appended to |
|
45 * sortable tr elements in the thead on a ascending sort. Default value: |
|
46 * "headerSortUp" |
|
47 * |
|
48 * @option String cssDesc (optional) A string of the class name to be appended |
|
49 * to sortable tr elements in the thead on a descending sort. Default |
|
50 * value: "headerSortDown" |
|
51 * |
|
52 * @option String sortInitialOrder (optional) A string of the inital sorting |
|
53 * order can be asc or desc. Default value: "asc" |
|
54 * |
|
55 * @option String sortMultisortKey (optional) A string of the multi-column sort |
|
56 * key. Default value: "shiftKey" |
|
57 * |
|
58 * @option String textExtraction (optional) A string of the text-extraction |
|
59 * method to use. For complex html structures inside td cell set this |
|
60 * option to "complex", on large tables the complex option can be slow. |
|
61 * Default value: "simple" |
|
62 * |
|
63 * @option Object headers (optional) An array containing the forces sorting |
|
64 * rules. This option let's you specify a default sorting rule. Default |
|
65 * value: null |
|
66 * |
|
67 * @option Array sortList (optional) An array containing the forces sorting |
|
68 * rules. This option let's you specify a default sorting rule. Default |
|
69 * value: null |
|
70 * |
|
71 * @option Array sortForce (optional) An array containing forced sorting rules. |
|
72 * This option let's you specify a default sorting rule, which is |
|
73 * prepended to user-selected rules. Default value: null |
|
74 * |
|
75 * @option Boolean sortLocaleCompare (optional) Boolean flag indicating whatever |
|
76 * to use String.localeCampare method or not. Default set to true. |
|
77 * |
|
78 * |
|
79 * @option Array sortAppend (optional) An array containing forced sorting rules. |
|
80 * This option let's you specify a default sorting rule, which is |
|
81 * appended to user-selected rules. Default value: null |
|
82 * |
|
83 * @option Boolean widthFixed (optional) Boolean flag indicating if tablesorter |
|
84 * should apply fixed widths to the table columns. This is usefull when |
|
85 * using the pager companion plugin. This options requires the dimension |
|
86 * jquery plugin. Default value: false |
|
87 * |
|
88 * @option Boolean cancelSelection (optional) Boolean flag indicating if |
|
89 * tablesorter should cancel selection of the table headers text. |
|
90 * Default value: true |
|
91 * |
|
92 * @option Boolean debug (optional) Boolean flag indicating if tablesorter |
|
93 * should display debuging information usefull for development. |
|
94 * |
|
95 * @type jQuery |
|
96 * |
|
97 * @name tablesorter |
|
98 * |
|
99 * @cat Plugins/Tablesorter |
|
100 * |
|
101 * @author Christian Bach/christian.bach@polyester.se |
|
102 */ |
|
103 |
|
104 (function ($) { |
|
105 $.extend({ |
|
106 tablesorter: new |
|
107 function () { |
|
108 |
|
109 var parsers = [], |
|
110 widgets = []; |
|
111 |
|
112 this.defaults = { |
|
113 cssHeader: "header", |
|
114 cssAsc: "headerSortUp", |
|
115 cssDesc: "headerSortDown", |
|
116 cssChildRow: "expand-child", |
|
117 sortInitialOrder: "asc", |
|
118 sortMultiSortKey: "shiftKey", |
|
119 sortForce: null, |
|
120 sortAppend: null, |
|
121 sortLocaleCompare: true, |
|
122 textExtraction: "simple", |
|
123 parsers: {}, widgets: [], |
|
124 widgetZebra: { |
|
125 css: ["even", "odd"] |
|
126 }, headers: {}, widthFixed: false, |
|
127 cancelSelection: true, |
|
128 sortList: [], |
|
129 headerList: [], |
|
130 dateFormat: "us", |
|
131 decimal: '/\.|\,/g', |
|
132 onRenderHeader: null, |
|
133 selectorHeaders: 'thead th', |
|
134 debug: false |
|
135 }; |
|
136 |
|
137 /* debuging utils */ |
|
138 |
|
139 function benchmark(s, d) { |
|
140 log(s + "," + (new Date().getTime() - d.getTime()) + "ms"); |
|
141 } |
|
142 |
|
143 this.benchmark = benchmark; |
|
144 |
|
145 function log(s) { |
|
146 if (typeof console != "undefined" && typeof console.debug != "undefined") { |
|
147 console.log(s); |
|
148 } else { |
|
149 alert(s); |
|
150 } |
|
151 } |
|
152 |
|
153 /* parsers utils */ |
|
154 |
|
155 function buildParserCache(table, $headers) { |
|
156 |
|
157 if (table.config.debug) { |
|
158 var parsersDebug = ""; |
|
159 } |
|
160 |
|
161 if (table.tBodies.length == 0) return; // In the case of empty tables |
|
162 var rows = table.tBodies[0].rows; |
|
163 |
|
164 if (rows[0]) { |
|
165 |
|
166 var list = [], |
|
167 cells = rows[0].cells, |
|
168 l = cells.length; |
|
169 |
|
170 for (var i = 0; i < l; i++) { |
|
171 |
|
172 var p = false; |
|
173 |
|
174 if ($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)) { |
|
175 |
|
176 p = getParserById($($headers[i]).metadata().sorter); |
|
177 |
|
178 } else if ((table.config.headers[i] && table.config.headers[i].sorter)) { |
|
179 |
|
180 p = getParserById(table.config.headers[i].sorter); |
|
181 } |
|
182 if (!p) { |
|
183 |
|
184 p = detectParserForColumn(table, rows, -1, i); |
|
185 } |
|
186 |
|
187 if (table.config.debug) { |
|
188 parsersDebug += "column:" + i + " parser:" + p.id + "\n"; |
|
189 } |
|
190 |
|
191 list.push(p); |
|
192 } |
|
193 } |
|
194 |
|
195 if (table.config.debug) { |
|
196 log(parsersDebug); |
|
197 } |
|
198 |
|
199 return list; |
|
200 }; |
|
201 |
|
202 function detectParserForColumn(table, rows, rowIndex, cellIndex) { |
|
203 var l = parsers.length, |
|
204 node = false, |
|
205 nodeValue = false, |
|
206 keepLooking = true; |
|
207 while (nodeValue == '' && keepLooking) { |
|
208 rowIndex++; |
|
209 if (rows[rowIndex]) { |
|
210 node = getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex); |
|
211 nodeValue = trimAndGetNodeText(table.config, node); |
|
212 if (table.config.debug) { |
|
213 log('Checking if value was empty on row:' + rowIndex); |
|
214 } |
|
215 } else { |
|
216 keepLooking = false; |
|
217 } |
|
218 } |
|
219 for (var i = 1; i < l; i++) { |
|
220 if (parsers[i].is(nodeValue, table, node)) { |
|
221 return parsers[i]; |
|
222 } |
|
223 } |
|
224 // 0 is always the generic parser (text) |
|
225 return parsers[0]; |
|
226 } |
|
227 |
|
228 function getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex) { |
|
229 return rows[rowIndex].cells[cellIndex]; |
|
230 } |
|
231 |
|
232 function trimAndGetNodeText(config, node) { |
|
233 return $.trim(getElementText(config, node)); |
|
234 } |
|
235 |
|
236 function getParserById(name) { |
|
237 var l = parsers.length; |
|
238 for (var i = 0; i < l; i++) { |
|
239 if (parsers[i].id.toLowerCase() == name.toLowerCase()) { |
|
240 return parsers[i]; |
|
241 } |
|
242 } |
|
243 return false; |
|
244 } |
|
245 |
|
246 /* utils */ |
|
247 |
|
248 function buildCache(table) { |
|
249 |
|
250 if (table.config.debug) { |
|
251 var cacheTime = new Date(); |
|
252 } |
|
253 |
|
254 var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0, |
|
255 totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0, |
|
256 parsers = table.config.parsers, |
|
257 cache = { |
|
258 row: [], |
|
259 normalized: [] |
|
260 }; |
|
261 |
|
262 for (var i = 0; i < totalRows; ++i) { |
|
263 |
|
264 /** Add the table data to main data array */ |
|
265 var c = $(table.tBodies[0].rows[i]), |
|
266 cols = []; |
|
267 |
|
268 // if this is a child row, add it to the last row's children and |
|
269 // continue to the next row |
|
270 if (c.hasClass(table.config.cssChildRow)) { |
|
271 cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c); |
|
272 // go to the next for loop |
|
273 continue; |
|
274 } |
|
275 |
|
276 cache.row.push(c); |
|
277 |
|
278 for (var j = 0; j < totalCells; ++j) { |
|
279 cols.push(parsers[j].format(getElementText(table.config, c[0].cells[j]), table, c[0].cells[j])); |
|
280 } |
|
281 |
|
282 cols.push(cache.normalized.length); // add position for rowCache |
|
283 cache.normalized.push(cols); |
|
284 cols = null; |
|
285 }; |
|
286 |
|
287 if (table.config.debug) { |
|
288 benchmark("Building cache for " + totalRows + " rows:", cacheTime); |
|
289 } |
|
290 |
|
291 return cache; |
|
292 }; |
|
293 |
|
294 function getElementText(config, node) { |
|
295 |
|
296 var text = ""; |
|
297 |
|
298 if (!node) return ""; |
|
299 |
|
300 if (!config.supportsTextContent) config.supportsTextContent = node.textContent || false; |
|
301 |
|
302 if (config.textExtraction == "simple") { |
|
303 if (config.supportsTextContent) { |
|
304 text = node.textContent; |
|
305 } else { |
|
306 if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) { |
|
307 text = node.childNodes[0].innerHTML; |
|
308 } else { |
|
309 text = node.innerHTML; |
|
310 } |
|
311 } |
|
312 } else { |
|
313 if (typeof(config.textExtraction) == "function") { |
|
314 text = config.textExtraction(node); |
|
315 } else { |
|
316 text = $(node).text(); |
|
317 } |
|
318 } |
|
319 return text; |
|
320 } |
|
321 |
|
322 function appendToTable(table, cache) { |
|
323 |
|
324 if (table.config.debug) { |
|
325 var appendTime = new Date() |
|
326 } |
|
327 |
|
328 var c = cache, |
|
329 r = c.row, |
|
330 n = c.normalized, |
|
331 totalRows = n.length, |
|
332 checkCell = (n[0].length - 1), |
|
333 tableBody = $(table.tBodies[0]), |
|
334 rows = []; |
|
335 |
|
336 |
|
337 for (var i = 0; i < totalRows; i++) { |
|
338 var pos = n[i][checkCell]; |
|
339 |
|
340 rows.push(r[pos]); |
|
341 |
|
342 if (!table.config.appender) { |
|
343 |
|
344 //var o = ; |
|
345 var l = r[pos].length; |
|
346 for (var j = 0; j < l; j++) { |
|
347 tableBody[0].appendChild(r[pos][j]); |
|
348 } |
|
349 |
|
350 // |
|
351 } |
|
352 } |
|
353 |
|
354 |
|
355 |
|
356 if (table.config.appender) { |
|
357 |
|
358 table.config.appender(table, rows); |
|
359 } |
|
360 |
|
361 rows = null; |
|
362 |
|
363 if (table.config.debug) { |
|
364 benchmark("Rebuilt table:", appendTime); |
|
365 } |
|
366 |
|
367 // apply table widgets |
|
368 applyWidget(table); |
|
369 |
|
370 // trigger sortend |
|
371 setTimeout(function () { |
|
372 $(table).trigger("sortEnd"); |
|
373 }, 0); |
|
374 |
|
375 }; |
|
376 |
|
377 function buildHeaders(table) { |
|
378 |
|
379 if (table.config.debug) { |
|
380 var time = new Date(); |
|
381 } |
|
382 |
|
383 var meta = ($.metadata) ? true : false; |
|
384 |
|
385 var header_index = computeTableHeaderCellIndexes(table); |
|
386 |
|
387 $tableHeaders = $(table.config.selectorHeaders, table).each(function (index) { |
|
388 |
|
389 this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex]; |
|
390 // this.column = index; |
|
391 this.order = formatSortingOrder(table.config.sortInitialOrder); |
|
392 |
|
393 |
|
394 this.count = this.order; |
|
395 |
|
396 if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) this.sortDisabled = true; |
|
397 if (checkHeaderOptionsSortingLocked(table, index)) this.order = this.lockedOrder = checkHeaderOptionsSortingLocked(table, index); |
|
398 |
|
399 if (!this.sortDisabled) { |
|
400 var $th = $(this).addClass(table.config.cssHeader); |
|
401 if (table.config.onRenderHeader) table.config.onRenderHeader.apply($th); |
|
402 } |
|
403 |
|
404 // add cell to headerList |
|
405 table.config.headerList[index] = this; |
|
406 }); |
|
407 |
|
408 if (table.config.debug) { |
|
409 benchmark("Built headers:", time); |
|
410 log($tableHeaders); |
|
411 } |
|
412 |
|
413 return $tableHeaders; |
|
414 |
|
415 }; |
|
416 |
|
417 // from: |
|
418 // http://www.javascripttoolbox.com/lib/table/examples.php |
|
419 // http://www.javascripttoolbox.com/temp/table_cellindex.html |
|
420 |
|
421 |
|
422 function computeTableHeaderCellIndexes(t) { |
|
423 var matrix = []; |
|
424 var lookup = {}; |
|
425 var thead = t.getElementsByTagName('THEAD')[0]; |
|
426 var trs = thead.getElementsByTagName('TR'); |
|
427 |
|
428 for (var i = 0; i < trs.length; i++) { |
|
429 var cells = trs[i].cells; |
|
430 for (var j = 0; j < cells.length; j++) { |
|
431 var c = cells[j]; |
|
432 |
|
433 var rowIndex = c.parentNode.rowIndex; |
|
434 var cellId = rowIndex + "-" + c.cellIndex; |
|
435 var rowSpan = c.rowSpan || 1; |
|
436 var colSpan = c.colSpan || 1 |
|
437 var firstAvailCol; |
|
438 if (typeof(matrix[rowIndex]) == "undefined") { |
|
439 matrix[rowIndex] = []; |
|
440 } |
|
441 // Find first available column in the first row |
|
442 for (var k = 0; k < matrix[rowIndex].length + 1; k++) { |
|
443 if (typeof(matrix[rowIndex][k]) == "undefined") { |
|
444 firstAvailCol = k; |
|
445 break; |
|
446 } |
|
447 } |
|
448 lookup[cellId] = firstAvailCol; |
|
449 for (var k = rowIndex; k < rowIndex + rowSpan; k++) { |
|
450 if (typeof(matrix[k]) == "undefined") { |
|
451 matrix[k] = []; |
|
452 } |
|
453 var matrixrow = matrix[k]; |
|
454 for (var l = firstAvailCol; l < firstAvailCol + colSpan; l++) { |
|
455 matrixrow[l] = "x"; |
|
456 } |
|
457 } |
|
458 } |
|
459 } |
|
460 return lookup; |
|
461 } |
|
462 |
|
463 function checkCellColSpan(table, rows, row) { |
|
464 var arr = [], |
|
465 r = table.tHead.rows, |
|
466 c = r[row].cells; |
|
467 |
|
468 for (var i = 0; i < c.length; i++) { |
|
469 var cell = c[i]; |
|
470 |
|
471 if (cell.colSpan > 1) { |
|
472 arr = arr.concat(checkCellColSpan(table, headerArr, row++)); |
|
473 } else { |
|
474 if (table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row + 1])) { |
|
475 arr.push(cell); |
|
476 } |
|
477 // headerArr[row] = (i+row); |
|
478 } |
|
479 } |
|
480 return arr; |
|
481 }; |
|
482 |
|
483 function checkHeaderMetadata(cell) { |
|
484 if (($.metadata) && ($(cell).metadata().sorter === false)) { |
|
485 return true; |
|
486 }; |
|
487 return false; |
|
488 } |
|
489 |
|
490 function checkHeaderOptions(table, i) { |
|
491 if ((table.config.headers[i]) && (table.config.headers[i].sorter === false)) { |
|
492 return true; |
|
493 }; |
|
494 return false; |
|
495 } |
|
496 |
|
497 function checkHeaderOptionsSortingLocked(table, i) { |
|
498 if ((table.config.headers[i]) && (table.config.headers[i].lockedOrder)) return table.config.headers[i].lockedOrder; |
|
499 return false; |
|
500 } |
|
501 |
|
502 function applyWidget(table) { |
|
503 var c = table.config.widgets; |
|
504 var l = c.length; |
|
505 for (var i = 0; i < l; i++) { |
|
506 |
|
507 getWidgetById(c[i]).format(table); |
|
508 } |
|
509 |
|
510 } |
|
511 |
|
512 function getWidgetById(name) { |
|
513 var l = widgets.length; |
|
514 for (var i = 0; i < l; i++) { |
|
515 if (widgets[i].id.toLowerCase() == name.toLowerCase()) { |
|
516 return widgets[i]; |
|
517 } |
|
518 } |
|
519 }; |
|
520 |
|
521 function formatSortingOrder(v) { |
|
522 if (typeof(v) != "Number") { |
|
523 return (v.toLowerCase() == "desc") ? 1 : 0; |
|
524 } else { |
|
525 return (v == 1) ? 1 : 0; |
|
526 } |
|
527 } |
|
528 |
|
529 function isValueInArray(v, a) { |
|
530 var l = a.length; |
|
531 for (var i = 0; i < l; i++) { |
|
532 if (a[i][0] == v) { |
|
533 return true; |
|
534 } |
|
535 } |
|
536 return false; |
|
537 } |
|
538 |
|
539 function setHeadersCss(table, $headers, list, css) { |
|
540 // remove all header information |
|
541 $headers.removeClass(css[0]).removeClass(css[1]); |
|
542 |
|
543 var h = []; |
|
544 $headers.each(function (offset) { |
|
545 if (!this.sortDisabled) { |
|
546 h[this.column] = $(this); |
|
547 } |
|
548 }); |
|
549 |
|
550 var l = list.length; |
|
551 for (var i = 0; i < l; i++) { |
|
552 h[list[i][0]].addClass(css[list[i][1]]); |
|
553 } |
|
554 } |
|
555 |
|
556 function fixColumnWidth(table, $headers) { |
|
557 var c = table.config; |
|
558 if (c.widthFixed) { |
|
559 var colgroup = $('<colgroup>'); |
|
560 $("tr:first td", table.tBodies[0]).each(function () { |
|
561 colgroup.append($('<col>').css('width', $(this).width())); |
|
562 }); |
|
563 $(table).prepend(colgroup); |
|
564 }; |
|
565 } |
|
566 |
|
567 function updateHeaderSortCount(table, sortList) { |
|
568 var c = table.config, |
|
569 l = sortList.length; |
|
570 for (var i = 0; i < l; i++) { |
|
571 var s = sortList[i], |
|
572 o = c.headerList[s[0]]; |
|
573 o.count = s[1]; |
|
574 o.count++; |
|
575 } |
|
576 } |
|
577 |
|
578 /* sorting methods */ |
|
579 |
|
580 function multisort(table, sortList, cache) { |
|
581 |
|
582 if (table.config.debug) { |
|
583 var sortTime = new Date(); |
|
584 } |
|
585 |
|
586 var dynamicExp = "var sortWrapper = function(a,b) {", |
|
587 l = sortList.length; |
|
588 |
|
589 // TODO: inline functions. |
|
590 for (var i = 0; i < l; i++) { |
|
591 |
|
592 var c = sortList[i][0]; |
|
593 var order = sortList[i][1]; |
|
594 // var s = (getCachedSortType(table.config.parsers,c) == "text") ? |
|
595 // ((order == 0) ? "sortText" : "sortTextDesc") : ((order == 0) ? |
|
596 // "sortNumeric" : "sortNumericDesc"); |
|
597 // var s = (table.config.parsers[c].type == "text") ? ((order == 0) |
|
598 // ? makeSortText(c) : makeSortTextDesc(c)) : ((order == 0) ? |
|
599 // makeSortNumeric(c) : makeSortNumericDesc(c)); |
|
600 var s = (table.config.parsers[c].type == "text") ? ((order == 0) ? makeSortFunction("text", "asc", c) : makeSortFunction("text", "desc", c)) : ((order == 0) ? makeSortFunction("numeric", "asc", c) : makeSortFunction("numeric", "desc", c)); |
|
601 var e = "e" + i; |
|
602 |
|
603 dynamicExp += "var " + e + " = " + s; // + "(a[" + c + "],b[" + c |
|
604 // + "]); "; |
|
605 dynamicExp += "if(" + e + ") { return " + e + "; } "; |
|
606 dynamicExp += "else { "; |
|
607 |
|
608 } |
|
609 |
|
610 // if value is the same keep orignal order |
|
611 var orgOrderCol = cache.normalized[0].length - 1; |
|
612 dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];"; |
|
613 |
|
614 for (var i = 0; i < l; i++) { |
|
615 dynamicExp += "}; "; |
|
616 } |
|
617 |
|
618 dynamicExp += "return 0; "; |
|
619 dynamicExp += "}; "; |
|
620 |
|
621 if (table.config.debug) { |
|
622 benchmark("Evaling expression:" + dynamicExp, new Date()); |
|
623 } |
|
624 |
|
625 eval(dynamicExp); |
|
626 |
|
627 cache.normalized.sort(sortWrapper); |
|
628 |
|
629 if (table.config.debug) { |
|
630 benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time:", sortTime); |
|
631 } |
|
632 |
|
633 return cache; |
|
634 }; |
|
635 |
|
636 function makeSortFunction(type, direction, index) { |
|
637 var a = "a[" + index + "]", |
|
638 b = "b[" + index + "]"; |
|
639 if (type == 'text' && direction == 'asc') { |
|
640 return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + a + " < " + b + ") ? -1 : 1 )));"; |
|
641 } else if (type == 'text' && direction == 'desc') { |
|
642 return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + b + " < " + a + ") ? -1 : 1 )));"; |
|
643 } else if (type == 'numeric' && direction == 'asc') { |
|
644 return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + a + " - " + b + "));"; |
|
645 } else if (type == 'numeric' && direction == 'desc') { |
|
646 return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + b + " - " + a + "));"; |
|
647 } |
|
648 }; |
|
649 |
|
650 function makeSortText(i) { |
|
651 return "((a[" + i + "] < b[" + i + "]) ? -1 : ((a[" + i + "] > b[" + i + "]) ? 1 : 0));"; |
|
652 }; |
|
653 |
|
654 function makeSortTextDesc(i) { |
|
655 return "((b[" + i + "] < a[" + i + "]) ? -1 : ((b[" + i + "] > a[" + i + "]) ? 1 : 0));"; |
|
656 }; |
|
657 |
|
658 function makeSortNumeric(i) { |
|
659 return "a[" + i + "]-b[" + i + "];"; |
|
660 }; |
|
661 |
|
662 function makeSortNumericDesc(i) { |
|
663 return "b[" + i + "]-a[" + i + "];"; |
|
664 }; |
|
665 |
|
666 function sortText(a, b) { |
|
667 if (table.config.sortLocaleCompare) return a.localeCompare(b); |
|
668 return ((a < b) ? -1 : ((a > b) ? 1 : 0)); |
|
669 }; |
|
670 |
|
671 function sortTextDesc(a, b) { |
|
672 if (table.config.sortLocaleCompare) return b.localeCompare(a); |
|
673 return ((b < a) ? -1 : ((b > a) ? 1 : 0)); |
|
674 }; |
|
675 |
|
676 function sortNumeric(a, b) { |
|
677 return a - b; |
|
678 }; |
|
679 |
|
680 function sortNumericDesc(a, b) { |
|
681 return b - a; |
|
682 }; |
|
683 |
|
684 function getCachedSortType(parsers, i) { |
|
685 return parsers[i].type; |
|
686 }; /* public methods */ |
|
687 this.construct = function (settings) { |
|
688 return this.each(function () { |
|
689 // if no thead or tbody quit. |
|
690 if (!this.tHead || !this.tBodies) return; |
|
691 // declare |
|
692 var $this, $document, $headers, cache, config, shiftDown = 0, |
|
693 sortOrder; |
|
694 // new blank config object |
|
695 this.config = {}; |
|
696 // merge and extend. |
|
697 config = $.extend(this.config, $.tablesorter.defaults, settings); |
|
698 // store common expression for speed |
|
699 $this = $(this); |
|
700 // save the settings where they read |
|
701 $.data(this, "tablesorter", config); |
|
702 // build headers |
|
703 $headers = buildHeaders(this); |
|
704 // try to auto detect column type, and store in tables config |
|
705 this.config.parsers = buildParserCache(this, $headers); |
|
706 // build the cache for the tbody cells |
|
707 cache = buildCache(this); |
|
708 // get the css class names, could be done else where. |
|
709 var sortCSS = [config.cssDesc, config.cssAsc]; |
|
710 // fixate columns if the users supplies the fixedWidth option |
|
711 fixColumnWidth(this); |
|
712 // apply event handling to headers |
|
713 // this is to big, perhaps break it out? |
|
714 $headers.click( |
|
715 |
|
716 function (e) { |
|
717 var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0; |
|
718 if (!this.sortDisabled && totalRows > 0) { |
|
719 // Only call sortStart if sorting is |
|
720 // enabled. |
|
721 $this.trigger("sortStart"); |
|
722 // store exp, for speed |
|
723 var $cell = $(this); |
|
724 // get current column index |
|
725 var i = this.column; |
|
726 // get current column sort order |
|
727 this.order = this.count++ % 2; |
|
728 // always sort on the locked order. |
|
729 if(this.lockedOrder) this.order = this.lockedOrder; |
|
730 |
|
731 // user only whants to sort on one |
|
732 // column |
|
733 if (!e[config.sortMultiSortKey]) { |
|
734 // flush the sort list |
|
735 config.sortList = []; |
|
736 if (config.sortForce != null) { |
|
737 var a = config.sortForce; |
|
738 for (var j = 0; j < a.length; j++) { |
|
739 if (a[j][0] != i) { |
|
740 config.sortList.push(a[j]); |
|
741 } |
|
742 } |
|
743 } |
|
744 // add column to sort list |
|
745 config.sortList.push([i, this.order]); |
|
746 // multi column sorting |
|
747 } else { |
|
748 // the user has clicked on an all |
|
749 // ready sortet column. |
|
750 if (isValueInArray(i, config.sortList)) { |
|
751 // revers the sorting direction |
|
752 // for all tables. |
|
753 for (var j = 0; j < config.sortList.length; j++) { |
|
754 var s = config.sortList[j], |
|
755 o = config.headerList[s[0]]; |
|
756 if (s[0] == i) { |
|
757 o.count = s[1]; |
|
758 o.count++; |
|
759 s[1] = o.count % 2; |
|
760 } |
|
761 } |
|
762 } else { |
|
763 // add column to sort list array |
|
764 config.sortList.push([i, this.order]); |
|
765 } |
|
766 }; |
|
767 setTimeout(function () { |
|
768 // set css for headers |
|
769 setHeadersCss($this[0], $headers, config.sortList, sortCSS); |
|
770 appendToTable( |
|
771 $this[0], multisort( |
|
772 $this[0], config.sortList, cache) |
|
773 ); |
|
774 }, 1); |
|
775 // stop normal event by returning false |
|
776 return false; |
|
777 } |
|
778 // cancel selection |
|
779 }).mousedown(function () { |
|
780 if (config.cancelSelection) { |
|
781 this.onselectstart = function () { |
|
782 return false |
|
783 }; |
|
784 return false; |
|
785 } |
|
786 }); |
|
787 // apply easy methods that trigger binded events |
|
788 $this.bind("update", function () { |
|
789 var me = this; |
|
790 setTimeout(function () { |
|
791 // rebuild parsers. |
|
792 me.config.parsers = buildParserCache( |
|
793 me, $headers); |
|
794 // rebuild the cache map |
|
795 cache = buildCache(me); |
|
796 }, 1); |
|
797 }).bind("updateCell", function (e, cell) { |
|
798 var config = this.config; |
|
799 // get position from the dom. |
|
800 var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex]; |
|
801 // update cache |
|
802 cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format( |
|
803 getElementText(config, cell), cell); |
|
804 }).bind("sorton", function (e, list) { |
|
805 $(this).trigger("sortStart"); |
|
806 config.sortList = list; |
|
807 // update and store the sortlist |
|
808 var sortList = config.sortList; |
|
809 // update header count index |
|
810 updateHeaderSortCount(this, sortList); |
|
811 // set css for headers |
|
812 setHeadersCss(this, $headers, sortList, sortCSS); |
|
813 // sort the table and append it to the dom |
|
814 appendToTable(this, multisort(this, sortList, cache)); |
|
815 }).bind("appendCache", function () { |
|
816 appendToTable(this, cache); |
|
817 }).bind("applyWidgetId", function (e, id) { |
|
818 getWidgetById(id).format(this); |
|
819 }).bind("applyWidgets", function () { |
|
820 // apply widgets |
|
821 applyWidget(this); |
|
822 }); |
|
823 if ($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) { |
|
824 config.sortList = $(this).metadata().sortlist; |
|
825 } |
|
826 // if user has supplied a sort list to constructor. |
|
827 if (config.sortList.length > 0) { |
|
828 $this.trigger("sorton", [config.sortList]); |
|
829 } |
|
830 // apply widgets |
|
831 applyWidget(this); |
|
832 }); |
|
833 }; |
|
834 this.addParser = function (parser) { |
|
835 var l = parsers.length, |
|
836 a = true; |
|
837 for (var i = 0; i < l; i++) { |
|
838 if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) { |
|
839 a = false; |
|
840 } |
|
841 } |
|
842 if (a) { |
|
843 parsers.push(parser); |
|
844 }; |
|
845 }; |
|
846 this.addWidget = function (widget) { |
|
847 widgets.push(widget); |
|
848 }; |
|
849 this.formatFloat = function (s) { |
|
850 var i = parseFloat(s); |
|
851 return (isNaN(i)) ? 0 : i; |
|
852 }; |
|
853 this.formatInt = function (s) { |
|
854 var i = parseInt(s); |
|
855 return (isNaN(i)) ? 0 : i; |
|
856 }; |
|
857 this.isDigit = function (s, config) { |
|
858 // replace all an wanted chars and match. |
|
859 return /^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g, ''))); |
|
860 }; |
|
861 this.clearTableBody = function (table) { |
|
862 if ($.browser.msie) { |
|
863 function empty() { |
|
864 while (this.firstChild) |
|
865 this.removeChild(this.firstChild); |
|
866 } |
|
867 empty.apply(table.tBodies[0]); |
|
868 } else { |
|
869 table.tBodies[0].innerHTML = ""; |
|
870 } |
|
871 }; |
|
872 } |
|
873 }); |
|
874 |
|
875 // extend plugin scope |
|
876 $.fn.extend({ |
|
877 tablesorter: $.tablesorter.construct |
|
878 }); |
|
879 |
|
880 // make shortcut |
|
881 var ts = $.tablesorter; |
|
882 |
|
883 // add default parsers |
|
884 ts.addParser({ |
|
885 id: "text", |
|
886 is: function (s) { |
|
887 return true; |
|
888 }, format: function (s) { |
|
889 return $.trim(s.toLocaleLowerCase()); |
|
890 }, type: "text" |
|
891 }); |
|
892 |
|
893 ts.addParser({ |
|
894 id: "digit", |
|
895 is: function (s, table) { |
|
896 var c = table.config; |
|
897 return $.tablesorter.isDigit(s, c); |
|
898 }, format: function (s) { |
|
899 return $.tablesorter.formatFloat(s); |
|
900 }, type: "numeric" |
|
901 }); |
|
902 |
|
903 ts.addParser({ |
|
904 id: "currency", |
|
905 is: function (s) { |
|
906 return /^[£$€?.]/.test(s); |
|
907 }, format: function (s) { |
|
908 return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g), "")); |
|
909 }, type: "numeric" |
|
910 }); |
|
911 |
|
912 ts.addParser({ |
|
913 id: "ipAddress", |
|
914 is: function (s) { |
|
915 return /^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s); |
|
916 }, format: function (s) { |
|
917 var a = s.split("."), |
|
918 r = "", |
|
919 l = a.length; |
|
920 for (var i = 0; i < l; i++) { |
|
921 var item = a[i]; |
|
922 if (item.length == 2) { |
|
923 r += "0" + item; |
|
924 } else { |
|
925 r += item; |
|
926 } |
|
927 } |
|
928 return $.tablesorter.formatFloat(r); |
|
929 }, type: "numeric" |
|
930 }); |
|
931 |
|
932 ts.addParser({ |
|
933 id: "url", |
|
934 is: function (s) { |
|
935 return /^(https?|ftp|file):\/\/$/.test(s); |
|
936 }, format: function (s) { |
|
937 return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//), '')); |
|
938 }, type: "text" |
|
939 }); |
|
940 |
|
941 ts.addParser({ |
|
942 id: "isoDate", |
|
943 is: function (s) { |
|
944 return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s); |
|
945 }, format: function (s) { |
|
946 return $.tablesorter.formatFloat((s != "") ? new Date(s.replace( |
|
947 new RegExp(/-/g), "/")).getTime() : "0"); |
|
948 }, type: "numeric" |
|
949 }); |
|
950 |
|
951 ts.addParser({ |
|
952 id: "percent", |
|
953 is: function (s) { |
|
954 return /\%$/.test($.trim(s)); |
|
955 }, format: function (s) { |
|
956 return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), "")); |
|
957 }, type: "numeric" |
|
958 }); |
|
959 |
|
960 ts.addParser({ |
|
961 id: "usLongDate", |
|
962 is: function (s) { |
|
963 return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/)); |
|
964 }, format: function (s) { |
|
965 return $.tablesorter.formatFloat(new Date(s).getTime()); |
|
966 }, type: "numeric" |
|
967 }); |
|
968 |
|
969 ts.addParser({ |
|
970 id: "shortDate", |
|
971 is: function (s) { |
|
972 return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s); |
|
973 }, format: function (s, table) { |
|
974 var c = table.config; |
|
975 s = s.replace(/\-/g, "/"); |
|
976 if (c.dateFormat == "us") { |
|
977 // reformat the string in ISO format |
|
978 s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$1/$2"); |
|
979 } else if (c.dateFormat == "uk") { |
|
980 // reformat the string in ISO format |
|
981 s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$2/$1"); |
|
982 } else if (c.dateFormat == "dd/mm/yy" || c.dateFormat == "dd-mm-yy") { |
|
983 s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, "$1/$2/$3"); |
|
984 } |
|
985 return $.tablesorter.formatFloat(new Date(s).getTime()); |
|
986 }, type: "numeric" |
|
987 }); |
|
988 ts.addParser({ |
|
989 id: "time", |
|
990 is: function (s) { |
|
991 return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s); |
|
992 }, format: function (s) { |
|
993 return $.tablesorter.formatFloat(new Date("2000/01/01 " + s).getTime()); |
|
994 }, type: "numeric" |
|
995 }); |
|
996 ts.addParser({ |
|
997 id: "metadata", |
|
998 is: function (s) { |
|
999 return false; |
|
1000 }, format: function (s, table, cell) { |
|
1001 var c = table.config, |
|
1002 p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName; |
|
1003 return $(cell).metadata()[p]; |
|
1004 }, type: "numeric" |
|
1005 }); |
|
1006 // add default widgets |
|
1007 ts.addWidget({ |
|
1008 id: "zebra", |
|
1009 format: function (table) { |
|
1010 if (table.config.debug) { |
|
1011 var time = new Date(); |
|
1012 } |
|
1013 var $tr, row = -1, |
|
1014 odd; |
|
1015 // loop through the visible rows |
|
1016 $("tr:visible", table.tBodies[0]).each(function (i) { |
|
1017 $tr = $(this); |
|
1018 // style children rows the same way the parent |
|
1019 // row was styled |
|
1020 if (!$tr.hasClass(table.config.cssChildRow)) row++; |
|
1021 odd = (row % 2 == 0); |
|
1022 $tr.removeClass( |
|
1023 table.config.widgetZebra.css[odd ? 0 : 1]).addClass( |
|
1024 table.config.widgetZebra.css[odd ? 1 : 0]) |
|
1025 }); |
|
1026 if (table.config.debug) { |
|
1027 $.tablesorter.benchmark("Applying Zebra widget", time); |
|
1028 } |
|
1029 } |
|
1030 }); |
|
1031 })(jQuery); |