jquery.tablesorter-2.7.js 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374
  1. /*!
  2. * TableSorter 2.7 - Client-side table sorting with ease!
  3. * @requires jQuery v1.2.6+
  4. *
  5. * Copyright (c) 2007 Christian Bach
  6. * Examples and docs at: http://tablesorter.com
  7. * Dual licensed under the MIT and GPL licenses:
  8. * http://www.opensource.org/licenses/mit-license.php
  9. * http://www.gnu.org/licenses/gpl.html
  10. *
  11. * @type jQuery
  12. * @name tablesorter
  13. * @cat Plugins/Tablesorter
  14. * @author Christian Bach/christian.bach@polyester.se
  15. * @contributor Rob Garrison/https://github.com/Mottie/tablesorter
  16. */
  17. /*jshint browser:true, jquery:true, unused:false, expr: true */
  18. /*global console:false, alert:false */
  19. !(function($) {
  20. "use strict";
  21. $.extend({
  22. /*jshint supernew:true */
  23. tablesorter: new function() {
  24. var ts = this;
  25. ts.version = "2.7";
  26. ts.parsers = [];
  27. ts.widgets = [];
  28. ts.defaults = {
  29. // *** appearance
  30. theme : 'default', // adds tablesorter-{theme} to the table for styling
  31. widthFixed : false, // adds colgroup to fix widths of columns
  32. showProcessing : false, // show an indeterminate timer icon in the header when the table is sorted or filtered.
  33. headerTemplate : '{content}',// header layout template (HTML ok); {content} = innerHTML, {icon} = <i/> (class from cssIcon)
  34. onRenderTemplate : null, // function(index, template){ return template; }, (template is a string)
  35. onRenderHeader : null, // function(index){}, (nothing to return)
  36. // *** functionality
  37. cancelSelection : true, // prevent text selection in the header
  38. dateFormat : 'mmddyyyy', // other options: "ddmmyyy" or "yyyymmdd"
  39. sortMultiSortKey : 'shiftKey', // key used to select additional columns
  40. sortResetKey : 'ctrlKey', // key used to remove sorting on a column
  41. usNumberFormat : true, // false for German "1.234.567,89" or French "1 234 567,89"
  42. delayInit : false, // if false, the parsed table contents will not update until the first sort
  43. serverSideSorting: false, // if true, server-side sorting should be performed because client-side sorting will be disabled, but the ui and events will still be used.
  44. // *** sort options
  45. headers : {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc.
  46. ignoreCase : true, // ignore case while sorting
  47. sortForce : null, // column(s) first sorted; always applied
  48. sortList : [], // Initial sort order; applied initially; updated when manually sorted
  49. sortAppend : null, // column(s) sorted last; always applied
  50. sortInitialOrder : 'asc', // sort direction on first click
  51. sortLocaleCompare: false, // replace equivalent character (accented characters)
  52. sortReset : false, // third click on the header will reset column to default - unsorted
  53. sortRestart : false, // restart sort to "sortInitialOrder" when clicking on previously unsorted columns
  54. emptyTo : 'bottom', // sort empty cell to bottom, top, none, zero
  55. stringTo : 'max', // sort strings in numerical column as max, min, top, bottom, zero
  56. textExtraction : 'simple', // text extraction method/function - function(node, table, cellIndex){}
  57. textSorter : null, // use custom text sorter - function(a,b){ return a.sort(b); } // basic sort
  58. // *** widget options
  59. widgets: [], // method to add widgets, e.g. widgets: ['zebra']
  60. widgetOptions : {
  61. zebra : [ 'even', 'odd' ] // zebra widget alternating row class names
  62. },
  63. initWidgets : true, // apply widgets on tablesorter initialization
  64. // *** callbacks
  65. initialized : null, // function(table){},
  66. // *** css class names
  67. tableClass : 'tablesorter',
  68. cssAsc : 'tablesorter-headerAsc',
  69. cssChildRow : 'tablesorter-childRow', // previously "expand-child"
  70. cssDesc : 'tablesorter-headerDesc',
  71. cssHeader : 'tablesorter-header',
  72. cssHeaderRow : 'tablesorter-headerRow',
  73. cssIcon : 'tablesorter-icon', // if this class exists, a <i> will be added to the header automatically
  74. cssInfoBlock : 'tablesorter-infoOnly', // don't sort tbody with this class name
  75. cssProcessing : 'tablesorter-processing', // processing icon applied to header during sort/filter
  76. // *** selectors
  77. selectorHeaders : '> thead th, > thead td',
  78. selectorSort : 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort
  79. selectorRemove : '.remove-me',
  80. // *** advanced
  81. debug : false,
  82. // *** Internal variables
  83. headerList: [],
  84. empties: {},
  85. strings: {},
  86. parsers: []
  87. // deprecated; but retained for backwards compatibility
  88. // widgetZebra: { css: ["even", "odd"] }
  89. };
  90. /* debuging utils */
  91. function log(s) {
  92. if (typeof console !== "undefined" && typeof console.log !== "undefined") {
  93. console.log(s);
  94. } else {
  95. alert(s);
  96. }
  97. }
  98. function benchmark(s, d) {
  99. log(s + " (" + (new Date().getTime() - d.getTime()) + "ms)");
  100. }
  101. ts.benchmark = benchmark;
  102. function getElementText(table, node, cellIndex) {
  103. if (!node) { return ""; }
  104. var c = table.config,
  105. t = c.textExtraction, text = "";
  106. if (t === "simple") {
  107. if (c.supportsTextContent) {
  108. text = node.textContent; // newer browsers support this
  109. } else {
  110. text = $(node).text();
  111. }
  112. } else {
  113. if (typeof(t) === "function") {
  114. text = t(node, table, cellIndex);
  115. } else if (typeof(t) === "object" && t.hasOwnProperty(cellIndex)) {
  116. text = t[cellIndex](node, table, cellIndex);
  117. } else {
  118. text = c.supportsTextContent ? node.textContent : $(node).text();
  119. }
  120. }
  121. return $.trim(text);
  122. }
  123. function detectParserForColumn(table, rows, rowIndex, cellIndex) {
  124. var i, l = ts.parsers.length,
  125. node = false,
  126. nodeValue = '',
  127. keepLooking = true;
  128. while (nodeValue === '' && keepLooking) {
  129. rowIndex++;
  130. if (rows[rowIndex]) {
  131. node = rows[rowIndex].cells[cellIndex];
  132. nodeValue = getElementText(table, node, cellIndex);
  133. if (table.config.debug) {
  134. log('Checking if value was empty on row ' + rowIndex + ', column: ' + cellIndex + ': ' + nodeValue);
  135. }
  136. } else {
  137. keepLooking = false;
  138. }
  139. }
  140. for (i = 1; i < l; i++) {
  141. if (ts.parsers[i].is(nodeValue, table, node)) {
  142. return ts.parsers[i];
  143. }
  144. }
  145. // 0 is always the generic parser (text)
  146. return ts.parsers[0];
  147. }
  148. function buildParserCache(table) {
  149. var c = table.config,
  150. tb = $(table.tBodies).filter(':not(.' + c.cssInfoBlock + ')'),
  151. rows, list, l, i, h, ch, p, parsersDebug = "";
  152. if ( tb.length === 0) {
  153. return c.debug ? log('*Empty table!* Not building a parser cache') : '';
  154. }
  155. rows = tb[0].rows;
  156. if (rows[0]) {
  157. list = [];
  158. l = rows[0].cells.length;
  159. for (i = 0; i < l; i++) {
  160. // tons of thanks to AnthonyM1229 for working out the following selector (issue #74) to make this work in IE8!
  161. // More fixes to this selector to work properly in iOS and jQuery 1.8+ (issue #132 & #174)
  162. h = c.$headers.filter(':not([colspan])');
  163. h = h.add( c.$headers.filter('[colspan="1"]') ) // ie8 fix
  164. .filter('[data-column="' + i + '"]:last');
  165. ch = c.headers[i];
  166. // get column parser
  167. p = ts.getParserById( ts.getData(h, ch, 'sorter') );
  168. // empty cells behaviour - keeping emptyToBottom for backwards compatibility
  169. c.empties[i] = ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' );
  170. // text strings behaviour in numerical sorts
  171. c.strings[i] = ts.getData(h, ch, 'string') || c.stringTo || 'max';
  172. if (!p) {
  173. p = detectParserForColumn(table, rows, -1, i);
  174. }
  175. if (c.debug) {
  176. parsersDebug += "column:" + i + "; parser:" + p.id + "; string:" + c.strings[i] + '; empty: ' + c.empties[i] + "\n";
  177. }
  178. list.push(p);
  179. }
  180. }
  181. if (c.debug) {
  182. log(parsersDebug);
  183. }
  184. return list;
  185. }
  186. /* utils */
  187. function buildCache(table) {
  188. var b = table.tBodies,
  189. tc = table.config,
  190. totalRows,
  191. totalCells,
  192. parsers = tc.parsers,
  193. t, v, i, j, k, c, cols, cacheTime, colMax = [];
  194. tc.cache = {};
  195. // if no parsers found, return - it's an empty table.
  196. if (!parsers) {
  197. return tc.debug ? log('*Empty table!* Not building a cache') : '';
  198. }
  199. if (tc.debug) {
  200. cacheTime = new Date();
  201. }
  202. // processing icon
  203. if (tc.showProcessing) {
  204. ts.isProcessing(table, true);
  205. }
  206. for (k = 0; k < b.length; k++) {
  207. tc.cache[k] = { row: [], normalized: [] };
  208. // ignore tbodies with class name from css.cssInfoBlock
  209. if (!$(b[k]).hasClass(tc.cssInfoBlock)) {
  210. totalRows = (b[k] && b[k].rows.length) || 0;
  211. totalCells = (b[k].rows[0] && b[k].rows[0].cells.length) || 0;
  212. for (i = 0; i < totalRows; ++i) {
  213. /** Add the table data to main data array */
  214. c = $(b[k].rows[i]);
  215. cols = [];
  216. // if this is a child row, add it to the last row's children and continue to the next row
  217. if (c.hasClass(tc.cssChildRow)) {
  218. tc.cache[k].row[tc.cache[k].row.length - 1] = tc.cache[k].row[tc.cache[k].row.length - 1].add(c);
  219. // go to the next for loop
  220. continue;
  221. }
  222. tc.cache[k].row.push(c);
  223. for (j = 0; j < totalCells; ++j) {
  224. t = getElementText(table, c[0].cells[j], j);
  225. // allow parsing if the string is empty, previously parsing would change it to zero,
  226. // in case the parser needs to extract data from the table cell attributes
  227. v = parsers[j].format(t, table, c[0].cells[j], j);
  228. cols.push(v);
  229. if ((parsers[j].type || '').toLowerCase() === "numeric") {
  230. colMax[j] = Math.max(Math.abs(v), colMax[j] || 0); // determine column max value (ignore sign)
  231. }
  232. }
  233. cols.push(tc.cache[k].normalized.length); // add position for rowCache
  234. tc.cache[k].normalized.push(cols);
  235. }
  236. tc.cache[k].colMax = colMax;
  237. }
  238. }
  239. if (tc.showProcessing) {
  240. ts.isProcessing(table); // remove processing icon
  241. }
  242. if (tc.debug) {
  243. benchmark("Building cache for " + totalRows + " rows", cacheTime);
  244. }
  245. }
  246. // init flag (true) used by pager plugin to prevent widget application
  247. function appendToTable(table, init) {
  248. var c = table.config,
  249. b = table.tBodies,
  250. rows = [],
  251. c2 = c.cache,
  252. r, n, totalRows, checkCell, $bk, $tb,
  253. i, j, k, l, pos, appendTime;
  254. if (!c2[0]) { return; } // empty table - fixes #206
  255. if (c.debug) {
  256. appendTime = new Date();
  257. }
  258. for (k = 0; k < b.length; k++) {
  259. $bk = $(b[k]);
  260. if (!$bk.hasClass(c.cssInfoBlock)) {
  261. // get tbody
  262. $tb = ts.processTbody(table, $bk, true);
  263. r = c2[k].row;
  264. n = c2[k].normalized;
  265. totalRows = n.length;
  266. checkCell = totalRows ? (n[0].length - 1) : 0;
  267. for (i = 0; i < totalRows; i++) {
  268. pos = n[i][checkCell];
  269. rows.push(r[pos]);
  270. // removeRows used by the pager plugin
  271. if (!c.appender || !c.removeRows) {
  272. l = r[pos].length;
  273. for (j = 0; j < l; j++) {
  274. $tb.append(r[pos][j]);
  275. }
  276. }
  277. }
  278. // restore tbody
  279. ts.processTbody(table, $tb, false);
  280. }
  281. }
  282. if (c.appender) {
  283. c.appender(table, rows);
  284. }
  285. if (c.debug) {
  286. benchmark("Rebuilt table", appendTime);
  287. }
  288. // apply table widgets
  289. if (!init) { ts.applyWidget(table); }
  290. // trigger sortend
  291. $(table).trigger("sortEnd", table);
  292. }
  293. // computeTableHeaderCellIndexes from:
  294. // http://www.javascripttoolbox.com/lib/table/examples.php
  295. // http://www.javascripttoolbox.com/temp/table_cellindex.html
  296. function computeThIndexes(t) {
  297. var matrix = [],
  298. lookup = {},
  299. trs = $(t).find('thead:eq(0), tfoot').children('tr'), // children tr in tfoot - see issue #196
  300. i, j, k, l, c, cells, rowIndex, cellId, rowSpan, colSpan, firstAvailCol, matrixrow;
  301. for (i = 0; i < trs.length; i++) {
  302. cells = trs[i].cells;
  303. for (j = 0; j < cells.length; j++) {
  304. c = cells[j];
  305. rowIndex = c.parentNode.rowIndex;
  306. cellId = rowIndex + "-" + c.cellIndex;
  307. rowSpan = c.rowSpan || 1;
  308. colSpan = c.colSpan || 1;
  309. if (typeof(matrix[rowIndex]) === "undefined") {
  310. matrix[rowIndex] = [];
  311. }
  312. // Find first available column in the first row
  313. for (k = 0; k < matrix[rowIndex].length + 1; k++) {
  314. if (typeof(matrix[rowIndex][k]) === "undefined") {
  315. firstAvailCol = k;
  316. break;
  317. }
  318. }
  319. lookup[cellId] = firstAvailCol;
  320. // add data-column
  321. $(c).attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex
  322. for (k = rowIndex; k < rowIndex + rowSpan; k++) {
  323. if (typeof(matrix[k]) === "undefined") {
  324. matrix[k] = [];
  325. }
  326. matrixrow = matrix[k];
  327. for (l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
  328. matrixrow[l] = "x";
  329. }
  330. }
  331. }
  332. }
  333. return lookup;
  334. }
  335. function formatSortingOrder(v) {
  336. // look for "d" in "desc" order; return true
  337. return (/^d/i.test(v) || v === 1);
  338. }
  339. function buildHeaders(table) {
  340. var header_index = computeThIndexes(table), ch, $t,
  341. h, i, t, lock, time, $tableHeaders, c = table.config;
  342. c.headerList = [], c.headerContent = [];
  343. if (c.debug) {
  344. time = new Date();
  345. }
  346. i = c.cssIcon ? '<i class="' + c.cssIcon + '"></i>' : ''; // add icon if cssIcon option exists
  347. $tableHeaders = $(table).find(c.selectorHeaders).each(function(index) {
  348. $t = $(this);
  349. ch = c.headers[index];
  350. c.headerContent[index] = this.innerHTML; // save original header content
  351. // set up header template
  352. t = c.headerTemplate.replace(/\{content\}/g, this.innerHTML).replace(/\{icon\}/g, i);
  353. if (c.onRenderTemplate) {
  354. h = c.onRenderTemplate.apply($t, [index, t]);
  355. if (h && typeof h === 'string') { t = h; } // only change t if something is returned
  356. }
  357. this.innerHTML = '<div class="tablesorter-header-inner">' + t + '</div>'; // faster than wrapInner
  358. if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index]); }
  359. this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex];
  360. this.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2];
  361. this.count = -1; // set to -1 because clicking on the header automatically adds one
  362. if (ts.getData($t, ch, 'sorter') === 'false') {
  363. this.sortDisabled = true;
  364. $t.addClass('sorter-false');
  365. } else {
  366. $t.removeClass('sorter-false');
  367. }
  368. this.lockedOrder = false;
  369. lock = ts.getData($t, ch, 'lockedOrder') || false;
  370. if (typeof(lock) !== 'undefined' && lock !== false) {
  371. this.order = this.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0];
  372. }
  373. $t.addClass( (this.sortDisabled ? 'sorter-false ' : ' ') + c.cssHeader );
  374. // add cell to headerList
  375. c.headerList[index] = this;
  376. // add to parent in case there are multiple rows
  377. $t.parent().addClass(c.cssHeaderRow);
  378. });
  379. if (table.config.debug) {
  380. benchmark("Built headers:", time);
  381. log($tableHeaders);
  382. }
  383. return $tableHeaders;
  384. }
  385. function setHeadersCss(table) {
  386. var f, i, j, l,
  387. c = table.config,
  388. list = c.sortList,
  389. css = [c.cssAsc, c.cssDesc],
  390. // find the footer
  391. $t = $(table).find('tfoot tr').children().removeClass(css.join(' '));
  392. // remove all header information
  393. c.$headers.removeClass(css.join(' '));
  394. l = list.length;
  395. for (i = 0; i < l; i++) {
  396. // direction = 2 means reset!
  397. if (list[i][1] !== 2) {
  398. // multicolumn sorting updating - choose the :last in case there are nested columns
  399. f = c.$headers.not('.sorter-false').filter('[data-column="' + list[i][0] + '"]' + (l === 1 ? ':last' : '') );
  400. if (f.length) {
  401. for (j = 0; j < f.length; j++) {
  402. if (!f[j].sortDisabled) {
  403. f.eq(j).addClass(css[list[i][1]]);
  404. // add sorted class to footer, if it exists
  405. if ($t.length) {
  406. $t.filter('[data-column="' + list[i][0] + '"]').eq(j).addClass(css[list[i][1]]);
  407. }
  408. }
  409. }
  410. }
  411. }
  412. }
  413. }
  414. function fixColumnWidth(table) {
  415. if (table.config.widthFixed && $(table).find('colgroup').length === 0) {
  416. var colgroup = $('<colgroup>'),
  417. overallWidth = $(table).width();
  418. $("tr:first td", table.tBodies[0]).each(function() {
  419. colgroup.append($('<col>').css('width', parseInt(($(this).width()/overallWidth)*1000, 10)/10 + '%'));
  420. });
  421. $(table).prepend(colgroup);
  422. }
  423. }
  424. function updateHeaderSortCount(table, list) {
  425. var s, t, o, c = table.config,
  426. sl = list || c.sortList;
  427. c.sortList = [];
  428. $.each(sl, function(i,v){
  429. // ensure all sortList values are numeric - fixes #127
  430. s = [ parseInt(v[0], 10), parseInt(v[1], 10) ];
  431. // make sure header exists
  432. o = c.headerList[s[0]];
  433. if (o) { // prevents error if sorton array is wrong
  434. c.sortList.push(s);
  435. t = $.inArray(s[1], o.order); // fixes issue #167
  436. o.count = t >= 0 ? t : s[1] % (c.sortReset ? 3 : 2);
  437. }
  438. });
  439. }
  440. function getCachedSortType(parsers, i) {
  441. return (parsers && parsers[i]) ? parsers[i].type || '' : '';
  442. }
  443. // sort multiple columns
  444. function multisort(table) { /*jshint loopfunc:true */
  445. var dynamicExp, sortWrapper, col, mx = 0, dir = 0, tc = table.config,
  446. sortList = tc.sortList, l = sortList.length, bl = table.tBodies.length,
  447. sortTime, i, j, k, c, colMax, cache, lc, s, e, order, orgOrderCol;
  448. if (tc.serverSideSorting || !tc.cache[0]) { // empty table - fixes #206
  449. return;
  450. }
  451. if (tc.debug) { sortTime = new Date(); }
  452. for (k = 0; k < bl; k++) {
  453. colMax = tc.cache[k].colMax;
  454. cache = tc.cache[k].normalized;
  455. lc = cache.length;
  456. orgOrderCol = (cache && cache[0]) ? cache[0].length - 1 : 0;
  457. cache.sort(function(a, b) {
  458. // cache is undefined here in IE, so don't use it!
  459. for (i = 0; i < l; i++) {
  460. c = sortList[i][0];
  461. order = sortList[i][1];
  462. // fallback to natural sort since it is more robust
  463. s = /n/i.test(getCachedSortType(tc.parsers, c)) ? "Numeric" : "Text";
  464. s += order === 0 ? "" : "Desc";
  465. if (/Numeric/.test(s) && tc.strings[c]) {
  466. // sort strings in numerical columns
  467. if (typeof (tc.string[tc.strings[c]]) === 'boolean') {
  468. dir = (order === 0 ? 1 : -1) * (tc.string[tc.strings[c]] ? -1 : 1);
  469. } else {
  470. dir = (tc.strings[c]) ? tc.string[tc.strings[c]] || 0 : 0;
  471. }
  472. }
  473. var sort = $.tablesorter["sort" + s](table, a[c], b[c], c, colMax[c], dir);
  474. if (sort) { return sort; }
  475. }
  476. return a[orgOrderCol] - b[orgOrderCol];
  477. });
  478. }
  479. if (tc.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time", sortTime); }
  480. }
  481. function resortComplete($table, callback){
  482. $table.trigger('updateComplete');
  483. if (typeof callback === "function") {
  484. callback($table[0]);
  485. }
  486. }
  487. function checkResort($table, flag, callback) {
  488. if (flag !== false) {
  489. $table.trigger("sorton", [$table[0].config.sortList, function(){
  490. resortComplete($table, callback);
  491. }]);
  492. } else {
  493. resortComplete($table, callback);
  494. }
  495. }
  496. /* public methods */
  497. ts.construct = function(settings) {
  498. return this.each(function() {
  499. // if no thead or tbody, or tablesorter is already present, quit
  500. if (!this.tHead || this.tBodies.length === 0 || this.hasInitialized === true) {
  501. return (this.config.debug) ? log('stopping initialization! No thead, tbody or tablesorter has already been initialized') : '';
  502. }
  503. // declare
  504. var $cell, $this = $(this),
  505. c, i, j, k = '', a, s, o, downTime,
  506. m = $.metadata;
  507. // initialization flag
  508. this.hasInitialized = false;
  509. // new blank config object
  510. this.config = {};
  511. // merge and extend
  512. c = $.extend(true, this.config, ts.defaults, settings);
  513. // save the settings where they read
  514. $.data(this, "tablesorter", c);
  515. if (c.debug) { $.data( this, 'startoveralltimer', new Date()); }
  516. // constants
  517. c.supportsTextContent = $('<span>x</span>')[0].textContent === 'x';
  518. c.supportsDataObject = parseFloat($.fn.jquery) >= 1.4;
  519. // digit sort text location; keeping max+/- for backwards compatibility
  520. c.string = { 'max': 1, 'min': -1, 'max+': 1, 'max-': -1, 'zero': 0, 'none': 0, 'null': 0, 'top': true, 'bottom': false };
  521. // add table theme class only if there isn't already one there
  522. if (!/tablesorter\-/.test($this.attr('class'))) {
  523. k = (c.theme !== '' ? ' tablesorter-' + c.theme : '');
  524. }
  525. $this.addClass(c.tableClass + k);
  526. // build headers
  527. c.$headers = buildHeaders(this);
  528. // try to auto detect column type, and store in tables config
  529. c.parsers = buildParserCache(this);
  530. // build the cache for the tbody cells
  531. // delayInit will delay building the cache until the user starts a sort
  532. if (!c.delayInit) { buildCache(this); }
  533. // apply event handling to headers
  534. // this is to big, perhaps break it out?
  535. c.$headers
  536. // http://stackoverflow.com/questions/5312849/jquery-find-self
  537. .find('*').andSelf().filter(c.selectorSort)
  538. .unbind('mousedown.tablesorter mouseup.tablesorter')
  539. .bind('mousedown.tablesorter mouseup.tablesorter', function(e, external) {
  540. // jQuery v1.2.6 doesn't have closest()
  541. var $cell = this.tagName.match('TH|TD') ? $(this) : $(this).parents('th, td').filter(':last'), cell = $cell[0];
  542. // only recognize left clicks
  543. if ((e.which || e.button) !== 1) { return false; }
  544. // set timer on mousedown
  545. if (e.type === 'mousedown') {
  546. downTime = new Date().getTime();
  547. return e.target.tagName === "INPUT" ? '' : !c.cancelSelection;
  548. }
  549. // ignore long clicks (prevents resizable widget from initializing a sort)
  550. if (external !== true && (new Date().getTime() - downTime > 250)) { return false; }
  551. if (c.delayInit && !c.cache) { buildCache($this[0]); }
  552. if (!cell.sortDisabled) {
  553. // Only call sortStart if sorting is enabled
  554. $this.trigger("sortStart", $this[0]);
  555. // store exp, for speed
  556. // $cell = $(this);
  557. k = !e[c.sortMultiSortKey];
  558. // get current column sort order
  559. cell.count = e[c.sortResetKey] ? 2 : (cell.count + 1) % (c.sortReset ? 3 : 2);
  560. // reset all sorts on non-current column - issue #30
  561. if (c.sortRestart) {
  562. i = cell;
  563. c.$headers.each(function() {
  564. // only reset counts on columns that weren't just clicked on and if not included in a multisort
  565. if (this !== i && (k || !$(this).is('.' + c.cssDesc + ',.' + c.cssAsc))) {
  566. this.count = -1;
  567. }
  568. });
  569. }
  570. // get current column index
  571. i = cell.column;
  572. // user only wants to sort on one column
  573. if (k) {
  574. // flush the sort list
  575. c.sortList = [];
  576. if (c.sortForce !== null) {
  577. a = c.sortForce;
  578. for (j = 0; j < a.length; j++) {
  579. if (a[j][0] !== i) {
  580. c.sortList.push(a[j]);
  581. }
  582. }
  583. }
  584. // add column to sort list
  585. o = cell.order[cell.count];
  586. if (o < 2) {
  587. c.sortList.push([i, o]);
  588. // add other columns if header spans across multiple
  589. if (cell.colSpan > 1) {
  590. for (j = 1; j < cell.colSpan; j++) {
  591. c.sortList.push([i + j, o]);
  592. }
  593. }
  594. }
  595. // multi column sorting
  596. } else {
  597. // get rid of the sortAppend before adding more - fixes issue #115
  598. if (c.sortAppend && c.sortList.length > 1) {
  599. if (ts.isValueInArray(c.sortAppend[0][0], c.sortList)) {
  600. c.sortList.pop();
  601. }
  602. }
  603. // the user has clicked on an already sorted column
  604. if (ts.isValueInArray(i, c.sortList)) {
  605. // reverse the sorting direction for all tables
  606. for (j = 0; j < c.sortList.length; j++) {
  607. s = c.sortList[j];
  608. o = c.headerList[s[0]];
  609. if (s[0] === i) {
  610. s[1] = o.order[o.count];
  611. if (s[1] === 2) {
  612. c.sortList.splice(j,1);
  613. o.count = -1;
  614. }
  615. }
  616. }
  617. } else {
  618. // add column to sort list array
  619. o = cell.order[cell.count];
  620. if (o < 2) {
  621. c.sortList.push([i, o]);
  622. // add other columns if header spans across multiple
  623. if (cell.colSpan > 1) {
  624. for (j = 1; j < cell.colSpan; j++) {
  625. c.sortList.push([i + j, o]);
  626. }
  627. }
  628. }
  629. }
  630. }
  631. if (c.sortAppend !== null) {
  632. a = c.sortAppend;
  633. for (j = 0; j < a.length; j++) {
  634. if (a[j][0] !== i) {
  635. c.sortList.push(a[j]);
  636. }
  637. }
  638. }
  639. // sortBegin event triggered immediately before the sort
  640. $this.trigger("sortBegin", $this[0]);
  641. // setTimeout needed so the processing icon shows up
  642. setTimeout(function(){
  643. // set css for headers
  644. setHeadersCss($this[0]);
  645. multisort($this[0]);
  646. appendToTable($this[0]);
  647. }, 1);
  648. }
  649. });
  650. if (c.cancelSelection) {
  651. // cancel selection
  652. c.$headers.each(function() {
  653. this.onselectstart = function() {
  654. return false;
  655. };
  656. });
  657. }
  658. // apply easy methods that trigger binded events
  659. $this
  660. .unbind('sortReset update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave')
  661. .bind("sortReset", function(){
  662. c.sortList = [];
  663. setHeadersCss(this);
  664. multisort(this);
  665. appendToTable(this);
  666. })
  667. .bind("update", function(e, resort, callback) {
  668. // remove rows/elements before update
  669. $(c.selectorRemove, this).remove();
  670. // rebuild parsers
  671. c.parsers = buildParserCache(this);
  672. // rebuild the cache map
  673. buildCache(this);
  674. checkResort($this, resort, callback);
  675. })
  676. .bind("updateCell", function(e, cell, resort, callback) {
  677. // get position from the dom
  678. var l, row, icell,
  679. t = this, $tb = $(this).find('tbody'),
  680. // update cache - format: function(s, table, cell, cellIndex)
  681. // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
  682. tbdy = $tb.index( $(cell).parents('tbody').filter(':last') ),
  683. $row = $(cell).parents('tr').filter(':last');
  684. cell = $(cell)[0]; // in case cell is a jQuery object
  685. // tbody may not exist if update is initialized while tbody is removed for processing
  686. if ($tb.length && tbdy >= 0) {
  687. row = $tb.eq(tbdy).find('tr').index( $row );
  688. icell = cell.cellIndex;
  689. l = t.config.cache[tbdy].normalized[row].length - 1;
  690. t.config.cache[tbdy].row[t.config.cache[tbdy].normalized[row][l]] = $row;
  691. t.config.cache[tbdy].normalized[row][icell] = c.parsers[icell].format( getElementText(t, cell, icell), t, cell, icell );
  692. checkResort($this, resort, callback);
  693. }
  694. })
  695. .bind("addRows", function(e, $row, resort, callback) {
  696. var i, rows = $row.filter('tr').length,
  697. dat = [], l = $row[0].cells.length, t = this,
  698. tbdy = $(this).find('tbody').index( $row.closest('tbody') );
  699. // fixes adding rows to an empty table - see issue #179
  700. if (!c.parsers) {
  701. c.parsers = buildParserCache(t);
  702. }
  703. // add each row
  704. for (i = 0; i < rows; i++) {
  705. // add each cell
  706. for (j = 0; j < l; j++) {
  707. dat[j] = c.parsers[j].format( getElementText(t, $row[i].cells[j], j), t, $row[i].cells[j], j );
  708. }
  709. // add the row index to the end
  710. dat.push(c.cache[tbdy].row.length);
  711. // update cache
  712. c.cache[tbdy].row.push([$row[i]]);
  713. c.cache[tbdy].normalized.push(dat);
  714. dat = [];
  715. }
  716. // resort using current settings
  717. checkResort($this, resort, callback);
  718. })
  719. .bind("sorton", function(e, list, callback, init) {
  720. $(this).trigger("sortStart", this);
  721. // update header count index
  722. updateHeaderSortCount(this, list);
  723. // set css for headers
  724. setHeadersCss(this);
  725. // sort the table and append it to the dom
  726. multisort(this);
  727. appendToTable(this, init);
  728. if (typeof callback === "function") {
  729. callback(this);
  730. }
  731. })
  732. .bind("appendCache", function(e, callback, init) {
  733. appendToTable(this, init);
  734. if (typeof callback === "function") {
  735. callback(this);
  736. }
  737. })
  738. .bind("applyWidgetId", function(e, id) {
  739. ts.getWidgetById(id).format(this, c, c.widgetOptions);
  740. })
  741. .bind("applyWidgets", function(e, init) {
  742. // apply widgets
  743. ts.applyWidget(this, init);
  744. })
  745. .bind("refreshWidgets", function(e, all, dontapply){
  746. ts.refreshWidgets(this, all, dontapply);
  747. })
  748. .bind("destroy", function(e, c, cb){
  749. ts.destroy(this, c, cb);
  750. });
  751. // get sort list from jQuery data or metadata
  752. // in jQuery < 1.4, an error occurs when calling $this.data()
  753. if (c.supportsDataObject && typeof $this.data().sortlist !== 'undefined') {
  754. c.sortList = $this.data().sortlist;
  755. } else if (m && ($this.metadata() && $this.metadata().sortlist)) {
  756. c.sortList = $this.metadata().sortlist;
  757. }
  758. // apply widget init code
  759. ts.applyWidget(this, true);
  760. // if user has supplied a sort list to constructor
  761. if (c.sortList.length > 0) {
  762. $this.trigger("sorton", [c.sortList, {}, !c.initWidgets]);
  763. } else if (c.initWidgets) {
  764. // apply widget format
  765. ts.applyWidget(this);
  766. }
  767. // fixate columns if the users supplies the fixedWidth option
  768. // do this after theme has been applied
  769. fixColumnWidth(this);
  770. // show processesing icon
  771. if (c.showProcessing) {
  772. $this
  773. .unbind('sortBegin sortEnd')
  774. .bind('sortBegin sortEnd', function(e) {
  775. ts.isProcessing($this[0], e.type === 'sortBegin');
  776. });
  777. }
  778. // initialized
  779. this.hasInitialized = true;
  780. if (c.debug) {
  781. ts.benchmark("Overall initialization time", $.data( this, 'startoveralltimer'));
  782. }
  783. $this.trigger('tablesorter-initialized', this);
  784. if (typeof c.initialized === 'function') { c.initialized(this); }
  785. });
  786. };
  787. // *** Process table ***
  788. // add processing indicator
  789. ts.isProcessing = function(table, toggle, $ths) {
  790. var c = table.config,
  791. // default to all headers
  792. $h = $ths || $(table).find('.' + c.cssHeader);
  793. if (toggle) {
  794. if (c.sortList.length > 0) {
  795. // get headers from the sortList
  796. $h = $h.filter(function(){
  797. // get data-column from attr to keep compatibility with jQuery 1.2.6
  798. return this.sortDisabled ? false : ts.isValueInArray( parseFloat($(this).attr('data-column')), c.sortList);
  799. });
  800. }
  801. $h.addClass(c.cssProcessing);
  802. } else {
  803. $h.removeClass(c.cssProcessing);
  804. }
  805. };
  806. // detach tbody but save the position
  807. // don't use tbody because there are portions that look for a tbody index (updateCell)
  808. ts.processTbody = function(table, $tb, getIt){
  809. var t, holdr;
  810. if (getIt) {
  811. $tb.before('<span class="tablesorter-savemyplace"/>');
  812. holdr = ($.fn.detach) ? $tb.detach() : $tb.remove();
  813. return holdr;
  814. }
  815. holdr = $(table).find('span.tablesorter-savemyplace');
  816. $tb.insertAfter( holdr );
  817. holdr.remove();
  818. };
  819. ts.clearTableBody = function(table) {
  820. $(table.tBodies).filter(':not(.' + table.config.cssInfoBlock + ')').empty();
  821. };
  822. ts.destroy = function(table, removeClasses, callback){
  823. var $t = $(table), c = table.config,
  824. $h = $t.find('thead:first');
  825. // clear flag in case the plugin is initialized again
  826. table.hasInitialized = false;
  827. // remove widget added rows
  828. $h.find('tr:not(.' + c.cssHeaderRow + ')').remove();
  829. // remove resizer widget stuff
  830. $h.find('.tablesorter-resizer').remove();
  831. // remove all widgets
  832. ts.refreshWidgets(table, true, true);
  833. // disable tablesorter
  834. $t
  835. .removeData('tablesorter')
  836. .unbind('sortReset update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave')
  837. .find('.' + c.cssHeader)
  838. .unbind('click mousedown mousemove mouseup')
  839. .removeClass(c.cssHeader + ' ' + c.cssAsc + ' ' + c.cssDesc)
  840. .find('.tablesorter-header-inner').each(function(){
  841. if (c.cssIcon !== '') { $(this).find('.' + c.cssIcon).remove(); }
  842. $(this).replaceWith( $(this).contents() );
  843. });
  844. if (removeClasses !== false) {
  845. $t.removeClass(c.tableClass);
  846. }
  847. if (typeof callback === 'function') {
  848. callback(table);
  849. }
  850. };
  851. // *** sort functions ***
  852. // regex used in natural sort
  853. ts.regex = [
  854. /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi, // chunk/tokenize numbers & letters
  855. /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, //date
  856. /^0x[0-9a-f]+$/i // hex
  857. ];
  858. // Natural sort - https://github.com/overset/javascript-natural-sort
  859. ts.sortText = function(table, a, b, col) {
  860. if (a === b) { return 0; }
  861. var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ],
  862. r = ts.regex, xN, xD, yN, yD, xF, yF, i, mx;
  863. if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : -e || -1; }
  864. if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : e || 1; }
  865. if (typeof c.textSorter === 'function') { return c.textSorter(a, b, table, col); }
  866. // chunk/tokenize
  867. xN = a.replace(r[0], '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0');
  868. yN = b.replace(r[0], '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0');
  869. // numeric, hex or date detection
  870. xD = parseInt(a.match(r[2]),16) || (xN.length !== 1 && a.match(r[1]) && Date.parse(a));
  871. yD = parseInt(b.match(r[2]),16) || (xD && b.match(r[1]) && Date.parse(b)) || null;
  872. // first try and sort Hex codes or Dates
  873. if (yD) {
  874. if ( xD < yD ) { return -1; }
  875. if ( xD > yD ) { return 1; }
  876. }
  877. mx = Math.max(xN.length, yN.length);
  878. // natural sorting through split numeric strings and default strings
  879. for (i = 0; i < mx; i++) {
  880. // find floats not starting with '0', string or 0 if not defined
  881. xF = isNaN(xN[i]) ? xN[i] || 0 : parseFloat(xN[i]) || 0;
  882. yF = isNaN(yN[i]) ? yN[i] || 0 : parseFloat(yN[i]) || 0;
  883. // handle numeric vs string comparison - number < string - (Kyle Adams)
  884. if (isNaN(xF) !== isNaN(yF)) { return (isNaN(xF)) ? 1 : -1; }
  885. // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
  886. if (typeof xF !== typeof yF) {
  887. xF += '';
  888. yF += '';
  889. }
  890. if (xF < yF) { return -1; }
  891. if (xF > yF) { return 1; }
  892. }
  893. return 0;
  894. };
  895. ts.sortTextDesc = function(table, a, b, col) {
  896. if (a === b) { return 0; }
  897. var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
  898. if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : e || 1; }
  899. if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : -e || -1; }
  900. if (typeof c.textSorter === 'function') { return c.textSorter(b, a, table, col); }
  901. return ts.sortText(table, b, a);
  902. };
  903. // return text string value by adding up ascii value
  904. // so the text is somewhat sorted when using a digital sort
  905. // this is NOT an alphanumeric sort
  906. ts.getTextValue = function(a, mx, d) {
  907. if (mx) {
  908. // make sure the text value is greater than the max numerical value (mx)
  909. var i, l = a.length, n = mx + d;
  910. for (i = 0; i < l; i++) {
  911. n += a.charCodeAt(i);
  912. }
  913. return d * n;
  914. }
  915. return 0;
  916. };
  917. ts.sortNumeric = function(table, a, b, col, mx, d) {
  918. if (a === b) { return 0; }
  919. var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
  920. if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : -e || -1; }
  921. if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : e || 1; }
  922. if (isNaN(a)) { a = ts.getTextValue(a, mx, d); }
  923. if (isNaN(b)) { b = ts.getTextValue(b, mx, d); }
  924. return a - b;
  925. };
  926. ts.sortNumericDesc = function(table, a, b, col, mx, d) {
  927. if (a === b) { return 0; }
  928. var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
  929. if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : e || 1; }
  930. if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : -e || -1; }
  931. if (isNaN(a)) { a = ts.getTextValue(a, mx, d); }
  932. if (isNaN(b)) { b = ts.getTextValue(b, mx, d); }
  933. return b - a;
  934. };
  935. // used when replacing accented characters during sorting
  936. ts.characterEquivalents = {
  937. "a" : "\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5", // áàâãäąå
  938. "A" : "\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5", // ÁÀÂÃÄĄÅ
  939. "c" : "\u00e7\u0107\u010d", // çćč
  940. "C" : "\u00c7\u0106\u010c", // ÇĆČ
  941. "e" : "\u00e9\u00e8\u00ea\u00eb\u011b\u0119", // éèêëěę
  942. "E" : "\u00c9\u00c8\u00ca\u00cb\u011a\u0118", // ÉÈÊËĚĘ
  943. "i" : "\u00ed\u00ec\u0130\u00ee\u00ef\u0131", // íìİîïı
  944. "I" : "\u00cd\u00cc\u0130\u00ce\u00cf", // ÍÌİÎÏ
  945. "o" : "\u00f3\u00f2\u00f4\u00f5\u00f6", // óòôõö
  946. "O" : "\u00d3\u00d2\u00d4\u00d5\u00d6", // ÓÒÔÕÖ
  947. "ss": "\u00df", // ß (s sharp)
  948. "SS": "\u1e9e", // ẞ (Capital sharp s)
  949. "u" : "\u00fa\u00f9\u00fb\u00fc\u016f", // úùûüů
  950. "U" : "\u00da\u00d9\u00db\u00dc\u016e" // ÚÙÛÜŮ
  951. };
  952. ts.replaceAccents = function(s) {
  953. var a, acc = '[', eq = ts.characterEquivalents;
  954. if (!ts.characterRegex) {
  955. ts.characterRegexArray = {};
  956. for (a in eq) {
  957. if (typeof a === 'string') {
  958. acc += eq[a];
  959. ts.characterRegexArray[a] = new RegExp('[' + eq[a] + ']', 'g');
  960. }
  961. }
  962. ts.characterRegex = new RegExp(acc + ']');
  963. }
  964. if (ts.characterRegex.test(s)) {
  965. for (a in eq) {
  966. if (typeof a === 'string') {
  967. s = s.replace( ts.characterRegexArray[a], a );
  968. }
  969. }
  970. }
  971. return s;
  972. };
  973. // *** utilities ***
  974. ts.isValueInArray = function(v, a) {
  975. var i, l = a.length;
  976. for (i = 0; i < l; i++) {
  977. if (a[i][0] === v) {
  978. return true;
  979. }
  980. }
  981. return false;
  982. };
  983. ts.addParser = function(parser) {
  984. var i, l = ts.parsers.length, a = true;
  985. for (i = 0; i < l; i++) {
  986. if (ts.parsers[i].id.toLowerCase() === parser.id.toLowerCase()) {
  987. a = false;
  988. }
  989. }
  990. if (a) {
  991. ts.parsers.push(parser);
  992. }
  993. };
  994. ts.getParserById = function(name) {
  995. var i, l = ts.parsers.length;
  996. for (i = 0; i < l; i++) {
  997. if (ts.parsers[i].id.toLowerCase() === (name.toString()).toLowerCase()) {
  998. return ts.parsers[i];
  999. }
  1000. }
  1001. return false;
  1002. };
  1003. ts.addWidget = function(widget) {
  1004. ts.widgets.push(widget);
  1005. };
  1006. ts.getWidgetById = function(name) {
  1007. var i, w, l = ts.widgets.length;
  1008. for (i = 0; i < l; i++) {
  1009. w = ts.widgets[i];
  1010. if (w && w.hasOwnProperty('id') && w.id.toLowerCase() === name.toLowerCase()) {
  1011. return w;
  1012. }
  1013. }
  1014. };
  1015. ts.applyWidget = function(table, init) {
  1016. var c = table.config,
  1017. wo = c.widgetOptions,
  1018. ws = c.widgets.sort().reverse(), // ensure that widgets are always applied in a certain order
  1019. time, i, w, l = ws.length;
  1020. // make zebra last
  1021. i = $.inArray('zebra', c.widgets);
  1022. if (i >= 0) {
  1023. c.widgets.splice(i,1);
  1024. c.widgets.push('zebra');
  1025. }
  1026. if (c.debug) {
  1027. time = new Date();
  1028. }
  1029. // add selected widgets
  1030. for (i = 0; i < l; i++) {
  1031. w = ts.getWidgetById(ws[i]);
  1032. if ( w ) {
  1033. if (init === true && w.hasOwnProperty('init')) {
  1034. w.init(table, w, c, wo);
  1035. } else if (!init && w.hasOwnProperty('format')) {
  1036. w.format(table, c, wo);
  1037. }
  1038. }
  1039. }
  1040. if (c.debug) {
  1041. benchmark("Completed " + (init === true ? "initializing" : "applying") + " widgets", time);
  1042. }
  1043. };
  1044. ts.refreshWidgets = function(table, doAll, dontapply) {
  1045. var i, c = table.config,
  1046. cw = c.widgets,
  1047. w = ts.widgets, l = w.length;
  1048. // remove previous widgets
  1049. for (i = 0; i < l; i++){
  1050. if ( w[i] && w[i].id && (doAll || $.inArray( w[i].id, cw ) < 0) ) {
  1051. if (c.debug) { log( 'Refeshing widgets: Removing ' + w[i].id ); }
  1052. if (w[i].hasOwnProperty('remove')) { w[i].remove(table, c, c.widgetOptions); }
  1053. }
  1054. }
  1055. if (dontapply !== true) {
  1056. ts.applyWidget(table, doAll);
  1057. }
  1058. };
  1059. // get sorter, string, empty, etc options for each column from
  1060. // jQuery data, metadata, header option or header class name ("sorter-false")
  1061. // priority = jQuery data > meta > headers option > header class name
  1062. ts.getData = function(h, ch, key) {
  1063. var val = '', $h = $(h), m, cl;
  1064. if (!$h.length) { return ''; }
  1065. m = $.metadata ? $h.metadata() : false;
  1066. cl = ' ' + ($h.attr('class') || '');
  1067. if (typeof $h.data(key) !== 'undefined' || typeof $h.data(key.toLowerCase()) !== 'undefined'){
  1068. // "data-lockedOrder" is assigned to "lockedorder"; but "data-locked-order" is assigned to "lockedOrder"
  1069. // "data-sort-initial-order" is assigned to "sortInitialOrder"
  1070. val += $h.data(key) || $h.data(key.toLowerCase());
  1071. } else if (m && typeof m[key] !== 'undefined') {
  1072. val += m[key];
  1073. } else if (ch && typeof ch[key] !== 'undefined') {
  1074. val += ch[key];
  1075. } else if (cl !== ' ' && cl.match(' ' + key + '-')) {
  1076. // include sorter class name "sorter-text", etc
  1077. val = cl.match( new RegExp(' ' + key + '-(\\w+)') )[1] || '';
  1078. }
  1079. return $.trim(val);
  1080. };
  1081. ts.formatFloat = function(s, table) {
  1082. if (typeof(s) !== 'string' || s === '') { return s; }
  1083. // allow using formatFloat without a table; defaults to US number format
  1084. var i,
  1085. t = table && table.config ? table.config.usNumberFormat !== false :
  1086. typeof table !== "undefined" ? table : true;
  1087. if (t) {
  1088. // US Format - 1,234,567.89 -> 1234567.89
  1089. s = s.replace(/,/g,'');
  1090. } else {
  1091. // German Format = 1.234.567,89 -> 1234567.89
  1092. // French Format = 1 234 567,89 -> 1234567.89
  1093. s = s.replace(/[\s|\.]/g,'').replace(/,/g,'.');
  1094. }
  1095. if(/^\s*\([.\d]+\)/.test(s)) {
  1096. // make (#) into a negative number -> (10) = -10
  1097. s = s.replace(/^\s*\(/,'-').replace(/\)/,'');
  1098. }
  1099. i = parseFloat(s);
  1100. // return the text instead of zero
  1101. return isNaN(i) ? $.trim(s) : i;
  1102. };
  1103. ts.isDigit = function(s) {
  1104. // replace all unwanted chars and match
  1105. return isNaN(s) ? (/^[\-+(]?\d+[)]?$/).test(s.toString().replace(/[,.'"\s]/g, '')) : true;
  1106. };
  1107. }()
  1108. });
  1109. // make shortcut
  1110. var ts = $.tablesorter;
  1111. // extend plugin scope
  1112. $.fn.extend({
  1113. tablesorter: ts.construct
  1114. });
  1115. // add default parsers
  1116. ts.addParser({
  1117. id: "text",
  1118. is: function(s, table, node) {
  1119. return true;
  1120. },
  1121. format: function(s, table, cell, cellIndex) {
  1122. var c = table.config;
  1123. s = $.trim( c.ignoreCase ? s.toLocaleLowerCase() : s );
  1124. return c.sortLocaleCompare ? ts.replaceAccents(s) : s;
  1125. },
  1126. type: "text"
  1127. });
  1128. ts.addParser({
  1129. id: "currency",
  1130. is: function(s) {
  1131. return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test(s); // £$€¤¥¢
  1132. },
  1133. format: function(s, table) {
  1134. return ts.formatFloat(s.replace(/[^\w,. \-()]/g, ""), table);
  1135. },
  1136. type: "numeric"
  1137. });
  1138. ts.addParser({
  1139. id: "ipAddress",
  1140. is: function(s) {
  1141. return (/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/).test(s);
  1142. },
  1143. format: function(s, table) {
  1144. var i, a = s.split("."),
  1145. r = "",
  1146. l = a.length;
  1147. for (i = 0; i < l; i++) {
  1148. r += ("00" + a[i]).slice(-3);
  1149. }
  1150. return ts.formatFloat(r, table);
  1151. },
  1152. type: "numeric"
  1153. });
  1154. ts.addParser({
  1155. id: "url",
  1156. is: function(s) {
  1157. return (/^(https?|ftp|file):\/\//).test(s);
  1158. },
  1159. format: function(s) {
  1160. return $.trim(s.replace(/(https?|ftp|file):\/\//, ''));
  1161. },
  1162. type: "text"
  1163. });
  1164. ts.addParser({
  1165. id: "isoDate",
  1166. is: function(s) {
  1167. return (/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/).test(s);
  1168. },
  1169. format: function(s, table) {
  1170. return ts.formatFloat((s !== "") ? (new Date(s.replace(/-/g, "/")).getTime() || "") : "", table);
  1171. },
  1172. type: "numeric"
  1173. });
  1174. ts.addParser({
  1175. id: "percent",
  1176. is: function(s) {
  1177. return (/(\d\s?%|%\s?\d)/).test(s);
  1178. },
  1179. format: function(s, table) {
  1180. return ts.formatFloat(s.replace(/%/g, ""), table);
  1181. },
  1182. type: "numeric"
  1183. });
  1184. ts.addParser({
  1185. id: "usLongDate",
  1186. is: function(s) {
  1187. // two digit years are not allowed cross-browser
  1188. return (/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i).test(s);
  1189. },
  1190. format: function(s, table) {
  1191. return ts.formatFloat( (new Date(s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ''), table);
  1192. },
  1193. type: "numeric"
  1194. });
  1195. ts.addParser({
  1196. id: "shortDate", // "mmddyyyy", "ddmmyyyy" or "yyyymmdd"
  1197. is: function(s) {
  1198. // testing for ####-##-####, so it's not perfect
  1199. return (/^(\d{1,2}|\d{4})[\/\-\,\.\s+]\d{1,2}[\/\-\.\,\s+](\d{1,2}|\d{4})$/).test(s);
  1200. },
  1201. format: function(s, table, cell, cellIndex) {
  1202. var c = table.config, ci = c.headerList[cellIndex],
  1203. format = ci.shortDateFormat;
  1204. if (typeof format === 'undefined') {
  1205. // cache header formatting so it doesn't getData for every cell in the column
  1206. format = ci.shortDateFormat = ts.getData( ci, c.headers[cellIndex], 'dateFormat') || c.dateFormat;
  1207. }
  1208. s = s.replace(/\s+/g," ").replace(/[\-|\.|\,]/g, "/");
  1209. if (format === "mmddyyyy") {
  1210. s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2");
  1211. } else if (format === "ddmmyyyy") {
  1212. s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$2/$1");
  1213. } else if (format === "yyyymmdd") {
  1214. s = s.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$1/$2/$3");
  1215. }
  1216. return ts.formatFloat( (new Date(s).getTime() || ''), table);
  1217. },
  1218. type: "numeric"
  1219. });
  1220. ts.addParser({
  1221. id: "time",
  1222. is: function(s) {
  1223. return (/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s);
  1224. },
  1225. format: function(s, table) {
  1226. return ts.formatFloat( (new Date("2000/01/01 " + s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ""), table);
  1227. },
  1228. type: "numeric"
  1229. });
  1230. ts.addParser({
  1231. id: "digit",
  1232. is: function(s) {
  1233. return ts.isDigit(s);
  1234. },
  1235. format: function(s, table) {
  1236. return ts.formatFloat(s.replace(/[^\w,. \-()]/g, ""), table);
  1237. },
  1238. type: "numeric"
  1239. });
  1240. ts.addParser({
  1241. id: "metadata",
  1242. is: function(s) {
  1243. return false;
  1244. },
  1245. format: function(s, table, cell) {
  1246. var c = table.config,
  1247. p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
  1248. return $(cell).metadata()[p];
  1249. },
  1250. type: "numeric"
  1251. });
  1252. // add default widgets
  1253. ts.addWidget({
  1254. id: "zebra",
  1255. format: function(table, c, wo) {
  1256. var $tb, $tv, $tr, row, even, time, k, l,
  1257. child = new RegExp(c.cssChildRow, 'i'),
  1258. b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')');
  1259. if (c.debug) {
  1260. time = new Date();
  1261. }
  1262. for (k = 0; k < b.length; k++ ) {
  1263. // loop through the visible rows
  1264. $tb = $(b[k]);
  1265. l = $tb.children('tr').length;
  1266. if (l > 1) {
  1267. row = 0;
  1268. $tv = $tb.children('tr:visible');
  1269. // revered back to using jQuery each - strangely it's the fastest method
  1270. /*jshint loopfunc:true */
  1271. $tv.each(function(){
  1272. $tr = $(this);
  1273. // style children rows the same way the parent row was styled
  1274. if (!child.test(this.className)) { row++; }
  1275. even = (row % 2 === 0);
  1276. $tr.removeClass(wo.zebra[even ? 1 : 0]).addClass(wo.zebra[even ? 0 : 1]);
  1277. });
  1278. }
  1279. }
  1280. if (c.debug) {
  1281. ts.benchmark("Applying Zebra widget", time);
  1282. }
  1283. },
  1284. remove: function(table, c, wo){
  1285. var k, $tb,
  1286. b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')'),
  1287. rmv = (c.widgetOptions.zebra || [ "even", "odd" ]).join(' ');
  1288. for (k = 0; k < b.length; k++ ){
  1289. $tb = $.tablesorter.processTbody(table, $(b[k]), true); // remove tbody
  1290. $tb.children().removeClass(rmv);
  1291. $.tablesorter.processTbody(table, $tb, false); // restore tbody
  1292. }
  1293. }
  1294. });
  1295. })(jQuery);