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