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