jquery.tablesorter

Сортировка таблиц: tablesorter для jQuery

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/10013/53278/jquerytablesorter.js

  1. /**!
  2. * TableSorter 2.17.8 - 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.  
  25. var ts = this;
  26.  
  27. ts.version = "2.17.8";
  28.  
  29. ts.parsers = [];
  30. ts.widgets = [];
  31. ts.defaults = {
  32.  
  33. // *** appearance
  34. theme : 'default', // adds tablesorter-{theme} to the table for styling
  35. widthFixed : false, // adds colgroup to fix widths of columns
  36. showProcessing : false, // show an indeterminate timer icon in the header when the table is sorted or filtered.
  37.  
  38. headerTemplate : '{content}',// header layout template (HTML ok); {content} = innerHTML, {icon} = <i/> (class from cssIcon)
  39. onRenderTemplate : null, // function(index, template){ return template; }, (template is a string)
  40. onRenderHeader : null, // function(index){}, (nothing to return)
  41.  
  42. // *** functionality
  43. cancelSelection : true, // prevent text selection in the header
  44. tabIndex : true, // add tabindex to header for keyboard accessibility
  45. dateFormat : 'mmddyyyy', // other options: "ddmmyyy" or "yyyymmdd"
  46. sortMultiSortKey : 'shiftKey', // key used to select additional columns
  47. sortResetKey : 'ctrlKey', // key used to remove sorting on a column
  48. usNumberFormat : true, // false for German "1.234.567,89" or French "1 234 567,89"
  49. delayInit : false, // if false, the parsed table contents will not update until the first sort
  50. 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.
  51.  
  52. // *** sort options
  53. headers : {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc.
  54. ignoreCase : true, // ignore case while sorting
  55. sortForce : null, // column(s) first sorted; always applied
  56. sortList : [], // Initial sort order; applied initially; updated when manually sorted
  57. sortAppend : null, // column(s) sorted last; always applied
  58. sortStable : false, // when sorting two rows with exactly the same content, the original sort order is maintained
  59.  
  60. sortInitialOrder : 'asc', // sort direction on first click
  61. sortLocaleCompare: false, // replace equivalent character (accented characters)
  62. sortReset : false, // third click on the header will reset column to default - unsorted
  63. sortRestart : false, // restart sort to "sortInitialOrder" when clicking on previously unsorted columns
  64.  
  65. emptyTo : 'bottom', // sort empty cell to bottom, top, none, zero
  66. stringTo : 'max', // sort strings in numerical column as max, min, top, bottom, zero
  67. textExtraction : 'basic', // text extraction method/function - function(node, table, cellIndex){}
  68. textAttribute : 'data-text',// data-attribute that contains alternate cell text (used in textExtraction function)
  69. textSorter : null, // choose overall or specific column sorter function(a, b, direction, table, columnIndex) [alt: ts.sortText]
  70. numberSorter : null, // choose overall numeric sorter function(a, b, direction, maxColumnValue)
  71.  
  72. // *** widget options
  73. widgets: [], // method to add widgets, e.g. widgets: ['zebra']
  74. widgetOptions : {
  75. zebra : [ 'even', 'odd' ] // zebra widget alternating row class names
  76. },
  77. initWidgets : true, // apply widgets on tablesorter initialization
  78.  
  79. // *** callbacks
  80. initialized : null, // function(table){},
  81.  
  82. // *** extra css class names
  83. tableClass : '',
  84. cssAsc : '',
  85. cssDesc : '',
  86. cssNone : '',
  87. cssHeader : '',
  88. cssHeaderRow : '',
  89. cssProcessing : '', // processing icon applied to header during sort/filter
  90.  
  91. cssChildRow : 'tablesorter-childRow', // class name indiciating that a row is to be attached to the its parent
  92. cssIcon : 'tablesorter-icon', // if this class exists, a <i> will be added to the header automatically
  93. cssInfoBlock : 'tablesorter-infoOnly', // don't sort tbody with this class name (only one class name allowed here!)
  94.  
  95. // *** selectors
  96. selectorHeaders : '> thead th, > thead td',
  97. selectorSort : 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort
  98. selectorRemove : '.remove-me',
  99.  
  100. // *** advanced
  101. debug : false,
  102.  
  103. // *** Internal variables
  104. headerList: [],
  105. empties: {},
  106. strings: {},
  107. parsers: []
  108.  
  109. // deprecated; but retained for backwards compatibility
  110. // widgetZebra: { css: ["even", "odd"] }
  111.  
  112. };
  113.  
  114. // internal css classes - these will ALWAYS be added to
  115. // the table and MUST only contain one class name - fixes #381
  116. ts.css = {
  117. table : 'tablesorter',
  118. cssHasChild: 'tablesorter-hasChildRow',
  119. childRow : 'tablesorter-childRow',
  120. header : 'tablesorter-header',
  121. headerRow : 'tablesorter-headerRow',
  122. headerIn : 'tablesorter-header-inner',
  123. icon : 'tablesorter-icon',
  124. info : 'tablesorter-infoOnly',
  125. processing : 'tablesorter-processing',
  126. sortAsc : 'tablesorter-headerAsc',
  127. sortDesc : 'tablesorter-headerDesc',
  128. sortNone : 'tablesorter-headerUnSorted'
  129. };
  130.  
  131. // labels applied to sortable headers for accessibility (aria) support
  132. ts.language = {
  133. sortAsc : 'Ascending sort applied, ',
  134. sortDesc : 'Descending sort applied, ',
  135. sortNone : 'No sort applied, ',
  136. nextAsc : 'activate to apply an ascending sort',
  137. nextDesc : 'activate to apply a descending sort',
  138. nextNone : 'activate to remove the sort'
  139. };
  140.  
  141. /* debuging utils */
  142. function log() {
  143. var a = arguments[0],
  144. s = arguments.length > 1 ? Array.prototype.slice.call(arguments) : a;
  145. if (typeof console !== "undefined" && typeof console.log !== "undefined") {
  146. console[ /error/i.test(a) ? 'error' : /warn/i.test(a) ? 'warn' : 'log' ](s);
  147. } else {
  148. alert(s);
  149. }
  150. }
  151.  
  152. function benchmark(s, d) {
  153. log(s + " (" + (new Date().getTime() - d.getTime()) + "ms)");
  154. }
  155.  
  156. ts.log = log;
  157. ts.benchmark = benchmark;
  158.  
  159. // $.isEmptyObject from jQuery v1.4
  160. function isEmptyObject(obj) {
  161. /*jshint forin: false */
  162. for (var name in obj) {
  163. return false;
  164. }
  165. return true;
  166. }
  167.  
  168. function getElementText(table, node, cellIndex) {
  169. if (!node) { return ""; }
  170. var te, c = table.config,
  171. t = c.textExtraction || '',
  172. text = "";
  173. if (t === "basic") {
  174. // check data-attribute first
  175. text = $(node).attr(c.textAttribute) || node.textContent || node.innerText || $(node).text() || "";
  176. } else {
  177. if (typeof(t) === "function") {
  178. text = t(node, table, cellIndex);
  179. } else if (typeof (te = ts.getColumnData( table, t, cellIndex )) === 'function') {
  180. text = te(node, table, cellIndex);
  181. } else {
  182. // previous "simple" method
  183. text = node.textContent || node.innerText || $(node).text() || "";
  184. }
  185. }
  186. return $.trim(text);
  187. }
  188.  
  189. function detectParserForColumn(table, rows, rowIndex, cellIndex) {
  190. var cur,
  191. i = ts.parsers.length,
  192. node = false,
  193. nodeValue = '',
  194. keepLooking = true;
  195. while (nodeValue === '' && keepLooking) {
  196. rowIndex++;
  197. if (rows[rowIndex]) {
  198. node = rows[rowIndex].cells[cellIndex];
  199. nodeValue = getElementText(table, node, cellIndex);
  200. if (table.config.debug) {
  201. log('Checking if value was empty on row ' + rowIndex + ', column: ' + cellIndex + ': "' + nodeValue + '"');
  202. }
  203. } else {
  204. keepLooking = false;
  205. }
  206. }
  207. while (--i >= 0) {
  208. cur = ts.parsers[i];
  209. // ignore the default text parser because it will always be true
  210. if (cur && cur.id !== 'text' && cur.is && cur.is(nodeValue, table, node)) {
  211. return cur;
  212. }
  213. }
  214. // nothing found, return the generic parser (text)
  215. return ts.getParserById('text');
  216. }
  217.  
  218. function buildParserCache(table) {
  219. var c = table.config,
  220. // update table bodies in case we start with an empty table
  221. tb = c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')'),
  222. rows, list, l, i, h, ch, np, p, e, time,
  223. j = 0,
  224. parsersDebug = "",
  225. len = tb.length;
  226. if ( len === 0) {
  227. return c.debug ? log('Warning: *Empty table!* Not building a parser cache') : '';
  228. } else if (c.debug) {
  229. time = new Date();
  230. log('Detecting parsers for each column');
  231. }
  232. list = {
  233. extractors: [],
  234. parsers: []
  235. };
  236. while (j < len) {
  237. rows = tb[j].rows;
  238. if (rows[j]) {
  239. l = c.columns; // rows[j].cells.length;
  240. for (i = 0; i < l; i++) {
  241. h = c.$headers.filter('[data-column="' + i + '"]:last');
  242. // get column indexed table cell
  243. ch = ts.getColumnData( table, c.headers, i );
  244. // get column parser/extractor
  245. e = ts.getParserById( ts.getData(h, ch, 'extractor') );
  246. p = ts.getParserById( ts.getData(h, ch, 'sorter') );
  247. np = ts.getData(h, ch, 'parser') === 'false';
  248. // empty cells behaviour - keeping emptyToBottom for backwards compatibility
  249. c.empties[i] = ( ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' ) ).toLowerCase();
  250. // text strings behaviour in numerical sorts
  251. c.strings[i] = ( ts.getData(h, ch, 'string') || c.stringTo || 'max' ).toLowerCase();
  252. if (np) {
  253. p = ts.getParserById('no-parser');
  254. }
  255. if (!e) {
  256. // For now, maybe detect someday
  257. e = false;
  258. }
  259. if (!p) {
  260. p = detectParserForColumn(table, rows, -1, i);
  261. }
  262. if (c.debug) {
  263. parsersDebug += "column:" + i + "; extractor:" + e.id + "; parser:" + p.id + "; string:" + c.strings[i] + '; empty: ' + c.empties[i] + "\n";
  264. }
  265. list.parsers[i] = p;
  266. list.extractors[i] = e;
  267. }
  268. }
  269. j += (list.parsers.length) ? len : 1;
  270. }
  271. if (c.debug) {
  272. log(parsersDebug ? parsersDebug : "No parsers detected");
  273. benchmark("Completed detecting parsers", time);
  274. }
  275. c.parsers = list.parsers;
  276. c.extractors = list.extractors;
  277. }
  278.  
  279. /* utils */
  280. function buildCache(table) {
  281. var cc, t, tx, v, i, j, k, $row, rows, cols, cacheTime,
  282. totalRows, rowData, colMax,
  283. c = table.config,
  284. $tb = c.$table.children('tbody'),
  285. extractors = c.extractors,
  286. parsers = c.parsers;
  287. c.cache = {};
  288. c.totalRows = 0;
  289. // if no parsers found, return - it's an empty table.
  290. if (!parsers) {
  291. return c.debug ? log('Warning: *Empty table!* Not building a cache') : '';
  292. }
  293. if (c.debug) {
  294. cacheTime = new Date();
  295. }
  296. // processing icon
  297. if (c.showProcessing) {
  298. ts.isProcessing(table, true);
  299. }
  300. for (k = 0; k < $tb.length; k++) {
  301. colMax = []; // column max value per tbody
  302. cc = c.cache[k] = {
  303. normalized: [] // array of normalized row data; last entry contains "rowData" above
  304. // colMax: # // added at the end
  305. };
  306.  
  307. // ignore tbodies with class name from c.cssInfoBlock
  308. if (!$tb.eq(k).hasClass(c.cssInfoBlock)) {
  309. totalRows = ($tb[k] && $tb[k].rows.length) || 0;
  310. for (i = 0; i < totalRows; ++i) {
  311. rowData = {
  312. // order: original row order #
  313. // $row : jQuery Object[]
  314. child: [] // child row text (filter widget)
  315. };
  316. /** Add the table data to main data array */
  317. $row = $($tb[k].rows[i]);
  318. rows = [ new Array(c.columns) ];
  319. cols = [];
  320. // if this is a child row, add it to the last row's children and continue to the next row
  321. // ignore child row class, if it is the first row
  322. if ($row.hasClass(c.cssChildRow) && i !== 0) {
  323. t = cc.normalized.length - 1;
  324. cc.normalized[t][c.columns].$row = cc.normalized[t][c.columns].$row.add($row);
  325. // add "hasChild" class name to parent row
  326. if (!$row.prev().hasClass(c.cssChildRow)) {
  327. $row.prev().addClass(ts.css.cssHasChild);
  328. }
  329. // save child row content (un-parsed!)
  330. rowData.child[t] = $.trim( $row[0].textContent || $row[0].innerText || $row.text() || "" );
  331. // go to the next for loop
  332. continue;
  333. }
  334. rowData.$row = $row;
  335. rowData.order = i; // add original row position to rowCache
  336. for (j = 0; j < c.columns; ++j) {
  337. if (typeof parsers[j] === 'undefined') {
  338. if (c.debug) {
  339. log('No parser found for cell:', $row[0].cells[j], 'does it have a header?');
  340. }
  341. continue;
  342. }
  343. t = getElementText(table, $row[0].cells[j], j);
  344. // do extract before parsing if there is one
  345. if (typeof extractors[j].id === 'undefined') {
  346. tx = t;
  347. } else {
  348. tx = extractors[j].format(t, table, $row[0].cells[j], j);
  349. }
  350. // allow parsing if the string is empty, previously parsing would change it to zero,
  351. // in case the parser needs to extract data from the table cell attributes
  352. v = parsers[j].id === 'no-parser' ? '' : parsers[j].format(tx, table, $row[0].cells[j], j);
  353. cols.push( c.ignoreCase && typeof v === 'string' ? v.toLowerCase() : v );
  354. if ((parsers[j].type || '').toLowerCase() === "numeric") {
  355. // determine column max value (ignore sign)
  356. colMax[j] = Math.max(Math.abs(v) || 0, colMax[j] || 0);
  357. }
  358. }
  359. // ensure rowData is always in the same location (after the last column)
  360. cols[c.columns] = rowData;
  361. cc.normalized.push(cols);
  362. }
  363. cc.colMax = colMax;
  364. // total up rows, not including child rows
  365. c.totalRows += cc.normalized.length;
  366. }
  367. }
  368. if (c.showProcessing) {
  369. ts.isProcessing(table); // remove processing icon
  370. }
  371. if (c.debug) {
  372. benchmark("Building cache for " + totalRows + " rows", cacheTime);
  373. }
  374. }
  375.  
  376. // init flag (true) used by pager plugin to prevent widget application
  377. function appendToTable(table, init) {
  378. var c = table.config,
  379. wo = c.widgetOptions,
  380. b = table.tBodies,
  381. rows = [],
  382. cc = c.cache,
  383. n, totalRows, $bk, $tb,
  384. i, k, appendTime;
  385. // empty table - fixes #206/#346
  386. if (isEmptyObject(cc)) {
  387. // run pager appender in case the table was just emptied
  388. return c.appender ? c.appender(table, rows) :
  389. table.isUpdating ? c.$table.trigger("updateComplete", table) : ''; // Fixes #532
  390. }
  391. if (c.debug) {
  392. appendTime = new Date();
  393. }
  394. for (k = 0; k < b.length; k++) {
  395. $bk = $(b[k]);
  396. if ($bk.length && !$bk.hasClass(c.cssInfoBlock)) {
  397. // get tbody
  398. $tb = ts.processTbody(table, $bk, true);
  399. n = cc[k].normalized;
  400. totalRows = n.length;
  401. for (i = 0; i < totalRows; i++) {
  402. rows.push(n[i][c.columns].$row);
  403. // removeRows used by the pager plugin; don't render if using ajax - fixes #411
  404. if (!c.appender || (c.pager && (!c.pager.removeRows || !wo.pager_removeRows) && !c.pager.ajax)) {
  405. $tb.append(n[i][c.columns].$row);
  406. }
  407. }
  408. // restore tbody
  409. ts.processTbody(table, $tb, false);
  410. }
  411. }
  412. if (c.appender) {
  413. c.appender(table, rows);
  414. }
  415. if (c.debug) {
  416. benchmark("Rebuilt table", appendTime);
  417. }
  418. // apply table widgets; but not before ajax completes
  419. if (!init && !c.appender) { ts.applyWidget(table); }
  420. if (table.isUpdating) {
  421. c.$table.trigger("updateComplete", table);
  422. }
  423. }
  424.  
  425. function formatSortingOrder(v) {
  426. // look for "d" in "desc" order; return true
  427. return (/^d/i.test(v) || v === 1);
  428. }
  429.  
  430. function buildHeaders(table) {
  431. var ch, $t,
  432. h, i, t, lock, time,
  433. c = table.config;
  434. c.headerList = [];
  435. c.headerContent = [];
  436. if (c.debug) {
  437. time = new Date();
  438. }
  439. // children tr in tfoot - see issue #196 & #547
  440. c.columns = ts.computeColumnIndex( c.$table.children('thead, tfoot').children('tr') );
  441. // add icon if cssIcon option exists
  442. i = c.cssIcon ? '<i class="' + ( c.cssIcon === ts.css.icon ? ts.css.icon : c.cssIcon + ' ' + ts.css.icon ) + '"></i>' : '';
  443. // redefine c.$headers here in case of an updateAll that replaces or adds an entire header cell - see #683
  444. c.$headers = $(table).find(c.selectorHeaders).each(function(index) {
  445. $t = $(this);
  446. // make sure to get header cell & not column indexed cell
  447. ch = ts.getColumnData( table, c.headers, index, true );
  448. // save original header content
  449. c.headerContent[index] = $(this).html();
  450. // if headerTemplate is empty, don't reformat the header cell
  451. if ( c.headerTemplate !== '' ) {
  452. // set up header template
  453. t = c.headerTemplate.replace(/\{content\}/g, $(this).html()).replace(/\{icon\}/g, i);
  454. if (c.onRenderTemplate) {
  455. h = c.onRenderTemplate.apply($t, [index, t]);
  456. if (h && typeof h === 'string') { t = h; } // only change t if something is returned
  457. }
  458. $(this).html('<div class="' + ts.css.headerIn + '">' + t + '</div>'); // faster than wrapInner
  459. }
  460. if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index]); }
  461. this.column = parseInt( $(this).attr('data-column'), 10);
  462. this.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2];
  463. this.count = -1; // set to -1 because clicking on the header automatically adds one
  464. this.lockedOrder = false;
  465. lock = ts.getData($t, ch, 'lockedOrder') || false;
  466. if (typeof lock !== 'undefined' && lock !== false) {
  467. this.order = this.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0];
  468. }
  469. $t.addClass(ts.css.header + ' ' + c.cssHeader);
  470. // add cell to headerList
  471. c.headerList[index] = this;
  472. // add to parent in case there are multiple rows
  473. $t.parent().addClass(ts.css.headerRow + ' ' + c.cssHeaderRow).attr('role', 'row');
  474. // allow keyboard cursor to focus on element
  475. if (c.tabIndex) { $t.attr("tabindex", 0); }
  476. }).attr({
  477. scope: 'col',
  478. role : 'columnheader'
  479. });
  480. // enable/disable sorting
  481. updateHeader(table);
  482. if (c.debug) {
  483. benchmark("Built headers:", time);
  484. log(c.$headers);
  485. }
  486. }
  487.  
  488. function commonUpdate(table, resort, callback) {
  489. var c = table.config;
  490. // remove rows/elements before update
  491. c.$table.find(c.selectorRemove).remove();
  492. // rebuild parsers
  493. buildParserCache(table);
  494. // rebuild the cache map
  495. buildCache(table);
  496. checkResort(c.$table, resort, callback);
  497. }
  498.  
  499. function updateHeader(table) {
  500. var s, $th, col,
  501. c = table.config;
  502. c.$headers.each(function(index, th){
  503. $th = $(th);
  504. col = ts.getColumnData( table, c.headers, index, true );
  505. // add "sorter-false" class if "parser-false" is set
  506. s = ts.getData( th, col, 'sorter' ) === 'false' || ts.getData( th, col, 'parser' ) === 'false';
  507. th.sortDisabled = s;
  508. $th[ s ? 'addClass' : 'removeClass' ]('sorter-false').attr('aria-disabled', '' + s);
  509. // aria-controls - requires table ID
  510. if (table.id) {
  511. if (s) {
  512. $th.removeAttr('aria-controls');
  513. } else {
  514. $th.attr('aria-controls', table.id);
  515. }
  516. }
  517. });
  518. }
  519.  
  520. function setHeadersCss(table) {
  521. var f, i, j,
  522. c = table.config,
  523. list = c.sortList,
  524. len = list.length,
  525. none = ts.css.sortNone + ' ' + c.cssNone,
  526. css = [ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc],
  527. aria = ['ascending', 'descending'],
  528. // find the footer
  529. $t = $(table).find('tfoot tr').children().add(c.$extraHeaders).removeClass(css.join(' '));
  530. // remove all header information
  531. c.$headers
  532. .removeClass(css.join(' '))
  533. .addClass(none).attr('aria-sort', 'none');
  534. for (i = 0; i < len; i++) {
  535. // direction = 2 means reset!
  536. if (list[i][1] !== 2) {
  537. // multicolumn sorting updating - choose the :last in case there are nested columns
  538. f = c.$headers.not('.sorter-false').filter('[data-column="' + list[i][0] + '"]' + (len === 1 ? ':last' : '') );
  539. if (f.length) {
  540. for (j = 0; j < f.length; j++) {
  541. if (!f[j].sortDisabled) {
  542. f.eq(j).removeClass(none).addClass(css[list[i][1]]).attr('aria-sort', aria[list[i][1]]);
  543. }
  544. }
  545. // add sorted class to footer & extra headers, if they exist
  546. if ($t.length) {
  547. $t.filter('[data-column="' + list[i][0] + '"]').removeClass(none).addClass(css[list[i][1]]);
  548. }
  549. }
  550. }
  551. }
  552. // add verbose aria labels
  553. c.$headers.not('.sorter-false').each(function(){
  554. var $this = $(this),
  555. nextSort = this.order[(this.count + 1) % (c.sortReset ? 3 : 2)],
  556. txt = $this.text() + ': ' +
  557. ts.language[ $this.hasClass(ts.css.sortAsc) ? 'sortAsc' : $this.hasClass(ts.css.sortDesc) ? 'sortDesc' : 'sortNone' ] +
  558. ts.language[ nextSort === 0 ? 'nextAsc' : nextSort === 1 ? 'nextDesc' : 'nextNone' ];
  559. $this.attr('aria-label', txt );
  560. });
  561. }
  562.  
  563. // automatically add col group, and column sizes if set
  564. function fixColumnWidth(table) {
  565. var colgroup, overallWidth,
  566. c = table.config;
  567. if (c.widthFixed && c.$table.find('colgroup').length === 0) {
  568. colgroup = $('<colgroup>');
  569. overallWidth = $(table).width();
  570. // only add col for visible columns - fixes #371
  571. $(table.tBodies).not('.' + c.cssInfoBlock).find("tr:first").children(":visible").each(function() {
  572. colgroup.append($('<col>').css('width', parseInt(($(this).width()/overallWidth)*1000, 10)/10 + '%'));
  573. });
  574. c.$table.prepend(colgroup);
  575. }
  576. }
  577.  
  578. function updateHeaderSortCount(table, list) {
  579. var s, t, o, col, primary,
  580. c = table.config,
  581. sl = list || c.sortList;
  582. c.sortList = [];
  583. $.each(sl, function(i,v){
  584. // ensure all sortList values are numeric - fixes #127
  585. col = parseInt(v[0], 10);
  586. // make sure header exists
  587. o = c.$headers.filter('[data-column="' + col + '"]:last')[0];
  588. if (o) { // prevents error if sorton array is wrong
  589. // o.count = o.count + 1;
  590. t = ('' + v[1]).match(/^(1|d|s|o|n)/);
  591. t = t ? t[0] : '';
  592. // 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext
  593. switch(t) {
  594. case '1': case 'd': // descending
  595. t = 1;
  596. break;
  597. case 's': // same direction (as primary column)
  598. // if primary sort is set to "s", make it ascending
  599. t = primary || 0;
  600. break;
  601. case 'o':
  602. s = o.order[(primary || 0) % (c.sortReset ? 3 : 2)];
  603. // opposite of primary column; but resets if primary resets
  604. t = s === 0 ? 1 : s === 1 ? 0 : 2;
  605. break;
  606. case 'n':
  607. o.count = o.count + 1;
  608. t = o.order[(o.count) % (c.sortReset ? 3 : 2)];
  609. break;
  610. default: // ascending
  611. t = 0;
  612. break;
  613. }
  614. primary = i === 0 ? t : primary;
  615. s = [ col, parseInt(t, 10) || 0 ];
  616. c.sortList.push(s);
  617. t = $.inArray(s[1], o.order); // fixes issue #167
  618. o.count = t >= 0 ? t : s[1] % (c.sortReset ? 3 : 2);
  619. }
  620. });
  621. }
  622.  
  623. function getCachedSortType(parsers, i) {
  624. return (parsers && parsers[i]) ? parsers[i].type || '' : '';
  625. }
  626.  
  627. function initSort(table, cell, event){
  628. if (table.isUpdating) {
  629. // let any updates complete before initializing a sort
  630. return setTimeout(function(){ initSort(table, cell, event); }, 50);
  631. }
  632. var arry, indx, col, order, s,
  633. c = table.config,
  634. key = !event[c.sortMultiSortKey],
  635. $table = c.$table;
  636. // Only call sortStart if sorting is enabled
  637. $table.trigger("sortStart", table);
  638. // get current column sort order
  639. cell.count = event[c.sortResetKey] ? 2 : (cell.count + 1) % (c.sortReset ? 3 : 2);
  640. // reset all sorts on non-current column - issue #30
  641. if (c.sortRestart) {
  642. indx = cell;
  643. c.$headers.each(function() {
  644. // only reset counts on columns that weren't just clicked on and if not included in a multisort
  645. if (this !== indx && (key || !$(this).is('.' + ts.css.sortDesc + ',.' + ts.css.sortAsc))) {
  646. this.count = -1;
  647. }
  648. });
  649. }
  650. // get current column index
  651. indx = cell.column;
  652. // user only wants to sort on one column
  653. if (key) {
  654. // flush the sort list
  655. c.sortList = [];
  656. if (c.sortForce !== null) {
  657. arry = c.sortForce;
  658. for (col = 0; col < arry.length; col++) {
  659. if (arry[col][0] !== indx) {
  660. c.sortList.push(arry[col]);
  661. }
  662. }
  663. }
  664. // add column to sort list
  665. order = cell.order[cell.count];
  666. if (order < 2) {
  667. c.sortList.push([indx, order]);
  668. // add other columns if header spans across multiple
  669. if (cell.colSpan > 1) {
  670. for (col = 1; col < cell.colSpan; col++) {
  671. c.sortList.push([indx + col, order]);
  672. }
  673. }
  674. }
  675. // multi column sorting
  676. } else {
  677. // get rid of the sortAppend before adding more - fixes issue #115 & #523
  678. if (c.sortAppend && c.sortList.length > 1) {
  679. for (col = 0; col < c.sortAppend.length; col++) {
  680. s = ts.isValueInArray(c.sortAppend[col][0], c.sortList);
  681. if (s >= 0) {
  682. c.sortList.splice(s,1);
  683. }
  684. }
  685. }
  686. // the user has clicked on an already sorted column
  687. if (ts.isValueInArray(indx, c.sortList) >= 0) {
  688. // reverse the sorting direction
  689. for (col = 0; col < c.sortList.length; col++) {
  690. s = c.sortList[col];
  691. order = c.$headers.filter('[data-column="' + s[0] + '"]:last')[0];
  692. if (s[0] === indx) {
  693. // order.count seems to be incorrect when compared to cell.count
  694. s[1] = order.order[cell.count];
  695. if (s[1] === 2) {
  696. c.sortList.splice(col,1);
  697. order.count = -1;
  698. }
  699. }
  700. }
  701. } else {
  702. // add column to sort list array
  703. order = cell.order[cell.count];
  704. if (order < 2) {
  705. c.sortList.push([indx, order]);
  706. // add other columns if header spans across multiple
  707. if (cell.colSpan > 1) {
  708. for (col = 1; col < cell.colSpan; col++) {
  709. c.sortList.push([indx + col, order]);
  710. }
  711. }
  712. }
  713. }
  714. }
  715. if (c.sortAppend !== null) {
  716. arry = c.sortAppend;
  717. for (col = 0; col < arry.length; col++) {
  718. if (arry[col][0] !== indx) {
  719. c.sortList.push(arry[col]);
  720. }
  721. }
  722. }
  723. // sortBegin event triggered immediately before the sort
  724. $table.trigger("sortBegin", table);
  725. // setTimeout needed so the processing icon shows up
  726. setTimeout(function(){
  727. // set css for headers
  728. setHeadersCss(table);
  729. multisort(table);
  730. appendToTable(table);
  731. $table.trigger("sortEnd", table);
  732. }, 1);
  733. }
  734.  
  735. // sort multiple columns
  736. function multisort(table) { /*jshint loopfunc:true */
  737. var i, k, num, col, sortTime, colMax,
  738. cache, order, sort, x, y,
  739. dir = 0,
  740. c = table.config,
  741. cts = c.textSorter || '',
  742. sortList = c.sortList,
  743. l = sortList.length,
  744. bl = table.tBodies.length;
  745. if (c.serverSideSorting || isEmptyObject(c.cache)) { // empty table - fixes #206/#346
  746. return;
  747. }
  748. if (c.debug) { sortTime = new Date(); }
  749. for (k = 0; k < bl; k++) {
  750. colMax = c.cache[k].colMax;
  751. cache = c.cache[k].normalized;
  752.  
  753. cache.sort(function(a, b) {
  754. // cache is undefined here in IE, so don't use it!
  755. for (i = 0; i < l; i++) {
  756. col = sortList[i][0];
  757. order = sortList[i][1];
  758. // sort direction, true = asc, false = desc
  759. dir = order === 0;
  760.  
  761. if (c.sortStable && a[col] === b[col] && l === 1) {
  762. return a[c.columns].order - b[c.columns].order;
  763. }
  764.  
  765. // fallback to natural sort since it is more robust
  766. num = /n/i.test(getCachedSortType(c.parsers, col));
  767. if (num && c.strings[col]) {
  768. // sort strings in numerical columns
  769. if (typeof (c.string[c.strings[col]]) === 'boolean') {
  770. num = (dir ? 1 : -1) * (c.string[c.strings[col]] ? -1 : 1);
  771. } else {
  772. num = (c.strings[col]) ? c.string[c.strings[col]] || 0 : 0;
  773. }
  774. // fall back to built-in numeric sort
  775. // var sort = $.tablesorter["sort" + s](table, a[c], b[c], c, colMax[c], dir);
  776. sort = c.numberSorter ? c.numberSorter(a[col], b[col], dir, colMax[col], table) :
  777. ts[ 'sortNumeric' + (dir ? 'Asc' : 'Desc') ](a[col], b[col], num, colMax[col], col, table);
  778. } else {
  779. // set a & b depending on sort direction
  780. x = dir ? a : b;
  781. y = dir ? b : a;
  782. // text sort function
  783. if (typeof(cts) === 'function') {
  784. // custom OVERALL text sorter
  785. sort = cts(x[col], y[col], dir, col, table);
  786. } else if (typeof(cts) === 'object' && cts.hasOwnProperty(col)) {
  787. // custom text sorter for a SPECIFIC COLUMN
  788. sort = cts[col](x[col], y[col], dir, col, table);
  789. } else {
  790. // fall back to natural sort
  791. sort = ts[ 'sortNatural' + (dir ? 'Asc' : 'Desc') ](a[col], b[col], col, table, c);
  792. }
  793. }
  794. if (sort) { return sort; }
  795. }
  796. return a[c.columns].order - b[c.columns].order;
  797. });
  798. }
  799. if (c.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time", sortTime); }
  800. }
  801.  
  802. function resortComplete($table, callback){
  803. var table = $table[0];
  804. if (table.isUpdating) {
  805. $table.trigger('updateComplete', table);
  806. }
  807. if ($.isFunction(callback)) {
  808. callback($table[0]);
  809. }
  810. }
  811.  
  812. function checkResort($table, flag, callback) {
  813. var sl = $table[0].config.sortList;
  814. // don't try to resort if the table is still processing
  815. // this will catch spamming of the updateCell method
  816. if (flag !== false && !$table[0].isProcessing && sl.length) {
  817. $table.trigger("sorton", [sl, function(){
  818. resortComplete($table, callback);
  819. }, true]);
  820. } else {
  821. resortComplete($table, callback);
  822. ts.applyWidget($table[0], false);
  823. }
  824. }
  825.  
  826. function bindMethods(table){
  827. var c = table.config,
  828. $table = c.$table;
  829. // apply easy methods that trigger bound events
  830. $table
  831. .unbind('sortReset update updateRows updateCell updateAll addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave '.split(' ').join(c.namespace + ' '))
  832. .bind("sortReset" + c.namespace, function(e, callback){
  833. e.stopPropagation();
  834. c.sortList = [];
  835. setHeadersCss(table);
  836. multisort(table);
  837. appendToTable(table);
  838. if ($.isFunction(callback)) {
  839. callback(table);
  840. }
  841. })
  842. .bind("updateAll" + c.namespace, function(e, resort, callback){
  843. e.stopPropagation();
  844. table.isUpdating = true;
  845. ts.refreshWidgets(table, true, true);
  846. ts.restoreHeaders(table);
  847. buildHeaders(table);
  848. ts.bindEvents(table, c.$headers, true);
  849. bindMethods(table);
  850. commonUpdate(table, resort, callback);
  851. })
  852. .bind("update" + c.namespace + " updateRows" + c.namespace, function(e, resort, callback) {
  853. e.stopPropagation();
  854. table.isUpdating = true;
  855. // update sorting (if enabled/disabled)
  856. updateHeader(table);
  857. commonUpdate(table, resort, callback);
  858. })
  859. .bind("updateCell" + c.namespace, function(e, cell, resort, callback) {
  860. e.stopPropagation();
  861. table.isUpdating = true;
  862. $table.find(c.selectorRemove).remove();
  863. // get position from the dom
  864. var v, t, row, icell,
  865. $tb = $table.find('tbody'),
  866. $cell = $(cell),
  867. // update cache - format: function(s, table, cell, cellIndex)
  868. // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
  869. tbdy = $tb.index( $.fn.closest ? $cell.closest('tbody') : $cell.parents('tbody').filter(':first') ),
  870. $row = $.fn.closest ? $cell.closest('tr') : $cell.parents('tr').filter(':first');
  871. cell = $cell[0]; // in case cell is a jQuery object
  872. // tbody may not exist if update is initialized while tbody is removed for processing
  873. if ($tb.length && tbdy >= 0) {
  874. row = $tb.eq(tbdy).find('tr').index( $row );
  875. icell = $cell.index();
  876. c.cache[tbdy].normalized[row][c.columns].$row = $row;
  877. if (typeof c.extractors[icell].id === 'undefined') {
  878. t = getElementText(table, cell, icell);
  879. } else {
  880. t = c.extractors[icell].format( getElementText(table, cell, icell), table, cell, icell );
  881. }
  882. v = c.parsers[icell].id === 'no-parser' ? '' :
  883. c.parsers[icell].format( t, table, cell, icell );
  884. c.cache[tbdy].normalized[row][icell] = c.ignoreCase && typeof v === 'string' ? v.toLowerCase() : v;
  885. if ((c.parsers[icell].type || '').toLowerCase() === "numeric") {
  886. // update column max value (ignore sign)
  887. c.cache[tbdy].colMax[icell] = Math.max(Math.abs(v) || 0, c.cache[tbdy].colMax[icell] || 0);
  888. }
  889. checkResort($table, resort, callback);
  890. }
  891. })
  892. .bind("addRows" + c.namespace, function(e, $row, resort, callback) {
  893. e.stopPropagation();
  894. table.isUpdating = true;
  895. if (isEmptyObject(c.cache)) {
  896. // empty table, do an update instead - fixes #450
  897. updateHeader(table);
  898. commonUpdate(table, resort, callback);
  899. } else {
  900. $row = $($row).attr('role', 'row'); // make sure we're using a jQuery object
  901. var i, j, l, t, v, rowData, cells,
  902. rows = $row.filter('tr').length,
  903. tbdy = $table.find('tbody').index( $row.parents('tbody').filter(':first') );
  904. // fixes adding rows to an empty table - see issue #179
  905. if (!(c.parsers && c.parsers.length)) {
  906. buildParserCache(table);
  907. }
  908. // add each row
  909. for (i = 0; i < rows; i++) {
  910. l = $row[i].cells.length;
  911. cells = [];
  912. rowData = {
  913. child: [],
  914. $row : $row.eq(i),
  915. order: c.cache[tbdy].normalized.length
  916. };
  917. // add each cell
  918. for (j = 0; j < l; j++) {
  919. if (typeof c.extractors[j].id === 'undefined') {
  920. t = getElementText(table, $row[i].cells[j], j);
  921. } else {
  922. t = c.extractors[j].format( getElementText(table, $row[i].cells[j], j), table, $row[i].cells[j], j );
  923. }
  924. v = c.parsers[j].id === 'no-parser' ? '' :
  925. c.parsers[j].format( t, table, $row[i].cells[j], j );
  926. cells[j] = c.ignoreCase && typeof v === 'string' ? v.toLowerCase() : v;
  927. if ((c.parsers[j].type || '').toLowerCase() === "numeric") {
  928. // update column max value (ignore sign)
  929. c.cache[tbdy].colMax[j] = Math.max(Math.abs(cells[j]) || 0, c.cache[tbdy].colMax[j] || 0);
  930. }
  931. }
  932. // add the row data to the end
  933. cells.push(rowData);
  934. // update cache
  935. c.cache[tbdy].normalized.push(cells);
  936. }
  937. // resort using current settings
  938. checkResort($table, resort, callback);
  939. }
  940. })
  941. .bind("updateComplete" + c.namespace, function(){
  942. table.isUpdating = false;
  943. })
  944. .bind("sorton" + c.namespace, function(e, list, callback, init) {
  945. var c = table.config;
  946. e.stopPropagation();
  947. $table.trigger("sortStart", this);
  948. // update header count index
  949. updateHeaderSortCount(table, list);
  950. // set css for headers
  951. setHeadersCss(table);
  952. // fixes #346
  953. if (c.delayInit && isEmptyObject(c.cache)) { buildCache(table); }
  954. $table.trigger("sortBegin", this);
  955. // sort the table and append it to the dom
  956. multisort(table);
  957. appendToTable(table, init);
  958. $table.trigger("sortEnd", this);
  959. ts.applyWidget(table);
  960. if ($.isFunction(callback)) {
  961. callback(table);
  962. }
  963. })
  964. .bind("appendCache" + c.namespace, function(e, callback, init) {
  965. e.stopPropagation();
  966. appendToTable(table, init);
  967. if ($.isFunction(callback)) {
  968. callback(table);
  969. }
  970. })
  971. .bind("updateCache" + c.namespace, function(e, callback){
  972. // rebuild parsers
  973. if (!(c.parsers && c.parsers.length)) {
  974. buildParserCache(table);
  975. }
  976. // rebuild the cache map
  977. buildCache(table);
  978. if ($.isFunction(callback)) {
  979. callback(table);
  980. }
  981. })
  982. .bind("applyWidgetId" + c.namespace, function(e, id) {
  983. e.stopPropagation();
  984. ts.getWidgetById(id).format(table, c, c.widgetOptions);
  985. })
  986. .bind("applyWidgets" + c.namespace, function(e, init) {
  987. e.stopPropagation();
  988. // apply widgets
  989. ts.applyWidget(table, init);
  990. })
  991. .bind("refreshWidgets" + c.namespace, function(e, all, dontapply){
  992. e.stopPropagation();
  993. ts.refreshWidgets(table, all, dontapply);
  994. })
  995. .bind("destroy" + c.namespace, function(e, c, cb){
  996. e.stopPropagation();
  997. ts.destroy(table, c, cb);
  998. })
  999. .bind("resetToLoadState" + c.namespace, function(){
  1000. // remove all widgets
  1001. ts.refreshWidgets(table, true, true);
  1002. // restore original settings; this clears out current settings, but does not clear
  1003. // values saved to storage.
  1004. c = $.extend(true, ts.defaults, c.originalSettings);
  1005. table.hasInitialized = false;
  1006. // setup the entire table again
  1007. ts.setup( table, c );
  1008. });
  1009. }
  1010.  
  1011. /* public methods */
  1012. ts.construct = function(settings) {
  1013. return this.each(function() {
  1014. var table = this,
  1015. // merge & extend config options
  1016. c = $.extend(true, {}, ts.defaults, settings);
  1017. // save initial settings
  1018. c.originalSettings = settings;
  1019. // create a table from data (build table widget)
  1020. if (!table.hasInitialized && ts.buildTable && this.tagName !== 'TABLE') {
  1021. // return the table (in case the original target is the table's container)
  1022. ts.buildTable(table, c);
  1023. } else {
  1024. ts.setup(table, c);
  1025. }
  1026. });
  1027. };
  1028.  
  1029. ts.setup = function(table, c) {
  1030. // if no thead or tbody, or tablesorter is already present, quit
  1031. if (!table || !table.tHead || table.tBodies.length === 0 || table.hasInitialized === true) {
  1032. return c.debug ? log('ERROR: stopping initialization! No table, thead, tbody or tablesorter has already been initialized') : '';
  1033. }
  1034.  
  1035. var k = '',
  1036. $table = $(table),
  1037. m = $.metadata;
  1038. // initialization flag
  1039. table.hasInitialized = false;
  1040. // table is being processed flag
  1041. table.isProcessing = true;
  1042. // make sure to store the config object
  1043. table.config = c;
  1044. // save the settings where they read
  1045. $.data(table, "tablesorter", c);
  1046. if (c.debug) { $.data( table, 'startoveralltimer', new Date()); }
  1047.  
  1048. // removing this in version 3 (only supports jQuery 1.7+)
  1049. c.supportsDataObject = (function(version) {
  1050. version[0] = parseInt(version[0], 10);
  1051. return (version[0] > 1) || (version[0] === 1 && parseInt(version[1], 10) >= 4);
  1052. })($.fn.jquery.split("."));
  1053. // digit sort text location; keeping max+/- for backwards compatibility
  1054. c.string = { 'max': 1, 'min': -1, 'emptymin': 1, 'emptymax': -1, 'zero': 0, 'none': 0, 'null': 0, 'top': true, 'bottom': false };
  1055. // ensure case insensitivity
  1056. c.emptyTo = c.emptyTo.toLowerCase();
  1057. c.stringTo = c.stringTo.toLowerCase();
  1058. // add table theme class only if there isn't already one there
  1059. if (!/tablesorter\-/.test($table.attr('class'))) {
  1060. k = (c.theme !== '' ? ' tablesorter-' + c.theme : '');
  1061. }
  1062. c.table = table;
  1063. c.$table = $table
  1064. .addClass(ts.css.table + ' ' + c.tableClass + k)
  1065. .attr('role', 'grid');
  1066. c.$headers = $table.find(c.selectorHeaders);
  1067.  
  1068. // give the table a unique id, which will be used in namespace binding
  1069. if (!c.namespace) {
  1070. c.namespace = '.tablesorter' + Math.random().toString(16).slice(2);
  1071. } else {
  1072. // make sure namespace starts with a period & doesn't have weird characters
  1073. c.namespace = '.' + c.namespace.replace(/\W/g,'');
  1074. }
  1075.  
  1076. c.$table.children().children('tr').attr('role', 'row');
  1077. c.$tbodies = $table.children('tbody:not(.' + c.cssInfoBlock + ')').attr({
  1078. 'aria-live' : 'polite',
  1079. 'aria-relevant' : 'all'
  1080. });
  1081. if (c.$table.find('caption').length) {
  1082. c.$table.attr('aria-labelledby', 'theCaption');
  1083. }
  1084. c.widgetInit = {}; // keep a list of initialized widgets
  1085. // change textExtraction via data-attribute
  1086. c.textExtraction = c.$table.attr('data-text-extraction') || c.textExtraction || 'basic';
  1087. // build headers
  1088. buildHeaders(table);
  1089. // fixate columns if the users supplies the fixedWidth option
  1090. // do this after theme has been applied
  1091. fixColumnWidth(table);
  1092. // try to auto detect column type, and store in tables config
  1093. buildParserCache(table);
  1094. // start total row count at zero
  1095. c.totalRows = 0;
  1096. // build the cache for the tbody cells
  1097. // delayInit will delay building the cache until the user starts a sort
  1098. if (!c.delayInit) { buildCache(table); }
  1099. // bind all header events and methods
  1100. ts.bindEvents(table, c.$headers, true);
  1101. bindMethods(table);
  1102. // get sort list from jQuery data or metadata
  1103. // in jQuery < 1.4, an error occurs when calling $table.data()
  1104. if (c.supportsDataObject && typeof $table.data().sortlist !== 'undefined') {
  1105. c.sortList = $table.data().sortlist;
  1106. } else if (m && ($table.metadata() && $table.metadata().sortlist)) {
  1107. c.sortList = $table.metadata().sortlist;
  1108. }
  1109. // apply widget init code
  1110. ts.applyWidget(table, true);
  1111. // if user has supplied a sort list to constructor
  1112. if (c.sortList.length > 0) {
  1113. $table.trigger("sorton", [c.sortList, {}, !c.initWidgets, true]);
  1114. } else {
  1115. setHeadersCss(table);
  1116. if (c.initWidgets) {
  1117. // apply widget format
  1118. ts.applyWidget(table, false);
  1119. }
  1120. }
  1121.  
  1122. // show processesing icon
  1123. if (c.showProcessing) {
  1124. $table
  1125. .unbind('sortBegin' + c.namespace + ' sortEnd' + c.namespace)
  1126. .bind('sortBegin' + c.namespace + ' sortEnd' + c.namespace, function(e) {
  1127. clearTimeout(c.processTimer);
  1128. ts.isProcessing(table);
  1129. if (e.type === 'sortBegin') {
  1130. c.processTimer = setTimeout(function(){
  1131. ts.isProcessing(table, true);
  1132. }, 500);
  1133. }
  1134. });
  1135. }
  1136.  
  1137. // initialized
  1138. table.hasInitialized = true;
  1139. table.isProcessing = false;
  1140. if (c.debug) {
  1141. ts.benchmark("Overall initialization time", $.data( table, 'startoveralltimer'));
  1142. }
  1143. $table.trigger('tablesorter-initialized', table);
  1144. if (typeof c.initialized === 'function') { c.initialized(table); }
  1145. };
  1146.  
  1147. ts.getColumnData = function(table, obj, indx, getCell){
  1148. if (typeof obj === 'undefined' || obj === null) { return; }
  1149. table = $(table)[0];
  1150. var result, $h, k,
  1151. c = table.config;
  1152. if (obj[indx]) {
  1153. return getCell ? obj[indx] : obj[c.$headers.index( c.$headers.filter('[data-column="' + indx + '"]:last') )];
  1154. }
  1155. for (k in obj) {
  1156. if (typeof k === 'string') {
  1157. if (getCell) {
  1158. // get header cell
  1159. $h = c.$headers.eq(indx).filter(k);
  1160. } else {
  1161. // get column indexed cell
  1162. $h = c.$headers.filter('[data-column="' + indx + '"]:last').filter(k);
  1163. }
  1164. if ($h.length) {
  1165. return obj[k];
  1166. }
  1167. }
  1168. }
  1169. return result;
  1170. };
  1171.  
  1172. // computeTableHeaderCellIndexes from:
  1173. // http://www.javascripttoolbox.com/lib/table/examples.php
  1174. // http://www.javascripttoolbox.com/temp/table_cellindex.html
  1175. ts.computeColumnIndex = function(trs) {
  1176. var matrix = [],
  1177. lookup = {},
  1178. cols = 0, // determine the number of columns
  1179. i, j, k, l, $cell, cell, cells, rowIndex, cellId, rowSpan, colSpan, firstAvailCol, matrixrow;
  1180. for (i = 0; i < trs.length; i++) {
  1181. cells = trs[i].cells;
  1182. for (j = 0; j < cells.length; j++) {
  1183. cell = cells[j];
  1184. $cell = $(cell);
  1185. rowIndex = cell.parentNode.rowIndex;
  1186. cellId = rowIndex + "-" + $cell.index();
  1187. rowSpan = cell.rowSpan || 1;
  1188. colSpan = cell.colSpan || 1;
  1189. if (typeof(matrix[rowIndex]) === "undefined") {
  1190. matrix[rowIndex] = [];
  1191. }
  1192. // Find first available column in the first row
  1193. for (k = 0; k < matrix[rowIndex].length + 1; k++) {
  1194. if (typeof(matrix[rowIndex][k]) === "undefined") {
  1195. firstAvailCol = k;
  1196. break;
  1197. }
  1198. }
  1199. lookup[cellId] = firstAvailCol;
  1200. cols = Math.max(firstAvailCol, cols);
  1201. // add data-column
  1202. $cell.attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex
  1203. for (k = rowIndex; k < rowIndex + rowSpan; k++) {
  1204. if (typeof(matrix[k]) === "undefined") {
  1205. matrix[k] = [];
  1206. }
  1207. matrixrow = matrix[k];
  1208. for (l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
  1209. matrixrow[l] = "x";
  1210. }
  1211. }
  1212. }
  1213. }
  1214. // may not be accurate if # header columns !== # tbody columns
  1215. return cols + 1; // add one because it's a zero-based index
  1216. };
  1217.  
  1218. // *** Process table ***
  1219. // add processing indicator
  1220. ts.isProcessing = function(table, toggle, $ths) {
  1221. table = $(table);
  1222. var c = table[0].config,
  1223. // default to all headers
  1224. $h = $ths || table.find('.' + ts.css.header);
  1225. if (toggle) {
  1226. // don't use sortList if custom $ths used
  1227. if (typeof $ths !== 'undefined' && c.sortList.length > 0) {
  1228. // get headers from the sortList
  1229. $h = $h.filter(function(){
  1230. // get data-column from attr to keep compatibility with jQuery 1.2.6
  1231. return this.sortDisabled ? false : ts.isValueInArray( parseFloat($(this).attr('data-column')), c.sortList) >= 0;
  1232. });
  1233. }
  1234. table.add($h).addClass(ts.css.processing + ' ' + c.cssProcessing);
  1235. } else {
  1236. table.add($h).removeClass(ts.css.processing + ' ' + c.cssProcessing);
  1237. }
  1238. };
  1239.  
  1240. // detach tbody but save the position
  1241. // don't use tbody because there are portions that look for a tbody index (updateCell)
  1242. ts.processTbody = function(table, $tb, getIt){
  1243. table = $(table)[0];
  1244. var holdr;
  1245. if (getIt) {
  1246. table.isProcessing = true;
  1247. $tb.before('<span class="tablesorter-savemyplace"/>');
  1248. holdr = ($.fn.detach) ? $tb.detach() : $tb.remove();
  1249. return holdr;
  1250. }
  1251. holdr = $(table).find('span.tablesorter-savemyplace');
  1252. $tb.insertAfter( holdr );
  1253. holdr.remove();
  1254. table.isProcessing = false;
  1255. };
  1256.  
  1257. ts.clearTableBody = function(table) {
  1258. $(table)[0].config.$tbodies.children().detach();
  1259. };
  1260.  
  1261. ts.bindEvents = function(table, $headers, core){
  1262. table = $(table)[0];
  1263. var downTime,
  1264. c = table.config;
  1265. if (core !== true) {
  1266. c.$extraHeaders = c.$extraHeaders ? c.$extraHeaders.add($headers) : $headers;
  1267. }
  1268. // apply event handling to headers and/or additional headers (stickyheaders, scroller, etc)
  1269. $headers
  1270. // http://stackoverflow.com/questions/5312849/jquery-find-self;
  1271. .find(c.selectorSort).add( $headers.filter(c.selectorSort) )
  1272. .unbind('mousedown mouseup sort keyup '.split(' ').join(c.namespace + ' '))
  1273. .bind('mousedown mouseup sort keyup '.split(' ').join(c.namespace + ' '), function(e, external) {
  1274. var cell, type = e.type;
  1275. // only recognize left clicks or enter
  1276. if ( ((e.which || e.button) !== 1 && !/sort|keyup/.test(type)) || (type === 'keyup' && e.which !== 13) ) {
  1277. return;
  1278. }
  1279. // ignore long clicks (prevents resizable widget from initializing a sort)
  1280. if (type === 'mouseup' && external !== true && (new Date().getTime() - downTime > 250)) { return; }
  1281. // set timer on mousedown
  1282. if (type === 'mousedown') {
  1283. downTime = new Date().getTime();
  1284. return /(input|select|button|textarea)/i.test(e.target.tagName) ? '' : !c.cancelSelection;
  1285. }
  1286. if (c.delayInit && isEmptyObject(c.cache)) { buildCache(table); }
  1287. // jQuery v1.2.6 doesn't have closest()
  1288. cell = $.fn.closest ? $(this).closest('th, td')[0] : /TH|TD/.test(this.tagName) ? this : $(this).parents('th, td')[0];
  1289. // reference original table headers and find the same cell
  1290. cell = c.$headers[ $headers.index( cell ) ];
  1291. if (!cell.sortDisabled) {
  1292. initSort(table, cell, e);
  1293. }
  1294. });
  1295. if (c.cancelSelection) {
  1296. // cancel selection
  1297. $headers
  1298. .attr('unselectable', 'on')
  1299. .bind('selectstart', false)
  1300. .css({
  1301. 'user-select': 'none',
  1302. 'MozUserSelect': 'none' // not needed for jQuery 1.8+
  1303. });
  1304. }
  1305. };
  1306.  
  1307. // restore headers
  1308. ts.restoreHeaders = function(table){
  1309. var c = $(table)[0].config;
  1310. // don't use c.$headers here in case header cells were swapped
  1311. c.$table.find(c.selectorHeaders).each(function(i){
  1312. // only restore header cells if it is wrapped
  1313. // because this is also used by the updateAll method
  1314. if ($(this).find('.' + ts.css.headerIn).length){
  1315. $(this).html( c.headerContent[i] );
  1316. }
  1317. });
  1318. };
  1319.  
  1320. ts.destroy = function(table, removeClasses, callback){
  1321. table = $(table)[0];
  1322. if (!table.hasInitialized) { return; }
  1323. // remove all widgets
  1324. ts.refreshWidgets(table, true, true);
  1325. var $t = $(table), c = table.config,
  1326. $h = $t.find('thead:first'),
  1327. $r = $h.find('tr.' + ts.css.headerRow).removeClass(ts.css.headerRow + ' ' + c.cssHeaderRow),
  1328. $f = $t.find('tfoot:first > tr').children('th, td');
  1329. if (removeClasses === false && $.inArray('uitheme', c.widgets) >= 0) {
  1330. // reapply uitheme classes, in case we want to maintain appearance
  1331. $t.trigger('applyWidgetId', ['uitheme']);
  1332. $t.trigger('applyWidgetId', ['zebra']);
  1333. }
  1334. // remove widget added rows, just in case
  1335. $h.find('tr').not($r).remove();
  1336. // disable tablesorter
  1337. $t
  1338. .removeData('tablesorter')
  1339. .unbind('sortReset update updateAll updateRows updateCell addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd resetToLoadState '.split(' ').join(c.namespace + ' '));
  1340. c.$headers.add($f)
  1341. .removeClass( [ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone].join(' ') )
  1342. .removeAttr('data-column')
  1343. .removeAttr('aria-label')
  1344. .attr('aria-disabled', 'true');
  1345. $r.find(c.selectorSort).unbind('mousedown mouseup keypress '.split(' ').join(c.namespace + ' '));
  1346. ts.restoreHeaders(table);
  1347. $t.toggleClass(ts.css.table + ' ' + c.tableClass + ' tablesorter-' + c.theme, removeClasses === false);
  1348. // clear flag in case the plugin is initialized again
  1349. table.hasInitialized = false;
  1350. delete table.config.cache;
  1351. if (typeof callback === 'function') {
  1352. callback(table);
  1353. }
  1354. };
  1355.  
  1356. // *** sort functions ***
  1357. // regex used in natural sort
  1358. ts.regex = {
  1359. chunk : /(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi, // chunk/tokenize numbers & letters
  1360. chunks: /(^\\0|\\0$)/, // replace chunks @ ends
  1361. hex: /^0x[0-9a-f]+$/i // hex
  1362. };
  1363.  
  1364. // Natural sort - https://github.com/overset/javascript-natural-sort (date sorting removed)
  1365. // this function will only accept strings, or you'll see "TypeError: undefined is not a function"
  1366. // I could add a = a.toString(); b = b.toString(); but it'll slow down the sort overall
  1367. ts.sortNatural = function(a, b) {
  1368. if (a === b) { return 0; }
  1369. var xN, xD, yN, yD, xF, yF, i, mx,
  1370. r = ts.regex;
  1371. // first try and sort Hex codes
  1372. if (r.hex.test(b)) {
  1373. xD = parseInt(a.match(r.hex), 16);
  1374. yD = parseInt(b.match(r.hex), 16);
  1375. if ( xD < yD ) { return -1; }
  1376. if ( xD > yD ) { return 1; }
  1377. }
  1378. // chunk/tokenize
  1379. xN = a.replace(r.chunk, '\\0$1\\0').replace(r.chunks, '').split('\\0');
  1380. yN = b.replace(r.chunk, '\\0$1\\0').replace(r.chunks, '').split('\\0');
  1381. mx = Math.max(xN.length, yN.length);
  1382. // natural sorting through split numeric strings and default strings
  1383. for (i = 0; i < mx; i++) {
  1384. // find floats not starting with '0', string or 0 if not defined
  1385. xF = isNaN(xN[i]) ? xN[i] || 0 : parseFloat(xN[i]) || 0;
  1386. yF = isNaN(yN[i]) ? yN[i] || 0 : parseFloat(yN[i]) || 0;
  1387. // handle numeric vs string comparison - number < string - (Kyle Adams)
  1388. if (isNaN(xF) !== isNaN(yF)) { return (isNaN(xF)) ? 1 : -1; }
  1389. // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
  1390. if (typeof xF !== typeof yF) {
  1391. xF += '';
  1392. yF += '';
  1393. }
  1394. if (xF < yF) { return -1; }
  1395. if (xF > yF) { return 1; }
  1396. }
  1397. return 0;
  1398. };
  1399.  
  1400. ts.sortNaturalAsc = function(a, b, col, table, c) {
  1401. if (a === b) { return 0; }
  1402. var e = c.string[ (c.empties[col] || c.emptyTo ) ];
  1403. if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
  1404. if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
  1405. return ts.sortNatural(a, b);
  1406. };
  1407.  
  1408. ts.sortNaturalDesc = function(a, b, col, table, c) {
  1409. if (a === b) { return 0; }
  1410. var e = c.string[ (c.empties[col] || c.emptyTo ) ];
  1411. if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
  1412. if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
  1413. return ts.sortNatural(b, a);
  1414. };
  1415.  
  1416. // basic alphabetical sort
  1417. ts.sortText = function(a, b) {
  1418. return a > b ? 1 : (a < b ? -1 : 0);
  1419. };
  1420.  
  1421. // return text string value by adding up ascii value
  1422. // so the text is somewhat sorted when using a digital sort
  1423. // this is NOT an alphanumeric sort
  1424. ts.getTextValue = function(a, num, mx) {
  1425. if (mx) {
  1426. // make sure the text value is greater than the max numerical value (mx)
  1427. var i, l = a ? a.length : 0, n = mx + num;
  1428. for (i = 0; i < l; i++) {
  1429. n += a.charCodeAt(i);
  1430. }
  1431. return num * n;
  1432. }
  1433. return 0;
  1434. };
  1435.  
  1436. ts.sortNumericAsc = function(a, b, num, mx, col, table) {
  1437. if (a === b) { return 0; }
  1438. var c = table.config,
  1439. e = c.string[ (c.empties[col] || c.emptyTo ) ];
  1440. if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
  1441. if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
  1442. if (isNaN(a)) { a = ts.getTextValue(a, num, mx); }
  1443. if (isNaN(b)) { b = ts.getTextValue(b, num, mx); }
  1444. return a - b;
  1445. };
  1446.  
  1447. ts.sortNumericDesc = function(a, b, num, mx, col, table) {
  1448. if (a === b) { return 0; }
  1449. var c = table.config,
  1450. e = c.string[ (c.empties[col] || c.emptyTo ) ];
  1451. if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
  1452. if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
  1453. if (isNaN(a)) { a = ts.getTextValue(a, num, mx); }
  1454. if (isNaN(b)) { b = ts.getTextValue(b, num, mx); }
  1455. return b - a;
  1456. };
  1457.  
  1458. ts.sortNumeric = function(a, b) {
  1459. return a - b;
  1460. };
  1461.  
  1462. // used when replacing accented characters during sorting
  1463. ts.characterEquivalents = {
  1464. "a" : "\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5", // áàâãäąå
  1465. "A" : "\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5", // ÁÀÂÃÄĄÅ
  1466. "c" : "\u00e7\u0107\u010d", // çćč
  1467. "C" : "\u00c7\u0106\u010c", // ÇĆČ
  1468. "e" : "\u00e9\u00e8\u00ea\u00eb\u011b\u0119", // éèêëěę
  1469. "E" : "\u00c9\u00c8\u00ca\u00cb\u011a\u0118", // ÉÈÊËĚĘ
  1470. "i" : "\u00ed\u00ec\u0130\u00ee\u00ef\u0131", // íìİîïı
  1471. "I" : "\u00cd\u00cc\u0130\u00ce\u00cf", // ÍÌİÎÏ
  1472. "o" : "\u00f3\u00f2\u00f4\u00f5\u00f6", // óòôõö
  1473. "O" : "\u00d3\u00d2\u00d4\u00d5\u00d6", // ÓÒÔÕÖ
  1474. "ss": "\u00df", // ß (s sharp)
  1475. "SS": "\u1e9e", // ẞ (Capital sharp s)
  1476. "u" : "\u00fa\u00f9\u00fb\u00fc\u016f", // úùûüů
  1477. "U" : "\u00da\u00d9\u00db\u00dc\u016e" // ÚÙÛÜŮ
  1478. };
  1479. ts.replaceAccents = function(s) {
  1480. var a, acc = '[', eq = ts.characterEquivalents;
  1481. if (!ts.characterRegex) {
  1482. ts.characterRegexArray = {};
  1483. for (a in eq) {
  1484. if (typeof a === 'string') {
  1485. acc += eq[a];
  1486. ts.characterRegexArray[a] = new RegExp('[' + eq[a] + ']', 'g');
  1487. }
  1488. }
  1489. ts.characterRegex = new RegExp(acc + ']');
  1490. }
  1491. if (ts.characterRegex.test(s)) {
  1492. for (a in eq) {
  1493. if (typeof a === 'string') {
  1494. s = s.replace( ts.characterRegexArray[a], a );
  1495. }
  1496. }
  1497. }
  1498. return s;
  1499. };
  1500.  
  1501. // *** utilities ***
  1502. ts.isValueInArray = function(column, arry) {
  1503. var indx, len = arry.length;
  1504. for (indx = 0; indx < len; indx++) {
  1505. if (arry[indx][0] === column) {
  1506. return indx;
  1507. }
  1508. }
  1509. return -1;
  1510. };
  1511.  
  1512. ts.addParser = function(parser) {
  1513. var i, l = ts.parsers.length, a = true;
  1514. for (i = 0; i < l; i++) {
  1515. if (ts.parsers[i].id.toLowerCase() === parser.id.toLowerCase()) {
  1516. a = false;
  1517. }
  1518. }
  1519. if (a) {
  1520. ts.parsers.push(parser);
  1521. }
  1522. };
  1523.  
  1524. ts.getParserById = function(name) {
  1525. /*jshint eqeqeq:false */
  1526. if (name == 'false') { return false; }
  1527. var i, l = ts.parsers.length;
  1528. for (i = 0; i < l; i++) {
  1529. if (ts.parsers[i].id.toLowerCase() === (name.toString()).toLowerCase()) {
  1530. return ts.parsers[i];
  1531. }
  1532. }
  1533. return false;
  1534. };
  1535.  
  1536. ts.addWidget = function(widget) {
  1537. ts.widgets.push(widget);
  1538. };
  1539.  
  1540. ts.hasWidget = function(table, name){
  1541. table = $(table);
  1542. return table.length && table[0].config && table[0].config.widgetInit[name] || false;
  1543. };
  1544.  
  1545. ts.getWidgetById = function(name) {
  1546. var i, w, l = ts.widgets.length;
  1547. for (i = 0; i < l; i++) {
  1548. w = ts.widgets[i];
  1549. if (w && w.hasOwnProperty('id') && w.id.toLowerCase() === name.toLowerCase()) {
  1550. return w;
  1551. }
  1552. }
  1553. };
  1554.  
  1555. ts.applyWidget = function(table, init) {
  1556. table = $(table)[0]; // in case this is called externally
  1557. var c = table.config,
  1558. wo = c.widgetOptions,
  1559. widgets = [],
  1560. time, w, wd;
  1561. // prevent numerous consecutive widget applications
  1562. if (init !== false && table.hasInitialized && (table.isApplyingWidgets || table.isUpdating)) { return; }
  1563. if (c.debug) { time = new Date(); }
  1564. if (c.widgets.length) {
  1565. table.isApplyingWidgets = true;
  1566. // ensure unique widget ids
  1567. c.widgets = $.grep(c.widgets, function(v, k){
  1568. return $.inArray(v, c.widgets) === k;
  1569. });
  1570. // build widget array & add priority as needed
  1571. $.each(c.widgets || [], function(i,n){
  1572. wd = ts.getWidgetById(n);
  1573. if (wd && wd.id) {
  1574. // set priority to 10 if not defined
  1575. if (!wd.priority) { wd.priority = 10; }
  1576. widgets[i] = wd;
  1577. }
  1578. });
  1579. // sort widgets by priority
  1580. widgets.sort(function(a, b){
  1581. return a.priority < b.priority ? -1 : a.priority === b.priority ? 0 : 1;
  1582. });
  1583. // add/update selected widgets
  1584. $.each(widgets, function(i,w){
  1585. if (w) {
  1586. if (init || !(c.widgetInit[w.id])) {
  1587. // set init flag first to prevent calling init more than once (e.g. pager)
  1588. c.widgetInit[w.id] = true;
  1589. if (w.hasOwnProperty('options')) {
  1590. wo = table.config.widgetOptions = $.extend( true, {}, w.options, wo );
  1591. }
  1592. if (w.hasOwnProperty('init')) {
  1593. w.init(table, w, c, wo);
  1594. }
  1595. }
  1596. if (!init && w.hasOwnProperty('format')) {
  1597. w.format(table, c, wo, false);
  1598. }
  1599. }
  1600. });
  1601. }
  1602. setTimeout(function(){
  1603. table.isApplyingWidgets = false;
  1604. }, 0);
  1605. if (c.debug) {
  1606. w = c.widgets.length;
  1607. benchmark("Completed " + (init === true ? "initializing " : "applying ") + w + " widget" + (w !== 1 ? "s" : ""), time);
  1608. }
  1609. };
  1610.  
  1611. ts.refreshWidgets = function(table, doAll, dontapply) {
  1612. table = $(table)[0]; // see issue #243
  1613. var i, c = table.config,
  1614. cw = c.widgets,
  1615. w = ts.widgets, l = w.length;
  1616. // remove previous widgets
  1617. for (i = 0; i < l; i++){
  1618. if ( w[i] && w[i].id && (doAll || $.inArray( w[i].id, cw ) < 0) ) {
  1619. if (c.debug) { log( 'Refeshing widgets: Removing "' + w[i].id + '"' ); }
  1620. // only remove widgets that have been initialized - fixes #442
  1621. if (w[i].hasOwnProperty('remove') && c.widgetInit[w[i].id]) {
  1622. w[i].remove(table, c, c.widgetOptions);
  1623. c.widgetInit[w[i].id] = false;
  1624. }
  1625. }
  1626. }
  1627. if (dontapply !== true) {
  1628. ts.applyWidget(table, doAll);
  1629. }
  1630. };
  1631.  
  1632. // get sorter, string, empty, etc options for each column from
  1633. // jQuery data, metadata, header option or header class name ("sorter-false")
  1634. // priority = jQuery data > meta > headers option > header class name
  1635. ts.getData = function(h, ch, key) {
  1636. var val = '', $h = $(h), m, cl;
  1637. if (!$h.length) { return ''; }
  1638. m = $.metadata ? $h.metadata() : false;
  1639. cl = ' ' + ($h.attr('class') || '');
  1640. if (typeof $h.data(key) !== 'undefined' || typeof $h.data(key.toLowerCase()) !== 'undefined'){
  1641. // "data-lockedOrder" is assigned to "lockedorder"; but "data-locked-order" is assigned to "lockedOrder"
  1642. // "data-sort-initial-order" is assigned to "sortInitialOrder"
  1643. val += $h.data(key) || $h.data(key.toLowerCase());
  1644. } else if (m && typeof m[key] !== 'undefined') {
  1645. val += m[key];
  1646. } else if (ch && typeof ch[key] !== 'undefined') {
  1647. val += ch[key];
  1648. } else if (cl !== ' ' && cl.match(' ' + key + '-')) {
  1649. // include sorter class name "sorter-text", etc; now works with "sorter-my-custom-parser"
  1650. val = cl.match( new RegExp('\\s' + key + '-([\\w-]+)') )[1] || '';
  1651. }
  1652. return $.trim(val);
  1653. };
  1654.  
  1655. ts.formatFloat = function(s, table) {
  1656. if (typeof s !== 'string' || s === '') { return s; }
  1657. // allow using formatFloat without a table; defaults to US number format
  1658. var i,
  1659. t = table && table.config ? table.config.usNumberFormat !== false :
  1660. typeof table !== "undefined" ? table : true;
  1661. if (t) {
  1662. // US Format - 1,234,567.89 -> 1234567.89
  1663. s = s.replace(/,/g,'');
  1664. } else {
  1665. // German Format = 1.234.567,89 -> 1234567.89
  1666. // French Format = 1 234 567,89 -> 1234567.89
  1667. s = s.replace(/[\s|\.]/g,'').replace(/,/g,'.');
  1668. }
  1669. if(/^\s*\([.\d]+\)/.test(s)) {
  1670. // make (#) into a negative number -> (10) = -10
  1671. s = s.replace(/^\s*\(([.\d]+)\)/, '-$1');
  1672. }
  1673. i = parseFloat(s);
  1674. // return the text instead of zero
  1675. return isNaN(i) ? $.trim(s) : i;
  1676. };
  1677.  
  1678. ts.isDigit = function(s) {
  1679. // replace all unwanted chars and match
  1680. return isNaN(s) ? (/^[\-+(]?\d+[)]?$/).test(s.toString().replace(/[,.'"\s]/g, '')) : true;
  1681. };
  1682.  
  1683. }()
  1684. });
  1685.  
  1686. // make shortcut
  1687. var ts = $.tablesorter;
  1688.  
  1689. // extend plugin scope
  1690. $.fn.extend({
  1691. tablesorter: ts.construct
  1692. });
  1693.  
  1694. // add default parsers
  1695. ts.addParser({
  1696. id: 'no-parser',
  1697. is: function() {
  1698. return false;
  1699. },
  1700. format: function() {
  1701. return '';
  1702. },
  1703. type: 'text'
  1704. });
  1705.  
  1706. ts.addParser({
  1707. id: "text",
  1708. is: function() {
  1709. return true;
  1710. },
  1711. format: function(s, table) {
  1712. var c = table.config;
  1713. if (s) {
  1714. s = $.trim( c.ignoreCase ? s.toLocaleLowerCase() : s );
  1715. s = c.sortLocaleCompare ? ts.replaceAccents(s) : s;
  1716. }
  1717. return s;
  1718. },
  1719. type: "text"
  1720. });
  1721.  
  1722. ts.addParser({
  1723. id: "digit",
  1724. is: function(s) {
  1725. return ts.isDigit(s);
  1726. },
  1727. format: function(s, table) {
  1728. var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ""), table);
  1729. return s && typeof n === 'number' ? n : s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
  1730. },
  1731. type: "numeric"
  1732. });
  1733.  
  1734. ts.addParser({
  1735. id: "currency",
  1736. is: function(s) {
  1737. return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[+\-,. ]/g,'')); // £$€¤¥¢
  1738. },
  1739. format: function(s, table) {
  1740. var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ""), table);
  1741. return s && typeof n === 'number' ? n : s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
  1742. },
  1743. type: "numeric"
  1744. });
  1745.  
  1746. ts.addParser({
  1747. id: "ipAddress",
  1748. is: function(s) {
  1749. return (/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/).test(s);
  1750. },
  1751. format: function(s, table) {
  1752. var i, a = s ? s.split(".") : '',
  1753. r = "",
  1754. l = a.length;
  1755. for (i = 0; i < l; i++) {
  1756. r += ("00" + a[i]).slice(-3);
  1757. }
  1758. return s ? ts.formatFloat(r, table) : s;
  1759. },
  1760. type: "numeric"
  1761. });
  1762.  
  1763. ts.addParser({
  1764. id: "url",
  1765. is: function(s) {
  1766. return (/^(https?|ftp|file):\/\//).test(s);
  1767. },
  1768. format: function(s) {
  1769. return s ? $.trim(s.replace(/(https?|ftp|file):\/\//, '')) : s;
  1770. },
  1771. parsed : true, // filter widget flag
  1772. type: "text"
  1773. });
  1774.  
  1775. ts.addParser({
  1776. id: "isoDate",
  1777. is: function(s) {
  1778. return (/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/).test(s);
  1779. },
  1780. format: function(s, table) {
  1781. return s ? ts.formatFloat((s !== "") ? (new Date(s.replace(/-/g, "/")).getTime() || s) : "", table) : s;
  1782. },
  1783. type: "numeric"
  1784. });
  1785.  
  1786. ts.addParser({
  1787. id: "percent",
  1788. is: function(s) {
  1789. return (/(\d\s*?%|%\s*?\d)/).test(s) && s.length < 15;
  1790. },
  1791. format: function(s, table) {
  1792. return s ? ts.formatFloat(s.replace(/%/g, ""), table) : s;
  1793. },
  1794. type: "numeric"
  1795. });
  1796.  
  1797. ts.addParser({
  1798. id: "usLongDate",
  1799. is: function(s) {
  1800. // two digit years are not allowed cross-browser
  1801. // Jan 01, 2013 12:34:56 PM or 01 Jan 2013
  1802. 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) || (/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i).test(s);
  1803. },
  1804. format: function(s, table) {
  1805. return s ? ts.formatFloat( (new Date(s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || s), table) : s;
  1806. },
  1807. type: "numeric"
  1808. });
  1809.  
  1810. ts.addParser({
  1811. id: "shortDate", // "mmddyyyy", "ddmmyyyy" or "yyyymmdd"
  1812. is: function(s) {
  1813. // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
  1814. return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g," ").replace(/[\-.,]/g, "/"));
  1815. },
  1816. format: function(s, table, cell, cellIndex) {
  1817. if (s) {
  1818. var c = table.config,
  1819. ci = c.$headers.filter('[data-column=' + cellIndex + ']:last'),
  1820. format = ci.length && ci[0].dateFormat || ts.getData( ci, ts.getColumnData( table, c.headers, cellIndex ), 'dateFormat') || c.dateFormat;
  1821. s = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/"); // escaped - because JSHint in Firefox was showing it as an error
  1822. if (format === "mmddyyyy") {
  1823. s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2");
  1824. } else if (format === "ddmmyyyy") {
  1825. s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$2/$1");
  1826. } else if (format === "yyyymmdd") {
  1827. s = s.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$1/$2/$3");
  1828. }
  1829. }
  1830. return s ? ts.formatFloat( (new Date(s).getTime() || s), table) : s;
  1831. },
  1832. type: "numeric"
  1833. });
  1834.  
  1835. ts.addParser({
  1836. id: "time",
  1837. is: function(s) {
  1838. return (/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s);
  1839. },
  1840. format: function(s, table) {
  1841. return s ? ts.formatFloat( (new Date("2000/01/01 " + s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || s), table) : s;
  1842. },
  1843. type: "numeric"
  1844. });
  1845.  
  1846. ts.addParser({
  1847. id: "metadata",
  1848. is: function() {
  1849. return false;
  1850. },
  1851. format: function(s, table, cell) {
  1852. var c = table.config,
  1853. p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
  1854. return $(cell).metadata()[p];
  1855. },
  1856. type: "numeric"
  1857. });
  1858.  
  1859. // add default widgets
  1860. ts.addWidget({
  1861. id: "zebra",
  1862. priority: 90,
  1863. format: function(table, c, wo) {
  1864. var $tb, $tv, $tr, row, even, time, k,
  1865. child = new RegExp(c.cssChildRow, 'i'),
  1866. b = c.$tbodies;
  1867. if (c.debug) {
  1868. time = new Date();
  1869. }
  1870. for (k = 0; k < b.length; k++ ) {
  1871. // loop through the visible rows
  1872. row = 0;
  1873. $tb = b.eq(k);
  1874. $tv = $tb.children('tr:visible').not(c.selectorRemove);
  1875. // revered back to using jQuery each - strangely it's the fastest method
  1876. /*jshint loopfunc:true */
  1877. $tv.each(function(){
  1878. $tr = $(this);
  1879. // style child rows the same way the parent row was styled
  1880. if (!child.test(this.className)) { row++; }
  1881. even = (row % 2 === 0);
  1882. $tr.removeClass(wo.zebra[even ? 1 : 0]).addClass(wo.zebra[even ? 0 : 1]);
  1883. });
  1884. }
  1885. if (c.debug) {
  1886. ts.benchmark("Applying Zebra widget", time);
  1887. }
  1888. },
  1889. remove: function(table, c, wo){
  1890. var k, $tb,
  1891. b = c.$tbodies,
  1892. rmv = (wo.zebra || [ "even", "odd" ]).join(' ');
  1893. for (k = 0; k < b.length; k++ ){
  1894. $tb = $.tablesorter.processTbody(table, b.eq(k), true); // remove tbody
  1895. $tb.children().removeClass(rmv);
  1896. $.tablesorter.processTbody(table, $tb, false); // restore tbody
  1897. }
  1898. }
  1899. });
  1900.  
  1901. })(jQuery);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址