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