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