jquery.tablesorter.js 39 KB

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