jquery-tablesorter 1.19.4 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/jquery-tablesorter/version.rb +2 -2
  4. data/vendor/assets/javascripts/jquery-tablesorter/addons/pager/jquery.tablesorter.pager.js +10 -10
  5. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.combined.js +210 -108
  6. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.js +151 -74
  7. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.widgets.js +59 -34
  8. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-input-select.js +62 -32
  9. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-alignChar.js +1 -1
  10. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-build-table.js +1 -1
  11. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-columnSelector.js +2 -2
  12. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-editable.js +15 -12
  13. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-filter-type-insideRange.js +6 -5
  14. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-filter.js +53 -28
  15. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-grouping.js +1 -1
  16. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-math.js +154 -76
  17. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-pager.js +9 -9
  18. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-print.js +2 -2
  19. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-resizable.js +3 -3
  20. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-scroller.js +1 -1
  21. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-sort2Hash.js +2 -2
  22. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-staticRow.js +1 -1
  23. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-stickyHeaders.js +2 -2
  24. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-view.js +2 -2
  25. metadata +2 -2
@@ -230,7 +230,7 @@
230
230
  tsColSel.adjustColspans( c, wo );
231
231
  // trigger columnUpdate if auto is true (it gets skipped in updateCols()
232
232
  if (colSel.auto) {
233
- c.$table.trigger(wo.columnSelector_updated);
233
+ c.$table.triggerHandler(wo.columnSelector_updated);
234
234
  }
235
235
  },
236
236
  addSelectors: function( prefix, column ) {
@@ -318,7 +318,7 @@
318
318
  ts.storage( c.$table[0], 'tablesorter-columnSelector', colSel.states );
319
319
  }
320
320
  tsColSel.adjustColspans( c, wo );
321
- c.$table.trigger(wo.columnSelector_updated);
321
+ c.$table.triggerHandler(wo.columnSelector_updated);
322
322
  },
323
323
 
324
324
  setUpColspan: function(c, wo) {
@@ -1,4 +1,4 @@
1
- /*! Widget: editable - updated 10/31/2015 (v2.24.0) *//*
1
+ /*! Widget: editable - updated 12/13/2015 (v2.25.0) *//*
2
2
  * Requires tablesorter v2.8+ and jQuery 1.7+
3
3
  * by Rob Garrison
4
4
  */
@@ -222,17 +222,20 @@
222
222
  .data( 'before', valid )
223
223
  .data( 'original', valid )
224
224
  .trigger( 'change' );
225
- $.tablesorter.updateCell( c, $this.closest( 'td' ), false, function() {
226
- if ( wo.editable_autoResort ) {
227
- setTimeout( function() {
228
- $.tablesorter.sortOn( c, c.sortList, function() {
229
- tse.editComplete( c, wo, c.$table.data( 'contentFocused' ), true );
230
- }, true );
231
- }, 10 );
232
- } else {
233
- tse.editComplete( c, wo, c.$table.data( 'contentFocused' ) );
234
- }
235
- });
225
+ // prevent error if table was destroyed - see #1099
226
+ if ( c.table.hasInitialized ) {
227
+ $.tablesorter.updateCell( c, $this.closest( 'td' ), false, function() {
228
+ if ( wo.editable_autoResort ) {
229
+ setTimeout( function() {
230
+ $.tablesorter.sortOn( c, c.sortList, function() {
231
+ tse.editComplete( c, wo, c.$table.data( 'contentFocused' ), true );
232
+ }, true );
233
+ }, 10 );
234
+ } else {
235
+ tse.editComplete( c, wo, c.$table.data( 'contentFocused' ) );
236
+ }
237
+ });
238
+ }
236
239
  return false;
237
240
  }
