vendor: 4,333 bytes, lines 1-66
1/* eslint-disable */ 2(function(factory){if (typeof define === 'function' && define.amd){define(['jquery'], factory);} else if (typeof module === 'object' && typeof module.exports === 'object'){module.exports = factory(require('jquery'));} else {factory(jQuery);}}(function(jQuery){ 3 /*! TableSorter (FORK) v2.31.3 *//* 4 * Client-side table sorting with ease! 5 * @requires jQuery v1.2.6+ 6 * 7 * Copyright (c) 2007 Christian Bach 8 * fork maintained by Rob Garrison 9 * 10 * Examples and original docs at: http://tablesorter.com 11 * Dual licensed under the MIT and GPL licenses: 12 * http://www.opensource.org/licenses/mit-license.php 13 * http://www.gnu.org/licenses/gpl.html 14 * 15 * @type jQuery 16 * @name tablesorter (FORK) 17 * @cat Plugins/Tablesorter 18 * @author Christian Bach - [email protected] 19 * @contributor Rob Garrison - https://github.com/Mottie/tablesorter 20 * @docs (fork) - https://mottie.github.io/tablesorter/docs/ 21 */ 22 /*jshint browser:true, jquery:true, unused:false, expr: true */ 23 ;( function( $ ) { 24 'use strict'; 25 var ts = $.tablesorter = { 26 27 version : '2.31.3', 28 29 parsers : [], 30 widgets : [], 31 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 resort : true, // default setting to trigger a resort after an 'update', 'addRows', 'updateCell', etc has completed 52 53 // *** sort options 54 headers : {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc. 55 ignoreCase : true, // ignore case while sorting 56 sortForce : null, // column(s) first sorted; always applied 57 sortList : [], // Initial sort order; applied initially; updated when manually sorted 58 sortAppend : null, // column(s) sorted last; always applied 59 sortStable : false, // when sorting two rows with exactly the same content, the original sort order is maintained 60 61 sortInitialOrder : 'asc', // sort direction on first click 62 sortLocaleCompare: false, // replace equivalent character (accented characters) 63 sortReset : false, // third click on the header will reset column to default - unsorted 64 sortRestart : false, // restart sort to 'sortInitialOrder' when clicking on previously unsorted columns 65 66 emptyTo : 'bottom', // sort empty cell to bottom, top, none, zero, emptyMax, emptyMin
67 stringTo : 'max', // sort strings in numerical column as max, min, top, bottom, zero 68 duplicateSpan : true, // colspan cells in the tbody will have duplicated content in the cache for each spanned column 69 textExtraction : 'basic', // text extraction method/function - function( node, table, cellIndex ) {} 70 textAttribute : 'data-text',// data-attribute that contains alternate cell text (used in default textExtraction function) 71 textSorter : null, // choose overall or specific column sorter function( a, b, direction, table, columnIndex ) [alt: ts.sortText] 72 numberSorter : null, // choose overall numeric sorter function( a, b, direction, maxColumnValue ) 73 74 // *** widget options 75 initWidgets : true, // apply widgets on tablesorter initialization 76 widgetClass : 'widget-{name}', // table class name template to match to include a widget 77 widgets : [], // method to add widgets, e.g. widgets: ['zebra'] 78 widgetOptions : { 79 zebra : [ 'even', 'odd' ] // zebra widget alternating row class names 80 }, 81 82 // *** callbacks 83 initialized : null, // function( table ) {}, 84 85 // *** extra css class names 86 tableClass : '', 87 cssAsc : '', 88 cssDesc : '', 89 cssNone : '', 90 cssHeader : '', 91 cssHeaderRow : '', 92 cssProcessing : '', // processing icon applied to header during sort/filter 93 94 cssChildRow : 'tablesorter-childRow', // class name indiciating that a row is to be attached to its parent 95 cssInfoBlock : 'tablesorter-infoOnly', // don't sort tbody with this class name (only one class name allowed here!) 96 cssNoSort : 'tablesorter-noSort', // class name added to element inside header; clicking on it won't cause a sort 97 cssIgnoreRow : 'tablesorter-ignoreRow',// header row to ignore; cells within this row will not be added to c.$headers 98 99 cssIcon : 'tablesorter-icon', // if this class does not exist, the {icon} will not be added from the headerTemplate 100 cssIconNone : '', // class name added to the icon when there is no column sort 101 cssIconAsc : '', // class name added to the icon when the column has an ascending sort 102 cssIconDesc : '', // class name added to the icon when the column has a descending sort 103 cssIconDisabled : '', // class name added to the icon when the column has a disabled sort 104 105 // *** events 106 pointerClick : 'click', 107 pointerDown : 'mousedown', 108 pointerUp : 'mouseup', 109 110 // *** selectors 111 selectorHeaders : '> thead th, > thead td', 112 selectorSort : 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort 113 selectorRemove : '.remove-me', 114 115 // *** advanced 116 debug : false, 117 118 // *** Internal variables 119 headerList: [], 120 empties: {}, 121 strings: {}, 122 parsers: [], 123 124 // *** parser options for validator; values must be falsy! 125 globalize: 0, 126 imgAttr: 0 127 128 // removed: widgetZebra: { css: ['even', 'odd'] } 129 130 }, 131 132 // internal css classes - these will ALWAYS be added to 133 // the table and MUST only contain one class name - fixes #381 134 css : { 135 table : 'tablesorter', 136 cssHasChild: 'tablesorter-hasChildRow', 137 childRow : 'tablesorter-childRow', 138 colgroup : 'tablesorter-colgroup', 139 header : 'tablesorter-header', 140 headerRow : 'tablesorter-headerRow', 141 headerIn : 'tablesorter-header-inner', 142 icon : 'tablesorter-icon', 143 processing : 'tablesorter-processing', 144 sortAsc : 'tablesorter-headerAsc', 145 sortDesc : 'tablesorter-headerDesc', 146 sortNone : 'tablesorter-headerUnSorted' 147 }, 148 149 // labels applied to sortable headers for accessibility (aria) support 150 language : { 151 sortAsc : 'Ascending sort applied, ',
vendor: 146,133 bytes, lines 152-2918
152 sortDesc : 'Descending sort applied, ', 153 sortNone : 'No sort applied, ', 154 sortDisabled : 'sorting is disabled', 155 nextAsc : 'activate to apply an ascending sort', 156 nextDesc : 'activate to apply a descending sort', 157 nextNone : 'activate to remove the sort' 158 }, 159 160 regex : { 161 templateContent : /\{content\}/g, 162 templateIcon : /\{icon\}/g, 163 templateName : /\{name\}/i, 164 spaces : /\s+/g, 165 nonWord : /\W/g, 166 formElements : /(input|select|button|textarea)/i, 167 168 // *** sort functions *** 169 // regex used in natural sort 170 // chunk/tokenize numbers & letters 171 chunk : /(^([+\-]?(?:\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi, 172 // replace chunks @ ends 173 chunks : /(^\\0|\\0$)/, 174 hex : /^0x[0-9a-f]+$/i, 175 176 // *** formatFloat *** 177 comma : /,/g, 178 digitNonUS : /[\s|\.]/g, 179 digitNegativeTest : /^\s*\([.\d]+\)/, 180 digitNegativeReplace : /^\s*\(([.\d]+)\)/, 181 182 // *** isDigit *** 183 digitTest : /^[\-+(]?\d+[)]?$/, 184 digitReplace : /[,.'"\s]/g 185 186 }, 187 188 // digit sort, text location 189 string : { 190 max : 1, 191 min : -1, 192 emptymin : 1, 193 emptymax : -1, 194 zero : 0, 195 none : 0, 196 'null' : 0, 197 top : true, 198 bottom : false 199 }, 200 201 keyCodes : { 202 enter : 13 203 }, 204 205 // placeholder date parser data (globalize) 206 dates : {}, 207 208 // These methods can be applied on table.config instance 209 instanceMethods : {}, 210 211 /* 212 ââââââ ââââââ ââââââ ââ ââ ââââââ 213 âââ ââââ ââ ââ ââ ââââââ 214 âââ ââââ ââ ââ ââ âââââ 215 ââââââ ââââââ ââ ââââââ ââ 216 */ 217 218 setup : function( table, c ) { 219 // if no thead or tbody, or tablesorter is already present, quit 220 if ( !table || !table.tHead || table.tBodies.length === 0 || table.hasInitialized === true ) { 221 if ( ts.debug(c, 'core') ) { 222 if ( table.hasInitialized ) { 223 console.warn( 'Stopping initialization. Tablesorter has already been initialized' ); 224 } else { 225 console.error( 'Stopping initialization! No table, thead or tbody', table ); 226 } 227 } 228 return; 229 } 230 231 var tmp = '', 232 $table = $( table ), 233 meta = $.metadata; 234 // initialization flag 235 table.hasInitialized = false; 236 // table is being processed flag 237 table.isProcessing = true; 238 // make sure to store the config object 239 table.config = c; 240 // save the settings where they read 241 $.data( table, 'tablesorter', c ); 242 if ( ts.debug(c, 'core') ) { 243 console[ console.group ? 'group' : 'log' ]( 'Initializing tablesorter v' + ts.version ); 244 $.data( table, 'startoveralltimer', new Date() ); 245 } 246 247 // removing this in version 3 (only supports jQuery 1.7+) 248 c.supportsDataObject = ( function( version ) { 249 version[ 0 ] = parseInt( version[ 0 ], 10 ); 250 return ( version[ 0 ] > 1 ) || ( version[ 0 ] === 1 && parseInt( version[ 1 ], 10 ) >= 4 ); 251 })( $.fn.jquery.split( '.' ) ); 252 // ensure case insensitivity 253 c.emptyTo = c.emptyTo.toLowerCase(); 254 c.stringTo = c.stringTo.toLowerCase(); 255 c.last = { sortList : [], clickedIndex : -1 }; 256 // add table theme class only if there isn't already one there 257 if ( !/tablesorter\-/.test( $table.attr( 'class' ) ) ) { 258 tmp = ( c.theme !== '' ? ' tablesorter-' + c.theme : '' ); 259 } 260 261 // give the table a unique id, which will be used in namespace binding 262 if ( !c.namespace ) { 263 c.namespace = '.tablesorter' + Math.random().toString( 16 ).slice( 2 ); 264 } else { 265 // make sure namespace starts with a period & doesn't have weird characters 266 c.namespace = '.' + c.namespace.replace( ts.regex.nonWord, '' ); 267 } 268 269 c.table = table; 270 c.$table = $table 271 // add namespace to table to allow bindings on extra elements to target 272 // the parent table (e.g. parser-input-select) 273 .addClass( ts.css.table + ' ' + c.tableClass + tmp + ' ' + c.namespace.slice(1) ) 274 .attr( 'role', 'grid' ); 275 c.$headers = $table.find( c.selectorHeaders ); 276 277 c.$table.children().children( 'tr' ).attr( 'role', 'row' ); 278 c.$tbodies = $table.children( 'tbody:not(.' + c.cssInfoBlock + ')' ).attr({ 279 'aria-live' : 'polite', 280 'aria-relevant' : 'all' 281 }); 282 if ( c.$table.children( 'caption' ).length ) { 283 tmp = c.$table.children( 'caption' )[ 0 ]; 284 if ( !tmp.id ) { tmp.id = c.namespace.slice( 1 ) + 'caption'; } 285 c.$table.attr( 'aria-labelledby', tmp.id ); 286 } 287 c.widgetInit = {}; // keep a list of initialized widgets 288 // change textExtraction via data-attribute 289 c.textExtraction = c.$table.attr( 'data-text-extraction' ) || c.textExtraction || 'basic'; 290 // build headers 291 ts.buildHeaders( c ); 292 // fixate columns if the users supplies the fixedWidth option 293 // do this after theme has been applied 294 ts.fixColumnWidth( table ); 295 // add widgets from class name 296 ts.addWidgetFromClass( table ); 297 // add widget options before parsing (e.g. grouping widget has parser settings) 298 ts.applyWidgetOptions( table ); 299 // try to auto detect column type, and store in tables config 300 ts.setupParsers( c ); 301 // start total row count at zero 302 c.totalRows = 0; 303 // only validate options while debugging. See #1528 304 if (c.debug) { 305 ts.validateOptions( c ); 306 } 307 // build the cache for the tbody cells 308 // delayInit will delay building the cache until the user starts a sort 309 if ( !c.delayInit ) { ts.buildCache( c ); } 310 // bind all header events and methods 311 ts.bindEvents( table, c.$headers, true ); 312 ts.bindMethods( c ); 313 // get sort list from jQuery data or metadata 314 // in jQuery < 1.4, an error occurs when calling $table.data() 315 if ( c.supportsDataObject && typeof $table.data().sortlist !== 'undefined' ) { 316 c.sortList = $table.data().sortlist; 317 } else if ( meta && ( $table.metadata() && $table.metadata().sortlist ) ) { 318 c.sortList = $table.metadata().sortlist; 319 } 320 // apply widget init code 321 ts.applyWidget( table, true ); 322 // if user has supplied a sort list to constructor 323 if ( c.sortList.length > 0 ) { 324 // save sortList before any sortAppend is added 325 c.last.sortList = c.sortList; 326 ts.sortOn( c, c.sortList, {}, !c.initWidgets ); 327 } else { 328 ts.setHeadersCss( c ); 329 if ( c.initWidgets ) { 330 // apply widget format 331 ts.applyWidget( table, false ); 332 } 333 } 334 335 // show processesing icon 336 if ( c.showProcessing ) { 337 $table 338 .unbind( 'sortBegin' + c.namespace + ' sortEnd' + c.namespace ) 339 .bind( 'sortBegin' + c.namespace + ' sortEnd' + c.namespace, function( e ) { 340 clearTimeout( c.timerProcessing ); 341 ts.isProcessing( table ); 342 if ( e.type === 'sortBegin' ) { 343 c.timerProcessing = setTimeout( function() { 344 ts.isProcessing( table, true ); 345 }, 500 ); 346 } 347 }); 348 } 349 350 // initialized 351 table.hasInitialized = true; 352 table.isProcessing = false; 353 if ( ts.debug(c, 'core') ) { 354 console.log( 'Overall initialization time:' + ts.benchmark( $.data( table, 'startoveralltimer' ) ) ); 355 if ( ts.debug(c, 'core') && console.groupEnd ) { console.groupEnd(); } 356 } 357 $table.triggerHandler( 'tablesorter-initialized', table ); 358 if ( typeof c.initialized === 'function' ) { 359 c.initialized( table ); 360 } 361 }, 362 363 bindMethods : function( c ) { 364 var $table = c.$table, 365 namespace = c.namespace, 366 events = ( 'sortReset update updateRows updateAll updateHeaders addRows updateCell updateComplete ' + 367 'sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup ' + 368 'mouseleave ' ).split( ' ' ) 369 .join( namespace + ' ' ); 370 // apply easy methods that trigger bound events 371 $table 372 .unbind( events.replace( ts.regex.spaces, ' ' ) ) 373 .bind( 'sortReset' + namespace, function( e, callback ) { 374 e.stopPropagation(); 375 // using this.config to ensure functions are getting a non-cached version of the config 376 ts.sortReset( this.config, function( table ) { 377 if (table.isApplyingWidgets) { 378 // multiple triggers in a row... filterReset, then sortReset - see #1361 379 // wait to update widgets 380 setTimeout( function() { 381 ts.applyWidget( table, '', callback ); 382 }, 100 ); 383 } else { 384 ts.applyWidget( table, '', callback ); 385 } 386 }); 387 }) 388 .bind( 'updateAll' + namespace, function( e, resort, callback ) { 389 e.stopPropagation(); 390 ts.updateAll( this.config, resort, callback ); 391 }) 392 .bind( 'update' + namespace + ' updateRows' + namespace, function( e, resort, callback ) { 393 e.stopPropagation(); 394 ts.update( this.config, resort, callback ); 395 }) 396 .bind( 'updateHeaders' + namespace, function( e, callback ) { 397 e.stopPropagation(); 398 ts.updateHeaders( this.config, callback ); 399 }) 400 .bind( 'updateCell' + namespace, function( e, cell, resort, callback ) { 401 e.stopPropagation(); 402 ts.updateCell( this.config, cell, resort, callback ); 403 }) 404 .bind( 'addRows' + namespace, function( e, $row, resort, callback ) { 405 e.stopPropagation(); 406 ts.addRows( this.config, $row, resort, callback ); 407 }) 408 .bind( 'updateComplete' + namespace, function() { 409 this.isUpdating = false; 410 }) 411 .bind( 'sorton' + namespace, function( e, list, callback, init ) { 412 e.stopPropagation(); 413 ts.sortOn( this.config, list, callback, init ); 414 }) 415 .bind( 'appendCache' + namespace, function( e, callback, init ) { 416 e.stopPropagation(); 417 ts.appendCache( this.config, init ); 418 if ( $.isFunction( callback ) ) { 419 callback( this ); 420 } 421 }) 422 // $tbodies variable is used by the tbody sorting widget 423 .bind( 'updateCache' + namespace, function( e, callback, $tbodies ) { 424 e.stopPropagation(); 425 ts.updateCache( this.config, callback, $tbodies ); 426 }) 427 .bind( 'applyWidgetId' + namespace, function( e, id ) { 428 e.stopPropagation(); 429 ts.applyWidgetId( this, id ); 430 }) 431 .bind( 'applyWidgets' + namespace, function( e, callback ) { 432 e.stopPropagation(); 433 // apply widgets (false = not initializing) 434 ts.applyWidget( this, false, callback ); 435 }) 436 .bind( 'refreshWidgets' + namespace, function( e, all, dontapply ) { 437 e.stopPropagation(); 438 ts.refreshWidgets( this, all, dontapply ); 439 }) 440 .bind( 'removeWidget' + namespace, function( e, name, refreshing ) { 441 e.stopPropagation(); 442 ts.removeWidget( this, name, refreshing ); 443 }) 444 .bind( 'destroy' + namespace, function( e, removeClasses, callback ) { 445 e.stopPropagation(); 446 ts.destroy( this, removeClasses, callback ); 447 }) 448 .bind( 'resetToLoadState' + namespace, function( e ) { 449 e.stopPropagation(); 450 // remove all widgets 451 ts.removeWidget( this, true, false ); 452 var tmp = $.extend( true, {}, c.originalSettings ); 453 // restore original settings; this clears out current settings, but does not clear 454 // values saved to storage. 455 c = $.extend( true, {}, ts.defaults, tmp ); 456 c.originalSettings = tmp; 457 this.hasInitialized = false; 458 // setup the entire table again 459 ts.setup( this, c ); 460 }); 461 }, 462 463 bindEvents : function( table, $headers, core ) { 464 table = $( table )[ 0 ]; 465 var tmp, 466 c = table.config, 467 namespace = c.namespace, 468 downTarget = null; 469 if ( core !== true ) { 470 $headers.addClass( namespace.slice( 1 ) + '_extra_headers' ); 471 tmp = ts.getClosest( $headers, 'table' ); 472 if ( tmp.length && tmp[ 0 ].nodeName === 'TABLE' && tmp[ 0 ] !== table ) { 473 $( tmp[ 0 ] ).addClass( namespace.slice( 1 ) + '_extra_table' ); 474 } 475 } 476 tmp = ( c.pointerDown + ' ' + c.pointerUp + ' ' + c.pointerClick + ' sort keyup ' ) 477 .replace( ts.regex.spaces, ' ' ) 478 .split( ' ' ) 479 .join( namespace + ' ' ); 480 // apply event handling to headers and/or additional headers (stickyheaders, scroller, etc) 481 $headers 482 // http://stackoverflow.com/questions/5312849/jquery-find-self; 483 .find( c.selectorSort ) 484 .add( $headers.filter( c.selectorSort ) ) 485 .unbind( tmp ) 486 .bind( tmp, function( e, external ) { 487 var $cell, cell, temp, 488 $target = $( e.target ), 489 // wrap event type in spaces, so the match doesn't trigger on inner words 490 type = ' ' + e.type + ' '; 491 // only recognize left clicks 492 if ( ( ( e.which || e.button ) !== 1 && !type.match( ' ' + c.pointerClick + ' | sort | keyup ' ) ) || 493 // allow pressing enter 494 ( type === ' keyup ' && e.which !== ts.keyCodes.enter ) || 495 // allow triggering a click event (e.which is undefined) & ignore physical clicks 496 ( type.match( ' ' + c.pointerClick + ' ' ) && typeof e.which !== 'undefined' ) ) { 497 return; 498 } 499 // ignore mouseup if mousedown wasn't on the same target 500 if ( type.match( ' ' + c.pointerUp + ' ' ) && downTarget !== e.target && external !== true ) { 501 return; 502 } 503 // set target on mousedown 504 if ( type.match( ' ' + c.pointerDown + ' ' ) ) { 505 downTarget = e.target; 506 // preventDefault needed or jQuery v1.3.2 and older throws an 507 // "Uncaught TypeError: handler.apply is not a function" error 508 temp = $target.jquery.split( '.' ); 509 if ( temp[ 0 ] === '1' && temp[ 1 ] < 4 ) { e.preventDefault(); } 510 return; 511 } 512 downTarget = null; 513 $cell = ts.getClosest( $( this ), '.' + ts.css.header ); 514 // prevent sort being triggered on form elements 515 if ( ts.regex.formElements.test( e.target.nodeName ) || 516 // nosort class name, or elements within a nosort container 517 $target.hasClass( c.cssNoSort ) || $target.parents( '.' + c.cssNoSort ).length > 0 || 518 // disabled cell directly clicked 519 $cell.hasClass( 'sorter-false' ) || 520 // elements within a button 521 $target.parents( 'button' ).length > 0 ) { 522 return !c.cancelSelection; 523 } 524 if ( c.delayInit && ts.isEmptyObject( c.cache ) ) { 525 ts.buildCache( c ); 526 } 527 // use column index from data-attribute or index of current row; fixes #1116 528 c.last.clickedIndex = $cell.attr( 'data-column' ) || $cell.index(); 529 cell = c.$headerIndexed[ c.last.clickedIndex ][0]; 530 if ( cell && !cell.sortDisabled ) { 531 ts.initSort( c, cell, e ); 532 } 533 }); 534 if ( c.cancelSelection ) { 535 // cancel selection 536 $headers 537 .attr( 'unselectable', 'on' ) 538 .bind( 'selectstart', false ) 539 .css({ 540 'user-select' : 'none', 541 'MozUserSelect' : 'none' // not needed for jQuery 1.8+ 542 }); 543 } 544 }, 545 546 buildHeaders : function( c ) { 547 var $temp, icon, timer, indx; 548 c.headerList = []; 549 c.headerContent = []; 550 c.sortVars = []; 551 if ( ts.debug(c, 'core') ) { 552 timer = new Date(); 553 } 554 // children tr in tfoot - see issue #196 & #547 555 // don't pass table.config to computeColumnIndex here - widgets (math) pass it to "quickly" index tbody cells 556 c.columns = ts.computeColumnIndex( c.$table.children( 'thead, tfoot' ).children( 'tr' ) ); 557 // add icon if cssIcon option exists 558 icon = c.cssIcon ? 559 '<i class="' + ( c.cssIcon === ts.css.icon ? ts.css.icon : c.cssIcon + ' ' + ts.css.icon ) + '"></i>' : 560 ''; 561 // redefine c.$headers here in case of an updateAll that replaces or adds an entire header cell - see #683 562 c.$headers = $( $.map( c.$table.find( c.selectorHeaders ), function( elem, index ) { 563 var configHeaders, header, column, template, tmp, 564 $elem = $( elem ); 565 // ignore cell (don't add it to c.$headers) if row has ignoreRow class 566 if ( ts.getClosest( $elem, 'tr' ).hasClass( c.cssIgnoreRow ) ) { return; } 567 // transfer data-column to element if not th/td - #1459 568 if ( !/(th|td)/i.test( elem.nodeName ) ) { 569 tmp = ts.getClosest( $elem, 'th, td' ); 570 $elem.attr( 'data-column', tmp.attr( 'data-column' ) ); 571 } 572 // make sure to get header cell & not column indexed cell 573 configHeaders = ts.getColumnData( c.table, c.headers, index, true ); 574 // save original header content 575 c.headerContent[ index ] = $elem.html(); 576 // if headerTemplate is empty, don't reformat the header cell 577 if ( c.headerTemplate !== '' && !$elem.find( '.' + ts.css.headerIn ).length ) { 578 // set up header template 579 template = c.headerTemplate 580 .replace( ts.regex.templateContent, $elem.html() ) 581 .replace( ts.regex.templateIcon, $elem.find( '.' + ts.css.icon ).length ? '' : icon ); 582 if ( c.onRenderTemplate ) { 583 header = c.onRenderTemplate.apply( $elem, [ index, template ] ); 584 // only change t if something is returned 585 if ( header && typeof header === 'string' ) { 586 template = header; 587 } 588 } 589 $elem.html( '<div class="' + ts.css.headerIn + '">' + template + '</div>' ); // faster than wrapInner 590 } 591 if ( c.onRenderHeader ) { 592 c.onRenderHeader.apply( $elem, [ index, c, c.$table ] ); 593 } 594 column = parseInt( $elem.attr( 'data-column' ), 10 ); 595 elem.column = column; 596 tmp = ts.getOrder( ts.getData( $elem, configHeaders, 'sortInitialOrder' ) || c.sortInitialOrder ); 597 // this may get updated numerous times if there are multiple rows 598 c.sortVars[ column ] = { 599 count : -1, // set to -1 because clicking on the header automatically adds one 600 order : tmp ? 601 ( c.sortReset ? [ 1, 0, 2 ] : [ 1, 0 ] ) : // desc, asc, unsorted 602 ( c.sortReset ? [ 0, 1, 2 ] : [ 0, 1 ] ), // asc, desc, unsorted 603 lockedOrder : false, 604 sortedBy : '' 605 }; 606 tmp = ts.getData( $elem, configHeaders, 'lockedOrder' ) || false; 607 if ( typeof tmp !== 'undefined' && tmp !== false ) { 608 c.sortVars[ column ].lockedOrder = true; 609 c.sortVars[ column ].order = ts.getOrder( tmp ) ? [ 1, 1 ] : [ 0, 0 ]; 610 } 611 // add cell to headerList 612 c.headerList[ index ] = elem; 613 $elem.addClass( ts.css.header + ' ' + c.cssHeader ); 614 // add to parent in case there are multiple rows 615 ts.getClosest( $elem, 'tr' ) 616 .addClass( ts.css.headerRow + ' ' + c.cssHeaderRow ) 617 .attr( 'role', 'row' ); 618 // allow keyboard cursor to focus on element 619 if ( c.tabIndex ) { 620 $elem.attr( 'tabindex', 0 ); 621 } 622 return elem; 623 }) ); 624 // cache headers per column 625 c.$headerIndexed = []; 626 for ( indx = 0; indx < c.columns; indx++ ) { 627 // colspan in header making a column undefined 628 if ( ts.isEmptyObject( c.sortVars[ indx ] ) ) { 629 c.sortVars[ indx ] = {}; 630 } 631 // Use c.$headers.parent() in case selectorHeaders doesn't point to the th/td 632 $temp = c.$headers.filter( '[data-column="' + indx + '"]' ); 633 // target sortable column cells, unless there are none, then use non-sortable cells 634 // .last() added in jQuery 1.4; use .filter(':last') to maintain compatibility with jQuery v1.2.6 635 c.$headerIndexed[ indx ] = $temp.length ? 636 $temp.not( '.sorter-false' ).length ? 637 $temp.not( '.sorter-false' ).filter( ':last' ) : 638 $temp.filter( ':last' ) : 639 $(); 640 } 641 c.$table.find( c.selectorHeaders ).attr({ 642 scope: 'col', 643 role : 'columnheader' 644 }); 645 // enable/disable sorting 646 ts.updateHeader( c ); 647 if ( ts.debug(c, 'core') ) { 648 console.log( 'Built headers:' + ts.benchmark( timer ) ); 649 console.log( c.$headers ); 650 } 651 }, 652 653 // Use it to add a set of methods to table.config which will be available for all tables. 654 // This should be done before table initialization 655 addInstanceMethods : function( methods ) { 656 $.extend( ts.instanceMethods, methods ); 657 }, 658 659 /* 660 ââââââ ââââââ ââââââ ââââââ ââââââ ââââââ ââââââ 661 ââââââ ââââââ ââââââ âââ ââââ ââââââ âââ 662 âââââ ââââââ âââââ âââ ââââ âââââ âââ 663 ââ ââ ââ ââ ââ ââââââ ââââââ ââ ââ ââââââ 664 */ 665 setupParsers : function( c, $tbodies ) { 666 var rows, list, span, max, colIndex, indx, header, configHeaders, 667 noParser, parser, extractor, time, tbody, len, 668 table = c.table, 669 tbodyIndex = 0, 670 debug = ts.debug(c, 'core'), 671 debugOutput = {}; 672 // update table bodies in case we start with an empty table 673 c.$tbodies = c.$table.children( 'tbody:not(.' + c.cssInfoBlock + ')' ); 674 tbody = typeof $tbodies === 'undefined' ? c.$tbodies : $tbodies; 675 len = tbody.length; 676 if ( len === 0 ) { 677 return debug ? console.warn( 'Warning: *Empty table!* Not building a parser cache' ) : ''; 678 } else if ( debug ) { 679 time = new Date(); 680 console[ console.group ? 'group' : 'log' ]( 'Detecting parsers for each column' ); 681 } 682 list = { 683 extractors: [], 684 parsers: [] 685 }; 686 while ( tbodyIndex < len ) { 687 rows = tbody[ tbodyIndex ].rows; 688 if ( rows.length ) { 689 colIndex = 0; 690 max = c.columns; 691 for ( indx = 0; indx < max; indx++ ) { 692 header = c.$headerIndexed[ colIndex ]; 693 if ( header && header.length ) { 694 // get column indexed table cell; adding true parameter fixes #1362 but 695 // it would break backwards compatibility... 696 configHeaders = ts.getColumnData( table, c.headers, colIndex ); // , true ); 697 // get column parser/extractor 698 extractor = ts.getParserById( ts.getData( header, configHeaders, 'extractor' ) ); 699 parser = ts.getParserById( ts.getData( header, configHeaders, 'sorter' ) ); 700 noParser = ts.getData( header, configHeaders, 'parser' ) === 'false'; 701 // empty cells behaviour - keeping emptyToBottom for backwards compatibility 702 c.empties[colIndex] = ( 703 ts.getData( header, configHeaders, 'empty' ) || 704 c.emptyTo || ( c.emptyToBottom ? 'bottom' : 'top' ) ).toLowerCase(); 705 // text strings behaviour in numerical sorts 706 c.strings[colIndex] = ( 707 ts.getData( header, configHeaders, 'string' ) || 708 c.stringTo || 709 'max' ).toLowerCase(); 710 if ( noParser ) { 711 parser = ts.getParserById( 'no-parser' ); 712 } 713 if ( !extractor ) { 714 // For now, maybe detect someday 715 extractor = false; 716 } 717 if ( !parser ) { 718 parser = ts.detectParserForColumn( c, rows, -1, colIndex ); 719 } 720 if ( debug ) { 721 debugOutput[ '(' + colIndex + ') ' + header.text() ] = { 722 parser : parser.id, 723 extractor : extractor ? extractor.id : 'none', 724 string : c.strings[ colIndex ], 725 empty : c.empties[ colIndex ] 726 }; 727 } 728 list.parsers[ colIndex ] = parser; 729 list.extractors[ colIndex ] = extractor; 730 span = header[ 0 ].colSpan - 1; 731 if ( span > 0 ) { 732 colIndex += span; 733 max += span; 734 while ( span + 1 > 0 ) { 735 // set colspan columns to use the same parsers & extractors 736 list.parsers[ colIndex - span ] = parser; 737 list.extractors[ colIndex - span ] = extractor; 738 span--; 739 } 740 } 741 } 742 colIndex++; 743 } 744 } 745 tbodyIndex += ( list.parsers.length ) ? len : 1; 746 } 747 if ( debug ) { 748 if ( !ts.isEmptyObject( debugOutput ) ) { 749 console[ console.table ? 'table' : 'log' ]( debugOutput ); 750 } else { 751 console.warn( ' No parsers detected!' ); 752 } 753 console.log( 'Completed detecting parsers' + ts.benchmark( time ) ); 754 if ( console.groupEnd ) { console.groupEnd(); } 755 } 756 c.parsers = list.parsers; 757 c.extractors = list.extractors; 758 }, 759 760 addParser : function( parser ) { 761 var indx, 762 len = ts.parsers.length, 763 add = true; 764 for ( indx = 0; indx < len; indx++ ) { 765 if ( ts.parsers[ indx ].id.toLowerCase() === parser.id.toLowerCase() ) { 766 add = false; 767 } 768 } 769 if ( add ) { 770 ts.parsers[ ts.parsers.length ] = parser; 771 } 772 }, 773 774 getParserById : function( name ) { 775 /*jshint eqeqeq:false */ // eslint-disable-next-line eqeqeq 776 if ( name == 'false' ) { return false; } 777 var indx, 778 len = ts.parsers.length; 779 for ( indx = 0; indx < len; indx++ ) { 780 if ( ts.parsers[ indx ].id.toLowerCase() === ( name.toString() ).toLowerCase() ) { 781 return ts.parsers[ indx ]; 782 } 783 } 784 return false; 785 }, 786 787 detectParserForColumn : function( c, rows, rowIndex, cellIndex ) { 788 var cur, $node, row, 789 indx = ts.parsers.length, 790 node = false, 791 nodeValue = '', 792 debug = ts.debug(c, 'core'), 793 keepLooking = true; 794 while ( nodeValue === '' && keepLooking ) { 795 rowIndex++; 796 row = rows[ rowIndex ]; 797 // stop looking after 50 empty rows 798 if ( row && rowIndex < 50 ) { 799 if ( row.className.indexOf( ts.cssIgnoreRow ) < 0 ) { 800 node = rows[ rowIndex ].cells[ cellIndex ]; 801 nodeValue = ts.getElementText( c, node, cellIndex ); 802 $node = $( node ); 803 if ( debug ) { 804 console.log( 'Checking if value was empty on row ' + rowIndex + ', column: ' + 805 cellIndex + ': "' + nodeValue + '"' ); 806 } 807 } 808 } else { 809 keepLooking = false; 810 } 811 } 812 while ( --indx >= 0 ) { 813 cur = ts.parsers[ indx ]; 814 // ignore the default text parser because it will always be true 815 if ( cur && cur.id !== 'text' && cur.is && cur.is( nodeValue, c.table, node, $node ) ) { 816 return cur; 817 } 818 } 819 // nothing found, return the generic parser (text) 820 return ts.getParserById( 'text' ); 821 }, 822 823 getElementText : function( c, node, cellIndex ) { 824 if ( !node ) { return ''; } 825 var tmp, 826 extract = c.textExtraction || '', 827 // node could be a jquery object 828 // http://jsperf.com/jquery-vs-instanceof-jquery/2 829 $node = node.jquery ? node : $( node ); 830 if ( typeof extract === 'string' ) { 831 // check data-attribute first when set to 'basic'; don't use node.innerText - it's really slow! 832 // http://www.kellegous.com/j/2013/02/27/innertext-vs-textcontent/ 833 if ( extract === 'basic' && typeof ( tmp = $node.attr( c.textAttribute ) ) !== 'undefined' ) { 834 return $.trim( tmp ); 835 } 836 return $.trim( node.textContent || $node.text() ); 837 } else { 838 if ( typeof extract === 'function' ) { 839 return $.trim( extract( $node[ 0 ], c.table, cellIndex ) ); 840 } else if ( typeof ( tmp = ts.getColumnData( c.table, extract, cellIndex ) ) === 'function' ) { 841 return $.trim( tmp( $node[ 0 ], c.table, cellIndex ) ); 842 } 843 } 844 // fallback 845 return $.trim( $node[ 0 ].textContent || $node.text() ); 846 }, 847 848 // centralized function to extract/parse cell contents 849 getParsedText : function( c, cell, colIndex, txt ) { 850 if ( typeof txt === 'undefined' ) { 851 txt = ts.getElementText( c, cell, colIndex ); 852 } 853 // if no parser, make sure to return the txt 854 var val = '' + txt, 855 parser = c.parsers[ colIndex ], 856 extractor = c.extractors[ colIndex ]; 857 if ( parser ) { 858 // do extract before parsing, if there is one 859 if ( extractor && typeof extractor.format === 'function' ) { 860 txt = extractor.format( txt, c.table, cell, colIndex ); 861 } 862 // allow parsing if the string is empty, previously parsing would change it to zero, 863 // in case the parser needs to extract data from the table cell attributes 864 val = parser.id === 'no-parser' ? '' : 865 // make sure txt is a string (extractor may have converted it) 866 parser.format( '' + txt, c.table, cell, colIndex ); 867 if ( c.ignoreCase && typeof val === 'string' ) { 868 val = val.toLowerCase(); 869 } 870 } 871 return val; 872 }, 873 874 /* 875 ââââââ ââââââ ââââââ ââ ââ ââââââ 876 ââ ââ ââââââ ââ ââ ââââââ ââââ 877 ââ ââ ââââââ ââ ââ ââââââ ââââ 878 ââââââ ââ ââ ââââââ ââ ââ ââââââ 879 */ 880 buildCache : function( c, callback, $tbodies ) { 881 var cache, val, txt, rowIndex, colIndex, tbodyIndex, $tbody, $row, 882 cols, $cells, cell, cacheTime, totalRows, rowData, prevRowData, 883 colMax, span, cacheIndex, hasParser, max, len, index, 884 table = c.table, 885 parsers = c.parsers, 886 debug = ts.debug(c, 'core'); 887 // update tbody variable 888 c.$tbodies = c.$table.children( 'tbody:not(.' + c.cssInfoBlock + ')' ); 889 $tbody = typeof $tbodies === 'undefined' ? c.$tbodies : $tbodies, 890 c.cache = {}; 891 c.totalRows = 0; 892 // if no parsers found, return - it's an empty table. 893 if ( !parsers ) { 894 return debug ? console.warn( 'Warning: *Empty table!* Not building a cache' ) : ''; 895 } 896 if ( debug ) { 897 cacheTime = new Date(); 898 } 899 // processing icon 900 if ( c.showProcessing ) { 901 ts.isProcessing( table, true ); 902 } 903 for ( tbodyIndex = 0; tbodyIndex < $tbody.length; tbodyIndex++ ) { 904 colMax = []; // column max value per tbody 905 cache = c.cache[ tbodyIndex ] = { 906 normalized: [] // array of normalized row data; last entry contains 'rowData' above 907 // colMax: # // added at the end 908 }; 909 910 totalRows = ( $tbody[ tbodyIndex ] && $tbody[ tbodyIndex ].rows.length ) || 0; 911 for ( rowIndex = 0; rowIndex < totalRows; ++rowIndex ) { 912 rowData = { 913 // order: original row order # 914 // $row : jQuery Object[] 915 child: [], // child row text (filter widget) 916 raw: [] // original row text 917 }; 918 /** Add the table data to main data array */ 919 $row = $( $tbody[ tbodyIndex ].rows[ rowIndex ] ); 920 cols = []; 921 // ignore "remove-me" rows 922 if ( $row.hasClass( c.selectorRemove.slice(1) ) ) { 923 continue; 924 } 925 // if this is a child row, add it to the last row's children and continue to the next row 926 // ignore child row class, if it is the first row 927 if ( $row.hasClass( c.cssChildRow ) && rowIndex !== 0 ) { 928 len = cache.normalized.length - 1; 929 prevRowData = cache.normalized[ len ][ c.columns ]; 930 prevRowData.$row = prevRowData.$row.add( $row ); 931 // add 'hasChild' class name to parent row 932 if ( !$row.prev().hasClass( c.cssChildRow ) ) { 933 $row.prev().addClass( ts.css.cssHasChild ); 934 } 935 // save child row content (un-parsed!) 936 $cells = $row.children( 'th, td' ); 937 len = prevRowData.child.length; 938 prevRowData.child[ len ] = []; 939 // child row content does not account for colspans/rowspans; so indexing may be off 940 cacheIndex = 0; 941 max = c.columns; 942 for ( colIndex = 0; colIndex < max; colIndex++ ) { 943 cell = $cells[ colIndex ]; 944 if ( cell ) { 945 prevRowData.child[ len ][ colIndex ] = ts.getParsedText( c, cell, colIndex ); 946 span = $cells[ colIndex ].colSpan - 1; 947 if ( span > 0 ) { 948 cacheIndex += span; 949 max += span; 950 } 951 } 952 cacheIndex++; 953 } 954 // go to the next for loop 955 continue; 956 } 957 rowData.$row = $row; 958 rowData.order = rowIndex; // add original row position to rowCache 959 cacheIndex = 0; 960 max = c.columns; 961 for ( colIndex = 0; colIndex < max; ++colIndex ) { 962 cell = $row[ 0 ].cells[ colIndex ]; 963 if ( cell && cacheIndex < c.columns ) { 964 hasParser = typeof parsers[ cacheIndex ] !== 'undefined'; 965 if ( !hasParser && debug ) { 966 console.warn( 'No parser found for row: ' + rowIndex + ', column: ' + colIndex + 967 '; cell containing: "' + $(cell).text() + '"; does it have a header?' ); 968 } 969 val = ts.getElementText( c, cell, cacheIndex ); 970 rowData.raw[ cacheIndex ] = val; // save original row text 971 // save raw column text even if there is no parser set 972 txt = ts.getParsedText( c, cell, cacheIndex, val ); 973 cols[ cacheIndex ] = txt; 974 if ( hasParser && ( parsers[ cacheIndex ].type || '' ).toLowerCase() === 'numeric' ) { 975 // determine column max value (ignore sign) 976 colMax[ cacheIndex ] = Math.max( Math.abs( txt ) || 0, colMax[ cacheIndex ] || 0 ); 977 } 978 // allow colSpan in tbody 979 span = cell.colSpan - 1; 980 if ( span > 0 ) { 981 index = 0; 982 while ( index <= span ) { 983 // duplicate text (or not) to spanned columns 984 // instead of setting duplicate span to empty string, use textExtraction to try to get a value 985 // see http://stackoverflow.com/q/36449711/145346 986 txt = c.duplicateSpan || index === 0 ? 987 txt : 988 typeof c.textExtraction !== 'string' ? 989 ts.getElementText( c, cell, cacheIndex + index ) || '' : 990 ''; 991 rowData.raw[ cacheIndex + index ] = txt; 992 cols[ cacheIndex + index ] = txt; 993 index++; 994 } 995 cacheIndex += span; 996 max += span; 997 } 998 } 999 cacheIndex++; 1000 } 1001 // ensure rowData is always in the same location (after the last column) 1002 cols[ c.columns ] = rowData; 1003 cache.normalized[ cache.normalized.length ] = cols; 1004 } 1005 cache.colMax = colMax; 1006 // total up rows, not including child rows 1007 c.totalRows += cache.normalized.length; 1008 1009 } 1010 if ( c.showProcessing ) { 1011 ts.isProcessing( table ); // remove processing icon 1012 } 1013 if ( debug ) { 1014 len = Math.min( 5, c.cache[ 0 ].normalized.length ); 1015 console[ console.group ? 'group' : 'log' ]( 'Building cache for ' + c.totalRows + 1016 ' rows (showing ' + len + ' rows in log) and ' + c.columns + ' columns' + 1017 ts.benchmark( cacheTime ) ); 1018 val = {}; 1019 for ( colIndex = 0; colIndex < c.columns; colIndex++ ) { 1020 for ( cacheIndex = 0; cacheIndex < len; cacheIndex++ ) { 1021 if ( !val[ 'row: ' + cacheIndex ] ) { 1022 val[ 'row: ' + cacheIndex ] = {}; 1023 } 1024 val[ 'row: ' + cacheIndex ][ c.$headerIndexed[ colIndex ].text() ] = 1025 c.cache[ 0 ].normalized[ cacheIndex ][ colIndex ]; 1026 } 1027 } 1028 console[ console.table ? 'table' : 'log' ]( val ); 1029 if ( console.groupEnd ) { console.groupEnd(); } 1030 } 1031 if ( $.isFunction( callback ) ) { 1032 callback( table ); 1033 } 1034 }, 1035 1036 getColumnText : function( table, column, callback, rowFilter ) { 1037 table = $( table )[0]; 1038 var tbodyIndex, rowIndex, cache, row, tbodyLen, rowLen, raw, parsed, $cell, result, 1039 hasCallback = typeof callback === 'function', 1040 allColumns = column === 'all', 1041 data = { raw : [], parsed: [], $cell: [] }, 1042 c = table.config; 1043 if ( ts.isEmptyObject( c ) ) { 1044 if ( ts.debug(c, 'core') ) { 1045 console.warn( 'No cache found - aborting getColumnText function!' ); 1046 } 1047 } else { 1048 tbodyLen = c.$tbodies.length; 1049 for ( tbodyIndex = 0; tbodyIndex < tbodyLen; tbodyIndex++ ) { 1050 cache = c.cache[ tbodyIndex ].normalized; 1051 rowLen = cache.length; 1052 for ( rowIndex = 0; rowIndex < rowLen; rowIndex++ ) { 1053 row = cache[ rowIndex ]; 1054 if ( rowFilter && !row[ c.columns ].$row.is( rowFilter ) ) { 1055 continue; 1056 } 1057 result = true; 1058 parsed = ( allColumns ) ? row.slice( 0, c.columns ) : row[ column ]; 1059 row = row[ c.columns ]; 1060 raw = ( allColumns ) ? row.raw : row.raw[ column ]; 1061 $cell = ( allColumns ) ? row.$row.children() : row.$row.children().eq( column ); 1062 if ( hasCallback ) { 1063 result = callback({ 1064 tbodyIndex : tbodyIndex, 1065 rowIndex : rowIndex, 1066 parsed : parsed, 1067 raw : raw, 1068 $row : row.$row, 1069 $cell : $cell 1070 }); 1071 } 1072 if ( result !== false ) { 1073 data.parsed[ data.parsed.length ] = parsed; 1074 data.raw[ data.raw.length ] = raw; 1075 data.$cell[ data.$cell.length ] = $cell; 1076 } 1077 } 1078 } 1079 // return everything 1080 return data; 1081 } 1082 }, 1083 1084 /* 1085 ââ ââ ââââââ ââââââ ââââââ ââââââ ââââââ 1086 ââ ââ ââââââ ââ ââ ââââââ ââ ââââ 1087 ââ ââ âââââ ââ ââ ââââââ ââ ââââ 1088 ââââââ ââ ââââââ ââ ââ ââ ââââââ 1089 */ 1090 setHeadersCss : function( c ) { 1091 var indx, column, 1092 list = c.sortList, 1093 len = list.length, 1094 none = ts.css.sortNone + ' ' + c.cssNone, 1095 css = [ ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc ], 1096 cssIcon = [ c.cssIconAsc, c.cssIconDesc, c.cssIconNone ], 1097 aria = [ 'ascending', 'descending' ], 1098 updateColumnSort = function($el, index) { 1099 $el 1100 .removeClass( none ) 1101 .addClass( css[ index ] ) 1102 .attr( 'aria-sort', aria[ index ] ) 1103 .find( '.' + ts.css.icon ) 1104 .removeClass( cssIcon[ 2 ] ) 1105 .addClass( cssIcon[ index ] ); 1106 }, 1107 // find the footer 1108 $extras = c.$table 1109 .find( 'tfoot tr' ) 1110 .children( 'td, th' ) 1111 .add( $( c.namespace + '_extra_headers' ) ) 1112 .removeClass( css.join( ' ' ) ), 1113 // remove all header information 1114 $sorted = c.$headers 1115 .add( $( 'thead ' + c.namespace + '_extra_headers' ) ) 1116 .removeClass( css.join( ' ' ) ) 1117 .addClass( none ) 1118 .attr( 'aria-sort', 'none' ) 1119 .find( '.' + ts.css.icon ) 1120 .removeClass( cssIcon.join( ' ' ) ) 1121 .end(); 1122 // add css none to all sortable headers 1123 $sorted 1124 .not( '.sorter-false' ) 1125 .find( '.' + ts.css.icon ) 1126 .addClass( cssIcon[ 2 ] ); 1127 // add disabled css icon class 1128 if ( c.cssIconDisabled ) { 1129 $sorted 1130 .filter( '.sorter-false' ) 1131 .find( '.' + ts.css.icon ) 1132 .addClass( c.cssIconDisabled ); 1133 } 1134 for ( indx = 0; indx < len; indx++ ) { 1135 // direction = 2 means reset! 1136 if ( list[ indx ][ 1 ] !== 2 ) { 1137 // multicolumn sorting updating - see #1005 1138 // .not(function() {}) needs jQuery 1.4 1139 // filter(function(i, el) {}) <- el is undefined in jQuery v1.2.6 1140 $sorted = c.$headers.filter( function( i ) { 1141 // only include headers that are in the sortList (this includes colspans) 1142 var include = true, 1143 $el = c.$headers.eq( i ), 1144 col = parseInt( $el.attr( 'data-column' ), 10 ), 1145 end = col + ts.getClosest( $el, 'th, td' )[0].colSpan; 1146 for ( ; col < end; col++ ) { 1147 include = include ? include || ts.isValueInArray( col, c.sortList ) > -1 : false; 1148 } 1149 return include; 1150 }); 1151 1152 // choose the :last in case there are nested columns 1153 $sorted = $sorted 1154 .not( '.sorter-false' ) 1155 .filter( '[data-column="' + list[ indx ][ 0 ] + '"]' + ( len === 1 ? ':last' : '' ) ); 1156 if ( $sorted.length ) { 1157 for ( column = 0; column < $sorted.length; column++ ) { 1158 if ( !$sorted[ column ].sortDisabled ) { 1159 updateColumnSort( $sorted.eq( column ), list[ indx ][ 1 ] ); 1160 } 1161 } 1162 } 1163 // add sorted class to footer & extra headers, if they exist 1164 if ( $extras.length ) { 1165 updateColumnSort( $extras.filter( '[data-column="' + list[ indx ][ 0 ] + '"]' ), list[ indx ][ 1 ] ); 1166 } 1167 } 1168 } 1169 // add verbose aria labels 1170 len = c.$headers.length; 1171 for ( indx = 0; indx < len; indx++ ) { 1172 ts.setColumnAriaLabel( c, c.$headers.eq( indx ) ); 1173 } 1174 }, 1175 1176 getClosest : function( $el, selector ) { 1177 // jQuery v1.2.6 doesn't have closest() 1178 if ( $.fn.closest ) { 1179 return $el.closest( selector ); 1180 } 1181 return $el.is( selector ) ? 1182 $el : 1183 $el.parents( selector ).filter( ':first' ); 1184 }, 1185 1186 // nextSort (optional), lets you disable next sort text 1187 setColumnAriaLabel : function( c, $header, nextSort ) { 1188 if ( $header.length ) { 1189 var column = parseInt( $header.attr( 'data-column' ), 10 ), 1190 vars = c.sortVars[ column ], 1191 tmp = $header.hasClass( ts.css.sortAsc ) ? 1192 'sortAsc' : 1193 $header.hasClass( ts.css.sortDesc ) ? 'sortDesc' : 'sortNone', 1194 txt = $.trim( $header.text() ) + ': ' + ts.language[ tmp ]; 1195 if ( $header.hasClass( 'sorter-false' ) || nextSort === false ) { 1196 txt += ts.language.sortDisabled; 1197 } else { 1198 tmp = ( vars.count + 1 ) % vars.order.length; 1199 nextSort = vars.order[ tmp ]; 1200 // if nextSort 1201 txt += ts.language[ nextSort === 0 ? 'nextAsc' : nextSort === 1 ? 'nextDesc' : 'nextNone' ]; 1202 } 1203 $header.attr( 'aria-label', txt ); 1204 if (vars.sortedBy) { 1205 $header.attr( 'data-sortedBy', vars.sortedBy ); 1206 } else { 1207 $header.removeAttr('data-sortedBy'); 1208 } 1209 } 1210 }, 1211 1212 updateHeader : function( c ) { 1213 var index, isDisabled, $header, col, 1214 table = c.table, 1215 len = c.$headers.length; 1216 for ( index = 0; index < len; index++ ) { 1217 $header = c.$headers.eq( index ); 1218 col = ts.getColumnData( table, c.headers, index, true ); 1219 // add 'sorter-false' class if 'parser-false' is set 1220 isDisabled = ts.getData( $header, col, 'sorter' ) === 'false' || ts.getData( $header, col, 'parser' ) === 'false'; 1221 ts.setColumnSort( c, $header, isDisabled ); 1222 } 1223 }, 1224 1225 setColumnSort : function( c, $header, isDisabled ) { 1226 var id = c.table.id; 1227 $header[ 0 ].sortDisabled = isDisabled; 1228 $header[ isDisabled ? 'addClass' : 'removeClass' ]( 'sorter-false' ) 1229 .attr( 'aria-disabled', '' + isDisabled ); 1230 // disable tab index on disabled cells 1231 if ( c.tabIndex ) { 1232 if ( isDisabled ) { 1233 $header.removeAttr( 'tabindex' ); 1234 } else { 1235 $header.attr( 'tabindex', '0' ); 1236 } 1237 } 1238 // aria-controls - requires table ID 1239 if ( id ) { 1240 if ( isDisabled ) { 1241 $header.removeAttr( 'aria-controls' ); 1242 } else { 1243 $header.attr( 'aria-controls', id ); 1244 } 1245 } 1246 }, 1247 1248 updateHeaderSortCount : function( c, list ) { 1249 var col, dir, group, indx, primary, temp, val, order, 1250 sortList = list || c.sortList, 1251 len = sortList.length; 1252 c.sortList = []; 1253 for ( indx = 0; indx < len; indx++ ) { 1254 val = sortList[ indx ]; 1255 // ensure all sortList values are numeric - fixes #127 1256 col = parseInt( val[ 0 ], 10 ); 1257 // prevents error if sorton array is wrong 1258 if ( col < c.columns ) { 1259 1260 // set order if not already defined - due to colspan header without associated header cell 1261 // adding this check prevents a javascript error 1262 if ( !c.sortVars[ col ].order ) { 1263 if ( ts.getOrder( c.sortInitialOrder ) ) { 1264 order = c.sortReset ? [ 1, 0, 2 ] : [ 1, 0 ]; 1265 } else { 1266 order = c.sortReset ? [ 0, 1, 2 ] : [ 0, 1 ]; 1267 } 1268 c.sortVars[ col ].order = order; 1269 c.sortVars[ col ].count = 0; 1270 } 1271 1272 order = c.sortVars[ col ].order; 1273 dir = ( '' + val[ 1 ] ).match( /^(1|d|s|o|n)/ ); 1274 dir = dir ? dir[ 0 ] : ''; 1275 // 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext 1276 switch ( dir ) { 1277 case '1' : case 'd' : // descending 1278 dir = 1; 1279 break; 1280 case 's' : // same direction (as primary column) 1281 // if primary sort is set to 's', make it ascending 1282 dir = primary || 0; 1283 break; 1284 case 'o' : 1285 temp = order[ ( primary || 0 ) % order.length ]; 1286 // opposite of primary column; but resets if primary resets 1287 dir = temp === 0 ? 1 : temp === 1 ? 0 : 2; 1288 break; 1289 case 'n' : 1290 dir = order[ ( ++c.sortVars[ col ].count ) % order.length ]; 1291 break; 1292 default : // ascending 1293 dir = 0; 1294 break; 1295 } 1296 primary = indx === 0 ? dir : primary; 1297 group = [ col, parseInt( dir, 10 ) || 0 ]; 1298 c.sortList[ c.sortList.length ] = group; 1299 dir = $.inArray( group[ 1 ], order ); // fixes issue #167 1300 c.sortVars[ col ].count = dir >= 0 ? dir : group[ 1 ] % order.length; 1301 } 1302 } 1303 }, 1304 1305 updateAll : function( c, resort, callback ) { 1306 var table = c.table; 1307 table.isUpdating = true; 1308 ts.refreshWidgets( table, true, true ); 1309 ts.buildHeaders( c ); 1310 ts.bindEvents( table, c.$headers, true ); 1311 ts.bindMethods( c ); 1312 ts.commonUpdate( c, resort, callback ); 1313 }, 1314 1315 update : function( c, resort, callback ) { 1316 var table = c.table; 1317 table.isUpdating = true; 1318 // update sorting (if enabled/disabled) 1319 ts.updateHeader( c ); 1320 ts.commonUpdate( c, resort, callback ); 1321 }, 1322 1323 // simple header update - see #989 1324 updateHeaders : function( c, callback ) { 1325 c.table.isUpdating = true; 1326 ts.buildHeaders( c ); 1327 ts.bindEvents( c.table, c.$headers, true ); 1328 ts.resortComplete( c, callback ); 1329 }, 1330 1331 updateCell : function( c, cell, resort, callback ) { 1332 // updateCell for child rows is a mess - we'll ignore them for now 1333 // eventually I'll break out the "update" row cache code to make everything consistent 1334 if ( $( cell ).closest( 'tr' ).hasClass( c.cssChildRow ) ) { 1335 console.warn('Tablesorter Warning! "updateCell" for child row content has been disabled, use "update" instead'); 1336 return; 1337 } 1338 if ( ts.isEmptyObject( c.cache ) ) { 1339 // empty table, do an update instead - fixes #1099 1340 ts.updateHeader( c ); 1341 ts.commonUpdate( c, resort, callback ); 1342 return; 1343 } 1344 c.table.isUpdating = true; 1345 c.$table.find( c.selectorRemove ).remove(); 1346 // get position from the dom 1347 var tmp, indx, row, icell, cache, len, 1348 $tbodies = c.$tbodies, 1349 $cell = $( cell ), 1350 // update cache - format: function( s, table, cell, cellIndex ) 1351 // no closest in jQuery v1.2.6 1352 tbodyIndex = $tbodies.index( ts.getClosest( $cell, 'tbody' ) ), 1353 tbcache = c.cache[ tbodyIndex ], 1354 $row = ts.getClosest( $cell, 'tr' ); 1355 cell = $cell[ 0 ]; // in case cell is a jQuery object 1356 // tbody may not exist if update is initialized while tbody is removed for processing 1357 if ( $tbodies.length && tbodyIndex >= 0 ) { 1358 row = $tbodies.eq( tbodyIndex ).find( 'tr' ).not( '.' + c.cssChildRow ).index( $row ); 1359 cache = tbcache.normalized[ row ]; 1360 len = $row[ 0 ].cells.length; 1361 if ( len !== c.columns ) { 1362 // colspan in here somewhere! 1363 icell = 0; 1364 tmp = false; 1365 for ( indx = 0; indx < len; indx++ ) { 1366 if ( !tmp && $row[ 0 ].cells[ indx ] !== cell ) { 1367 icell += $row[ 0 ].cells[ indx ].colSpan; 1368 } else { 1369 tmp = true; 1370 } 1371 } 1372 } else { 1373 icell = $cell.index(); 1374 } 1375 tmp = ts.getElementText( c, cell, icell ); // raw 1376 cache[ c.columns ].raw[ icell ] = tmp; 1377 tmp = ts.getParsedText( c, cell, icell, tmp ); 1378 cache[ icell ] = tmp; // parsed 1379 if ( ( c.parsers[ icell ].type || '' ).toLowerCase() === 'numeric' ) { 1380 // update column max value (ignore sign) 1381 tbcache.colMax[ icell ] = Math.max( Math.abs( tmp ) || 0, tbcache.colMax[ icell ] || 0 ); 1382 } 1383 tmp = resort !== 'undefined' ? resort : c.resort; 1384 if ( tmp !== false ) { 1385 // widgets will be reapplied 1386 ts.checkResort( c, tmp, callback ); 1387 } else { 1388 // don't reapply widgets is resort is false, just in case it causes 1389 // problems with element focus 1390 ts.resortComplete( c, callback ); 1391 } 1392 } else { 1393 if ( ts.debug(c, 'core') ) { 1394 console.error( 'updateCell aborted, tbody missing or not within the indicated table' ); 1395 } 1396 c.table.isUpdating = false; 1397 } 1398 }, 1399 1400 addRows : function( c, $row, resort, callback ) { 1401 var txt, val, tbodyIndex, rowIndex, rows, cellIndex, len, order, 1402 cacheIndex, rowData, cells, cell, span, 1403 // allow passing a row string if only one non-info tbody exists in the table 1404 valid = typeof $row === 'string' && c.$tbodies.length === 1 && /<tr/.test( $row || '' ), 1405 table = c.table; 1406 if ( valid ) { 1407 $row = $( $row ); 1408 c.$tbodies.append( $row ); 1409 } else if ( 1410 !$row || 1411 // row is a jQuery object? 1412 !( $row instanceof $ ) || 1413 // row contained in the table? 1414 ( ts.getClosest( $row, 'table' )[ 0 ] !== c.table ) 1415 ) { 1416 if ( ts.debug(c, 'core') ) { 1417 console.error( 'addRows method requires (1) a jQuery selector reference to rows that have already ' + 1418 'been added to the table, or (2) row HTML string to be added to a table with only one tbody' ); 1419 } 1420 return false; 1421 } 1422 table.isUpdating = true; 1423 if ( ts.isEmptyObject( c.cache ) ) { 1424 // empty table, do an update instead - fixes #450 1425 ts.updateHeader( c ); 1426 ts.commonUpdate( c, resort, callback ); 1427 } else { 1428 rows = $row.filter( 'tr' ).attr( 'role', 'row' ).length; 1429 tbodyIndex = c.$tbodies.index( $row.parents( 'tbody' ).filter( ':first' ) ); 1430 // fixes adding rows to an empty table - see issue #179 1431 if ( !( c.parsers && c.parsers.length ) ) { 1432 ts.setupParsers( c ); 1433 } 1434 // add each row 1435 for ( rowIndex = 0; rowIndex < rows; rowIndex++ ) { 1436 cacheIndex = 0; 1437 len = $row[ rowIndex ].cells.length; 1438 order = c.cache[ tbodyIndex ].normalized.length; 1439 cells = []; 1440 rowData = { 1441 child : [], 1442 raw : [], 1443 $row : $row.eq( rowIndex ), 1444 order : order 1445 }; 1446 // add each cell 1447 for ( cellIndex = 0; cellIndex < len; cellIndex++ ) { 1448 cell = $row[ rowIndex ].cells[ cellIndex ]; 1449 txt = ts.getElementText( c, cell, cacheIndex ); 1450 rowData.raw[ cacheIndex ] = txt; 1451 val = ts.getParsedText( c, cell, cacheIndex, txt ); 1452 cells[ cacheIndex ] = val; 1453 if ( ( c.parsers[ cacheIndex ].type || '' ).toLowerCase() === 'numeric' ) { 1454 // update column max value (ignore sign) 1455 c.cache[ tbodyIndex ].colMax[ cacheIndex ] = 1456 Math.max( Math.abs( val ) || 0, c.cache[ tbodyIndex ].colMax[ cacheIndex ] || 0 ); 1457 } 1458 span = cell.colSpan - 1; 1459 if ( span > 0 ) { 1460 cacheIndex += span; 1461 } 1462 cacheIndex++; 1463 } 1464 // add the row data to the end 1465 cells[ c.columns ] = rowData; 1466 // update cache 1467 c.cache[ tbodyIndex ].normalized[ order ] = cells; 1468 } 1469 // resort using current settings 1470 ts.checkResort( c, resort, callback ); 1471 } 1472 }, 1473 1474 updateCache : function( c, callback, $tbodies ) { 1475 // rebuild parsers 1476 if ( !( c.parsers && c.parsers.length ) ) { 1477 ts.setupParsers( c, $tbodies ); 1478 } 1479 // rebuild the cache map 1480 ts.buildCache( c, callback, $tbodies ); 1481 }, 1482 1483 // init flag (true) used by pager plugin to prevent widget application 1484 // renamed from appendToTable 1485 appendCache : function( c, init ) { 1486 var parsed, totalRows, $tbody, $curTbody, rowIndex, tbodyIndex, appendTime, 1487 table = c.table, 1488 $tbodies = c.$tbodies, 1489 rows = [], 1490 cache = c.cache; 1491 // empty table - fixes #206/#346 1492 if ( ts.isEmptyObject( cache ) ) { 1493 // run pager appender in case the table was just emptied 1494 return c.appender ? c.appender( table, rows ) : 1495 table.isUpdating ? c.$table.triggerHandler( 'updateComplete', table ) : ''; // Fixes #532 1496 } 1497 if ( ts.debug(c, 'core') ) { 1498 appendTime = new Date(); 1499 } 1500 for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { 1501 $tbody = $tbodies.eq( tbodyIndex ); 1502 if ( $tbody.length ) { 1503 // detach tbody for manipulation 1504 $curTbody = ts.processTbody( table, $tbody, true ); 1505 parsed = cache[ tbodyIndex ].normalized; 1506 totalRows = parsed.length; 1507 for ( rowIndex = 0; rowIndex < totalRows; rowIndex++ ) { 1508 rows[rows.length] = parsed[ rowIndex ][ c.columns ].$row; 1509 // removeRows used by the pager plugin; don't render if using ajax - fixes #411 1510 if ( !c.appender || ( c.pager && !c.pager.removeRows && !c.pager.ajax ) ) { 1511 $curTbody.append( parsed[ rowIndex ][ c.columns ].$row ); 1512 } 1513 } 1514 // restore tbody 1515 ts.processTbody( table, $curTbody, false ); 1516 } 1517 } 1518 if ( c.appender ) { 1519 c.appender( table, rows ); 1520 } 1521 if ( ts.debug(c, 'core') ) { 1522 console.log( 'Rebuilt table' + ts.benchmark( appendTime ) ); 1523 } 1524 // apply table widgets; but not before ajax completes 1525 if ( !init && !c.appender ) { 1526 ts.applyWidget( table ); 1527 } 1528 if ( table.isUpdating ) { 1529 c.$table.triggerHandler( 'updateComplete', table ); 1530 } 1531 }, 1532 1533 commonUpdate : function( c, resort, callback ) { 1534 // remove rows/elements before update 1535 c.$table.find( c.selectorRemove ).remove(); 1536 // rebuild parsers 1537 ts.setupParsers( c ); 1538 // rebuild the cache map 1539 ts.buildCache( c ); 1540 ts.checkResort( c, resort, callback ); 1541 }, 1542 1543 /* 1544 ââââââ ââââââ ââââââ ââââââ ââ ââââââ ââââââ 1545 âââ ââ ââ ââââââ ââ ââ ââ ââ ââ âââ 1546 âââ ââ ââ âââââ ââ ââ ââ ââ ââ âââ 1547 ââââââ ââââââ ââ ââ ââ ââ ââ ââ ââââââ 1548 */ 1549 initSort : function( c, cell, event ) { 1550 if ( c.table.isUpdating ) { 1551 // let any updates complete before initializing a sort 1552 return setTimeout( function() { 1553 ts.initSort( c, cell, event ); 1554 }, 50 ); 1555 } 1556 1557 var arry, indx, headerIndx, dir, temp, tmp, $header, 1558 notMultiSort = !event[ c.sortMultiSortKey ], 1559 table = c.table, 1560 len = c.$headers.length, 1561 th = ts.getClosest( $( cell ), 'th, td' ), 1562 col = parseInt( th.attr( 'data-column' ), 10 ), 1563 sortedBy = event.type === 'mouseup' ? 'user' : event.type, 1564 order = c.sortVars[ col ].order; 1565 th = th[0]; 1566 // Only call sortStart if sorting is enabled 1567 c.$table.triggerHandler( 'sortStart', table ); 1568 // get current column sort order 1569 tmp = ( c.sortVars[ col ].count + 1 ) % order.length; 1570 c.sortVars[ col ].count = event[ c.sortResetKey ] ? 2 : tmp; 1571 // reset all sorts on non-current column - issue #30 1572 if ( c.sortRestart ) { 1573 for ( headerIndx = 0; headerIndx < len; headerIndx++ ) { 1574 $header = c.$headers.eq( headerIndx ); 1575 tmp = parseInt( $header.attr( 'data-column' ), 10 ); 1576 // only reset counts on columns that weren't just clicked on and if not included in a multisort 1577 if ( col !== tmp && ( notMultiSort || $header.hasClass( ts.css.sortNone ) ) ) { 1578 c.sortVars[ tmp ].count = -1; 1579 } 1580 } 1581 } 1582 // user only wants to sort on one column 1583 if ( notMultiSort ) { 1584 $.each( c.sortVars, function( i ) { 1585 c.sortVars[ i ].sortedBy = ''; 1586 }); 1587 // flush the sort list 1588 c.sortList = []; 1589 c.last.sortList = []; 1590 if ( c.sortForce !== null ) { 1591 arry = c.sortForce; 1592 for ( indx = 0; indx < arry.length; indx++ ) { 1593 if ( arry[ indx ][ 0 ] !== col ) { 1594 c.sortList[ c.sortList.length ] = arry[ indx ]; 1595 c.sortVars[ arry[ indx ][ 0 ] ].sortedBy = 'sortForce'; 1596 } 1597 } 1598 } 1599 // add column to sort list 1600 dir = order[ c.sortVars[ col ].count ]; 1601 if ( dir < 2 ) { 1602 c.sortList[ c.sortList.length ] = [ col, dir ]; 1603 c.sortVars[ col ].sortedBy = sortedBy; 1604 // add other columns if header spans across multiple 1605 if ( th.colSpan > 1 ) { 1606 for ( indx = 1; indx < th.colSpan; indx++ ) { 1607 c.sortList[ c.sortList.length ] = [ col + indx, dir ]; 1608 // update count on columns in colSpan 1609 c.sortVars[ col + indx ].count = $.inArray( dir, order ); 1610 c.sortVars[ col + indx ].sortedBy = sortedBy; 1611 } 1612 } 1613 } 1614 // multi column sorting 1615 } else { 1616 // get rid of the sortAppend before adding more - fixes issue #115 & #523 1617 c.sortList = $.extend( [], c.last.sortList ); 1618 1619 // the user has clicked on an already sorted column 1620 if ( ts.isValueInArray( col, c.sortList ) >= 0 ) { 1621 // reverse the sorting direction 1622 c.sortVars[ col ].sortedBy = sortedBy; 1623 for ( indx = 0; indx < c.sortList.length; indx++ ) { 1624 tmp = c.sortList[ indx ]; 1625 if ( tmp[ 0 ] === col ) { 1626 // order.count seems to be incorrect when compared to cell.count 1627 tmp[ 1 ] = order[ c.sortVars[ col ].count ]; 1628 if ( tmp[1] === 2 ) { 1629 c.sortList.splice( indx, 1 ); 1630 c.sortVars[ col ].count = -1; 1631 } 1632 } 1633 } 1634 } else { 1635 // add column to sort list array 1636 dir = order[ c.sortVars[ col ].count ]; 1637 c.sortVars[ col ].sortedBy = sortedBy; 1638 if ( dir < 2 ) { 1639 c.sortList[ c.sortList.length ] = [ col, dir ]; 1640 // add other columns if header spans across multiple 1641 if ( th.colSpan > 1 ) { 1642 for ( indx = 1; indx < th.colSpan; indx++ ) { 1643 c.sortList[ c.sortList.length ] = [ col + indx, dir ]; 1644 // update count on columns in colSpan 1645 c.sortVars[ col + indx ].count = $.inArray( dir, order ); 1646 c.sortVars[ col + indx ].sortedBy = sortedBy; 1647 } 1648 } 1649 } 1650 } 1651 } 1652 // save sort before applying sortAppend 1653 c.last.sortList = $.extend( [], c.sortList ); 1654 if ( c.sortList.length && c.sortAppend ) { 1655 arry = $.isArray( c.sortAppend ) ? c.sortAppend : c.sortAppend[ c.sortList[ 0 ][ 0 ] ]; 1656 if ( !ts.isEmptyObject( arry ) ) { 1657 for ( indx = 0; indx < arry.length; indx++ ) { 1658 if ( arry[ indx ][ 0 ] !== col && ts.isValueInArray( arry[ indx ][ 0 ], c.sortList ) < 0 ) { 1659 dir = arry[ indx ][ 1 ]; 1660 temp = ( '' + dir ).match( /^(a|d|s|o|n)/ ); 1661 if ( temp ) { 1662 tmp = c.sortList[ 0 ][ 1 ]; 1663 switch ( temp[ 0 ] ) { 1664 case 'd' : 1665 dir = 1; 1666 break; 1667 case 's' : 1668 dir = tmp; 1669 break; 1670 case 'o' : 1671 dir = tmp === 0 ? 1 : 0; 1672 break; 1673 case 'n' : 1674 dir = ( tmp + 1 ) % order.length; 1675 break; 1676 default: 1677 dir = 0; 1678 break; 1679 } 1680 } 1681 c.sortList[ c.sortList.length ] = [ arry[ indx ][ 0 ], dir ]; 1682 c.sortVars[ arry[ indx ][ 0 ] ].sortedBy = 'sortAppend'; 1683 } 1684 } 1685 } 1686 } 1687 // sortBegin event triggered immediately before the sort 1688 c.$table.triggerHandler( 'sortBegin', table ); 1689 // setTimeout needed so the processing icon shows up 1690 setTimeout( function() { 1691 // set css for headers 1692 ts.setHeadersCss( c ); 1693 ts.multisort( c ); 1694 ts.appendCache( c ); 1695 c.$table.triggerHandler( 'sortBeforeEnd', table ); 1696 c.$table.triggerHandler( 'sortEnd', table ); 1697 }, 1 ); 1698 }, 1699 1700 // sort multiple columns 1701 multisort : function( c ) { /*jshint loopfunc:true */ 1702 var tbodyIndex, sortTime, colMax, rows, tmp, 1703 table = c.table, 1704 sorter = [], 1705 dir = 0, 1706 textSorter = c.textSorter || '', 1707 sortList = c.sortList, 1708 sortLen = sortList.length, 1709 len = c.$tbodies.length; 1710 if ( c.serverSideSorting || ts.isEmptyObject( c.cache ) ) { 1711 // empty table - fixes #206/#346 1712 return; 1713 } 1714 if ( ts.debug(c, 'core') ) { sortTime = new Date(); } 1715 // cache textSorter to optimize speed 1716 if ( typeof textSorter === 'object' ) { 1717 colMax = c.columns; 1718 while ( colMax-- ) { 1719 tmp = ts.getColumnData( table, textSorter, colMax ); 1720 if ( typeof tmp === 'function' ) { 1721 sorter[ colMax ] = tmp; 1722 } 1723 } 1724 } 1725 for ( tbodyIndex = 0; tbodyIndex < len; tbodyIndex++ ) { 1726 colMax = c.cache[ tbodyIndex ].colMax; 1727 rows = c.cache[ tbodyIndex ].normalized; 1728 1729 rows.sort( function( a, b ) { 1730 var sortIndex, num, col, order, sort, x, y; 1731 // rows is undefined here in IE, so don't use it! 1732 for ( sortIndex = 0; sortIndex < sortLen; sortIndex++ ) { 1733 col = sortList[ sortIndex ][ 0 ]; 1734 order = sortList[ sortIndex ][ 1 ]; 1735 // sort direction, true = asc, false = desc 1736 dir = order === 0; 1737 1738 if ( c.sortStable && a[ col ] === b[ col ] && sortLen === 1 ) { 1739 return a[ c.columns ].order - b[ c.columns ].order; 1740 } 1741 1742 // fallback to natural sort since it is more robust 1743 num = /n/i.test( ts.getSortType( c.parsers, col ) ); 1744 if ( num && c.strings[ col ] ) { 1745 // sort strings in numerical columns 1746 if ( typeof ( ts.string[ c.strings[ col ] ] ) === 'boolean' ) { 1747 num = ( dir ? 1 : -1 ) * ( ts.string[ c.strings[ col ] ] ? -1 : 1 ); 1748 } else { 1749 num = ( c.strings[ col ] ) ? ts.string[ c.strings[ col ] ] || 0 : 0; 1750 } 1751 // fall back to built-in numeric sort 1752 // var sort = $.tablesorter['sort' + s]( a[col], b[col], dir, colMax[col], table ); 1753 sort = c.numberSorter ? c.numberSorter( a[ col ], b[ col ], dir, colMax[ col ], table ) : 1754 ts[ 'sortNumeric' + ( dir ? 'Asc' : 'Desc' ) ]( a[ col ], b[ col ], num, colMax[ col ], col, c ); 1755 } else { 1756 // set a & b depending on sort direction 1757 x = dir ? a : b; 1758 y = dir ? b : a; 1759 // text sort function 1760 if ( typeof textSorter === 'function' ) { 1761 // custom OVERALL text sorter 1762 sort = textSorter( x[ col ], y[ col ], dir, col, table ); 1763 } else if ( typeof sorter[ col ] === 'function' ) { 1764 // custom text sorter for a SPECIFIC COLUMN 1765 sort = sorter[ col ]( x[ col ], y[ col ], dir, col, table ); 1766 } else { 1767 // fall back to natural sort 1768 sort = ts[ 'sortNatural' + ( dir ? 'Asc' : 'Desc' ) ]( a[ col ] || '', b[ col ] || '', col, c ); 1769 } 1770 } 1771 if ( sort ) { return sort; } 1772 } 1773 return a[ c.columns ].order - b[ c.columns ].order; 1774 }); 1775 } 1776 if ( ts.debug(c, 'core') ) { 1777 console.log( 'Applying sort ' + sortList.toString() + ts.benchmark( sortTime ) ); 1778 } 1779 }, 1780 1781 resortComplete : function( c, callback ) { 1782 if ( c.table.isUpdating ) { 1783 c.$table.triggerHandler( 'updateComplete', c.table ); 1784 } 1785 if ( $.isFunction( callback ) ) { 1786 callback( c.table ); 1787 } 1788 }, 1789 1790 checkResort : function( c, resort, callback ) { 1791 var sortList = $.isArray( resort ) ? resort : c.sortList, 1792 // if no resort parameter is passed, fallback to config.resort (true by default) 1793 resrt = typeof resort === 'undefined' ? c.resort : resort; 1794 // don't try to resort if the table is still processing 1795 // this will catch spamming of the updateCell method 1796 if ( resrt !== false && !c.serverSideSorting && !c.table.isProcessing ) { 1797 if ( sortList.length ) { 1798 ts.sortOn( c, sortList, function() { 1799 ts.resortComplete( c, callback ); 1800 }, true ); 1801 } else { 1802 ts.sortReset( c, function() { 1803 ts.resortComplete( c, callback ); 1804 ts.applyWidget( c.table, false ); 1805 } ); 1806 } 1807 } else { 1808 ts.resortComplete( c, callback ); 1809 ts.applyWidget( c.table, false ); 1810 } 1811 }, 1812 1813 sortOn : function( c, list, callback, init ) { 1814 var indx, 1815 table = c.table; 1816 c.$table.triggerHandler( 'sortStart', table ); 1817 for (indx = 0; indx < c.columns; indx++) { 1818 c.sortVars[ indx ].sortedBy = ts.isValueInArray( indx, list ) > -1 ? 'sorton' : ''; 1819 } 1820 // update header count index 1821 ts.updateHeaderSortCount( c, list ); 1822 // set css for headers 1823 ts.setHeadersCss( c ); 1824 // fixes #346 1825 if ( c.delayInit && ts.isEmptyObject( c.cache ) ) { 1826 ts.buildCache( c ); 1827 } 1828 c.$table.triggerHandler( 'sortBegin', table ); 1829 // sort the table and append it to the dom 1830 ts.multisort( c ); 1831 ts.appendCache( c, init ); 1832 c.$table.triggerHandler( 'sortBeforeEnd', table ); 1833 c.$table.triggerHandler( 'sortEnd', table ); 1834 ts.applyWidget( table ); 1835 if ( $.isFunction( callback ) ) { 1836 callback( table ); 1837 } 1838 }, 1839 1840 sortReset : function( c, callback ) { 1841 c.sortList = []; 1842 var indx; 1843 for (indx = 0; indx < c.columns; indx++) { 1844 c.sortVars[ indx ].count = -1; 1845 c.sortVars[ indx ].sortedBy = ''; 1846 } 1847 ts.setHeadersCss( c ); 1848 ts.multisort( c ); 1849 ts.appendCache( c ); 1850 if ( $.isFunction( callback ) ) { 1851 callback( c.table ); 1852 } 1853 }, 1854 1855 getSortType : function( parsers, column ) { 1856 return ( parsers && parsers[ column ] ) ? parsers[ column ].type || '' : ''; 1857 }, 1858 1859 getOrder : function( val ) { 1860 // look for 'd' in 'desc' order; return true 1861 return ( /^d/i.test( val ) || val === 1 ); 1862 }, 1863 1864 // Natural sort - https://github.com/overset/javascript-natural-sort (date sorting removed) 1865 sortNatural : function( a, b ) { 1866 if ( a === b ) { return 0; } 1867 a = ( a || '' ).toString(); 1868 b = ( b || '' ).toString(); 1869 var aNum, bNum, aFloat, bFloat, indx, max, 1870 regex = ts.regex; 1871 // first try and sort Hex codes 1872 if ( regex.hex.test( b ) ) { 1873 aNum = parseInt( a.match( regex.hex ), 16 ); 1874 bNum = parseInt( b.match( regex.hex ), 16 ); 1875 if ( aNum < bNum ) { return -1; } 1876 if ( aNum > bNum ) { return 1; } 1877 } 1878 // chunk/tokenize 1879 aNum = a.replace( regex.chunk, '\\0$1\\0' ).replace( regex.chunks, '' ).split( '\\0' ); 1880 bNum = b.replace( regex.chunk, '\\0$1\\0' ).replace( regex.chunks, '' ).split( '\\0' ); 1881 max = Math.max( aNum.length, bNum.length ); 1882 // natural sorting through split numeric strings and default strings 1883 for ( indx = 0; indx < max; indx++ ) { 1884 // find floats not starting with '0', string or 0 if not defined 1885 aFloat = isNaN( aNum[ indx ] ) ? aNum[ indx ] || 0 : parseFloat( aNum[ indx ] ) || 0; 1886 bFloat = isNaN( bNum[ indx ] ) ? bNum[ indx ] || 0 : parseFloat( bNum[ indx ] ) || 0; 1887 // handle numeric vs string comparison - number < string - (Kyle Adams) 1888 if ( isNaN( aFloat ) !== isNaN( bFloat ) ) { return isNaN( aFloat ) ? 1 : -1; } 1889 // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2' 1890 if ( typeof aFloat !== typeof bFloat ) { 1891 aFloat += ''; 1892 bFloat += ''; 1893 } 1894 if ( aFloat < bFloat ) { return -1; } 1895 if ( aFloat > bFloat ) { return 1; } 1896 } 1897 return 0; 1898 }, 1899 1900 sortNaturalAsc : function( a, b, col, c ) { 1901 if ( a === b ) { return 0; } 1902 var empty = ts.string[ ( c.empties[ col ] || c.emptyTo ) ]; 1903 if ( a === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? -1 : 1 ) : -empty || -1; } 1904 if ( b === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? 1 : -1 ) : empty || 1; } 1905 return ts.sortNatural( a, b ); 1906 }, 1907 1908 sortNaturalDesc : function( a, b, col, c ) { 1909 if ( a === b ) { return 0; } 1910 var empty = ts.string[ ( c.empties[ col ] || c.emptyTo ) ]; 1911 if ( a === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? -1 : 1 ) : empty || 1; } 1912 if ( b === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? 1 : -1 ) : -empty || -1; } 1913 return ts.sortNatural( b, a ); 1914 }, 1915 1916 // basic alphabetical sort 1917 sortText : function( a, b ) { 1918 return a > b ? 1 : ( a < b ? -1 : 0 ); 1919 }, 1920 1921 // return text string value by adding up ascii value 1922 // so the text is somewhat sorted when using a digital sort 1923 // this is NOT an alphanumeric sort 1924 getTextValue : function( val, num, max ) { 1925 if ( max ) { 1926 // make sure the text value is greater than the max numerical value (max) 1927 var indx, 1928 len = val ? val.length : 0, 1929 n = max + num; 1930 for ( indx = 0; indx < len; indx++ ) { 1931 n += val.charCodeAt( indx ); 1932 } 1933 return num * n; 1934 } 1935 return 0; 1936 }, 1937 1938 sortNumericAsc : function( a, b, num, max, col, c ) { 1939 if ( a === b ) { return 0; } 1940 var empty = ts.string[ ( c.empties[ col ] || c.emptyTo ) ]; 1941 if ( a === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? -1 : 1 ) : -empty || -1; } 1942 if ( b === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? 1 : -1 ) : empty || 1; } 1943 if ( isNaN( a ) ) { a = ts.getTextValue( a, num, max ); } 1944 if ( isNaN( b ) ) { b = ts.getTextValue( b, num, max ); } 1945 return a - b; 1946 }, 1947 1948 sortNumericDesc : function( a, b, num, max, col, c ) { 1949 if ( a === b ) { return 0; } 1950 var empty = ts.string[ ( c.empties[ col ] || c.emptyTo ) ]; 1951 if ( a === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? -1 : 1 ) : empty || 1; } 1952 if ( b === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? 1 : -1 ) : -empty || -1; } 1953 if ( isNaN( a ) ) { a = ts.getTextValue( a, num, max ); } 1954 if ( isNaN( b ) ) { b = ts.getTextValue( b, num, max ); } 1955 return b - a; 1956 }, 1957 1958 sortNumeric : function( a, b ) { 1959 return a - b; 1960 }, 1961 1962 /* 1963 ââ ââ ââ ââ ââââââ ââââââ ââââââ ââââââ ââââââ 1964 ââ ââ ââ ââ ââ ââ ââ âââ ââââ ââ âââ 1965 ââ ââ ââ ââ ââ ââ ââ âââ ââââ ââ âââ 1966 ââââââââ ââ ââââââ ââââââ ââââââ ââ ââââââ 1967 */ 1968 addWidget : function( widget ) { 1969 if ( widget.id && !ts.isEmptyObject( ts.getWidgetById( widget.id ) ) ) { 1970 console.warn( '"' + widget.id + '" widget was loaded more than once!' ); 1971 } 1972 ts.widgets[ ts.widgets.length ] = widget; 1973 }, 1974 1975 hasWidget : function( $table, name ) { 1976 $table = $( $table ); 1977 return $table.length && $table[ 0 ].config && $table[ 0 ].config.widgetInit[ name ] || false; 1978 }, 1979 1980 getWidgetById : function( name ) { 1981 var indx, widget, 1982 len = ts.widgets.length; 1983 for ( indx = 0; indx < len; indx++ ) { 1984 widget = ts.widgets[ indx ]; 1985 if ( widget && widget.id && widget.id.toLowerCase() === name.toLowerCase() ) { 1986 return widget; 1987 } 1988 } 1989 }, 1990 1991 applyWidgetOptions : function( table ) { 1992 var indx, widget, wo, 1993 c = table.config, 1994 len = c.widgets.length; 1995 if ( len ) { 1996 for ( indx = 0; indx < len; indx++ ) { 1997 widget = ts.getWidgetById( c.widgets[ indx ] ); 1998 if ( widget && widget.options ) { 1999 wo = $.extend( true, {}, widget.options ); 2000 c.widgetOptions = $.extend( true, wo, c.widgetOptions ); 2001 // add widgetOptions to defaults for option validator 2002 $.extend( true, ts.defaults.widgetOptions, widget.options ); 2003 } 2004 } 2005 } 2006 }, 2007 2008 addWidgetFromClass : function( table ) { 2009 var len, indx, 2010 c = table.config, 2011 // look for widgets to apply from table class 2012 // don't match from 'ui-widget-content'; use \S instead of \w to include widgets 2013 // with dashes in the name, e.g. "widget-test-2" extracts out "test-2" 2014 regex = '^' + c.widgetClass.replace( ts.regex.templateName, '(\\S+)+' ) + '$', 2015 widgetClass = new RegExp( regex, 'g' ), 2016 // split up table class (widget id's can include dashes) - stop using match 2017 // otherwise only one widget gets extracted, see #1109 2018 widgets = ( table.className || '' ).split( ts.regex.spaces ); 2019 if ( widgets.length ) { 2020 len = widgets.length; 2021 for ( indx = 0; indx < len; indx++ ) { 2022 if ( widgets[ indx ].match( widgetClass ) ) { 2023 c.widgets[ c.widgets.length ] = widgets[ indx ].replace( widgetClass, '$1' ); 2024 } 2025 } 2026 } 2027 }, 2028 2029 applyWidgetId : function( table, id, init ) { 2030 table = $(table)[0]; 2031 var applied, time, name, 2032 c = table.config, 2033 wo = c.widgetOptions, 2034 debug = ts.debug(c, 'core'), 2035 widget = ts.getWidgetById( id ); 2036 if ( widget ) { 2037 name = widget.id; 2038 applied = false; 2039 // add widget name to option list so it gets reapplied after sorting, filtering, etc 2040 if ( $.inArray( name, c.widgets ) < 0 ) { 2041 c.widgets[ c.widgets.length ] = name; 2042 } 2043 if ( debug ) { time = new Date(); } 2044 2045 if ( init || !( c.widgetInit[ name ] ) ) { 2046 // set init flag first to prevent calling init more than once (e.g. pager) 2047 c.widgetInit[ name ] = true; 2048 if ( table.hasInitialized ) { 2049 // don't reapply widget options on tablesorter init 2050 ts.applyWidgetOptions( table ); 2051 } 2052 if ( typeof widget.init === 'function' ) { 2053 applied = true; 2054 if ( debug ) { 2055 console[ console.group ? 'group' : 'log' ]( 'Initializing ' + name + ' widget' ); 2056 } 2057 widget.init( table, widget, c, wo ); 2058 } 2059 } 2060 if ( !init && typeof widget.format === 'function' ) { 2061 applied = true; 2062 if ( debug ) { 2063 console[ console.group ? 'group' : 'log' ]( 'Updating ' + name + ' widget' ); 2064 } 2065 widget.format( table, c, wo, false ); 2066 } 2067 if ( debug ) { 2068 if ( applied ) { 2069 console.log( 'Completed ' + ( init ? 'initializing ' : 'applying ' ) + name + ' widget' + ts.benchmark( time ) ); 2070 if ( console.groupEnd ) { console.groupEnd(); } 2071 } 2072 } 2073 } 2074 }, 2075 2076 applyWidget : function( table, init, callback ) { 2077 table = $( table )[ 0 ]; // in case this is called externally 2078 var indx, len, names, widget, time, 2079 c = table.config, 2080 debug = ts.debug(c, 'core'), 2081 widgets = []; 2082 // prevent numerous consecutive widget applications 2083 if ( init !== false && table.hasInitialized && ( table.isApplyingWidgets || table.isUpdating ) ) { 2084 return; 2085 } 2086 if ( debug ) { time = new Date(); } 2087 ts.addWidgetFromClass( table ); 2088 // prevent "tablesorter-ready" from firing multiple times in a row 2089 clearTimeout( c.timerReady ); 2090 if ( c.widgets.length ) { 2091 table.isApplyingWidgets = true; 2092 // ensure unique widget ids 2093 c.widgets = $.grep( c.widgets, function( val, index ) { 2094 return $.inArray( val, c.widgets ) === index; 2095 }); 2096 names = c.widgets || []; 2097 len = names.length; 2098 // build widget array & add priority as needed 2099 for ( indx = 0; indx < len; indx++ ) { 2100 widget = ts.getWidgetById( names[ indx ] ); 2101 if ( widget && widget.id ) { 2102 // set priority to 10 if not defined 2103 if ( !widget.priority ) { widget.priority = 10; } 2104 widgets[ indx ] = widget; 2105 } else if ( debug ) { 2106 console.warn( '"' + names[ indx ] + '" was enabled, but the widget code has not been loaded!' ); 2107 } 2108 } 2109 // sort widgets by priority 2110 widgets.sort( function( a, b ) { 2111 return a.priority < b.priority ? -1 : a.priority === b.priority ? 0 : 1; 2112 }); 2113 // add/update selected widgets 2114 len = widgets.length; 2115 if ( debug ) { 2116 console[ console.group ? 'group' : 'log' ]( 'Start ' + ( init ? 'initializing' : 'applying' ) + ' widgets' ); 2117 } 2118 for ( indx = 0; indx < len; indx++ ) { 2119 widget = widgets[ indx ]; 2120 if ( widget && widget.id ) { 2121 ts.applyWidgetId( table, widget.id, init ); 2122 } 2123 } 2124 if ( debug && console.groupEnd ) { console.groupEnd(); } 2125 } 2126 c.timerReady = setTimeout( function() { 2127 table.isApplyingWidgets = false; 2128 $.data( table, 'lastWidgetApplication', new Date() ); 2129 c.$table.triggerHandler( 'tablesorter-ready' ); 2130 // callback executed on init only 2131 if ( !init && typeof callback === 'function' ) { 2132 callback( table ); 2133 } 2134 if ( debug ) { 2135 widget = c.widgets.length; 2136 console.log( 'Completed ' + 2137 ( init === true ? 'initializing ' : 'applying ' ) + widget + 2138 ' widget' + ( widget !== 1 ? 's' : '' ) + ts.benchmark( time ) ); 2139 } 2140 }, 10 ); 2141 }, 2142 2143 removeWidget : function( table, name, refreshing ) { 2144 table = $( table )[ 0 ]; 2145 var index, widget, indx, len, 2146 c = table.config; 2147 // if name === true, add all widgets from $.tablesorter.widgets 2148 if ( name === true ) { 2149 name = []; 2150 len = ts.widgets.length; 2151 for ( indx = 0; indx < len; indx++ ) { 2152 widget = ts.widgets[ indx ]; 2153 if ( widget && widget.id ) { 2154 name[ name.length ] = widget.id; 2155 } 2156 } 2157 } else { 2158 // name can be either an array of widgets names, 2159 // or a space/comma separated list of widget names 2160 name = ( $.isArray( name ) ? name.join( ',' ) : name || '' ).toLowerCase().split( /[\s,]+/ ); 2161 } 2162 len = name.length; 2163 for ( index = 0; index < len; index++ ) { 2164 widget = ts.getWidgetById( name[ index ] ); 2165 indx = $.inArray( name[ index ], c.widgets ); 2166 // don't remove the widget from config.widget if refreshing 2167 if ( indx >= 0 && refreshing !== true ) { 2168 c.widgets.splice( indx, 1 ); 2169 } 2170 if ( widget && widget.remove ) { 2171 if ( ts.debug(c, 'core') ) { 2172 console.log( ( refreshing ? 'Refreshing' : 'Removing' ) + ' "' + name[ index ] + '" widget' ); 2173 } 2174 widget.remove( table, c, c.widgetOptions, refreshing ); 2175 c.widgetInit[ name[ index ] ] = false; 2176 } 2177 } 2178 c.$table.triggerHandler( 'widgetRemoveEnd', table ); 2179 }, 2180 2181 refreshWidgets : function( table, doAll, dontapply ) { 2182 table = $( table )[ 0 ]; // see issue #243 2183 var indx, widget, 2184 c = table.config, 2185 curWidgets = c.widgets, 2186 widgets = ts.widgets, 2187 len = widgets.length, 2188 list = [], 2189 callback = function( table ) { 2190 $( table ).triggerHandler( 'refreshComplete' ); 2191 }; 2192 // remove widgets not defined in config.widgets, unless doAll is true 2193 for ( indx = 0; indx < len; indx++ ) { 2194 widget = widgets[ indx ]; 2195 if ( widget && widget.id && ( doAll || $.inArray( widget.id, curWidgets ) < 0 ) ) { 2196 list[ list.length ] = widget.id; 2197 } 2198 } 2199 ts.removeWidget( table, list.join( ',' ), true ); 2200 if ( dontapply !== true ) { 2201 // call widget init if 2202 ts.applyWidget( table, doAll || false, callback ); 2203 if ( doAll ) { 2204 // apply widget format 2205 ts.applyWidget( table, false, callback ); 2206 } 2207 } else { 2208 callback( table ); 2209 } 2210 }, 2211 2212 /* 2213 ââ ââ ââââââ ââ ââ ââ ââââââ ââ ââââââ ââââââ 2214 ââ ââ ââ ââ ââ ââ ââ ââ ââââ âââ 2215 ââ ââ ââ ââ ââ ââ ââ ââ ââââ âââ 2216 ââââââ ââ ââ ââââââ ââ ââ ââ ââââââ ââââââ 2217 */ 2218 benchmark : function( diff ) { 2219 return ( ' (' + ( new Date().getTime() - diff.getTime() ) + ' ms)' ); 2220 }, 2221 // deprecated ts.log 2222 log : function() { 2223 console.log( arguments ); 2224 }, 2225 debug : function(c, name) { 2226 return c && ( 2227 c.debug === true || 2228 typeof c.debug === 'string' && c.debug.indexOf(name) > -1 2229 ); 2230 }, 2231 2232 // $.isEmptyObject from jQuery v1.4 2233 isEmptyObject : function( obj ) { 2234 /*jshint forin: false */ 2235 for ( var name in obj ) { 2236 return false; 2237 } 2238 return true; 2239 }, 2240 2241 isValueInArray : function( column, arry ) { 2242 var indx, 2243 len = arry && arry.length || 0; 2244 for ( indx = 0; indx < len; indx++ ) { 2245 if ( arry[ indx ][ 0 ] === column ) { 2246 return indx; 2247 } 2248 } 2249 return -1; 2250 }, 2251 2252 formatFloat : function( str, table ) { 2253 if ( typeof str !== 'string' || str === '' ) { return str; } 2254 // allow using formatFloat without a table; defaults to US number format 2255 var num, 2256 usFormat = table && table.config ? table.config.usNumberFormat !== false : 2257 typeof table !== 'undefined' ? table : true; 2258 if ( usFormat ) { 2259 // US Format - 1,234,567.89 -> 1234567.89 2260 str = str.replace( ts.regex.comma, '' ); 2261 } else { 2262 // German Format = 1.234.567,89 -> 1234567.89 2263 // French Format = 1 234 567,89 -> 1234567.89 2264 str = str.replace( ts.regex.digitNonUS, '' ).replace( ts.regex.comma, '.' ); 2265 } 2266 if ( ts.regex.digitNegativeTest.test( str ) ) { 2267 // make (#) into a negative number -> (10) = -10 2268 str = str.replace( ts.regex.digitNegativeReplace, '-$1' ); 2269 } 2270 num = parseFloat( str ); 2271 // return the text instead of zero 2272 return isNaN( num ) ? $.trim( str ) : num; 2273 }, 2274 2275 isDigit : function( str ) { 2276 // replace all unwanted chars and match 2277 return isNaN( str ) ? 2278 ts.regex.digitTest.test( str.toString().replace( ts.regex.digitReplace, '' ) ) : 2279 str !== ''; 2280 }, 2281 2282 // computeTableHeaderCellIndexes from: 2283 // http://www.javascripttoolbox.com/lib/table/examples.php 2284 // http://www.javascripttoolbox.com/temp/table_cellindex.html 2285 computeColumnIndex : function( $rows, c ) { 2286 var i, j, k, l, cell, cells, rowIndex, rowSpan, colSpan, firstAvailCol, 2287 // total columns has been calculated, use it to set the matrixrow 2288 columns = c && c.columns || 0, 2289 matrix = [], 2290 matrixrow = new Array( columns ); 2291 for ( i = 0; i < $rows.length; i++ ) { 2292 cells = $rows[ i ].cells; 2293 for ( j = 0; j < cells.length; j++ ) { 2294 cell = cells[ j ]; 2295 rowIndex = i; 2296 rowSpan = cell.rowSpan || 1; 2297 colSpan = cell.colSpan || 1; 2298 if ( typeof matrix[ rowIndex ] === 'undefined' ) { 2299 matrix[ rowIndex ] = []; 2300 } 2301 // Find first available column in the first row 2302 for ( k = 0; k < matrix[ rowIndex ].length + 1; k++ ) { 2303 if ( typeof matrix[ rowIndex ][ k ] === 'undefined' ) { 2304 firstAvailCol = k; 2305 break; 2306 } 2307 } 2308 // jscs:disable disallowEmptyBlocks 2309 if ( columns && cell.cellIndex === firstAvailCol ) { 2310 // don't to anything 2311 } else if ( cell.setAttribute ) { 2312 // jscs:enable disallowEmptyBlocks 2313 // add data-column (setAttribute = IE8+) 2314 cell.setAttribute( 'data-column', firstAvailCol ); 2315 } else { 2316 // remove once we drop support for IE7 - 1/12/2016 2317 $( cell ).attr( 'data-column', firstAvailCol ); 2318 } 2319 for ( k = rowIndex; k < rowIndex + rowSpan; k++ ) { 2320 if ( typeof matrix[ k ] === 'undefined' ) { 2321 matrix[ k ] = []; 2322 } 2323 matrixrow = matrix[ k ]; 2324 for ( l = firstAvailCol; l < firstAvailCol + colSpan; l++ ) { 2325 matrixrow[ l ] = 'x'; 2326 } 2327 } 2328 } 2329 } 2330 ts.checkColumnCount($rows, matrix, matrixrow.length); 2331 return matrixrow.length; 2332 }, 2333 2334 checkColumnCount : function($rows, matrix, columns) { 2335 // this DOES NOT report any tbody column issues, except for the math and 2336 // and column selector widgets 2337 var i, len, 2338 valid = true, 2339 cells = []; 2340 for ( i = 0; i < matrix.length; i++ ) { 2341 // some matrix entries are undefined when testing the footer because 2342 // it is using the rowIndex property 2343 if ( matrix[i] ) { 2344 len = matrix[i].length; 2345 if ( matrix[i].length !== columns ) { 2346 valid = false; 2347 break; 2348 } 2349 } 2350 } 2351 if ( !valid ) { 2352 $rows.each( function( indx, el ) { 2353 var cell = el.parentElement.nodeName; 2354 if ( cells.indexOf( cell ) < 0 ) { 2355 cells.push( cell ); 2356 } 2357 }); 2358 console.error( 2359 'Invalid or incorrect number of columns in the ' + 2360 cells.join( ' or ' ) + '; expected ' + columns + 2361 ', but found ' + len + ' columns' 2362 ); 2363 } 2364 }, 2365 2366 // automatically add a colgroup with col elements set to a percentage width 2367 fixColumnWidth : function( table ) { 2368 table = $( table )[ 0 ]; 2369 var overallWidth, percent, $tbodies, len, index, 2370 c = table.config, 2371 $colgroup = c.$table.children( 'colgroup' ); 2372 // remove plugin-added colgroup, in case we need to refresh the widths 2373 if ( $colgroup.length && $colgroup.hasClass( ts.css.colgroup ) ) { 2374 $colgroup.remove(); 2375 } 2376 if ( c.widthFixed && c.$table.children( 'colgroup' ).length === 0 ) { 2377 $colgroup = $( '<colgroup class="' + ts.css.colgroup + '">' ); 2378 overallWidth = c.$table.width(); 2379 // only add col for visible columns - fixes #371 2380 $tbodies = c.$tbodies.find( 'tr:first' ).children( ':visible' ); 2381 len = $tbodies.length; 2382 for ( index = 0; index < len; index++ ) { 2383 percent = parseInt( ( $tbodies.eq( index ).width() / overallWidth ) * 1000, 10 ) / 10 + '%'; 2384 $colgroup.append( $( '<col>' ).css( 'width', percent ) ); 2385 } 2386 c.$table.prepend( $colgroup ); 2387 } 2388 }, 2389 2390 // get sorter, string, empty, etc options for each column from 2391 // jQuery data, metadata, header option or header class name ('sorter-false') 2392 // priority = jQuery data > meta > headers option > header class name 2393 getData : function( header, configHeader, key ) { 2394 var meta, cl4ss, 2395 val = '', 2396 $header = $( header ); 2397 if ( !$header.length ) { return ''; } 2398 meta = $.metadata ? $header.metadata() : false; 2399 cl4ss = ' ' + ( $header.attr( 'class' ) || '' ); 2400 if ( typeof $header.data( key ) !== 'undefined' || 2401 typeof $header.data( key.toLowerCase() ) !== 'undefined' ) { 2402 // 'data-lockedOrder' is assigned to 'lockedorder'; but 'data-locked-order' is assigned to 'lockedOrder' 2403 // 'data-sort-initial-order' is assigned to 'sortInitialOrder' 2404 val += $header.data( key ) || $header.data( key.toLowerCase() ); 2405 } else if ( meta && typeof meta[ key ] !== 'undefined' ) { 2406 val += meta[ key ]; 2407 } else if ( configHeader && typeof configHeader[ key ] !== 'undefined' ) { 2408 val += configHeader[ key ]; 2409 } else if ( cl4ss !== ' ' && cl4ss.match( ' ' + key + '-' ) ) { 2410 // include sorter class name 'sorter-text', etc; now works with 'sorter-my-custom-parser' 2411 val = cl4ss.match( new RegExp( '\\s' + key + '-([\\w-]+)' ) )[ 1 ] || ''; 2412 } 2413 return $.trim( val ); 2414 }, 2415 2416 getColumnData : function( table, obj, indx, getCell, $headers ) { 2417 if ( typeof obj !== 'object' || obj === null ) { 2418 return obj; 2419 } 2420 table = $( table )[ 0 ]; 2421 var $header, key, 2422 c = table.config, 2423 $cells = ( $headers || c.$headers ), 2424 // c.$headerIndexed is not defined initially 2425 $cell = c.$headerIndexed && c.$headerIndexed[ indx ] || 2426 $cells.find( '[data-column="' + indx + '"]:last' ); 2427 if ( typeof obj[ indx ] !== 'undefined' ) { 2428 return getCell ? obj[ indx ] : obj[ $cells.index( $cell ) ]; 2429 } 2430 for ( key in obj ) { 2431 if ( typeof key === 'string' ) { 2432 $header = $cell 2433 // header cell with class/id 2434 .filter( key ) 2435 // find elements within the header cell with cell/id 2436 .add( $cell.find( key ) ); 2437 if ( $header.length ) { 2438 return obj[ key ]; 2439 } 2440 } 2441 } 2442 return; 2443 }, 2444 2445 // *** Process table *** 2446 // add processing indicator 2447 isProcessing : function( $table, toggle, $headers ) { 2448 $table = $( $table ); 2449 var c = $table[ 0 ].config, 2450 // default to all headers 2451 $header = $headers || $table.find( '.' + ts.css.header ); 2452 if ( toggle ) { 2453 // don't use sortList if custom $headers used 2454 if ( typeof $headers !== 'undefined' && c.sortList.length > 0 ) { 2455 // get headers from the sortList 2456 $header = $header.filter( function() { 2457 // get data-column from attr to keep compatibility with jQuery 1.2.6 2458 return this.sortDisabled ? 2459 false : 2460 ts.isValueInArray( parseFloat( $( this ).attr( 'data-column' ) ), c.sortList ) >= 0; 2461 }); 2462 } 2463 $table.add( $header ).addClass( ts.css.processing + ' ' + c.cssProcessing ); 2464 } else { 2465 $table.add( $header ).removeClass( ts.css.processing + ' ' + c.cssProcessing ); 2466 } 2467 }, 2468 2469 // detach tbody but save the position 2470 // don't use tbody because there are portions that look for a tbody index (updateCell) 2471 processTbody : function( table, $tb, getIt ) { 2472 table = $( table )[ 0 ]; 2473 if ( getIt ) { 2474 table.isProcessing = true; 2475 $tb.before( '<colgroup class="tablesorter-savemyplace"/>' ); 2476 return $.fn.detach ? $tb.detach() : $tb.remove(); 2477 } 2478 var holdr = $( table ).find( 'colgroup.tablesorter-savemyplace' ); 2479 $tb.insertAfter( holdr ); 2480 holdr.remove(); 2481 table.isProcessing = false; 2482 }, 2483 2484 clearTableBody : function( table ) { 2485 $( table )[ 0 ].config.$tbodies.children().detach(); 2486 }, 2487 2488 // used when replacing accented characters during sorting 2489 characterEquivalents : { 2490 'a' : '\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5', // Ã¡Ã Ã¢Ã£Ã¤Ä Ã¥ 2491 'A' : '\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5', // ÃÃÃÃÃÄà 2492 'c' : '\u00e7\u0107\u010d', // çÄÄ 2493 'C' : '\u00c7\u0106\u010c', // ÃÄÄ 2494 'e' : '\u00e9\u00e8\u00ea\u00eb\u011b\u0119', // éèêëÄÄ 2495 'E' : '\u00c9\u00c8\u00ca\u00cb\u011a\u0118', // ÃÃÃÃÄÄ 2496 'i' : '\u00ed\u00ec\u0130\u00ee\u00ef\u0131', // Ãìİîïı 2497 'I' : '\u00cd\u00cc\u0130\u00ce\u00cf', // ÃÃİÃà 2498 'o' : '\u00f3\u00f2\u00f4\u00f5\u00f6\u014d', // óòôõöŠ2499 'O' : '\u00d3\u00d2\u00d4\u00d5\u00d6\u014c', // ÃÃÃÃÃÅ 2500 'ss': '\u00df', // à (s sharp) 2501 'SS': '\u1e9e', // Ạ(Capital sharp s) 2502 'u' : '\u00fa\u00f9\u00fb\u00fc\u016f', // úùûüů 2503 'U' : '\u00da\u00d9\u00db\u00dc\u016e' // ÃÃÃÃÅ® 2504 }, 2505 2506 replaceAccents : function( str ) { 2507 var chr, 2508 acc = '[', 2509 eq = ts.characterEquivalents; 2510 if ( !ts.characterRegex ) { 2511 ts.characterRegexArray = {}; 2512 for ( chr in eq ) { 2513 if ( typeof chr === 'string' ) { 2514 acc += eq[ chr ]; 2515 ts.characterRegexArray[ chr ] = new RegExp( '[' + eq[ chr ] + ']', 'g' ); 2516 } 2517 } 2518 ts.characterRegex = new RegExp( acc + ']' ); 2519 } 2520 if ( ts.characterRegex.test( str ) ) { 2521 for ( chr in eq ) { 2522 if ( typeof chr === 'string' ) { 2523 str = str.replace( ts.characterRegexArray[ chr ], chr ); 2524 } 2525 } 2526 } 2527 return str; 2528 }, 2529 2530 validateOptions : function( c ) { 2531 var setting, setting2, typ, timer, 2532 // ignore options containing an array 2533 ignore = 'headers sortForce sortList sortAppend widgets'.split( ' ' ), 2534 orig = c.originalSettings; 2535 if ( orig ) { 2536 if ( ts.debug(c, 'core') ) { 2537 timer = new Date(); 2538 } 2539 for ( setting in orig ) { 2540 typ = typeof ts.defaults[setting]; 2541 if ( typ === 'undefined' ) { 2542 console.warn( 'Tablesorter Warning! "table.config.' + setting + '" option not recognized' ); 2543 } else if ( typ === 'object' ) { 2544 for ( setting2 in orig[setting] ) { 2545 typ = ts.defaults[setting] && typeof ts.defaults[setting][setting2]; 2546 if ( $.inArray( setting, ignore ) < 0 && typ === 'undefined' ) { 2547 console.warn( 'Tablesorter Warning! "table.config.' + setting + '.' + setting2 + '" option not recognized' ); 2548 } 2549 } 2550 } 2551 } 2552 if ( ts.debug(c, 'core') ) { 2553 console.log( 'validate options time:' + ts.benchmark( timer ) ); 2554 } 2555 } 2556 }, 2557 2558 // restore headers 2559 restoreHeaders : function( table ) { 2560 var index, $cell, 2561 c = $( table )[ 0 ].config, 2562 $headers = c.$table.find( c.selectorHeaders ), 2563 len = $headers.length; 2564 // don't use c.$headers here in case header cells were swapped 2565 for ( index = 0; index < len; index++ ) { 2566 $cell = $headers.eq( index ); 2567 // only restore header cells if it is wrapped 2568 // because this is also used by the updateAll method 2569 if ( $cell.find( '.' + ts.css.headerIn ).length ) { 2570 $cell.html( c.headerContent[ index ] ); 2571 } 2572 } 2573 }, 2574 2575 destroy : function( table, removeClasses, callback ) { 2576 table = $( table )[ 0 ]; 2577 if ( !table.hasInitialized ) { return; } 2578 // remove all widgets 2579 ts.removeWidget( table, true, false ); 2580 var events, 2581 $t = $( table ), 2582 c = table.config, 2583 $h = $t.find( 'thead:first' ), 2584 $r = $h.find( 'tr.' + ts.css.headerRow ).removeClass( ts.css.headerRow + ' ' + c.cssHeaderRow ), 2585 $f = $t.find( 'tfoot:first > tr' ).children( 'th, td' ); 2586 if ( removeClasses === false && $.inArray( 'uitheme', c.widgets ) >= 0 ) { 2587 // reapply uitheme classes, in case we want to maintain appearance 2588 $t.triggerHandler( 'applyWidgetId', [ 'uitheme' ] ); 2589 $t.triggerHandler( 'applyWidgetId', [ 'zebra' ] ); 2590 } 2591 // remove widget added rows, just in case 2592 $h.find( 'tr' ).not( $r ).remove(); 2593 // disable tablesorter - not using .unbind( namespace ) because namespacing was 2594 // added in jQuery v1.4.3 - see http://api.jquery.com/event.namespace/ 2595 events = 'sortReset update updateRows updateAll updateHeaders updateCell addRows updateComplete sorton ' + 2596 'appendCache updateCache applyWidgetId applyWidgets refreshWidgets removeWidget destroy mouseup mouseleave ' + 2597 'keypress sortBegin sortEnd resetToLoadState '.split( ' ' ) 2598 .join( c.namespace + ' ' ); 2599 $t 2600 .removeData( 'tablesorter' ) 2601 .unbind( events.replace( ts.regex.spaces, ' ' ) ); 2602 c.$headers 2603 .add( $f ) 2604 .removeClass( [ ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone ].join( ' ' ) ) 2605 .removeAttr( 'data-column' ) 2606 .removeAttr( 'aria-label' ) 2607 .attr( 'aria-disabled', 'true' ); 2608 $r 2609 .find( c.selectorSort ) 2610 .unbind( ( 'mousedown mouseup keypress '.split( ' ' ).join( c.namespace + ' ' ) ).replace( ts.regex.spaces, ' ' ) ); 2611 ts.restoreHeaders( table ); 2612 $t.toggleClass( ts.css.table + ' ' + c.tableClass + ' tablesorter-' + c.theme, removeClasses === false ); 2613 $t.removeClass(c.namespace.slice(1)); 2614 // clear flag in case the plugin is initialized again 2615 table.hasInitialized = false; 2616 delete table.config.cache; 2617 if ( typeof callback === 'function' ) { 2618 callback( table ); 2619 } 2620 if ( ts.debug(c, 'core') ) { 2621 console.log( 'tablesorter has been removed' ); 2622 } 2623 } 2624 2625 }; 2626 2627 $.fn.tablesorter = function( settings ) { 2628 return this.each( function() { 2629 var table = this, 2630 // merge & extend config options 2631 c = $.extend( true, {}, ts.defaults, settings, ts.instanceMethods ); 2632 // save initial settings 2633 c.originalSettings = settings; 2634 // create a table from data (build table widget) 2635 if ( !table.hasInitialized && ts.buildTable && this.nodeName !== 'TABLE' ) { 2636 // return the table (in case the original target is the table's container) 2637 ts.buildTable( table, c ); 2638 } else { 2639 ts.setup( table, c ); 2640 } 2641 }); 2642 }; 2643 2644 // set up debug logs 2645 if ( !( window.console && window.console.log ) ) { 2646 // access $.tablesorter.logs for browsers that don't have a console... 2647 ts.logs = []; 2648 /*jshint -W020 */ 2649 console = {}; 2650 console.log = console.warn = console.error = console.table = function() { 2651 var arg = arguments.length > 1 ? arguments : arguments[0]; 2652 ts.logs[ ts.logs.length ] = { date: Date.now(), log: arg }; 2653 }; 2654 } 2655 2656 // add default parsers 2657 ts.addParser({ 2658 id : 'no-parser', 2659 is : function() { 2660 return false; 2661 }, 2662 format : function() { 2663 return ''; 2664 }, 2665 type : 'text' 2666 }); 2667 2668 ts.addParser({ 2669 id : 'text', 2670 is : function() { 2671 return true; 2672 }, 2673 format : function( str, table ) { 2674 var c = table.config; 2675 if ( str ) { 2676 str = $.trim( c.ignoreCase ? str.toLocaleLowerCase() : str ); 2677 str = c.sortLocaleCompare ? ts.replaceAccents( str ) : str; 2678 } 2679 return str; 2680 }, 2681 type : 'text' 2682 }); 2683 2684 ts.regex.nondigit = /[^\w,. \-()]/g; 2685 ts.addParser({ 2686 id : 'digit', 2687 is : function( str ) { 2688 return ts.isDigit( str ); 2689 }, 2690 format : function( str, table ) { 2691 var num = ts.formatFloat( ( str || '' ).replace( ts.regex.nondigit, '' ), table ); 2692 return str && typeof num === 'number' ? num : 2693 str ? $.trim( str && table.config.ignoreCase ? str.toLocaleLowerCase() : str ) : str; 2694 }, 2695 type : 'numeric' 2696 }); 2697 2698 ts.regex.currencyReplace = /[+\-,. ]/g; 2699 ts.regex.currencyTest = /^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/; 2700 ts.addParser({ 2701 id : 'currency', 2702 is : function( str ) { 2703 str = ( str || '' ).replace( ts.regex.currencyReplace, '' ); 2704 // test for £$â¬Â¤Â¥Â¢ 2705 return ts.regex.currencyTest.test( str ); 2706 }, 2707 format : function( str, table ) { 2708 var num = ts.formatFloat( ( str || '' ).replace( ts.regex.nondigit, '' ), table ); 2709 return str && typeof num === 'number' ? num : 2710 str ? $.trim( str && table.config.ignoreCase ? str.toLocaleLowerCase() : str ) : str; 2711 }, 2712 type : 'numeric' 2713 }); 2714 2715 // too many protocols to add them all https://en.wikipedia.org/wiki/URI_scheme 2716 // now, this regex can be updated before initialization 2717 ts.regex.urlProtocolTest = /^(https?|ftp|file):\/\//; 2718 ts.regex.urlProtocolReplace = /(https?|ftp|file):\/\/(www\.)?/; 2719 ts.addParser({ 2720 id : 'url', 2721 is : function( str ) { 2722 return ts.regex.urlProtocolTest.test( str ); 2723 }, 2724 format : function( str ) { 2725 return str ? $.trim( str.replace( ts.regex.urlProtocolReplace, '' ) ) : str; 2726 }, 2727 type : 'text' 2728 }); 2729 2730 ts.regex.dash = /-/g; 2731 ts.regex.isoDate = /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/; 2732 ts.addParser({ 2733 id : 'isoDate', 2734 is : function( str ) { 2735 return ts.regex.isoDate.test( str ); 2736 }, 2737 format : function( str ) { 2738 var date = str ? new Date( str.replace( ts.regex.dash, '/' ) ) : str; 2739 return date instanceof Date && isFinite( date ) ? date.getTime() : str; 2740 }, 2741 type : 'numeric' 2742 }); 2743 2744 ts.regex.percent = /%/g; 2745 ts.regex.percentTest = /(\d\s*?%|%\s*?\d)/; 2746 ts.addParser({ 2747 id : 'percent', 2748 is : function( str ) { 2749 return ts.regex.percentTest.test( str ) && str.length < 15; 2750 }, 2751 format : function( str, table ) { 2752 return str ? ts.formatFloat( str.replace( ts.regex.percent, '' ), table ) : str; 2753 }, 2754 type : 'numeric' 2755 }); 2756 2757 // added image parser to core v2.17.9 2758 ts.addParser({ 2759 id : 'image', 2760 is : function( str, table, node, $node ) { 2761 return $node.find( 'img' ).length > 0; 2762 }, 2763 format : function( str, table, cell ) { 2764 return $( cell ).find( 'img' ).attr( table.config.imgAttr || 'alt' ) || str; 2765 }, 2766 parsed : true, // filter widget flag 2767 type : 'text' 2768 }); 2769 2770 ts.regex.dateReplace = /(\S)([AP]M)$/i; // used by usLongDate & time parser 2771 ts.regex.usLongDateTest1 = /^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i; 2772 ts.regex.usLongDateTest2 = /^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i; 2773 ts.addParser({ 2774 id : 'usLongDate', 2775 is : function( str ) { 2776 // two digit years are not allowed cross-browser 2777 // Jan 01, 2013 12:34:56 PM or 01 Jan 2013 2778 return ts.regex.usLongDateTest1.test( str ) || ts.regex.usLongDateTest2.test( str ); 2779 }, 2780 format : function( str ) { 2781 var date = str ? new Date( str.replace( ts.regex.dateReplace, '$1 $2' ) ) : str; 2782 return date instanceof Date && isFinite( date ) ? date.getTime() : str; 2783 }, 2784 type : 'numeric' 2785 }); 2786 2787 // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included 2788 ts.regex.shortDateTest = /(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/; 2789 // escaped "-" because JSHint in Firefox was showing it as an error 2790 ts.regex.shortDateReplace = /[\-.,]/g; 2791 // XXY covers MDY & DMY formats 2792 ts.regex.shortDateXXY = /(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/; 2793 ts.regex.shortDateYMD = /(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/; 2794 ts.convertFormat = function( dateString, format ) { 2795 dateString = ( dateString || '' ) 2796 .replace( ts.regex.spaces, ' ' ) 2797 .replace( ts.regex.shortDateReplace, '/' ); 2798 if ( format === 'mmddyyyy' ) { 2799 dateString = dateString.replace( ts.regex.shortDateXXY, '$3/$1/$2' ); 2800 } else if ( format === 'ddmmyyyy' ) { 2801 dateString = dateString.replace( ts.regex.shortDateXXY, '$3/$2/$1' ); 2802 } else if ( format === 'yyyymmdd' ) { 2803 dateString = dateString.replace( ts.regex.shortDateYMD, '$1/$2/$3' ); 2804 } 2805 var date = new Date( dateString ); 2806 return date instanceof Date && isFinite( date ) ? date.getTime() : ''; 2807 }; 2808 2809 ts.addParser({ 2810 id : 'shortDate', // 'mmddyyyy', 'ddmmyyyy' or 'yyyymmdd' 2811 is : function( str ) { 2812 str = ( str || '' ).replace( ts.regex.spaces, ' ' ).replace( ts.regex.shortDateReplace, '/' ); 2813 return ts.regex.shortDateTest.test( str ); 2814 }, 2815 format : function( str, table, cell, cellIndex ) { 2816 if ( str ) { 2817 var c = table.config, 2818 $header = c.$headerIndexed[ cellIndex ], 2819 format = $header.length && $header.data( 'dateFormat' ) || 2820 ts.getData( $header, ts.getColumnData( table, c.headers, cellIndex ), 'dateFormat' ) || 2821 c.dateFormat; 2822 // save format because getData can be slow... 2823 if ( $header.length ) { 2824 $header.data( 'dateFormat', format ); 2825 } 2826 return ts.convertFormat( str, format ) || str; 2827 } 2828 return str; 2829 }, 2830 type : 'numeric' 2831 }); 2832 2833 // match 24 hour time & 12 hours time + am/pm - see http://regexr.com/3c3tk 2834 ts.regex.timeTest = /^(0?[1-9]|1[0-2]):([0-5]\d)(\s[AP]M)$|^((?:[01]\d|[2][0-4]):[0-5]\d)$/i; 2835 ts.regex.timeMatch = /(0?[1-9]|1[0-2]):([0-5]\d)(\s[AP]M)|((?:[01]\d|[2][0-4]):[0-5]\d)/i; 2836 ts.addParser({ 2837 id : 'time', 2838 is : function( str ) { 2839 return ts.regex.timeTest.test( str ); 2840 }, 2841 format : function( str ) { 2842 // isolate time... ignore month, day and year 2843 var temp, 2844 timePart = ( str || '' ).match( ts.regex.timeMatch ), 2845 orig = new Date( str ), 2846 // no time component? default to 00:00 by leaving it out, but only if str is defined 2847 time = str && ( timePart !== null ? timePart[ 0 ] : '00:00 AM' ), 2848 date = time ? new Date( '2000/01/01 ' + time.replace( ts.regex.dateReplace, '$1 $2' ) ) : time; 2849 if ( date instanceof Date && isFinite( date ) ) { 2850 temp = orig instanceof Date && isFinite( orig ) ? orig.getTime() : 0; 2851 // if original string was a valid date, add it to the decimal so the column sorts in some kind of order 2852 // luckily new Date() ignores the decimals 2853 return temp ? parseFloat( date.getTime() + '.' + orig.getTime() ) : date.getTime(); 2854 } 2855 return str; 2856 }, 2857 type : 'numeric' 2858 }); 2859 2860 ts.addParser({ 2861 id : 'metadata', 2862 is : function() { 2863 return false; 2864 }, 2865 format : function( str, table, cell ) { 2866 var c = table.config, 2867 p = ( !c.parserMetadataName ) ? 'sortValue' : c.parserMetadataName; 2868 return $( cell ).metadata()[ p ]; 2869 }, 2870 type : 'numeric' 2871 }); 2872 2873 /* 2874 ââââââ ââââââ ââââââ ââââââ ââââââ 2875 âââ ââââ ââââââ ââââââ ââââââ 2876 âââ ââââ ââââââ âââââ ââââââ 2877 ââââââ ââââââ ââââââ ââ ââ ââ ââ 2878 */ 2879 // add default widgets 2880 ts.addWidget({ 2881 id : 'zebra', 2882 priority : 90, 2883 format : function( table, c, wo ) { 2884 var $visibleRows, $row, count, isEven, tbodyIndex, rowIndex, len, 2885 child = new RegExp( c.cssChildRow, 'i' ), 2886 $tbodies = c.$tbodies.add( $( c.namespace + '_extra_table' ).children( 'tbody:not(.' + c.cssInfoBlock + ')' ) ); 2887 for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { 2888 // loop through the visible rows 2889 count = 0; 2890 $visibleRows = $tbodies.eq( tbodyIndex ).children( 'tr:visible' ).not( c.selectorRemove ); 2891 len = $visibleRows.length; 2892 for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { 2893 $row = $visibleRows.eq( rowIndex ); 2894 // style child rows the same way the parent row was styled 2895 if ( !child.test( $row[ 0 ].className ) ) { count++; } 2896 isEven = ( count % 2 === 0 ); 2897 $row 2898 .removeClass( wo.zebra[ isEven ? 1 : 0 ] ) 2899 .addClass( wo.zebra[ isEven ? 0 : 1 ] ); 2900 } 2901 } 2902 }, 2903 remove : function( table, c, wo, refreshing ) { 2904 if ( refreshing ) { return; } 2905 var tbodyIndex, $tbody, 2906 $tbodies = c.$tbodies, 2907 toRemove = ( wo.zebra || [ 'even', 'odd' ] ).join( ' ' ); 2908 for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { 2909 $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody 2910 $tbody.children().removeClass( toRemove ); 2911 ts.processTbody( table, $tbody, false ); // restore tbody 2912 } 2913 } 2914 }); 2915 2916 })( jQuery ); 2917 return jQuery.tablesorter;})); 2918
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.