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