238
241
  } else if ( !valid && e.type !== 'keydown' ) {
@@ -1,4 +1,4 @@
1
- /*! Widget: filter, insideRange filter type - updated 11/22/2015 (v2.24.6) */
1
+ /*! Widget: filter, insideRange filter type - updated 12/10/2015 (v2.25.0) */
2
2
  ;(function($){
3
3
  'use strict';
4
4
 
@@ -15,14 +15,15 @@
15
15
  };
16
16
 
17
17
  ts.filter.types.insideRange = function( c, data ) {
18
- if ( isDigit.test( data.iFilter ) && range.test( data.iExact ) ) {
18
+ // don't look for an inside range if "any" match is enabled... multiple "-" really screw things up
19
+ if ( !data.anyMatch && isDigit.test( data.iFilter ) && range.test( data.iExact ) ) {
19
20
  var t, val, low, high,
20
21
  index = data.index,
21
22
  cell = data.$cells[ index ],
22
23
  parts = data.iExact.split( range ),
23
- format = c.parsers[data.index].format;
24
- // the cell does not contain a range
25
- if ( parts && parts.length < 2 ) {
24
+ format = c.parsers[data.index] && c.parsers[data.index].format;
25
+ // the cell does not contain a range or the parser isn't defined
26
+ if ( parts && parts.length < 2 || typeof format !== 'function' ) {
26
27
  return null;
27
28
  }
28
29
  // format each side part of the range using the assigned parser
@@ -1,4 +1,4 @@
1
- /*! Widget: filter - updated 11/10/2015 (v2.24.4) *//*
1
+ /*! Widget: filter - updated 12/13/2015 (v2.25.0) *//*
2
2
  * Requires tablesorter v2.8+ and jQuery 1.7+
3
3
  * by Rob Garrison
4
4
  */
@@ -208,7 +208,7 @@
208
208
  table = c.table,
209
209
  parsed = data.parsed[ data.index ],
210
210
  query = ts.formatFloat( data.iFilter.replace( tsfRegex.operators, '' ), table ),
211
- parser = c.parsers[ data.index ],
211
+ parser = c.parsers[ data.index ] || {},
212
212
  savedSearch = query;
213
213
  // parse filter value in case we're comparing numbers ( dates )
214
214
  if ( parsed || parser.type === 'numeric' ) {
@@ -348,6 +348,7 @@
348
348
 
349
349
  var options, string, txt, $header, column, filters, val, fxn, noSelect;
350
350
  c.$table.addClass( 'hasFilters' );
351
+ c.lastSearch = [];
351
352
 
352
353
  // define timers so using clearTimeout won't cause an undefined error
353
354
  wo.filter_searchTimer = null;
@@ -430,7 +431,7 @@
430
431
  if ( wo.filter_reset instanceof $ ) {
431
432
  // reset contains a jQuery object, bind to it
432
433
  wo.filter_reset.click( function() {
433
- c.$table.trigger( 'filterReset' );
434
+ c.$table.triggerHandler( 'filterReset' );
434
435
  });
435
436
  } else if ( $( wo.filter_reset ).length ) {
436
437
  // reset is a jQuery selector, use event delegation
@@ -438,7 +439,7 @@
438
439
  .undelegate( wo.filter_reset, 'click' + c.namespace + 'filter' )
439
440
  .delegate( wo.filter_reset, 'click' + c.namespace + 'filter', function() {
440
441
  // trigger a reset event, so other functions ( filter_formatter ) know when to reset
441
- c.$table.trigger( 'filterReset' );
442
+ c.$table.triggerHandler( 'filterReset' );
442
443
  });
443
444
  }
444
445
  }
@@ -540,7 +541,7 @@
540
541
  ts.setFilters( table, filters, true );
541
542
  }
542
543
  }
543
- c.$table.trigger( 'filterFomatterUpdate' );
544
+ c.$table.triggerHandler( 'filterFomatterUpdate' );
544
545
  // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers
545
546
  setTimeout( function() {
546
547
  if ( !wo.filter_initialized ) {
@@ -550,7 +551,7 @@
550
551
  });
551
552
  // if filter widget is added after pager has initialized; then set filter init flag
552
553
  if ( c.pager && c.pager.initialized && !wo.filter_initialized ) {
553
- c.$table.trigger( 'filterFomatterUpdate' );
554
+ c.$table.triggerHandler( 'filterFomatterUpdate' );
554
555
  setTimeout( function() {
555
556
  tsf.filterInitComplete( c );
556
557
  }, 100 );
@@ -573,7 +574,7 @@
573
574
  count = 0,
574
575
  completed = function() {
575
576
  wo.filter_initialized = true;
576
- c.$table.trigger( 'filterInit', c );
577
+ c.$table.triggerHandler( 'filterInit', c );
577
578
  tsf.findRows( c.table, c.$table.data( 'lastSearch' ) || [] );
578
579
  };
579
580
  if ( $.isEmptyObject( wo.filter_formatter ) ) {
@@ -628,7 +629,7 @@
628
629
  for ( indx = 0; indx <= c.columns; indx++ ) {
629
630
  // include data-column='all' external filters
630
631
  col = indx === c.columns ? 'all' : indx;
631
- filters[indx] = $filters
632
+ filters[ indx ] = $filters
632
633
  .filter( '[data-column="' + col + '"]' )
633
634
  .attr( wo.filter_defaultAttrib ) || filters[indx] || '';
634
635
  }
@@ -650,11 +651,12 @@
650
651
  buildFilter = '<tr role="row" class="' + tscss.filterRow + ' ' + c.cssIgnoreRow + '">';
651
652
  for ( column = 0; column < columns; column++ ) {
652
653
  if ( c.$headerIndexed[ column ].length ) {
653
- buildFilter += '<td data-column="' + column + '"';
654
654
  // account for entire column set with colspan. See #1047
655
655
  tmp = c.$headerIndexed[ column ] && c.$headerIndexed[ column ][0].colSpan || 0;
656
656
  if ( tmp > 1 ) {
657
- buildFilter += ' colspan="' + tmp + '"';
657
+ buildFilter += '<td data-column="' + column + '-' + ( column + tmp - 1 ) + '" colspan="' + tmp + '"';
658
+ } else {
659
+ buildFilter += '<td data-column="' + column + '"';
658
660
  }
659
661
  if ( arry ) {
660
662
  buildFilter += ( cellFilter[ column ] ? ' class="' + cellFilter[ column ] + '"' : '' );
@@ -673,7 +675,8 @@
673
675
  // assuming last cell of a column is the main column
674
676
  $header = c.$headerIndexed[ column ];
675
677
  if ( $header && $header.length ) {
676
- $filter = c.$filters.filter( '[data-column="' + column + '"]' );
678
+ // $filter = c.$filters.filter( '[data-column="' + column + '"]' );
679
+ $filter = tsf.getColumnElm( c, c.$filters, column );
677
680
  ffxn = ts.getColumnData( table, wo.filter_functions, column );
678
681
  makeSelect = ( wo.filter_functions && ffxn && typeof ffxn !== 'function' ) ||
679
682
  $header.hasClass( 'filter-select' );
@@ -713,7 +716,8 @@
713
716
  name = ( $.isArray( wo.filter_cssFilter ) ?
714
717
  ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) :
715
718
  wo.filter_cssFilter ) || '';
716
- buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column );
719
+ // copy data-column from table cell (it will include colspan)
720
+ buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', $filter.attr( 'data-column' ) );
717
721
  if ( disabled ) {
718
722
  buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true;
719
723
  }
@@ -821,7 +825,7 @@
821
825
  // show/hide filter row as needed
822
826
  c.$table
823
827
  .find( '.' + tscss.filterRow )
824
- .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
828
+ .triggerHandler( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
825
829
  }
826
830
  // return if the last search is the same; but filter === false when updating the search
827
831
  // see example-widget-filter.html filter toggle buttons
@@ -832,6 +836,8 @@
832
836
  c.lastCombinedFilter = null;
833
837
  c.lastSearch = [];
834
838
  }
839
+ // define filter inside it is false
840
+ filters = filters || [];
835
841
  // convert filters to strings - see #1070
836
842
  filters = Array.prototype.map ?
837
843
  filters.map( String ) :
@@ -839,7 +845,7 @@
839
845
  filters.join( '\u0000' ).split( '\u0000' );
840
846
 
841
847
  if ( wo.filter_initialized ) {
842
- c.$table.trigger( 'filterStart', [ filters ] );
848
+ c.$table.triggerHandler( 'filterStart', [ filters ] );
843
849
  }
844
850
  if ( c.showProcessing ) {
845
851
  // give it time for the processing icon to kick in
@@ -920,22 +926,18 @@
920
926
  }
921
927
  return $input || $();
922
928
  },
923
- multipleColumns: function( c, $input ) {
929
+ findRange: function( c, val, ignoreRanges ) {
924
930
  // look for multiple columns '1-3,4-6,8' in data-column
925
931
  var temp, ranges, range, start, end, singles, i, indx, len,
926
- wo = c.widgetOptions,
927
- // only target 'all' column inputs on initialization
928
- // & don't target 'all' column inputs if they don't exist
929
- targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length,
930
- columns = [],
931
- val = $.trim( tsf.getLatestSearch( $input ).attr( 'data-column' ) || '' );
932
- if ( /^[0-9]+$/.test(val)) {
933
- return parseInt( val, 10 );
932
+ columns = [];
933
+ if ( /^[0-9]+$/.test( val ) ) {
934
+ // always return an array
935
+ return [ parseInt( val, 10 ) ];
934
936
  }
935
937
  // process column range
936
- if ( targets && /-/.test( val ) ) {
938
+ if ( !ignoreRanges && /-/.test( val ) ) {
937
939
  ranges = val.match( /(\d+)\s*-\s*(\d+)/g );
938
- len = ranges.length;
940
+ len = ranges ? ranges.length : 0;
939
941
  for ( indx = 0; indx < len; indx++ ) {
940
942
  range = ranges[indx].split( /\s*-\s*/ );
941
943
  start = parseInt( range[0], 10 ) || 0;
@@ -954,7 +956,7 @@
954
956
  }
955
957
  }
956
958
  // process single columns
957
- if ( targets && /,/.test( val ) ) {
959
+ if ( !ignoreRanges && /,/.test( val ) ) {
958
960
  singles = val.split( /\s*,\s*/ );
959
961
  len = singles.length;
960
962
  for ( i = 0; i < len; i++ ) {
@@ -974,6 +976,23 @@
974
976
  }
975
977
  return columns;
976
978
  },
979
+ getColumnElm: function( c, $elements, column ) {
980
+ // data-column may contain multiple columns '1-3,5-6,8'
981
+ // replaces: c.$filters.filter( '[data-column="' + column + '"]' );
982
+ return $elements.filter( function() {
983
+ var cols = tsf.findRange( c, $( this ).attr( 'data-column' ) );
984
+ return $.inArray( column, cols ) > -1;
985
+ });
986
+ },
987
+ multipleColumns: function( c, $input ) {
988
+ // look for multiple columns '1-3,4-6,8' in data-column
989
+ var wo = c.widgetOptions,
990
+ // only target 'all' column inputs on initialization
991
+ // & don't target 'all' column inputs if they don't exist
992
+ targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length,
993
+ val = $.trim( tsf.getLatestSearch( $input ).attr( 'data-column' ) || '' );
994
+ return tsf.findRange( c, val, !targets );
995
+ },
977
996
  processTypes: function( c, data, vars ) {
978
997
  var ffxn,
979
998
  filterMatched = null,
@@ -1087,6 +1106,11 @@
1087
1106
  data.filter = ts.replaceAccents( data.filter );
1088
1107
  }
1089
1108
 
1109
+ // replace column specific default filters - see #1088
1110
+ if ( wo.filter_defaultFilter && tsfRegex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) {
1111
+ data.filter = tsf.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] );
1112
+ }
1113
+
1090
1114
  // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ),
1091
1115
  // data.filter = case sensitive
1092
1116
  data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter;
@@ -1375,7 +1399,8 @@
1375
1399
  console.log( 'Completed filter widget search' + ts.benchmark(time) );
1376
1400
  }
1377
1401
  if ( wo.filter_initialized ) {
1378
- c.$table.trigger( 'filterEnd', c );
1402
+ c.$table.triggerHandler( 'filterBeforeEnd', c );
1403
+ c.$table.triggerHandler( 'filterEnd', c );
1379
1404
  }
1380
1405
  setTimeout( function() {
1381
1406
  ts.applyWidget( c.table ); // make sure zebra widget is applied
@@ -1740,7 +1765,7 @@
1740
1765
  c.lastCombinedFilter = null;
1741
1766
  c.lastSearch = [];
1742
1767
  tsf.searching( c.table, filter, skipFirst );
1743
- c.$table.trigger( 'filterFomatterUpdate' );
1768
+ c.$table.triggerHandler( 'filterFomatterUpdate' );
1744
1769
  }
1745
1770
  return !!valid;
1746
1771
  };
@@ -123,7 +123,7 @@
123
123
  tsg.findColumnGroups( c, wo, data );
124
124
  tsg.processHeaders( c, wo, data );
125
125
 
126
- c.$table.trigger(wo.group_complete);
126
+ c.$table.triggerHandler(wo.group_complete);
127
127
  }
128
128
  },
129
129
 
@@ -1,4 +1,4 @@
1
- /*! Widget: math - updated 11/22/2015 (v2.24.6) *//*
1
+ /*! Widget: math - updated 12/13/2015 (v2.25.0) *//*
2
2
  * Requires tablesorter v2.16+ and jQuery 1.7+
3
3
  * by Rob Garrison
4
4
  */
@@ -22,37 +22,36 @@
22
22
  // name = function returning invalid results
23
23
  // errorIndex = math.error index with an explanation of the error
24
24
  console.log( name, math.error[ errorIndex ] );
25
- return c && c.widgetOptions.math_none || 'none'; // text for cell
25
+ return c && c.widgetOptions.math_none || ''; // text for cell
26
26
  },
27
27
 
28
- events : ( 'tablesorter-initialized update updateAll updateRows addRows updateCell ' +
29
- 'filterReset filterEnd ' ).split(' ').join('.tsmath '),
28
+ events : ( 'tablesorter-initialized update updateAll updateRows addRows updateCell filterReset ' )
29
+ .split(' ').join('.tsmath '),
30
30
 
31
31
  processText : function( c, $cell ) {
32
- var txt = $cell.attr( c.textAttribute );
33
- if ( typeof txt === 'undefined' ) {
34
- txt = $cell[0].textContent || $cell.text();
35
- }
32
+ var txt = ts.getElementText( c, $cell, math.getCellIndex( $cell ) );
36
33
  txt = ts.formatFloat( txt.replace( /[^\w,. \-()]/g, '' ), c.table ) || 0;
37
34
  // isNaN('') => false
38
35
  return isNaN( txt ) ? 0 : txt;
39
36
  },
40
37
 
41
38
  // get all of the row numerical values in an arry
42
- getRow : function( c, $el ) {
39
+ getRow : function( c, $el, hasFilter ) {
43
40
  var $cells,
44
41
  wo = c.widgetOptions,
45
42
  arry = [],
46
43
  $row = $el.closest( 'tr' ),
47
- isFiltered = $row.hasClass( wo.filter_filteredRow || 'filtered' ),
48
- hasFilter = wo.math_rowFilter;
44
+ isFiltered = $row.hasClass( wo.filter_filteredRow || 'filtered' );
49
45
  if ( hasFilter ) {
50
46
  $row = $row.filter( hasFilter );
51
47
  }
52
- if ( !isFiltered || hasFilter ) {
48
+ if ( hasFilter || !isFiltered ) {
53
49
  $cells = $row.children().not( '[' + wo.math_dataAttrib + '=ignore]' );
54
50
  if ( wo.math_ignore.length ) {
55
- $cells = $cells.not( '[data-column=' + wo.math_ignore.join( '],[data-column=' ) + ']' );
51
+ $cells = $cells.filter( function( indx ) {
52
+ // using $.inArray is not optimal (needed for IE8)
53
+ return $.inArray( math.getCellIndex( $( this ) ), wo.math_ignore ) === -1;
54
+ });
56
55
  }
57
56
  arry = $cells.not( $el ).map( function() {
58
57
  return math.processText( c, $( this ) );
@@ -62,31 +61,38 @@
62
61
  },
63
62
 
64
63
  // get all of the column numerical values in an arry
65
- getColumn : function( c, $el, type ) {
64
+ getColumn : function( c, $el, type, hasFilter ) {
66
65
  var index, $t, $tr, len, $mathRows, mathAbove,
67
- arry = [],
68
66
  wo = c.widgetOptions,
69
- hasFilter = wo.math_rowFilter,
67
+ arry = [],
68
+ $row = $el.closest( 'tr' ),
70
69
  mathAttr = wo.math_dataAttrib,
70
+ mathIgnore = '[' + mathAttr + '=ignore]',
71
71
  filtered = wo.filter_filteredRow || 'filtered',
72
- cIndex = parseInt( $el.attr( 'data-column' ), 10 ),
72
+ cIndex = math.getCellIndex( $el ),
73
+ // get all rows to keep row indexing
73
74
  $rows = c.$table.children( 'tbody' ).children(),
74
- $row = $el.closest( 'tr' );
75
- // make sure tfoot rows are AFTER the tbody rows
76
- // $rows.add( c.$table.children( 'tfoot' ).children() );
75
+ mathAttrs = [
76
+ '[' + mathAttr + '^=above]',
77
+ '[' + mathAttr + '^=below]',
78
+ '[' + mathAttr + '^=col]',
79
+ '[' + mathAttr + '^=all]'
80
+ ];
77
81
  if ( type === 'above' ) {
78
82
  len = $rows.index( $row );
79
83
  index = len;
80
84
  while ( index >= 0 ) {
81
85
  $tr = $rows.eq( index );
86
+ mathAbove = $tr.children().filter( mathAttrs[0] ).length;
82
87
  if ( hasFilter ) {
83
- $tr = $tr.filter( wo.math_rowFilter );
88
+ $tr = $tr.filter( hasFilter );
84
89
  }
85
- $t = $tr.children().filter( '[data-column=' + cIndex + ']' );
86
- mathAbove = $t.filter( '[' + mathAttr + '^=above]' ).length;
90
+ $t = $tr.children().filter( function( indx ) {
91
+ return math.getCellIndex( $( this ) ) === cIndex;
92
+ });
87
93
  // ignore filtered rows & rows with data-math="ignore" (and starting row)
88
- if ( ( ( !$tr.hasClass( filtered ) || hasFilter ) &&
89
- $tr.not( '[' + mathAttr + '=ignore]' ).length &&
94
+ if ( ( ( hasFilter || !$tr.hasClass( filtered ) ) &&
95
+ $tr.not( mathIgnore ).length &&
90
96
  index !== len ) ||
91
97
  mathAbove && index !== len ) {
92
98
  // stop calculating 'above', when encountering another 'above'
@@ -103,31 +109,34 @@
103
109
  // index + 1 to ignore starting node
104
110
  for ( index = $rows.index( $row ) + 1; index < len; index++ ) {
105
111
  $tr = $rows.eq( index );
112
+ if ( $tr.children().filter( mathAttrs[1] ).length ) {
113
+ break;
114
+ }
106
115
  if ( hasFilter ) {
107
116
  $tr = $tr.filter( hasFilter );
108
117
  }
109
- $t = $tr.children().filter( '[data-column=' + cIndex + ']' );
110
- if ( $t.filter( '[' + mathAttr + '^=below]' ).length ) {
111
- break;
112
- }
113
- if ( ( !$tr.hasClass( filtered ) || hasFilter ) &&
114
- $tr.not( '[' + mathAttr + '=ignore]' ).length &&
115
- $t.length ) {
118
+ $t = $tr.children().filter( function( indx ) {
119
+ return math.getCellIndex( $( this ) ) === cIndex;
120
+ });
121
+ if ( ( hasFilter || !$tr.hasClass( filtered ) ) &&
122
+ $tr.not( mathIgnore ).length &&
123
+ $t.length ) {
116
124
  arry.push( math.processText( c, $t ) );
117
125
  }
118
126
  }
119
-
120
127
  } else {
121
- $mathRows = $rows.not( '[' + mathAttr + '=ignore]' );
128
+ $mathRows = $rows.not( mathIgnore );
122
129
  len = $mathRows.length;
123
130
  for ( index = 0; index < len; index++ ) {
124
131
  $tr = $mathRows.eq( index );
125
132
  if ( hasFilter ) {
126
133
  $tr = $tr.filter( hasFilter );
127
134
  }
128
- $t = $tr.children().filter( '[data-column=' + cIndex + ']' );
129
- if ( ( !$tr.hasClass( filtered ) || hasFilter ) &&
130
- $t.not( '[' + mathAttr + '^=above],[' + mathAttr + '^=below],[' + mathAttr + '^=col]' ).length &&
135
+ $t = $tr.children().filter( function( indx ) {
136
+ return math.getCellIndex( $( this ) ) === cIndex;
137
+ });
138
+ if ( ( hasFilter || !$tr.hasClass( filtered ) ) &&
139
+ $t.not( mathAttrs.join( ',' ) ).length &&
131
140
  !$t.is( $el ) ) {
132
141
  arry.push( math.processText( c, $t ) );
133
142
  }
@@ -137,27 +146,27 @@
137
146
  },
138
147
 
139
148
  // get all of the column numerical values in an arry
140
- getAll : function( c ) {
149
+ getAll : function( c, hasFilter ) {
141
150
  var $t, col, $row, rowIndex, rowLen, $cells, cellIndex, cellLen,
142
151
  arry = [],
143
152
  wo = c.widgetOptions,
144
153
  mathAttr = wo.math_dataAttrib,
154
+ mathIgnore = '[' + mathAttr + '=ignore]',
145
155
  filtered = wo.filter_filteredRow || 'filtered',
146
- hasFilter = wo.filter_rowFilter,
147
- $rows = c.$table.children( 'tbody' ).children().not( '[' + mathAttr + '=ignore]' );
156
+ $rows = c.$table.children( 'tbody' ).children().not( mathIgnore );
148
157
  rowLen = $rows.length;
149
158
  for ( rowIndex = 0; rowIndex < rowLen; rowIndex++ ) {
150
159
  $row = $rows.eq( rowIndex );
151
160
  if ( hasFilter ) {
152
161
  $row = $row.filter( hasFilter );
153
162
  }
154
- if ( !$row.hasClass( filtered ) || hasFilter ) {
155
- $cells = $row.children().not( '[' + mathAttr + '=ignore]' );
163
+ if ( hasFilter || !$row.hasClass( filtered ) ) {
164
+ $cells = $row.children().not( mathIgnore );
156
165
  cellLen = $cells.length;
157
166
  // $row.children().each(function(){
158
167
  for ( cellIndex = 0; cellIndex < cellLen; cellIndex++ ) {
159
168
  $t = $cells.eq( cellIndex );
160
- col = parseInt( $t.attr( 'data-column' ), 10);
169
+ col = math.getCellIndex( $t );
161
170
  if ( !$t.filter( '[' + mathAttr + ']' ).length && $.inArray( col, wo.math_ignore ) < 0 ) {
162
171
  arry.push( math.processText( c, $t ) );
163
172
  }
@@ -170,17 +179,48 @@
170
179
  setColumnIndexes : function( c ) {
171
180
  c.$table.after( '<div id="_tablesorter_table_placeholder"></div>' );
172
181
  // detach table from DOM to speed up column indexing
173
- var $table = c.$table.detach();
174
- ts.computeColumnIndex( $table.children( 'tbody' ).children() );
182
+ var $table = c.$table.detach(),
183
+ last = 1,
184
+ // only target rows with a colspan or rows included in a rowspan
185
+ $rows = $table.children( 'tbody' ).children().filter( function() {
186
+ var cells, indx, len,
187
+ $this = $( this ),
188
+ include = $this.children( '[colspan]' ).length > 0;
189
+ if ( last > 1 ) {
190
+ last--;
191
+ include = true;
192
+ } else if ( last < 1 ) {
193
+ last = 1;
194
+ }
195
+ if ( $this.children( '[rowspan]' ).length > 0 ) {
196
+ cells = this.cells;
197
+ // find max rowspan (in case more than one cell has a rowspan)
198
+ for ( indx = 0; indx < cells.length; indx++ ) {
199
+ last = Math.max( cells[ indx ].rowSpan, last );
200
+ }
201
+ }
202
+ return include;
203
+ });
204
+ // pass `c` (table.config) to computeColumnIndex so it won't add a data-column
205
+ // to every tbody cell, just the ones where the .cellIndex property doesn't match
206
+ // the calculated cell index - hopefully fixes the lag issue in #1048
207
+ ts.computeColumnIndex( $rows, c );
175
208
  $( '#_tablesorter_table_placeholder' )
176
209
  .after( $table )
177
210
  .remove();
178
211
  },
179
212
 
213
+ getCellIndex : function( $cell ) {
214
+ var indx = $cell.attr( 'data-column' );
215
+ return typeof indx === 'undefined' ? $cell[0].cellIndex : parseInt( indx, 10 );
216
+ },
217
+
180
218
  recalculate : function(c, wo, init) {
181
219
  if ( c && ( !wo.math_isUpdating || init ) ) {
182
220
 
183
- var undef, time, mathAttr, $mathCells;
221
+ var undef, time, mathAttr, $mathCells, indx, len,
222
+ changed = false,
223
+ filters = {};
184
224
  if ( c.debug ) {
185
225
  time = new Date();
186
226
  }
@@ -196,7 +236,7 @@
196
236
  // all non-info tbody cells
197
237
  mathAttr = wo.math_dataAttrib;
198
238
  $mathCells = c.$tbodies.children( 'tr' ).children( '[' + mathAttr + ']' );
199
- math.mathType( c, $mathCells, wo.math_priority );
239
+ changed = math.mathType( c, $mathCells, wo.math_priority ) || changed;
200
240
 
201
241
  // only info tbody cells
202
242
  $mathCells = c.$table
@@ -207,20 +247,33 @@
207
247
 
208
248
  // find the 'all' total
209
249
  $mathCells = c.$table.children().children( 'tr' ).children( '[' + mathAttr + '^=all]' );
210
- math.mathType( c, $mathCells, [ 'all' ] );
250
+ len = $mathCells.length;
251
+ // get math filter, if any
252
+ // hasFilter = $row.attr( mathAttr + '-filter' ) || wo.math_rowFilter;
253
+ $mathCells.each( function( indx, cell ) {
254
+ var $cell = $( cell ),
255
+ filter = $mathCells.eq( indx ).attr( mathAttr + '-filter' ) || wo.math_rowFilter;
256
+ filters[ filter ] = filters[ filter ] ? filters[ filter ].add( $cell ) : $cell;
257
+ });
258
+ $.each( filters, function( hasFilter, $cells ) {
259
+ changed = math.mathType( c, $cells, [ 'all' ], hasFilter ) || changed;
260
+ });
211
261
 
212
- wo.math_isUpdating = true;
213
- if ( c.debug ) {
214
- console[ console.group ? 'group' : 'log' ]( 'Math widget triggering an update after recalculation' );
215
- }
262
+ // trigger an update only if cells inside the tbody changed
263
+ if ( changed ) {
264
+ wo.math_isUpdating = true;
265
+ if ( c.debug ) {
266
+ console[ console.group ? 'group' : 'log' ]( 'Math widget triggering an update after recalculation' );
267
+ }
216
268
 
217
- // update internal cache
218
- ts.update( c, undef, function(){
219
- math.updateComplete( c );
220
- });
269
+ // update internal cache
270
+ ts.update( c, undef, function(){
271
+ math.updateComplete( c );
272
+ });
221
273
 
222
- if ( c.debug ) {
223
- console.log( 'Math widget update completed' + ts.benchmark( time ) );
274
+ if ( c.debug ) {
275
+ console.log( 'Math widget update completed' + ts.benchmark( time ) );
276
+ }
224
277
  }
225
278
  }
226
279
  },
@@ -231,23 +284,25 @@
231
284
  wo.math_isUpdating = false;
232
285
  },
233
286
 
234
- mathType : function( c, $cells, priority ) {
287
+ mathType : function( c, $cells, priority, hasFilter ) {
235
288
  if ( $cells.length ) {
236
- var formula, result, $el, arry, getAll, $targetCells, index, len,
289
+ var getAll,
290
+ changed = false,
237
291
  wo = c.widgetOptions,
238
292
  mathAttr = wo.math_dataAttrib,
239
293
  equations = ts.equations;
240
294
  if ( priority[0] === 'all' ) {
241
- // no need to get all cells more than once
242
- getAll = math.getAll( c );
295
+ // mathType is called multiple times if more than one "hasFilter" is used
296
+ getAll = math.getAll( c, hasFilter );
243
297
  }
244
298
  if (c.debug) {
245
299
  console[ console.group ? 'group' : 'log' ]( 'Tablesorter Math widget recalculation' );
246
300
  }
247
301
  // $.each is okay here... only 4 priorities
248
302
  $.each( priority, function( i, type ) {
249
- $targetCells = $cells.filter( '[' + mathAttr + '^=' + type + ']' );
250
- len = $targetCells.length;
303
+ var index, arry, formula, result, $el,
304
+ $targetCells = $cells.filter( '[' + mathAttr + '^=' + type + ']' ),
305
+ len = $targetCells.length;
251
306
  if ( len ) {
252
307
  if (c.debug) {
253
308
  console[ console.group ? 'group' : 'log' ]( type );
@@ -258,39 +313,58 @@
258
313
  if ( $el.parent().hasClass( wo.filter_filteredRow || 'filtered' ) ) {
259
314
  continue;
260
315
  }
316
+ hasFilter = $el.attr( mathAttr + '-filter' ) || wo.math_rowFilter;
261
317
  formula = ( $el.attr( mathAttr ) || '' ).replace( type + '-', '' );
262
- arry = ( type === 'row' ) ? math.getRow( c, $el ) :
263
- ( type === 'all' ) ? getAll : math.getColumn( c, $el, type );
318
+ arry = ( type === 'row' ) ? math.getRow( c, $el, hasFilter ) :
319
+ ( type === 'all' ) ? getAll : math.getColumn( c, $el, type, hasFilter );
264
320
  if ( equations[ formula ] ) {
265
321
  if ( arry.length ) {
266
322
  result = equations[ formula ]( arry, c );
267
323
  if ( c.debug ) {
268
- console.log( $el.attr( mathAttr ), arry, '=', result );
324
+ console.log( $el.attr( mathAttr ), hasFilter ? '("' + hasFilter + '")' : '', arry, '=', result );
269
325
  }
270
326
  } else {
271
327
  // mean will return a divide by zero error, everything else shows an undefined error
272
328
  result = math.invalid( c, formula, formula === 'mean' ? 0 : 'undef' );
273
329
  }
274
- math.output( $el, wo, result, arry );
330
+ changed = math.output( $el, c, result, arry ) || changed;
275
331
  }
276
332
  }
277
333
  if ( c.debug && console.groupEnd ) { console.groupEnd(); }
278
334
  }
279
335
  });
280
336
  if ( c.debug && console.groupEnd ) { console.groupEnd(); }
337
+ return changed;
281
338
  }
339
+ return false;
282
340
  },
283
341
 
284
- output : function( $cell, wo, value, arry ) {
342
+ output : function( $cell, c, value, arry ) {
285
343
  // get mask from cell data-attribute: data-math-mask="#,##0.00"
286
- var mask = $cell.attr( 'data-' + wo.math_data + '-mask' ) || wo.math_mask,
344
+ var $el,
345
+ wo = c.widgetOptions,
346
+ changed = false,
347
+ prev = $cell.html(),
348
+ mask = $cell.attr( 'data-' + wo.math_data + '-mask' ) || wo.math_mask,
287
349
  result = ts.formatMask( mask, value, wo.math_wrapPrefix, wo.math_wrapSuffix );
288
350
  if ( typeof wo.math_complete === 'function' ) {
289
351
  result = wo.math_complete( $cell, wo, result, value, arry );
290
352
  }
291
353
  if ( result !== false ) {
354
+ changed = prev !== result;
292
355
  $cell.html( result );
293
356
  }
357
+ // check if in a regular tbody, otherwise don't pass a changed flag
358
+ // to prevent unnecessary updating of the table cache
359
+ if ( changed ) {
360
+ $el = $cell.closest( 'tbody' );
361
+ // content was changed in a tfoot, info-only tbody or the resulting tbody is in a nested table
362
+ // then don't signal a change
363
+ if ( !$el.length || $el.hasClass( c.cssInfoBlock ) || $el.parent()[0] !== c.table ) {
364
+ return false;
365
+ }
366
+ }
367
+ return changed;
294
368
  }
295
369
 
296
370
  };
@@ -525,20 +599,24 @@
525
599
  },
526
600
  init : function( table, thisWidget, c, wo ) {
527
601
  // filterEnd fires after updateComplete
528
- var update = ts.hasWidget( table, 'filter' ) ? 'filterEnd' : 'updateComplete';
602
+ var update = ( ts.hasWidget( table, 'filter' ) ? 'filterEnd' : 'updateComplete' ) + '.tsmath';
603
+ // filterEnd is when the pager hides rows... so bind to pagerComplete
604
+ math.events += ( ts.hasWidget( table, 'pager' ) ? 'pagerComplete' : 'filterEnd' ) + '.tsmath ';
529
605
  c.$table
530
- .off( ( math.events + ' updateComplete.tsmath ' + wo.math_event ).replace( /\s+/g, ' ' ) )
531
- .on( math.events + ' ' + wo.math_event, function( e ) {
606
+ .off( ( math.events + 'updateComplete.tsmath ' + wo.math_event ).replace( /\s+/g, ' ' ) )
607
+ .on( math.events + wo.math_event, function( e ) {
608
+ if ( !this.hasInitialized ) { return; }
532
609
  var init = e.type === 'tablesorter-initialized';
533
610
  if ( !wo.math_isUpdating || init ) {
534
- if ( !/filter/.test( e.type ) ) {
611
+ // don't setColumnIndexes on init here, or it gets done twice
612
+ if ( !/filter/.test( e.type ) && !init ) {
535
613
  // redo data-column indexes on update
536
- math.setColumnIndexes( c ) ;
614
+ math.setColumnIndexes( c );
537
615
  }
538
616
  math.recalculate( c, wo, init );
539
617
  }
540
618
  })
541
- .on( update + '.tsmath', function() {
619
+ .on( update, function() {
542
620
  setTimeout( function(){
543
621
  math.updateComplete( c );
544
622
  }, 40 );