jquery-tablesorter 1.19.4 → 1.20.0

Sign up to get free protection for your applications and to get access to all the features.
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 );