math_rowFilter can now be overridden by the row's data-math-filter attribute.
-
On initialization, only tbody cells with a rowspan or colspan are now processed and only get a "data-column" set if the internal cellIndex doesn't match the calculated cell index. This should improve performance and reduce lag after initialization and updating the table (see issue #1048.
+
+ Lots of optimizations!
+
+
On initialization, only tbody cells with a rowspan or colspan are now processed and only get a "data-column" set if the internal cellIndex doesn't match the calculated cell index.
+
Initial calculation is no longer performed three times in a row
+
The cache is now only updated if cell content inside of a sortable tbody was modified.
+
These changes should improve performance and reduce lag after initialization and updating the table (see issue #1048.
+
+
In v2.24.6, added math_rowFilter option.
@@ -192,25 +200,36 @@
Added math_none option which allows customizing the text added to a cell when there are no matching math elements (.
Fixed an issue with a sum column encountering a cell without a defined "data-math" attribute returning an empty string instead of zero. See issue #873.
-
Fixed a javascript error occurring on empty tables & now all updates will reapply column indexing to tbody cells - I know this isn't ideal as it would be slow on larger tables.
+
In v2.22.0,
+
+
Fixed an issue with a sum column encountering a cell without a defined "data-math" attribute returning an empty string instead of zero. See issue #873.
+
Fixed a javascript error occurring on empty tables & now all updates will reapply column indexing to tbody cells - I know this isn't ideal as it would be slow on larger tables.
+
+
+
In v2.19.1, added math_event option & fixed an issue with event unbinding in jQuery version < 1.9.
+
In v2.17.1,
+
+
Values added to the data-attribute set by the textAttribute option will now be used in the calculation instead of the actual cell content.
+
The Grand Total cells now shows a higher precision value to emphasize this point.
+
+
+
In v2.16.4, added:
+
+
Two new options: math_prefix and math_suffix, which will be added before or after the prefix or suffix, respectively.
+
Added "Mask Examples" section with examples, and how to use the $.tablesorter.formatMask function.
+
+
-
-
In v2.19.1, added math_event option & fixed an issue with event unbinding in jQuery version < 1.9.
-
In v2.17.1,
-
-
Values added to the data-attribute set by the textAttribute option will now be used in the calculation instead of the actual cell content.
-
The Grand Total cells now shows a higher precision value to emphasize this point.
-
-
-
In v2.16.4, added:
-
-
Two new options: math_prefix and math_suffix, which will be added before or after the prefix or suffix, respectively.
-
Added "Mask Examples" section with examples, and how to use the $.tablesorter.formatMask function.
-
-
+
+
+
+
This widget will only work in tablesorter version 2.16+ and jQuery version 1.7+.
It adds basic math capabilities. A full list of default formulas is listed in the "Attribute Settings" section.
Add your own custom formulas which manipulating an array of values gathered from the table by row, column or a column block (above).
@@ -218,7 +237,7 @@
The widget will update the calculations based on filtered rows, and will update if any data within the table changes (using update events).
This widget is not optimized for very large tables, for two reasons:
-
On initialization, it cycles through every table row, calculates the column index, and adds a data-column attribute (Fixed in v2.24.7).
+
On initialization, it cycles through every table row, calculates the column index , and adds a data-column attribute (Fixed in v2.24.7).
It uses the update method whenever it recalculates values to make the results sortable. This occurs when any of the update methods are used and after the table is filtered.
@@ -442,6 +461,7 @@ math_suffix : '{content}!'
math_rowFilter : ':visible:not(.filtered)'
In v2.24.7, this setting my be overridden in specific rows by adding a data-math-filter attribute (The "math" portion is set by the math_data option)
<tr data-math-filter=":visible">...</tr>
+ Note The data-math-filter attribute is ignored if set to an empty string; instead use data-math-filter="*" to target all rows as done in the first example on this page.
@@ -580,9 +600,17 @@ BAD => No minus (-) here! $#,###.00 or [-] here either <= BAD
col-sum
col-sum
+
+
Visible Total
+
+
Grand Total
-
all-sum
+
+
diff --git a/js/widgets/widget-math.js b/js/widgets/widget-math.js
index 8790781b..9b3aa052 100644
--- a/js/widgets/widget-math.js
+++ b/js/widgets/widget-math.js
@@ -25,8 +25,8 @@
return c && c.widgetOptions.math_none || 'none'; // text for cell
},
- events : ( 'tablesorter-initialized update updateAll updateRows addRows updateCell ' +
- 'filterReset filterEnd ' ).split(' ').join('.tsmath '),
+ events : ( 'tablesorter-initialized update updateAll updateRows addRows updateCell filterReset ' )
+ .split(' ').join('.tsmath '),
processText : function( c, $cell ) {
var txt = ts.getElementText( c, $cell, math.getCellIndex( $cell ) );
@@ -36,17 +36,16 @@
},
// get all of the row numerical values in an arry
- getRow : function( c, $el ) {
+ getRow : function( c, $el, hasFilter ) {
var $cells,
wo = c.widgetOptions,
arry = [],
$row = $el.closest( 'tr' ),
- isFiltered = $row.hasClass( wo.filter_filteredRow || 'filtered' ),
- hasFilter = $row.attr( wo.math_dataAttrib + '-filter' ) || wo.math_rowFilter;
+ isFiltered = $row.hasClass( wo.filter_filteredRow || 'filtered' );
if ( hasFilter ) {
$row = $row.filter( hasFilter );
}
- if ( !isFiltered || hasFilter ) {
+ if ( hasFilter || !isFiltered ) {
$cells = $row.children().not( '[' + wo.math_dataAttrib + '=ignore]' );
if ( wo.math_ignore.length ) {
$cells = $cells.filter( function( indx ) {
@@ -62,33 +61,38 @@
},
// get all of the column numerical values in an arry
- getColumn : function( c, $el, type ) {
+ getColumn : function( c, $el, type, hasFilter ) {
var index, $t, $tr, len, $mathRows, mathAbove,
wo = c.widgetOptions,
arry = [],
$row = $el.closest( 'tr' ),
mathAttr = wo.math_dataAttrib,
- hasFilter = $row.attr( mathAttr + '-filter' ) || wo.math_rowFilter,
+ mathIgnore = '[' + mathAttr + '=ignore]',
filtered = wo.filter_filteredRow || 'filtered',
cIndex = math.getCellIndex( $el ),
- $rows = c.$table.children( 'tbody' ).children();
- // make sure tfoot rows are AFTER the tbody rows
- // $rows.add( c.$table.children( 'tfoot' ).children() );
+ // get all rows to keep row indexing
+ $rows = c.$table.children( 'tbody' ).children(),
+ mathAttrs = [
+ '[' + mathAttr + '^=above]',
+ '[' + mathAttr + '^=below]',
+ '[' + mathAttr + '^=col]',
+ '[' + mathAttr + '^=all]'
+ ];
if ( type === 'above' ) {
len = $rows.index( $row );
index = len;
while ( index >= 0 ) {
$tr = $rows.eq( index );
+ mathAbove = $tr.children().filter( mathAttrs[0] ).length;
if ( hasFilter ) {
$tr = $tr.filter( hasFilter );
}
$t = $tr.children().filter( function( indx ) {
return math.getCellIndex( $( this ) ) === cIndex;
});
- mathAbove = $t.filter( '[' + mathAttr + '^=above]' ).length;
// ignore filtered rows & rows with data-math="ignore" (and starting row)
- if ( ( ( !$tr.hasClass( filtered ) || hasFilter ) &&
- $tr.not( '[' + mathAttr + '=ignore]' ).length &&
+ if ( ( ( hasFilter || !$tr.hasClass( filtered ) ) &&
+ $tr.not( mathIgnore ).length &&
index !== len ) ||
mathAbove && index !== len ) {
// stop calculating 'above', when encountering another 'above'
@@ -105,24 +109,23 @@
// index + 1 to ignore starting node
for ( index = $rows.index( $row ) + 1; index < len; index++ ) {
$tr = $rows.eq( index );
+ if ( $tr.children().filter( mathAttrs[1] ).length ) {
+ break;
+ }
if ( hasFilter ) {
$tr = $tr.filter( hasFilter );
}
$t = $tr.children().filter( function( indx ) {
return math.getCellIndex( $( this ) ) === cIndex;
});
- if ( $t.filter( '[' + mathAttr + '^=below]' ).length ) {
- break;
- }
- if ( ( !$tr.hasClass( filtered ) || hasFilter ) &&
- $tr.not( '[' + mathAttr + '=ignore]' ).length &&
- $t.length ) {
+ if ( ( hasFilter || !$tr.hasClass( filtered ) ) &&
+ $tr.not( mathIgnore ).length &&
+ $t.length ) {
arry.push( math.processText( c, $t ) );
}
}
-
} else {
- $mathRows = $rows.not( '[' + mathAttr + '=ignore]' );
+ $mathRows = $rows.not( mathIgnore );
len = $mathRows.length;
for ( index = 0; index < len; index++ ) {
$tr = $mathRows.eq( index );
@@ -132,8 +135,8 @@
$t = $tr.children().filter( function( indx ) {
return math.getCellIndex( $( this ) ) === cIndex;
});
- if ( ( !$tr.hasClass( filtered ) || hasFilter ) &&
- $t.not( '[' + mathAttr + '^=above],[' + mathAttr + '^=below],[' + mathAttr + '^=col]' ).length &&
+ if ( ( hasFilter || !$tr.hasClass( filtered ) ) &&
+ $t.not( mathAttrs.join( ',' ) ).length &&
!$t.is( $el ) ) {
arry.push( math.processText( c, $t ) );
}
@@ -143,22 +146,22 @@
},
// get all of the column numerical values in an arry
- getAll : function( c ) {
+ getAll : function( c, hasFilter ) {
var $t, col, $row, rowIndex, rowLen, $cells, cellIndex, cellLen,
arry = [],
wo = c.widgetOptions,
mathAttr = wo.math_dataAttrib,
+ mathIgnore = '[' + mathAttr + '=ignore]',
filtered = wo.filter_filteredRow || 'filtered',
- hasFilter = wo.filter_rowFilter,
- $rows = c.$table.children( 'tbody' ).children().not( '[' + mathAttr + '=ignore]' );
+ $rows = c.$table.children( 'tbody' ).children().not( mathIgnore );
rowLen = $rows.length;
for ( rowIndex = 0; rowIndex < rowLen; rowIndex++ ) {
$row = $rows.eq( rowIndex );
if ( hasFilter ) {
$row = $row.filter( hasFilter );
}
- if ( !$row.hasClass( filtered ) || hasFilter ) {
- $cells = $row.children().not( '[' + mathAttr + '=ignore]' );
+ if ( hasFilter || !$row.hasClass( filtered ) ) {
+ $cells = $row.children().not( mathIgnore );
cellLen = $cells.length;
// $row.children().each(function(){
for ( cellIndex = 0; cellIndex < cellLen; cellIndex++ ) {
@@ -215,7 +218,9 @@
recalculate : function(c, wo, init) {
if ( c && ( !wo.math_isUpdating || init ) ) {
- var undef, time, mathAttr, $mathCells;
+ var undef, time, mathAttr, $mathCells, indx, len,
+ changed = false,
+ filters = {};
if ( c.debug ) {
time = new Date();
}
@@ -231,7 +236,7 @@
// all non-info tbody cells
mathAttr = wo.math_dataAttrib;
$mathCells = c.$tbodies.children( 'tr' ).children( '[' + mathAttr + ']' );
- math.mathType( c, $mathCells, wo.math_priority );
+ changed = changed || math.mathType( c, $mathCells, wo.math_priority );
// only info tbody cells
$mathCells = c.$table
@@ -242,20 +247,33 @@
// find the 'all' total
$mathCells = c.$table.children().children( 'tr' ).children( '[' + mathAttr + '^=all]' );
- math.mathType( c, $mathCells, [ 'all' ] );
-
- wo.math_isUpdating = true;
- if ( c.debug ) {
- console[ console.group ? 'group' : 'log' ]( 'Math widget triggering an update after recalculation' );
- }
-
- // update internal cache
- ts.update( c, undef, function(){
- math.updateComplete( c );
+ len = $mathCells.length;
+ // get math filter, if any
+ // hasFilter = $row.attr( mathAttr + '-filter' ) || wo.math_rowFilter;
+ $mathCells.each( function( indx, cell ) {
+ var $cell = $( cell ),
+ filter = $mathCells.eq( indx ).attr( mathAttr + '-filter' ) || wo.math_rowFilter;
+ filters[ filter ] = filters[ filter ] ? filters[ filter ].add( $cell ) : $cell;
+ });
+ $.each( filters, function( hasFilter, $cells ) {
+ changed = changed || math.mathType( c, $cells, [ 'all' ], hasFilter );
});
- if ( c.debug ) {
- console.log( 'Math widget update completed' + ts.benchmark( time ) );
+ // trigger an update only if cells inside the tbody changed
+ if ( changed ) {
+ wo.math_isUpdating = true;
+ if ( c.debug ) {
+ console[ console.group ? 'group' : 'log' ]( 'Math widget triggering an update after recalculation' );
+ }
+
+ // update internal cache
+ ts.update( c, undef, function(){
+ math.updateComplete( c );
+ });
+
+ if ( c.debug ) {
+ console.log( 'Math widget update completed' + ts.benchmark( time ) );
+ }
}
}
},
@@ -266,23 +284,25 @@
wo.math_isUpdating = false;
},
- mathType : function( c, $cells, priority ) {
+ mathType : function( c, $cells, priority, hasFilter ) {
if ( $cells.length ) {
- var formula, result, $el, arry, getAll, $targetCells, index, len,
+ var getAll,
+ changed = false,
wo = c.widgetOptions,
mathAttr = wo.math_dataAttrib,
equations = ts.equations;
if ( priority[0] === 'all' ) {
- // no need to get all cells more than once
- getAll = math.getAll( c );
+ // mathType is called multiple times if more than one "hasFilter" is used
+ getAll = math.getAll( c, hasFilter );
}
if (c.debug) {
console[ console.group ? 'group' : 'log' ]( 'Tablesorter Math widget recalculation' );
}
// $.each is okay here... only 4 priorities
$.each( priority, function( i, type ) {
- $targetCells = $cells.filter( '[' + mathAttr + '^=' + type + ']' );
- len = $targetCells.length;
+ var index, arry, formula, result, $el,
+ $targetCells = $cells.filter( '[' + mathAttr + '^=' + type + ']' ),
+ len = $targetCells.length;
if ( len ) {
if (c.debug) {
console[ console.group ? 'group' : 'log' ]( type );
@@ -293,39 +313,58 @@
if ( $el.parent().hasClass( wo.filter_filteredRow || 'filtered' ) ) {
continue;
}
+ hasFilter = hasFilter || $el.attr( mathAttr + '-filter' ) || wo.math_rowFilter;
formula = ( $el.attr( mathAttr ) || '' ).replace( type + '-', '' );
- arry = ( type === 'row' ) ? math.getRow( c, $el ) :
- ( type === 'all' ) ? getAll : math.getColumn( c, $el, type );
+ arry = ( type === 'row' ) ? math.getRow( c, $el, hasFilter ) :
+ ( type === 'all' ) ? getAll : math.getColumn( c, $el, type, hasFilter );
if ( equations[ formula ] ) {
if ( arry.length ) {
result = equations[ formula ]( arry, c );
if ( c.debug ) {
- console.log( $el.attr( mathAttr ), arry, '=', result );
+ console.log( $el.attr( mathAttr ), hasFilter ? '("' + hasFilter + '")' : '', arry, '=', result );
}
} else {
// mean will return a divide by zero error, everything else shows an undefined error
result = math.invalid( c, formula, formula === 'mean' ? 0 : 'undef' );
}
- math.output( $el, wo, result, arry );
+ changed = math.output( $el, c, result, arry ) || changed;
}
}
if ( c.debug && console.groupEnd ) { console.groupEnd(); }
}
});
if ( c.debug && console.groupEnd ) { console.groupEnd(); }
+ return changed;
}
+ return false;
},
- output : function( $cell, wo, value, arry ) {
+ output : function( $cell, c, value, arry ) {
// get mask from cell data-attribute: data-math-mask="#,##0.00"
- var mask = $cell.attr( 'data-' + wo.math_data + '-mask' ) || wo.math_mask,
+ var $el,
+ wo = c.widgetOptions,
+ changed = false,
+ prev = $cell.html(),
+ mask = $cell.attr( 'data-' + wo.math_data + '-mask' ) || wo.math_mask,
result = ts.formatMask( mask, value, wo.math_wrapPrefix, wo.math_wrapSuffix );
if ( typeof wo.math_complete === 'function' ) {
result = wo.math_complete( $cell, wo, result, value, arry );
}
if ( result !== false ) {
+ changed = prev !== result;
$cell.html( result );
}
+ // check if in a regular tbody, otherwise don't pass a changed flag
+ // to prevent unnecessary updating of the table cache
+ if ( changed ) {
+ $el = $cell.closest( 'tbody' );
+ // content was changed in a tfoot, info-only tbody or the resulting tbody is in a nested table
+ // then don't signal a change
+ if ( !$el.length || $el.hasClass( c.cssInfoBlock ) || $el.parent()[0] !== c.table ) {
+ return false;
+ }
+ }
+ return changed;
}
};
@@ -560,10 +599,13 @@
},
init : function( table, thisWidget, c, wo ) {
// filterEnd fires after updateComplete
- var update = ts.hasWidget( table, 'filter' ) ? 'filterEnd' : 'updateComplete';
+ var update = ( ts.hasWidget( table, 'filter' ) ? 'filterEnd' : 'updateComplete' ) + '.tsmath';
+ // filterEnd is when the pager hides rows... so bind to pagerComplete
+ math.events += ( ts.hasWidget( table, 'pager' ) ? 'pagerComplete' : 'filterEnd' ) + '.tsmath ';
c.$table
- .off( ( math.events + ' updateComplete.tsmath ' + wo.math_event ).replace( /\s+/g, ' ' ) )
- .on( math.events + ' ' + wo.math_event, function( e ) {
+ .off( ( math.events + 'updateComplete.tsmath ' + wo.math_event ).replace( /\s+/g, ' ' ) )
+ .on( math.events + wo.math_event, function( e ) {
+ if ( !this.hasInitialized ) { return; }
var init = e.type === 'tablesorter-initialized';
if ( !wo.math_isUpdating || init ) {
// don't setColumnIndexes on init here, or it gets done twice
@@ -574,7 +616,7 @@
math.recalculate( c, wo, init );
}
})
- .on( update + '.tsmath', function() {
+ .on( update, function() {
setTimeout( function(){
math.updateComplete( c );
}, 40 );