mirror of
https://github.com/Mottie/tablesorter.git
synced 2024-12-05 05:04:20 +00:00
Math: lots of tweaks. See #1083
- Initial calculation performed once - changed binding to either "filterEnd" or "pagerComplete", not both. - Added change flag so a cache update is only performed when cell content inside of a sortable tbody was modified. - Fix data-math-filter on "all" cell calculation with fixes to getRow & getColumn functions so that filters get priority over the "filtered" row check.
This commit is contained in:
parent
3ada0de10a
commit
4e1c96d437
@ -18,7 +18,7 @@
|
||||
<!-- Tablesorter: required -->
|
||||
<link rel="stylesheet" href="../css/theme.blue.css">
|
||||
<script src="../js/jquery.tablesorter.js"></script>
|
||||
<script src="../js/jquery.tablesorter.widgets.js"></script>
|
||||
<script src="../js/widgets/widget-filter.js"></script>
|
||||
|
||||
<script src="../js/widgets/widget-math.js"></script>
|
||||
|
||||
@ -182,7 +182,15 @@
|
||||
<li>In <span class="version">v2.24.7</span>
|
||||
<ul>
|
||||
<li><code>math_rowFilter</code> can now be overridden by the row's <code>data-math-filter</code> attribute.</li>
|
||||
<li>On initialization, only tbody cells with a <code>rowspan</code> or <code>colspan</code> are now processed and only get a "data-column" set if the internal <code>cellIndex</code> doesn't match the calculated cell index. This should improve performance and reduce lag after initialization and updating the table (see <a href="https://github.com/Mottie/tablesorter/issues/1048">issue #1048</a>.</li>
|
||||
<li>
|
||||
Lots of optimizations!
|
||||
<ul>
|
||||
<li>On initialization, only tbody cells with a <code>rowspan</code> or <code>colspan</code> are now processed and only get a "data-column" set if the internal <code>cellIndex</code> doesn't match the calculated cell index.</li>
|
||||
<li>Initial calculation is no longer performed three times in a row</li>
|
||||
<li>The cache is now only updated if cell content inside of a sortable tbody was modified.</li>
|
||||
<li>These changes should improve performance and reduce lag after initialization and updating the table (see <a href="https://github.com/Mottie/tablesorter/issues/1048">issue #1048</a>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>In <span class="version">v2.24.6</span>, added <code>math_rowFilter</code> option.</li>
|
||||
@ -192,6 +200,12 @@
|
||||
<li>Added <code>math_none</code> option which allows customizing the text added to a cell when there are no matching math elements (.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="accordion start-closed">
|
||||
<h3 id="old-notes"><a href="#">Older Notes</a></h3>
|
||||
<div>
|
||||
<ul>
|
||||
<li>In <span class="version updated">v2.22.0</span>,
|
||||
<ul>
|
||||
<li>Fixed an issue with a sum column encountering a cell without a defined "data-math" attribute returning an empty string instead of zero. See <a href="https://github.com/Mottie/tablesorter/issues/873">issue #873</a>.</li>
|
||||
@ -209,8 +223,13 @@
|
||||
<ul>
|
||||
<li>Two new options: <code>math_prefix</code> and <code>math_suffix</code>, which will be added before or after the prefix or suffix, respectively.</li>
|
||||
<li>Added "Mask Examples" section with examples, and how to use the <code>$.tablesorter.formatMask</code> function.</li>
|
||||
</ul><br>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li>This widget will <strong>only work</strong> in tablesorter version 2.16+ and jQuery version 1.7+.</li>
|
||||
<li>It adds basic math capabilities. A full list of default formulas is listed in the "Attribute Settings" section.</li>
|
||||
<li>Add your own custom formulas which manipulating an array of values gathered from the table by row, column or a column block (above).</li>
|
||||
@ -218,7 +237,7 @@
|
||||
<li>The widget will update the calculations based on filtered rows, and will update if any data within the table changes (using update events).</li>
|
||||
<li>This widget is not optimized for very large tables, for two reasons:
|
||||
<ul>
|
||||
<li>On initialization, it cycles through every table row, calculates the column index<del>, and adds a <code>data-column</code> attribute</del> (Fixed in <span class="version updated">v2.24.7</span>).</li>
|
||||
<li>On initialization, it cycles through every table row, calculates the column index <del>, and adds a <code>data-column</code> attribute</del> (Fixed in <span class="version updated">v2.24.7</span>).</li>
|
||||
<li>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.</li>
|
||||
</ul>
|
||||
</li>
|
||||
@ -442,6 +461,7 @@ math_suffix : '{content}<span class="red">!</span>'
|
||||
math_rowFilter : ':visible:not(.filtered)'</pre>
|
||||
In <span class="version">v2.24.7</span>, this setting my be overridden in specific rows by adding a <code>data-math-filter</code> attribute (The "math" portion is set by the <code>math_data</code> option)
|
||||
<pre class="prettyprint lang-html"><tr data-math-filter=":visible">...</tr></pre>
|
||||
<span class="label label-info">Note</span> The <code>data-math-filter</code> attribute is ignored if set to an empty string; instead use <code>data-math-filter="*"</code> to target all rows as done in the first example on this page.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -580,9 +600,17 @@ BAD => No minus (-) here! $#,###.00 or [-] here either <= BAD</textarea>
|
||||
<th data-math="col-sum">col-sum</th>
|
||||
<th data-math="col-sum">col-sum</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="5">Visible Total</th>
|
||||
<th data-math="all-sum"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="5">Grand Total</th>
|
||||
<th data-math="all-sum" data-math-mask="##0.0000">all-sum</th>
|
||||
<!--
|
||||
The "data-math-filter" attribute can not be set to an empty string AND override the widgetOptions.math_rowFilter,
|
||||
so set to to an asterisk ("*") to target all rows.
|
||||
-->
|
||||
<th data-math="all-sum" data-math-filter="*" data-math-mask="##0.0000"></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
|
||||
|
@ -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 &&
|
||||
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,8 +247,20 @@
|
||||
|
||||
// find the 'all' total
|
||||
$mathCells = c.$table.children().children( 'tr' ).children( '[' + mathAttr + '^=all]' );
|
||||
math.mathType( c, $mathCells, [ 'all' ] );
|
||||
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 );
|
||||
});
|
||||
|
||||
// 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' );
|
||||
@ -258,6 +275,7 @@
|
||||
console.log( 'Math widget update completed' + ts.benchmark( time ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateComplete : function( c ) {
|
||||
@ -266,22 +284,24 @@
|
||||
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 + ']' );
|
||||
var index, arry, formula, result, $el,
|
||||
$targetCells = $cells.filter( '[' + mathAttr + '^=' + type + ']' ),
|
||||
len = $targetCells.length;
|
||||
if ( len ) {
|
||||
if (c.debug) {
|
||||
@ -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 );
|
||||
|
Loading…
Reference in New Issue
Block a user