final move of files
[web.mtrack] / js / jquery.tablesorter.js
1 /*
2  * 
3  * TableSorter 2.0 - Client-side table sorting with ease!
4  * Version 2.0.3
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 in ascending order.
23  * 
24  * @example $('table').tablesorter({ headers: { 0: { sorter: false}, 1: {sorter: false} } });
25  * @desc Create a tablesorter interface and disableing the first and secound column headers.
26  * 
27  * @example $('table').tablesorter({ 0: {sorter:"integer"}, 1: {sorter:"currency"} });
28  * @desc Create a tablesorter interface and set a column parser for the first and secound column.
29  * 
30  * 
31  * @param Object settings An object literal containing key/value pairs to provide optional settings.
32  * 
33  * @option String cssHeader (optional)                  A string of the class name to be appended to sortable tr elements in the thead of the table. 
34  *                                                                                              Default value: "header"
35  * 
36  * @option String cssAsc (optional)                     A string of the class name to be appended to sortable tr elements in the thead on a ascending sort. 
37  *                                                                                              Default value: "headerSortUp"
38  * 
39  * @option String cssDesc (optional)                    A string of the class name to be appended to sortable tr elements in the thead on a descending sort. 
40  *                                                                                              Default value: "headerSortDown"
41  * 
42  * @option String sortInitialOrder (optional)   A string of the inital sorting order can be asc or desc. 
43  *                                                                                              Default value: "asc"
44  * 
45  * @option String sortMultisortKey (optional)   A string of the multi-column sort key. 
46  *                                                                                              Default value: "shiftKey"
47  * 
48  * @option String textExtraction (optional)     A string of the text-extraction method to use. 
49  *                                                                                              For complex html structures inside td cell set this option to "complex", 
50  *                                                                                              on large tables the complex option can be slow. 
51  *                                                                                              Default value: "simple"
52  * 
53  * @option Object headers (optional)                    An array containing the forces sorting rules. 
54  *                                                                                              This option let's you specify a default sorting rule. 
55  *                                                                                              Default value: null
56  * 
57  * @option Array sortList (optional)                    An array containing the forces sorting rules. 
58  *                                                                                              This option let's you specify a default sorting rule. 
59  *                                                                                              Default value: null
60  * 
61  * @option Array sortForce (optional)                   An array containing forced sorting rules. 
62  *                                                                                              This option let's you specify a default sorting rule, which is prepended to user-selected rules.
63  *                                                                                              Default value: null
64  *  
65   * @option Array sortAppend (optional)                         An array containing forced sorting rules. 
66  *                                                                                              This option let's you specify a default sorting rule, which is appended to user-selected rules.
67  *                                                                                              Default value: null
68  * 
69  * @option Boolean widthFixed (optional)                Boolean flag indicating if tablesorter should apply fixed widths to the table columns.
70  *                                                                                              This is usefull when using the pager companion plugin.
71  *                                                                                              This options requires the dimension jquery plugin.
72  *                                                                                              Default value: false
73  *
74  * @option Boolean cancelSelection (optional)   Boolean flag indicating if tablesorter should cancel selection of the table headers text.
75  *                                                                                              Default value: true
76  *
77  * @option Boolean debug (optional)                     Boolean flag indicating if tablesorter should display debuging information usefull for development.
78  *
79  * @type jQuery
80  *
81  * @name tablesorter
82  * 
83  * @cat Plugins/Tablesorter
84  * 
85  * @author Christian Bach/christian.bach@polyester.se
86  */
87
88 (function($) {
89         $.extend({
90                 tablesorter: new function() {
91                         
92                         var parsers = [], widgets = [];
93                         
94                         this.defaults = {
95                                 cssHeader: "header",
96                                 cssAsc: "headerSortUp",
97                                 cssDesc: "headerSortDown",
98                                 sortInitialOrder: "asc",
99                                 sortMultiSortKey: "shiftKey",
100                                 sortForce: null,
101                                 sortAppend: null,
102                                 textExtraction: "simple",
103                                 parsers: {}, 
104                                 widgets: [],            
105                                 widgetZebra: {css: ["even","odd"]},
106                                 headers: {},
107                                 widthFixed: false,
108                                 cancelSelection: true,
109                                 sortList: [],
110                                 headerList: [],
111                                 dateFormat: "us",
112                                 decimal: '.',
113                                 debug: false
114                         };
115                         
116                         /* debuging utils */
117                         function benchmark(s,d) {
118                                 log(s + "," + (new Date().getTime() - d.getTime()) + "ms");
119                         }
120                         
121                         this.benchmark = benchmark;
122                         
123                         function log(s) {
124                                 if (typeof console != "undefined" && typeof console.debug != "undefined") {
125                                         console.log(s);
126                                 } else {
127                                         alert(s);
128                                 }
129                         }
130                                                 
131                         /* parsers utils */
132                         function buildParserCache(table,$headers) {
133                                 
134                                 if(table.config.debug) { var parsersDebug = ""; }
135                                 
136                                 var rows = table.tBodies[0].rows;
137                                 
138                                 if(table.tBodies[0].rows[0]) {
139
140                                         var list = [], cells = rows[0].cells, l = cells.length;
141                                         
142                                         for (var i=0;i < l; i++) {
143                                                 var p = false;
144                                                 
145                                                 if($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)  ) {
146                                                 
147                                                         p = getParserById($($headers[i]).metadata().sorter);    
148                                                 
149                                                 } else if((table.config.headers[i] && table.config.headers[i].sorter)) {
150         
151                                                         p = getParserById(table.config.headers[i].sorter);
152                                                 }
153                                                 if(!p) {
154                                                         p = detectParserForColumn(table,cells[i]);
155                                                 }
156         
157                                                 if(table.config.debug) { parsersDebug += "column:" + i + " parser:" +p.id + "\n"; }
158         
159                                                 list.push(p);
160                                         }
161                                 }
162                                 
163                                 if(table.config.debug) { log(parsersDebug); }
164
165                                 return list;
166                         };
167                         
168                         function detectParserForColumn(table,node) {
169                                 var l = parsers.length;
170                                 for(var i=1; i < l; i++) {
171                                         if(parsers[i].is($.trim(getElementText(table.config,node)),table,node)) {
172                                                 return parsers[i];
173                                         }
174                                 }
175                                 // 0 is always the generic parser (text)
176                                 return parsers[0];
177                         }
178                         
179                         function getParserById(name) {
180                                 var l = parsers.length;
181                                 for(var i=0; i < l; i++) {
182                                         if(parsers[i].id.toLowerCase() == name.toLowerCase()) { 
183                                                 return parsers[i];
184                                         }
185                                 }
186                                 return false;
187                         }
188                         
189                         /* utils */
190                         function buildCache(table) {
191                                 
192                                 if(table.config.debug) { var cacheTime = new Date(); }
193                                 
194                                 
195                                 var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0,
196                                         totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0,
197                                         parsers = table.config.parsers, 
198                                         cache = {row: [], normalized: []};
199                                 
200                                         for (var i=0;i < totalRows; ++i) {
201                                         
202                                                 /** Add the table data to main data array */
203                                                 var c = table.tBodies[0].rows[i], cols = [];
204                                         
205                                                 cache.row.push($(c));
206                                                 
207                                                 for(var j=0; j < totalCells; ++j) {
208                                                         cols.push(parsers[j].format(getElementText(table.config,c.cells[j]),table,c.cells[j])); 
209                                                 }
210                                                                                                 
211                                                 cols.push(i); // add position for rowCache
212                                                 cache.normalized.push(cols);
213                                                 cols = null;
214                                         };
215                                 
216                                 if(table.config.debug) { benchmark("Building cache for " + totalRows + " rows:", cacheTime); }
217                                 
218                                 return cache;
219                         };
220                         
221                         function getElementText(config,node) {
222                                 
223                                 if(!node) return "";
224                                                                 
225                                 var t = "";
226                                 
227                                 if(config.textExtraction == "simple") {
228                                         if(node.childNodes[0] && node.childNodes[0].hasChildNodes()) {
229                                                 t = node.childNodes[0].innerHTML;
230                                         } else {
231                                                 t = node.innerHTML;
232                                         }
233                                 } else {
234                                         if(typeof(config.textExtraction) == "function") {
235                                                 t = config.textExtraction(node);
236                                         } else { 
237                                                 t = $(node).text();
238                                         }       
239                                 }
240                                 return t;
241                         }
242                         
243                         function appendToTable(table,cache) {
244                                 
245                                 if(table.config.debug) {var appendTime = new Date()}
246                                 
247                                 var c = cache, 
248                                         r = c.row, 
249                                         n= c.normalized, 
250                                         totalRows = n.length, 
251                                         checkCell = (n[0].length-1), 
252                                         tableBody = $(table.tBodies[0]),
253                                         rows = [];
254                                 
255                                 for (var i=0;i < totalRows; i++) {
256                                         rows.push(r[n[i][checkCell]]);  
257                                         if(!table.config.appender) {
258                                                 
259                                                 var o = r[n[i][checkCell]];
260                                                 var l = o.length;
261                                                 for(var j=0; j < l; j++) {
262                                                         
263                                                         tableBody[0].appendChild(o[j]);
264                                                 
265                                                 }
266                                                 
267                                                 //tableBody.append(r[n[i][checkCell]]);
268                                         }
269                                 }       
270                                 
271                                 if(table.config.appender) {
272                                 
273                                         table.config.appender(table,rows);      
274                                 }
275                                 
276                                 rows = null;
277                                 
278                                 if(table.config.debug) { benchmark("Rebuilt table:", appendTime); }
279                                                                 
280                                 //apply table widgets
281                                 applyWidget(table);
282                                 
283                                 // trigger sortend
284                                 setTimeout(function() {
285                                         $(table).trigger("sortEnd");    
286                                 },0);
287                                 
288                         };
289                         
290                         function buildHeaders(table) {
291                                 
292                                 if(table.config.debug) { var time = new Date(); }
293                                 
294                                 var meta = ($.metadata) ? true : false, tableHeadersRows = [];
295                         
296                                 for(var i = 0; i < table.tHead.rows.length; i++) { tableHeadersRows[i]=0; };
297                                 
298                                 $tableHeaders = $("thead th",table);
299                 
300                                 $tableHeaders.each(function(index) {
301                                                         
302                                         this.count = 0;
303                                         this.column = index;
304                                         this.order = formatSortingOrder(table.config.sortInitialOrder);
305                                         
306                                         if(checkHeaderMetadata(this) || checkHeaderOptions(table,index)) this.sortDisabled = true;
307                                         
308                                         if(!this.sortDisabled) {
309                                                 $(this).addClass(table.config.cssHeader);
310                                         }
311                                         
312                                         // add cell to headerList
313                                         table.config.headerList[index]= this;
314                                 });
315                                 
316                                 if(table.config.debug) { benchmark("Built headers:", time); log($tableHeaders); }
317                                 
318                                 return $tableHeaders;
319                                 
320                         };
321                                                 
322                         function checkCellColSpan(table, rows, row) {
323                 var arr = [], r = table.tHead.rows, c = r[row].cells;
324                                 
325                                 for(var i=0; i < c.length; i++) {
326                                         var cell = c[i];
327                                         
328                                         if ( cell.colSpan > 1) { 
329                                                 arr = arr.concat(checkCellColSpan(table, headerArr,row++));
330                                         } else  {
331                                                 if(table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row+1])) {
332                                                         arr.push(cell);
333                                                 }
334                                                 //headerArr[row] = (i+row);
335                                         }
336                                 }
337                                 return arr;
338                         };
339                         
340                         function checkHeaderMetadata(cell) {
341                                 if(($.metadata) && ($(cell).metadata().sorter === false)) { return true; };
342                                 return false;
343                         }
344                         
345                         function checkHeaderOptions(table,i) {  
346                                 if((table.config.headers[i]) && (table.config.headers[i].sorter === false)) { return true; };
347                                 return false;
348                         }
349                         
350                         function applyWidget(table) {
351                                 var c = table.config.widgets;
352                                 var l = c.length;
353                                 for(var i=0; i < l; i++) {
354                                         
355                                         getWidgetById(c[i]).format(table);
356                                 }
357                                 
358                         }
359                         
360                         function getWidgetById(name) {
361                                 var l = widgets.length;
362                                 for(var i=0; i < l; i++) {
363                                         if(widgets[i].id.toLowerCase() == name.toLowerCase() ) {
364                                                 return widgets[i]; 
365                                         }
366                                 }
367                         };
368                         
369                         function formatSortingOrder(v) {
370                                 
371                                 if(typeof(v) != "Number") {
372                                         i = (v.toLowerCase() == "desc") ? 1 : 0;
373                                 } else {
374                                         i = (v == (0 || 1)) ? v : 0;
375                                 }
376                                 return i;
377                         }
378                         
379                         function isValueInArray(v, a) {
380                                 var l = a.length;
381                                 for(var i=0; i < l; i++) {
382                                         if(a[i][0] == v) {
383                                                 return true;    
384                                         }
385                                 }
386                                 return false;
387                         }
388                                 
389                         function setHeadersCss(table,$headers, list, css) {
390                                 // remove all header information
391                                 $headers.removeClass(css[0]).removeClass(css[1]);
392                                 
393                                 var h = [];
394                                 $headers.each(function(offset) {
395                                                 if(!this.sortDisabled) {
396                                                         h[this.column] = $(this);                                       
397                                                 }
398                                 });
399                                 
400                                 var l = list.length; 
401                                 for(var i=0; i < l; i++) {
402                                         h[list[i][0]].addClass(css[list[i][1]]);
403                                 }
404                         }
405                         
406                         function fixColumnWidth(table,$headers) {
407                                 var c = table.config;
408                                 if(c.widthFixed) {
409                                         var colgroup = $('<colgroup>');
410                                         $("tr:first td",table.tBodies[0]).each(function() {
411                                                 colgroup.append($('<col>').css('width',$(this).width()));
412                                         });
413                                         $(table).prepend(colgroup);
414                                 };
415                         }
416                         
417                         function updateHeaderSortCount(table,sortList) {
418                                 var c = table.config, l = sortList.length;
419                                 for(var i=0; i < l; i++) {
420                                         var s = sortList[i], o = c.headerList[s[0]];
421                                         o.count = s[1];
422                                         o.count++;
423                                 }
424                         }
425                         
426                         /* sorting methods */
427                         function multisort(table,sortList,cache) {
428                                 
429                                 if(table.config.debug) { var sortTime = new Date(); }
430                                 
431                                 var dynamicExp = "var sortWrapper = function(a,b) {", l = sortList.length;
432                                         
433                                 for(var i=0; i < l; i++) {
434                                         
435                                         var c = sortList[i][0];
436                                         var order = sortList[i][1];
437                                         var s = (getCachedSortType(table.config.parsers,c) == "text") ? ((order == 0) ? "sortText" : "sortTextDesc") : ((order == 0) ? "sortNumeric" : "sortNumericDesc");
438                                         
439                                         var e = "e" + i;
440                                         
441                                         dynamicExp += "var " + e + " = " + s + "(a[" + c + "],b[" + c + "]); ";
442                                         dynamicExp += "if(" + e + ") { return " + e + "; } ";
443                                         dynamicExp += "else { ";
444                                 }
445                                 
446                                 // if value is the same keep orignal order      
447                                 var orgOrderCol = cache.normalized[0].length - 1;
448                                 dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];";
449                                                 
450                                 for(var i=0; i < l; i++) {
451                                         dynamicExp += "}; ";
452                                 }
453                                 
454                                 dynamicExp += "return 0; ";     
455                                 dynamicExp += "}; ";    
456                                 
457                                 eval(dynamicExp);
458                                 
459                                 cache.normalized.sort(sortWrapper);
460                                 
461                                 if(table.config.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order+ " time:", sortTime); }
462                                 
463                                 return cache;
464                         };
465                         
466                         function sortText(a,b) {
467                                 return ((a < b) ? -1 : ((a > b) ? 1 : 0));
468                         };
469                         
470                         function sortTextDesc(a,b) {
471                                 return ((b < a) ? -1 : ((b > a) ? 1 : 0));
472                         };      
473                         
474                         function sortNumeric(a,b) {
475                                 return a-b;
476                         };
477                         
478                         function sortNumericDesc(a,b) {
479                                 return b-a;
480                         };
481                         
482                         function getCachedSortType(parsers,i) {
483                                 return parsers[i].type;
484                         };
485                         
486                         /* public methods */
487                         this.construct = function(settings) {
488
489                                 return this.each(function() {
490                                         
491                                         if(!this.tHead || !this.tBodies) return;
492                                         
493                                         var $this, $document,$headers, cache, config, shiftDown = 0, sortOrder;
494                                         
495                                         this.config = {};
496                                         
497                                         config = $.extend(this.config, $.tablesorter.defaults, settings);
498                                         
499                                         // store common expression for speed                                    
500                                         $this = $(this);
501                                         
502                                         // build headers
503                                         $headers = buildHeaders(this);
504                                         
505                                         // try to auto detect column type, and store in tables config
506                                         this.config.parsers = buildParserCache(this,$headers);
507                                         
508                                         
509                                         // build the cache for the tbody cells
510                                         cache = buildCache(this);
511                                         
512                                         // get the css class names, could be done else where.
513                                         var sortCSS = [config.cssDesc,config.cssAsc];
514                                         
515                                         // fixate columns if the users supplies the fixedWidth option
516                                         fixColumnWidth(this);
517                                         
518                                         // apply event handling to headers
519                                         // this is to big, perhaps break it out?
520                                         $headers.click(function(e) {
521                                                 
522                                                 $this.trigger("sortStart");
523                                                 
524                                                 var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0;
525                                                 
526                                                 if(!this.sortDisabled && totalRows > 0) {
527                                                         
528                                                         
529                                                         // store exp, for speed
530                                                         var $cell = $(this);
531         
532                                                         // get current column index
533                                                         var i = this.column;
534                                                         
535                                                         // get current column sort order
536                                                         this.order = this.count++ % 2;
537                                                         
538                                                         // user only whants to sort on one column
539                                                         if(!e[config.sortMultiSortKey]) {
540                                                                 
541                                                                 // flush the sort list
542                                                                 config.sortList = [];
543                                                                 
544                                                                 if(config.sortForce != null) {
545                                                                         var a = config.sortForce; 
546                                                                         for(var j=0; j < a.length; j++) {
547                                                                                 if(a[j][0] != i) {
548                                                                                         config.sortList.push(a[j]);
549                                                                                 }
550                                                                         }
551                                                                 }
552                                                                 
553                                                                 // add column to sort list
554                                                                 config.sortList.push([i,this.order]);
555                                                         
556                                                         // multi column sorting
557                                                         } else {
558                                                                 // the user has clicked on an all ready sortet column.
559                                                                 if(isValueInArray(i,config.sortList)) {  
560                                                                         
561                                                                         // revers the sorting direction for all tables.
562                                                                         for(var j=0; j < config.sortList.length; j++) {
563                                                                                 var s = config.sortList[j], o = config.headerList[s[0]];
564                                                                                 if(s[0] == i) {
565                                                                                         o.count = s[1];
566                                                                                         o.count++;
567                                                                                         s[1] = o.count % 2;
568                                                                                 }
569                                                                         }       
570                                                                 } else {
571                                                                         // add column to sort list array
572                                                                         config.sortList.push([i,this.order]);
573                                                                 }
574                                                         };
575                                                         setTimeout(function() {
576                                                                 //set css for headers
577                                                                 setHeadersCss($this[0],$headers,config.sortList,sortCSS);
578                                                                 appendToTable($this[0],multisort($this[0],config.sortList,cache));
579                                                         },1);
580                                                         // stop normal event by returning false
581                                                         return false;
582                                                 }
583                                         // cancel selection     
584                                         }).mousedown(function() {
585                                                 if(config.cancelSelection) {
586                                                         this.onselectstart = function() {return false};
587                                                         return false;
588                                                 }
589                                         });
590                                         
591                                         // apply easy methods that trigger binded events
592                                         $this.bind("update",function() {
593                                                 
594                                                 // rebuild parsers.
595                                                 this.config.parsers = buildParserCache(this,$headers);
596                                                 
597                                                 // rebuild the cache map
598                                                 cache = buildCache(this);
599                                                 
600                                         }).bind("sorton",function(e,list) {
601                                                 
602                                                 $(this).trigger("sortStart");
603                                                 
604                                                 config.sortList = list;
605                                                 
606                                                 // update and store the sortlist
607                                                 var sortList = config.sortList;
608                                                 
609                                                 // update header count index
610                                                 updateHeaderSortCount(this,sortList);
611                                                 
612                                                 //set css for headers
613                                                 setHeadersCss(this,$headers,sortList,sortCSS);
614                                                 
615                                                 
616                                                 // sort the table and append it to the dom
617                                                 appendToTable(this,multisort(this,sortList,cache));
618
619                                         }).bind("appendCache",function() {
620                                                 
621                                                 appendToTable(this,cache);
622                                         
623                                         }).bind("applyWidgetId",function(e,id) {
624                                                 
625                                                 getWidgetById(id).format(this);
626                                                 
627                                         }).bind("applyWidgets",function() {
628                                                 // apply widgets
629                                                 applyWidget(this);
630                                         });
631                                         
632                                         if($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) {
633                                                 config.sortList = $(this).metadata().sortlist;
634                                         }
635                                         // if user has supplied a sort list to constructor.
636                                         if(config.sortList.length > 0) {
637                                                 $this.trigger("sorton",[config.sortList]);      
638                                         }
639                                         
640                                         // apply widgets
641                                         applyWidget(this);
642                                 });
643                         };
644                         
645                         this.addParser = function(parser) {
646                                 var l = parsers.length, a = true;
647                                 for(var i=0; i < l; i++) {
648                                         if(parsers[i].id.toLowerCase() == parser.id.toLowerCase()) {
649                                                 a = false;
650                                         }
651                                 }
652                                 if(a) { parsers.push(parser); };
653                         };
654                         
655                         this.addWidget = function(widget) {
656                                 widgets.push(widget);
657                         };
658                         
659                         this.formatFloat = function(s) {
660                                 var i = parseFloat(s);
661                                 return (isNaN(i)) ? 0 : i;
662                         };
663                         this.formatInt = function(s) {
664                                 var i = parseInt(s);
665                                 return (isNaN(i)) ? 0 : i;
666                         };
667                         
668                         this.isDigit = function(s,config) {
669                                 var DECIMAL = '\\' + config.decimal;
670                                 var exp = '/(^[+]?0(' + DECIMAL +'0+)?$)|(^([-+]?[1-9][0-9]*)$)|(^([-+]?((0?|[1-9][0-9]*)' + DECIMAL +'(0*[1-9][0-9]*)))$)|(^[-+]?[1-9]+[0-9]*' + DECIMAL +'0+$)/';
671                                 return RegExp(exp).test($.trim(s));
672                         };
673                         
674                         this.clearTableBody = function(table) {
675                                 if($.browser.msie) {
676                                         function empty() {
677                                                 while ( this.firstChild ) this.removeChild( this.firstChild );
678                                         }
679                                         empty.apply(table.tBodies[0]);
680                                 } else {
681                                         table.tBodies[0].innerHTML = "";
682                                 }
683                         };
684                 }
685         });
686         
687         // extend plugin scope
688         $.fn.extend({
689         tablesorter: $.tablesorter.construct
690         });
691         
692         var ts = $.tablesorter;
693         
694         // add default parsers
695         ts.addParser({
696                 id: "text",
697                 is: function(s) {
698                         return true;
699                 },
700                 format: function(s) {
701                         return $.trim(s.toLowerCase());
702                 },
703                 type: "text"
704         });
705         
706         ts.addParser({
707                 id: "digit",
708                 is: function(s,table) {
709                         var c = table.config;
710                         return $.tablesorter.isDigit(s,c);
711                 },
712                 format: function(s) {
713                         return $.tablesorter.formatFloat(s);
714                 },
715                 type: "numeric"
716         });
717         
718         ts.addParser({
719                 id: "currency",
720                 is: function(s) {
721                         return /^[£$€?.]/.test(s);
722                 },
723                 format: function(s) {
724                         return $.tablesorter.formatFloat(s.replace(new RegExp(/[^0-9.]/g),""));
725                 },
726                 type: "numeric"
727         });
728         
729         ts.addParser({
730                 id: "ipAddress",
731                 is: function(s) {
732                         return /^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);
733                 },
734                 format: function(s) {
735                         var a = s.split("."), r = "", l = a.length;
736                         for(var i = 0; i < l; i++) {
737                                 var item = a[i];
738                                 if(item.length == 2) {
739                                         r += "0" + item;
740                                 } else {
741                                         r += item;
742                                 }
743                         }
744                         return $.tablesorter.formatFloat(r);
745                 },
746                 type: "numeric"
747         });
748         
749         ts.addParser({
750                 id: "url",
751                 is: function(s) {
752                         return /^(https?|ftp|file):\/\/$/.test(s);
753                 },
754                 format: function(s) {
755                         return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''));
756                 },
757                 type: "text"
758         });
759         
760         ts.addParser({
761                 id: "isoDate",
762                 is: function(s) {
763                         return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);
764                 },
765                 format: function(s) {
766                         return $.tablesorter.formatFloat((s != "") ? new Date(s.replace(new RegExp(/-/g),"/")).getTime() : "0");
767                 },
768                 type: "numeric"
769         });
770                 
771         ts.addParser({
772                 id: "percent",
773                 is: function(s) { 
774                         return /\%$/.test($.trim(s));
775                 },
776                 format: function(s) {
777                         return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""));
778                 },
779                 type: "numeric"
780         });
781
782         ts.addParser({
783                 id: "usLongDate",
784                 is: function(s) {
785                         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)))$/));
786                 },
787                 format: function(s) {
788                         return $.tablesorter.formatFloat(new Date(s).getTime());
789                 },
790                 type: "numeric"
791         });
792
793         ts.addParser({
794                 id: "shortDate",
795                 is: function(s) {
796                         return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);
797                 },
798                 format: function(s,table) {
799                         var c = table.config;
800                         s = s.replace(/\-/g,"/");
801                         if(c.dateFormat == "us") {
802                                 // reformat the string in ISO format
803                                 s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$1/$2");
804                         } else if(c.dateFormat == "uk") {
805                                 //reformat the string in ISO format
806                                 s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$2/$1");
807                         } else if(c.dateFormat == "dd/mm/yy" || c.dateFormat == "dd-mm-yy") {
808                                 s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, "$1/$2/$3");     
809                         }
810                         return $.tablesorter.formatFloat(new Date(s).getTime());
811                 },
812                 type: "numeric"
813         });
814
815         ts.addParser({
816             id: "time",
817             is: function(s) {
818                 return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);
819             },
820             format: function(s) {
821                 return $.tablesorter.formatFloat(new Date("2000/01/01 " + s).getTime());
822             },
823           type: "numeric"
824         });
825         
826         
827         ts.addParser({
828             id: "metadata",
829             is: function(s) {
830                 return false;
831             },
832             format: function(s,table,cell) {
833                         var c = table.config, p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
834                 return $(cell).metadata()[p];
835             },
836           type: "numeric"
837         });
838         
839         // add default widgets
840         ts.addWidget({
841                 id: "zebra",
842                 format: function(table) {
843                         if(table.config.debug) { var time = new Date(); }
844                         $("tr:visible",table.tBodies[0])
845                 .filter(':even')
846                 .removeClass(table.config.widgetZebra.css[1]).addClass(table.config.widgetZebra.css[0])
847                 .end().filter(':odd')
848                 .removeClass(table.config.widgetZebra.css[0]).addClass(table.config.widgetZebra.css[1]);
849                         if(table.config.debug) { $.tablesorter.benchmark("Applying Zebra widget", time); }
850                 }
851         });     
852 })(jQuery);