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