jquery.tablesorter.js 41 KB

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