PageSourceSearch

https://www.rilem.net/global/vendor/mottie/tablesorter/js/widgets/widget-math.js

js rilem.net collected 2026-09-24 20:01:10 UTC 22,954 bytes, 668 lines download raw bytes

1/*! Widget: math - updated 12/1/2019 (v2.31.2) *//*
2* Requires tablesorter v2.16+ and jQuery 1.7+
3* by Rob Garrison
4*/
5/*jshint browser:true, jquery:true, unused:false */
6/*global jQuery: false */
7;( function( $ ) {
8	'use strict';
9
10	var ts = $.tablesorter,
11
12	math = {
13
14		error: {
15			0       : 'Infinity result: Divide by zero',
16			1       : 'Need more than one element to make this calculation',
17			'undef' : 'No elements found'
18		},
19
20		// value returned when calculation is not possible, e.g. no values, dividing by zero, etc.
21		invalid : function( c, name, errorIndex ) {
22			// name = function returning invalid results
23			// errorIndex = math.error index with an explanation of the error
24			console.warn( name, math.error[ errorIndex ] );
25			return c && c.widgetOptions.math_none || ''; // text for cell
26		},
27
28		events : ( 'tablesorter-initialized update updateAll updateRows addRows updateCell filterReset ' )
29			.split(' ').join('.tsmath '),
30
31		processText : function( c, $cell ) {
32			var tmp,
33				wo = c.widgetOptions,
34				txt = ts.getElementText( c, $cell, math.getCellIndex( $cell ) ),
35				prefix = c.widgetOptions.math_prefix;
36			if (wo.math_textAttr) {
37				txt = $cell.attr(wo.math_textAttr) || txt;
38			}
39			if ( /</.test( prefix ) ) {
40				// prefix contains HTML; remove it & any text before using formatFloat
41				tmp = $( '<div>' + prefix + '</div>' ).text()
42					.replace(/\{content\}/g, '').trim();
43				txt = txt.replace( tmp, '' );
44			}
45			txt = ts.formatFloat( txt.replace( /[^\w,. \-()]/g, '' ), c.table ) || 0;
46			// isNaN('') => false
47			return isNaN( txt ) ? 0 : txt;
48		},
49
50		// get all of the row numerical values in an arry
51		getRow : function( c, $el, hasFilter ) {
52			var $cells,
53				wo = c.widgetOptions,
54				arry = [],
55				$row = $el.closest( 'tr' ),
56				isFiltered = $row.hasClass( wo.filter_filteredRow || 'filtered' );
57			if ( hasFilter ) {
58				$row = $row.filter( hasFilter );
59			}
60			if ( hasFilter || !isFiltered ) {
61				$cells = $row.children().not( '[' + wo.math_dataAttrib + '=ignore]' );
62				if ( wo.math_ignore.length ) {
63					$cells = $cells.filter( function() {
64						// using $.inArray is not optimal (needed for IE8)
65						return $.inArray( math.getCellIndex( $( this ) ), wo.math_ignore ) === -1;
66					});
67				}
68				arry = $cells.not( $el ).map( function() {
69					return math.processText( c, $( this ) );
70				}).get();
71			}
72			return arry;
73		},
74
75		// get all of the column numerical values in an arry
76		getColumn : function( c, $el, type, hasFilter ) {
77			var index, $t, $tr, len, $mathRows, mathAbove,
78				wo = c.widgetOptions,
79				arry = [],
80				$row = $el.closest( 'tr' ),
81				mathAttr = wo.math_dataAttrib,
82				mathIgnore = '[' + mathAttr + '=ignore]',
83				filtered = wo.filter_filteredRow || 'filtered',
84				cIndex = math.getCellIndex( $el ),
85				// get all rows to keep row indexing
86				$rows = c.$table.children( 'tbody' ).children(),
87				mathAttrs = [
88					'[' + mathAttr + '^=above]',
89					'[' + mathAttr + '^=below]',
90					'[' + mathAttr + '^=col]',
91					'[' + mathAttr + '^=all]'
92				];
93			if ( type === 'above' ) {
94				len = $rows.index( $row );
95				index = len;
96				while ( index >= 0 ) {
97					$tr = $rows.eq( index );
98					mathAbove = $tr.children().filter( mathAttrs[0] ).length;
99					if ( hasFilter ) {
100						$tr = $tr.filter( hasFilter );
101					}
102					$t = $tr.children().filter( function() {
103						return math.getCellIndex( $( this ) ) === cIndex;
104					});
105					// ignore filtered rows & rows with data-math="ignore" (and starting row)
106					if ( ( ( hasFilter || !$tr.hasClass( filtered ) ) &&
107							$tr.not( mathIgnore ).length &&
108							index !== len ) ||
109							mathAbove && index !== len ) {
110						// stop calculating 'above', when encountering another 'above'
111						if ( mathAbove ) {
112							index = 0;
113						} else if ( $t.length && $t.not( mathIgnore ).length ) {
114							arry[ arry.length ] = math.processText( c, $t );
115						}
116					}
117					index--;
118				}
119			} else if ( type === 'below' ) {
120				len = $rows.length;
121				// index + 1 to ignore starting node
122				for ( index = $rows.index( $row ) + 1; index < len; index++ ) {
123					$tr = $rows.eq( index );
124					if ( $tr.children().filter( mathAttrs[1] ).length ) {
125						break;
126					}
127					if ( hasFilter ) {
128						$tr = $tr.filter( hasFilter );
129					}
130					$t = $tr.children().filter( function() {
131						return math.getCellIndex( $( this ) ) === cIndex;
132					});
133					if ( ( hasFilter || !$tr.hasClass( filtered ) ) &&
134						$tr.not( mathIgnore ).length &&
135						$t.length && $t.not( mathIgnore ) ) {
136						arry[ arry.length ] = math.processText( c, $t );
137					}
138				}
139			} else {
140				$mathRows = $rows.not( mathIgnore );
141				len = $mathRows.length;
142				for ( index = 0; index < len; index++ ) {
143					$tr = $mathRows.eq( index );
144					if ( hasFilter ) {
145						$tr = $tr.filter( hasFilter );
146					}
147					$t = $tr.children().filter( function() {
148						return math.getCellIndex( $( this ) ) === cIndex;
149					});
150					if ( ( hasFilter || !$tr.hasClass( filtered ) ) &&
151						$t.not( mathAttrs.join( ',' ) ).length &&
152						!$t.is( $el ) && $t.not( mathIgnore ).length
153					) {
154						arry[ arry.length ] = math.processText( c, $t );
155					}
156				}
157			}
158			return arry;
159		},
160
161		// get all of the column numerical values in an arry
162		getAll : function( c, hasFilter ) {
163			var $t, col, $row, rowIndex, rowLen, $cells, cellIndex, cellLen,
164				arry = [],
165				wo = c.widgetOptions,
166				mathAttr = wo.math_dataAttrib,
167				mathIgnore = '[' + mathAttr + '=ignore]',
168				filtered = wo.filter_filteredRow || 'filtered',
169				$rows = c.$table.children( 'tbody' ).children().not( mathIgnore );
170			rowLen = $rows.length;
171			for ( rowIndex = 0; rowIndex < rowLen; rowIndex++ ) {
172				$row = $rows.eq( rowIndex );
173				if ( hasFilter ) {
174					$row = $row.filter( hasFilter );
175				}
176				if ( hasFilter || !$row.hasClass( filtered ) ) {
177					$cells = $row.children().not( mathIgnore );
178					cellLen = $cells.length;
179					for ( cellIndex = 0; cellIndex < cellLen; cellIndex++ ) {
180						$t = $cells.eq( cellIndex );
181						col = math.getCellIndex( $t );
182						if ( !$t.filter( '[' + mathAttr + ']' ).length && $.inArray( col, wo.math_ignore ) < 0 ) {
183							arry[ arry.length ] = math.processText( c, $t );
184						}
185					}
186				}
187			}
188			return arry;
189		},
190
191		setColumnIndexes : function( c ) {
192			var $table = c.$table,
193				last = 1,
194				// only target rows with a colspan or rows included in a rowspan
195				$rows = $table.children( 'tbody' ).children().filter( function() {
196					var cells, indx,
197						$this = $( this ),
198						include = $this.children( '[colspan]' ).length > 0;
199					if ( last > 1 ) {
200						last--;
201						include = true;
202					} else if ( last < 1 ) {
203						last = 1;
204					}
205					if ( $this.children( '[rowspan]' ).length > 0 ) {
206						cells = this.cells;
207						// find max rowspan (in case more than one cell has a rowspan)
208						for ( indx = 0; indx < cells.length; indx++ ) {
209							last = Math.max( cells[ indx ].rowSpan, last );
210						}
211					}
212					return include;
213				});
214			// pass `c` (table.config) to computeColumnIndex so it won't add a data-column
215			// to every tbody cell, just the ones where the .cellIndex property doesn't match
216			// the calculated cell index - hopefully fixes the lag issue in #1048
217			ts.computeColumnIndex( $rows, c );
218		},
219
220		getCellIndex : function( $cell ) {
221			var indx = $cell.attr( 'data-column' );
222			return typeof indx === 'undefined' ? $cell[0].cellIndex : parseInt( indx, 10 );
223		},
224
225		recalculate : function(c, wo, init) {
226			if ( c && ( !wo.math_isUpdating || init ) ) {
227
228				var time, mathAttr, $mathCells, indx, len,
229					changed = false,
230					filters = {};
231				if ( c.debug || wo.math_debug ) {
232					time = new Date();
233				}
234
235				// add data-column attributes to all table cells
236				if ( init ) {
237					math.setColumnIndexes( c ) ;
238				}
239
240				// data-attribute name (defaults to data-math)
241				wo.math_dataAttrib = 'data-' + (wo.math_data || 'math');
242
243				// all non-info tbody cells
244				mathAttr = wo.math_dataAttrib;
245				$mathCells = c.$tbodies.children( 'tr' ).children( '[' + mathAttr + ']' );
246				changed = math.mathType( c, $mathCells, wo.math_priority ) || changed;
247				// only info tbody cells
248				$mathCells = c.$table
249					.children( '.' + c.cssInfoBlock + ', tfoot' )
250					.children( 'tr' )
251					.children( '[' + mathAttr + ']' );
252				math.mathType( c, $mathCells, wo.math_priority );
253
254				// find the 'all' total
255				$mathCells = c.$table.children().children( 'tr' ).children( '[' + mathAttr + '^=all]' );
256				len = $mathCells.length;
257				// get math filter, if any
258				// hasFilter = $row.attr( mathAttr + '-filter' ) || wo.math_rowFilter;
259				for (indx = 0; indx < len; indx++) {
260					var $cell = $mathCells.eq( indx ),
261						filter = $cell.attr( mathAttr + '-filter' ) || wo.math_rowFilter;
262					filters[ filter ] = filters[ filter ] ? filters[ filter ].add( $cell ) : $cell;
263				}
264				$.each( filters, function( hasFilter, $cells ) {
265					changed = math.mathType( c, $cells, [ 'all' ], hasFilter ) || changed;
266				});
267
268				// trigger an update only if cells inside the tbody changed
269				if ( changed ) {
270					wo.math_isUpdating = true;
271					if ( c.debug || wo.math_debug ) {
272						console[ console.group ? 'group' : 'log' ]( 'Math widget updating the cache after recalculation' );
273					}
274
275					// update internal cache, but ignore "remove-me" rows and do not resort
276					ts.updateCache( c, function() {
277						math.updateComplete( c );
278						if ( !init && typeof wo.math_completed === 'function' ) {
279							wo.math_completed( c );
280						}
281						if ( c.debug || wo.math_debug ) {
282							console.log( 'Math widget update completed' + ts.benchmark( time ) );
283						}
284					});
285				} else {
286					if ( !init && typeof wo.math_completed === 'function' ) {
287						wo.math_completed( c );
288					}
289					if ( c.debug || wo.math_debug ) {
290						console.log( 'Math widget found no changes in data' + ts.benchmark( time ) );
291					}
292				}
293			}
294		},
295
296		updateComplete : function( c ) {
297			var wo = c.widgetOptions;
298			if ( wo.math_isUpdating && (c.debug || wo.math_debug ) && console.groupEnd ) {
299				console.groupEnd();
300			}
301			wo.math_isUpdating = false;
302		},
303
304		mathType : function( c, $cells, priority, hasFilter ) {
305			if ( $cells.length ) {
306				var getAll,
307					changed = false,
308					wo = c.widgetOptions,
309					mathAttr = wo.math_dataAttrib,
310					equations = ts.equations;
311				if ( priority[0] === 'all' ) {
312					// mathType is called multiple times if more than one "hasFilter" is used
313					getAll = math.getAll( c, hasFilter );
314				}
315				if (c.debug || wo.math_debug) {
316					console[ console.group ? 'group' : 'log' ]( 'Tablesorter Math widget recalculation' );
317				}
318				// $.each is okay here... only 4 priorities
319				$.each( priority, function( i, type ) {
320					var index, arry, formula, result, $el,
321						$targetCells = $cells.filter( '[' + mathAttr + '^=' + type + ']' ),
322						len = $targetCells.length;
323					if ( len ) {
324						if (c.debug || wo.math_debug) {
325							console[ console.group ? 'group' : 'log' ]( type );
326						}
327						for ( index = 0; index < len; index++ ) {
328							$el = $targetCells.eq( index );
329							// Row is filtered, no need to do further checking
330							if ( $el.parent().hasClass( wo.filter_filteredRow || 'filtered' ) ) {
331								continue;
332							}
333							hasFilter = $el.attr( mathAttr + '-filter' ) || wo.math_rowFilter;
334							formula = ( $el.attr( mathAttr ) || '' ).replace( type + '-', '' );
335							arry = ( type === 'row' ) ? math.getRow( c, $el, hasFilter ) :
336								( type === 'all' ) ? getAll : math.getColumn( c, $el, type, hasFilter );
337							if ( equations[ formula ] ) {
338								if ( arry.length ) {
339									result = equations[ formula ]( arry, c );
340									if ( c.debug || wo.math_debug ) {
341										console.log( $el.attr( mathAttr ), hasFilter ? '("' + hasFilter + '")' : '', arry, '=', result );
342									}
343								} else {
344									// mean will return a divide by zero error, everything else shows an undefined error
345									result = math.invalid( c, formula, formula === 'mean' ? 0 : 'undef' );
346								}
347								changed = math.output( $el, c, result, arry ) || changed;
348							}
349						}
350						if ( ( c.debug || wo.math_debug ) && console.groupEnd ) { console.groupEnd(); }
351					}
352				});
353				if ( ( c.debug || wo.math_debug ) && console.groupEnd ) { console.groupEnd(); }
354				return changed;
355			}
356			return false;
357		},
358
359		output : function( $cell, c, value, arry ) {
360			// get mask from cell data-attribute: data-math-mask="#,##0.00"
361			var $el,
362				wo = c.widgetOptions,
363				changed = false,
364				prev = $cell.html(),
365				mask = $cell.attr( 'data-' + wo.math_data + '-mask' ) || wo.math_mask,
366				target = $cell.attr( 'data-' + wo.math_data + '-target' ) || '',
367				result = ts.formatMask( mask, value, wo.math_prefix, wo.math_suffix );
368			if (target) {
369				$el = $cell.find(target);
370				if ($el.length) {
371					$cell = $el;
372				}
373			}
374			if ( typeof wo.math_complete === 'function' ) {
375				result = wo.math_complete( $cell, wo, result, value, arry );
376			}
377			if ( result !== false ) {
378				changed = prev !== result;
379				$cell.html( result );
380			}
381			// check if in a regular tbody, otherwise don't pass a changed flag
382			// to prevent unnecessary updating of the table cache
383			if ( changed ) {
384				$el = $cell.closest( 'tbody' );
385				// content was changed in a tfoot, info-only tbody or the resulting tbody is in a nested table
386				// then don't signal a change
387				if ( !$el.length || $el.hasClass( c.cssInfoBlock ) || $el.parent()[0] !== c.table ) {
388					return false;
389				}
390			}
391			return changed;
392		}
393
394	};
395
396	// Modified from https://code.google.com/p/javascript-number-formatter/
397	/**
398	* @preserve IntegraXor Web SCADA - JavaScript Number Formatter
399	* http:// www.integraxor.com/
400	* author: KPL, KHL
401	* (c)2011 ecava
402	* Dual licensed under the MIT or GPL Version 2 licenses.
403	*/
404	ts.formatMask = function( mask, val, tmpPrefix, tmpSuffix ) {
405		if ( !mask || isNaN( +val ) ) {
406			return val; // return as it is.
407		}
408
409		var isNegative, result, decimal, group, posLeadZero, posTrailZero, posSeparator, part, szSep,
410			integer, str, offset, index, end, inv,
411			suffix = '',
412
413			// find prefix/suffix
414			len = mask.length,
415			start = mask.search( /[0-9\-\+#]/ ),
416			tmp = start > 0 ? mask.substring( 0, start ) : '',
417			prefix = tmp;
418		if ( tmpPrefix ) {
419			if ( /\{content\}/.test( tmpPrefix || '' ) ) {
420				prefix = ( tmpPrefix || '' ).replace( /\{content\}/g, tmp || '' );
421			} else {
422				prefix = ( tmpPrefix || '' ) + tmp;
423			}
424		}
425		// reverse string: not an ideal method if there are surrogate pairs
426		inv = mask.split( '' ).reverse().join( '' );
427		end = inv.search( /[0-9\-\+#]/ );
428		index = len - end;
429		index += ( mask.substring( index, index + 1 ) === '.' ) ? 1 : 0;
430		tmp = end > 0 ? mask.substring( index, len ) : '';
431		suffix = tmp;
432		if ( tmpSuffix ) {
433			if ( /\{content\}/.test( tmpSuffix || '' ) ) {
434				suffix = ( tmpSuffix || '' ).replace( /\{content\}/g, tmp || '' );
435			} else {
436				suffix = tmp + ( tmpSuffix || '' );
437			}
438		}
439		mask = mask.substring( start, index );
440
441		// convert any string to number according to formation sign.
442		val = mask.charAt( 0 ) === '-' ? -val : +val;
443		isNegative = val < 0 ? val = -val : 0; // process only abs(), and turn on flag.
444
445		// search for separator for grp & decimal, anything not digit, not +/- sign, not #.
446		result = mask.match( /[^\d\-\+#]/g );
447		decimal = ( result && result[ result.length - 1 ] ) || '.'; // treat the right most symbol as decimal
448		group = ( result && result[ 1 ] && result[ 0 ] ) || ',';  // treat the left most symbol as group separator
449
450		// split the decimal for the format string if any.
451		mask = mask.split( decimal );
452		// Fix the decimal first, toFixed will auto fill trailing zero.
453		val = val.toFixed( mask[ 1 ] && mask[ 1 ].length );
454		val = +( val ) + ''; // convert number to string to trim off *all* trailing decimal zero(es)
455
456		// fill back any trailing zero according to format
457		posTrailZero = mask[ 1 ] && mask[ 1 ].lastIndexOf( '0' ); // look for last zero in format
458		part = val.split( '.' );
459		// integer will get !part[1]
460		if ( !part[ 1 ] || ( part[ 1 ] && part[ 1 ].length <= posTrailZero ) ) {
461			val = ( +val ).toFixed( posTrailZero + 1 );
462		}
463		szSep = mask[ 0 ].split( group ); // look for separator
464		mask[ 0 ] = szSep.join( '' ); // join back without separator for counting the pos of any leading 0.
465
466		posLeadZero = mask[ 0 ] && mask[ 0 ].indexOf( '0' );
467		if ( posLeadZero > -1 ) {
468			while ( part[ 0 ].length < ( mask[ 0 ].length - posLeadZero ) ) {
469				part[ 0 ] = '0' + part[ 0 ];
470			}
471		} else if ( +part[ 0 ] === 0 ) {
472			part[ 0 ] = '';
473		}
474
475		val = val.split( '.' );
476		val[ 0 ] = part[ 0 ];
477
478		// process the first group separator from decimal (.) only, the rest ignore.
479		// get the length of the last slice of split result.
480		posSeparator = ( szSep[ 1 ] && szSep[ szSep.length - 1 ].length );
481		if ( posSeparator ) {
482			integer = val[ 0 ];
483			str = '';
484			offset = integer.length % posSeparator;
485			len = integer.length;
486			for ( index = 0; index < len; index++ ) {
487				str += integer.charAt( index ); // ie6 only support charAt for sz.
488				// -posSeparator so that won't trail separator on full length
489				/*jshint -W018 */
490				if ( !( ( index - offset + 1 ) % posSeparator ) && index < len - posSeparator ) {
491					str += group;
492				}
493			}
494			val[ 0 ] = str;
495		}
496
497		val[ 1 ] = ( mask[ 1 ] && val[ 1 ] ) ? decimal + val[ 1 ] : '';
498		// put back any negation, combine integer and fraction, and add back prefix & suffix
499		return prefix + ( ( isNegative ? '-' : '' ) + val[ 0 ] + val[ 1 ] ) + suffix;
500	};
501
502	ts.equations = {
503		count : function( arry ) {
504			return arry.length;
505		},
506		sum : function( arry ) {
507			var index,
508				len = arry.length,
509				total = 0;
510			for ( index = 0; index < len; index++ ) {
511				total += arry[ index ];
512			}
513			return total;
514		},
515		mean : function( arry ) {
516			var total = ts.equations.sum( arry );
517			return total / arry.length;
518		},
519		median : function( arry, c ) {
520			var half,
521				len = arry.length;
522			if ( len > 1 ) {
523				// https://gist.github.com/caseyjustus/1166258
524				arry.sort( function( a, b ) { return a - b; } );
525				half = Math.floor( len / 2 );
526				return ( len % 2 ) ? arry[ half ] : ( arry[ half - 1 ] + arry[ half ] ) / 2;
527			}
528			return math.invalid( c, 'median', 1 );
529		},
530		mode : function( arry ) {
531			// http://stackoverflow.com/a/3451640/145346
532			var index, el, m,
533				modeMap = {},
534				maxCount = 1,
535				modes = [ arry[ 0 ] ];
536			for ( index = 0; index < arry.length; index++ ) {
537				el = arry[ index ];
538				modeMap[ el ] = modeMap[ el ] ? modeMap[ el ] + 1 : 1;
539				m = modeMap[ el ];
540				if ( m > maxCount ) {
541					modes = [ el ];
542					maxCount = m;
543				} else if ( m === maxCount ) {
544					modes[ modes.length ] = el;
545					maxCount = m;
546				}
547			}
548			// returns arry of modes if there is a tie
549			return modes.sort( function( a, b ) { return a - b; } );
550		},
551		max : function(arry) {
552			return Math.max.apply( Math, arry );
553		},
554		min : function(arry) {
555			return Math.min.apply( Math, arry );
556		},
557		range: function(arry) {
558			var v = arry.sort( function( a, b ) { return a - b; } );
559			return v[ arry.length - 1 ] - v[ 0 ];
560		},
561		// common variance equation
562		// (not accessible via data-attribute setting)
563		variance: function( arry, population, c ) {
564			var divisor,
565				avg = ts.equations.mean( arry ),
566				v = 0,
567				i = arry.length;
568			while ( i-- ) {
569				v += Math.pow( ( arry[ i ] - avg ), 2 );
570			}
571			divisor = ( arry.length - ( population ? 0 : 1 ) );
572			if ( divisor === 0 ) {
573				return math.invalid( c, 'variance', 0 );
574			}
575			v /= divisor;
576			return v;
577		},
578		// variance (population)
579		varp : function( arry, c ) {
580			return ts.equations.variance( arry, true, c );
581		},
582		// variance (sample)
583		vars : function( arry, c ) {
584			return ts.equations.variance( arry, false, c );
585		},
586		// standard deviation (sample)
587		stdevs : function( arry, c ) {
588			var vars = ts.equations.variance( arry, false, c );
589			return Math.sqrt( vars );
590		},
591		// standard deviation (population)
592		stdevp : function( arry, c ) {
593			var varp = ts.equations.variance( arry, true, c );
594			return Math.sqrt( varp );
595		}
596	};
597
598	// add new widget called repeatHeaders
599	// ************************************
600	ts.addWidget({
601		id: 'math',
602		priority: 100,
603		options: {
604			math_data     : 'math',
605			math_debug    : false,
606			// column index to ignore
607			math_ignore   : [],
608			// mask info: https://code.google.com/p/javascript-number-formatter/
609			math_mask     : '#,##0.00',
610			// complete executed after each fucntion
611			math_complete : null, // function($cell, wo, result, value, arry) { return result; },
612			// math_completed called after all math calculations have completed
613			math_completed: function( /* config */ ) {},
614			// order of calculation; 'all' is last
615			math_priority : [ 'row', 'above', 'below', 'col' ],
616			// template for or just prepend the mask prefix & suffix with this HTML
617			// e.g. '<span class="red">{content}</span>'
618			math_prefix   : '',
619			math_suffix   : '',
620			// cell attribute containing the math value to use
621			math_textAttr : '',
622			// no matching math elements found (text added to cell)
623			math_none     : 'N/A',
624			math_event    : 'recalculate',
625			// use this filter to target specific rows (e.g. ':visible', or ':not(.empty-row)')
626			math_rowFilter: ''
627		},
628		init : function( table, thisWidget, c, wo ) {
629			// filterEnd fires after updateComplete
630			var update = ( ts.hasWidget( table, 'filter' ) ? 'filterEnd' : 'updateComplete' ) + '.tsmath';
631			// filterEnd is when the pager hides rows... so bind to pagerComplete
632			math.events += ( ts.hasWidget( table, 'pager' ) ? 'pagerComplete' : 'filterEnd' ) + '.tsmath ';
633			c.$table
634				.off( ( math.events + 'updateComplete.tsmath ' + wo.math_event ).replace( /\s+/g, ' ' ) )
635				.on( math.events + wo.math_event, function( e ) {
636					if ( !this.hasInitialized ) { return; }
637					var init = e.type === 'tablesorter-initialized';
638					if ( !wo.math_isUpdating || init ) {
639						// don't setColumnIndexes on init here, or it gets done twice
640						if ( !/filter/.test( e.type ) && !init ) {
641							// redo data-column indexes on update
642							math.setColumnIndexes( c );
643						}
644						math.recalculate( c, wo, init );
645					}
646				})
647				.on( update, function() {
648					setTimeout( function() {
649						math.updateComplete( c );
650					}, 40 );
651				});
652			wo.math_isUpdating = false;
653			// math widget initialized after table - see #946
654			if ( table.hasInitialized ) {
655				math.recalculate( c, wo, true );
656			}
657		},
658		// this remove function is called when using the refreshWidgets method or when destroying the tablesorter plugin
659		// this function only applies to tablesorter v2.4+
660		remove: function( table, c, wo, refreshing ) {
661			if ( refreshing ) { return; }
662			c.$table
663				.off( ( math.events + ' updateComplete.tsmath ' + wo.math_event ).replace( /\s+/g, ' ' ) )
664				.children().children( 'tr' ).children( '[data-' + wo.math_data + ']' ).empty();
665		}
666	});
667
668})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.