From 764426d2c4db2fa933ff993f4149b33db858810a Mon Sep 17 00:00:00 2001 From: Mottie Date: Sun, 15 Feb 2015 16:43:20 -0600 Subject: [PATCH 1/7] Core: make getElementText function public --- js/jquery.tablesorter.js | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/js/jquery.tablesorter.js b/js/jquery.tablesorter.js index 144e725d..3fc3d56f 100644 --- a/js/jquery.tablesorter.js +++ b/js/jquery.tablesorter.js @@ -180,37 +180,39 @@ return true; } - function getElementText(table, node, cellIndex) { + ts.getElementText = function(c, node, cellIndex) { if (!node) { return ''; } var te, - $node = $(node), - c = table.config, - t = c.textExtraction || ''; + t = c.textExtraction || '', + // node could be a jquery object + // http://jsperf.com/jquery-vs-instanceof-jquery/2 + $node = node.jquery ? node : $(node); if (typeof(t) === 'string') { // check data-attribute first when set to 'basic'; don't use node.innerText - it's really slow! - return $.trim( (t === 'basic' ? $node.attr(c.textAttribute) || node.textContent : node.textContent ) || $node.text() || '' ); + return $.trim( ( t === 'basic' ? $node.attr(c.textAttribute) || node.textContent : node.textContent ) || $node.text() || '' ); } else { if (typeof(t) === 'function') { - return $.trim( t(node, table, cellIndex) ); - } else if (typeof (te = ts.getColumnData( table, t, cellIndex )) === 'function') { - return $.trim( te(node, table, cellIndex) ); + return $.trim( t($node[0], table, cellIndex) ); + } else if (typeof (te = ts.getColumnData( c.table, t, cellIndex )) === 'function') { + return $.trim( te($node[0], c.table, cellIndex) ); } } // fallback - return $.trim( node.textContent || $node.text() || '' ); + return $.trim( $node[0].textContent || $node.text() || '' ); } function detectParserForColumn(table, rows, rowIndex, cellIndex) { var cur, $node, - i = ts.parsers.length, - node = false, - nodeValue = '', - keepLooking = true; + c = table.config, + i = ts.parsers.length, + node = false, + nodeValue = '', + keepLooking = true; while (nodeValue === '' && keepLooking) { rowIndex++; if (rows[rowIndex]) { node = rows[rowIndex].cells[cellIndex]; - nodeValue = getElementText(table, node, cellIndex); + nodeValue = ts.getElementText(c, node, cellIndex); $node = $(node); if (table.config.debug) { log('Checking if value was empty on row ' + rowIndex + ', column: ' + cellIndex + ': "' + nodeValue + '"'); @@ -356,7 +358,7 @@ } continue; } - t = getElementText(table, $row[0].cells[j], j); + t = ts.getElementText(c, $row[0].cells[j], j); rowData.raw.push(t); // save original row text // do extract before parsing if there is one if (typeof extractors[j].id === 'undefined') { @@ -902,9 +904,9 @@ icell = $cell.index(); c.cache[tbdy].normalized[row][c.columns].$row = $row; if (typeof c.extractors[icell].id === 'undefined') { - t = getElementText(table, cell, icell); + t = ts.getElementText(c, cell, icell); } else { - t = c.extractors[icell].format( getElementText(table, cell, icell), table, cell, icell ); + t = c.extractors[icell].format( ts.getElementText(c, cell, icell), table, cell, icell ); } v = c.parsers[icell].id === 'no-parser' ? '' : c.parsers[icell].format( t, table, cell, icell ); @@ -955,9 +957,9 @@ // add each cell for (j = 0; j < l; j++) { if (typeof c.extractors[j].id === 'undefined') { - t = getElementText(table, $row[i].cells[j], j); + t = ts.getElementText(c, $row[i].cells[j], j); } else { - t = c.extractors[j].format( getElementText(table, $row[i].cells[j], j), table, $row[i].cells[j], j ); + t = c.extractors[j].format( ts.getElementText(c, $row[i].cells[j], j), table, $row[i].cells[j], j ); } v = c.parsers[j].id === 'no-parser' ? '' : c.parsers[j].format( t, table, $row[i].cells[j], j ); From 39f50d90c933987b4751a12926153e9237dc01bd Mon Sep 17 00:00:00 2001 From: Mottie Date: Tue, 17 Feb 2015 14:49:23 -0600 Subject: [PATCH 2/7] Core: add `cssNoSort` option --- docs/index.html | 8 ++++++++ js/jquery.tablesorter.js | 11 +++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/index.html b/docs/index.html index 31a9363b..338c00a7 100644 --- a/docs/index.html +++ b/docs/index.html @@ -832,6 +832,14 @@ + + cssNoSort + String + "tablesorter-noSort" + Class name added to element inside header. Clicking on that element won't cause a sort. (v2.19.2). + + + String diff --git a/js/jquery.tablesorter.js b/js/jquery.tablesorter.js index 3fc3d56f..9c541e08 100644 --- a/js/jquery.tablesorter.js +++ b/js/jquery.tablesorter.js @@ -105,6 +105,7 @@ cssIconDesc : '', // class name added to the icon when the column has a descending sort cssInfoBlock : 'tablesorter-infoOnly', // don't sort tbody with this class name (only one class name allowed here!) cssAllowClicks : 'tablesorter-allowClicks', // class name added to table header which allows clicks to bubble up + cssNoSort : 'tablesorter-noSort', // class name added to element inside header; clicking on it won't cause a sort cssIgnoreRow : 'tablesorter-ignoreRow', // header row to ignore; cells within this row will not be added to c.$headers // *** selectors @@ -1344,10 +1345,12 @@ // set timer on mousedown if (type === 'mousedown') { downTime = new Date().getTime(); - cell = $.fn.closest ? $(e.target).closest('td,th') : $(e.target).parents('td,th').filter(':first'); - return /(input|select|button|textarea)/i.test(e.target.tagName) || - // allow clicks to contents of selected cells - cell.hasClass(c.cssAllowClicks) ? '' : !c.cancelSelection; + return; + } + cell = $.fn.closest ? $(e.target).closest('td,th') : $(e.target).parents('td,th').filter(':first'); + // allow clicks to contents of selected cells + if ( /(input|select|button|textarea)/i.test(e.target.tagName) || ( cell.hasClass(c.cssAllowClicks) ) || $(e.target).hasClass(c.cssNoSort) ) { + return !c.cancelSelection; } if (c.delayInit && isEmptyObject(c.cache)) { buildCache(table); } // jQuery v1.2.6 doesn't have closest() From 0c8aa7e225c4be92256ea70f86a93531a0a2461b Mon Sep 17 00:00:00 2001 From: Mottie Date: Tue, 17 Feb 2015 16:10:52 -0600 Subject: [PATCH 3/7] Core: remove cssAllowClicks & update cssNoSort --- docs/index.html | 28 ++++++++++++++++++---------- js/jquery.tablesorter.js | 14 ++++++++++---- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/docs/index.html b/docs/index.html index 338c00a7..f2549ad7 100644 --- a/docs/index.html +++ b/docs/index.html @@ -824,19 +824,11 @@ - - cssAllowClicks - String - "tablesorter-allowClicks" - Class name added to table header which allows clicks to bubble up. (v2.18.1). - - - cssNoSort String "tablesorter-noSort" - Class name added to element inside header. Clicking on that element won't cause a sort. (v2.19.2). + Class name added to element inside header. Clicking on that element, or any elements within it won't cause a sort. (v2.19.2). @@ -1982,10 +1974,26 @@ $(function(){ - Deprecated Options + Deprecated/Removed Options + + + + String + + This option was removed! + It has been replaced by cssNoSort which does the opposite of what this class name was supposed to do. +
+

This option was not working as intended, so it was completely removed - sorry for the lack of notice.

+

Previous default was "tablesorter-allowClicks"

+ Class name added to table header which allows clicks to bubble up. (added v2.18.1; removed in v2.19.2). +
+ + + + diff --git a/js/jquery.tablesorter.js b/js/jquery.tablesorter.js index 9c541e08..f77e6d08 100644 --- a/js/jquery.tablesorter.js +++ b/js/jquery.tablesorter.js @@ -1335,7 +1335,9 @@ .find(c.selectorSort).add( $headers.filter(c.selectorSort) ) .unbind( $.trim('mousedown mouseup sort keyup '.split(' ').join(c.namespace + ' ')) ) .bind( $.trim('mousedown mouseup sort keyup '.split(' ').join(c.namespace + ' ')), function(e, external) { - var cell, type = e.type; + var cell, + $target = $(e.target), + type = e.type; // only recognize left clicks or enter if ( ((e.which || e.button) !== 1 && !/sort|keyup/.test(type)) || (type === 'keyup' && e.which !== 13) ) { return; @@ -1347,9 +1349,13 @@ downTime = new Date().getTime(); return; } - cell = $.fn.closest ? $(e.target).closest('td,th') : $(e.target).parents('td,th').filter(':first'); - // allow clicks to contents of selected cells - if ( /(input|select|button|textarea)/i.test(e.target.tagName) || ( cell.hasClass(c.cssAllowClicks) ) || $(e.target).hasClass(c.cssNoSort) ) { + cell = $.fn.closest ? $(e.target).closest('td,th') : $target.parents('td,th').filter(':first'); + // prevent sort being triggered on form elements + if ( /(input|select|button|textarea)/i.test(e.target.tagName) || + // nosort class name, or elements within a nosort container + $target.hasClass(c.cssNoSort) || $target.parents(c.cssNoSort).length > 0 || + // elements within a button + $target.parents('button').length > 0 ) { return !c.cancelSelection; } if (c.delayInit && isEmptyObject(c.cache)) { buildCache(table); } From df97b1db8e4fc37f6c5970ba75eb927741124c39 Mon Sep 17 00:00:00 2001 From: Mottie Date: Tue, 17 Feb 2015 16:22:03 -0600 Subject: [PATCH 4/7] Core: remove cssAllowClicks default --- js/jquery.tablesorter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/jquery.tablesorter.js b/js/jquery.tablesorter.js index f77e6d08..b203f61f 100644 --- a/js/jquery.tablesorter.js +++ b/js/jquery.tablesorter.js @@ -104,7 +104,6 @@ cssIconAsc : '', // class name added to the icon when the column has an ascending sort cssIconDesc : '', // class name added to the icon when the column has a descending sort cssInfoBlock : 'tablesorter-infoOnly', // don't sort tbody with this class name (only one class name allowed here!) - cssAllowClicks : 'tablesorter-allowClicks', // class name added to table header which allows clicks to bubble up cssNoSort : 'tablesorter-noSort', // class name added to element inside header; clicking on it won't cause a sort cssIgnoreRow : 'tablesorter-ignoreRow', // header row to ignore; cells within this row will not be added to c.$headers @@ -1349,7 +1348,7 @@ downTime = new Date().getTime(); return; } - cell = $.fn.closest ? $(e.target).closest('td,th') : $target.parents('td,th').filter(':first'); + cell = $.fn.closest ? $target.closest('td,th') : $target.parents('td,th').filter(':first'); // prevent sort being triggered on form elements if ( /(input|select|button|textarea)/i.test(e.target.tagName) || // nosort class name, or elements within a nosort container From 2065d0c8f11da365d7280a8887eb422484d71230 Mon Sep 17 00:00:00 2001 From: Mottie Date: Tue, 17 Feb 2015 16:37:06 -0600 Subject: [PATCH 5/7] Core: Fix noSort --- js/jquery.tablesorter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/jquery.tablesorter.js b/js/jquery.tablesorter.js index b203f61f..84b3547c 100644 --- a/js/jquery.tablesorter.js +++ b/js/jquery.tablesorter.js @@ -1352,7 +1352,7 @@ // prevent sort being triggered on form elements if ( /(input|select|button|textarea)/i.test(e.target.tagName) || // nosort class name, or elements within a nosort container - $target.hasClass(c.cssNoSort) || $target.parents(c.cssNoSort).length > 0 || + $target.hasClass(c.cssNoSort) || $target.parents('.' + c.cssNoSort).length > 0 || // elements within a button $target.parents('button').length > 0 ) { return !c.cancelSelection; From c2f39080d2505209864d8246564390b046e9eea4 Mon Sep 17 00:00:00 2001 From: Ilya Vorontsov Date: Wed, 18 Feb 2015 21:41:28 +0300 Subject: [PATCH 6/7] Update jquery.tablesorter.js fix little bug in new getElementText --- js/jquery.tablesorter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/jquery.tablesorter.js b/js/jquery.tablesorter.js index 84b3547c..c4485dc1 100644 --- a/js/jquery.tablesorter.js +++ b/js/jquery.tablesorter.js @@ -192,7 +192,7 @@ return $.trim( ( t === 'basic' ? $node.attr(c.textAttribute) || node.textContent : node.textContent ) || $node.text() || '' ); } else { if (typeof(t) === 'function') { - return $.trim( t($node[0], table, cellIndex) ); + return $.trim( t($node[0], c.table, cellIndex) ); } else if (typeof (te = ts.getColumnData( c.table, t, cellIndex )) === 'function') { return $.trim( te($node[0], c.table, cellIndex) ); } From 34567c0ccb29954eb1922dbd38cb94cc5684d2b6 Mon Sep 17 00:00:00 2001 From: prijutme4ty Date: Sun, 15 Feb 2015 00:00:04 +0300 Subject: [PATCH 7/7] Make +config.cache+ indexed the same way as +config.$tbodies+ is, i.e. ignore info blocks. Remove unused var, rename some local vars into more specific ones; --- addons/pager/jquery.tablesorter.pager.js | 10 +- docs/index.html | 5 +- js/jquery.tablesorter.js | 134 +++++++++++------------ js/jquery.tablesorter.widgets.js | 48 ++++---- js/widgets/widget-chart.js | 5 +- js/widgets/widget-grouping.js | 8 +- js/widgets/widget-pager.js | 10 +- testing/testing.js | 38 ++++--- 8 files changed, 124 insertions(+), 134 deletions(-) diff --git a/addons/pager/jquery.tablesorter.pager.js b/addons/pager/jquery.tablesorter.pager.js index a921d01f..cbc488e2 100644 --- a/addons/pager/jquery.tablesorter.pager.js +++ b/addons/pager/jquery.tablesorter.pager.js @@ -135,18 +135,15 @@ }, calcFilters = function(table, p) { - var tbodyIndex, - c = table.config, + var c = table.config, hasFilters = c.$table.hasClass('hasFilters'); if (hasFilters && !p.ajaxUrl) { if ($.isEmptyObject(c.cache)) { // delayInit: true so nothing is in the cache p.filteredRows = p.totalRows = c.$tbodies.eq(0).children('tr').not( p.countChildRows ? '' : '.' + c.cssChildRow ).length; } else { - // just in case the pager tbody isn't the first tbody - tbodyIndex = c.$table.children('tbody').index( c.$tbodies.eq(0) ); p.filteredRows = 0; - $.each(c.cache[tbodyIndex].normalized, function(i, el) { + $.each(c.cache[0].normalized, function(i, el) { p.filteredRows += p.regexRows.test(el[c.columns].$row[0].className) ? 0 : 1; }); } @@ -659,8 +656,7 @@ c.$table.trigger('updateCache', [ function(){ var i, rows = [], - tbodyIndex = c.$table.children('tbody').index( c.$tbodies.eq(0) ), - n = table.config.cache[tbodyIndex].normalized; + n = table.config.cache[0].normalized; p.totalRows = n.length; for (i = 0; i < p.totalRows; i++) { rows.push(n[i][c.columns].$row); diff --git a/docs/index.html b/docs/index.html index f2549ad7..463b913a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -5325,12 +5325,12 @@ var config = $('#mytable').data('tablesorter'); Object - Internal list of table contents (v2.0.18; v2.19.1 ) + Internal list of table contents (v2.0.18; v2.19.2 )

This object contains the following:
    -
  • tbody index +
  • tbody index (non-info block only, numeration is the same as for config.$tbodies v2.19.2)
    • colMax
        @@ -5796,7 +5796,6 @@ var p = $('#mytable').data('tablesorter').pager; For example, if you want to get the parsed values for the rows currently displayed within the pager, use the table.config.pager.cacheIndex variable as follows:
        var c = $('table')[0].config,
         	p = c.pager,
        -	// the cache may not have a zero index if there are any "info-only" tbodies above the main tbody
         	cache = c.cache[0].normalized,
         	cachedValues = [];
         $.each( p.cacheIndex, function(i, v) {
        diff --git a/js/jquery.tablesorter.js b/js/jquery.tablesorter.js
        index c4485dc1..e80d612b 100644
        --- a/js/jquery.tablesorter.js
        +++ b/js/jquery.tablesorter.js
        @@ -136,7 +136,6 @@
         				headerRow  : 'tablesorter-headerRow',
         				headerIn   : 'tablesorter-header-inner',
         				icon       : 'tablesorter-icon',
        -				info       : 'tablesorter-infoOnly',
         				processing : 'tablesorter-processing',
         				sortAsc    : 'tablesorter-headerAsc',
         				sortDesc   : 'tablesorter-headerDesc',
        @@ -295,10 +294,10 @@
         
         			/* utils */
         			function buildCache(table) {
        -				var cc, t, tx, v, i, j, k, $row, rows, cols, cacheTime,
        +				var cc, t, tx, v, i, j, k, $row, cols, cacheTime,
         					totalRows, rowData, colMax,
         					c = table.config,
        -					$tb = c.$table.children('tbody'),
        +					$tb = c.$tbodies,
         					extractors = c.extractors,
         					parsers = c.parsers;
         				c.cache = {};
        @@ -321,68 +320,65 @@
         						// colMax: #   // added at the end
         					};
         
        -					// ignore tbodies with class name from c.cssInfoBlock
        -					if (!$tb.eq(k).hasClass(c.cssInfoBlock)) {
        -						totalRows = ($tb[k] && $tb[k].rows.length) || 0;
        -						for (i = 0; i < totalRows; ++i) {
        -							rowData = {
        -								// order: original row order #
        -								// $row : jQuery Object[]
        -								child: [], // child row text (filter widget)
        -								raw: []    // original row text
        -							};
        -							/** Add the table data to main data array */
        -							$row = $($tb[k].rows[i]);
        -							rows = [ new Array(c.columns) ];
        -							cols = [];
        -							// if this is a child row, add it to the last row's children and continue to the next row
        -							// ignore child row class, if it is the first row
        -							if ($row.hasClass(c.cssChildRow) && i !== 0) {
        -								t = cc.normalized.length - 1;
        -								cc.normalized[t][c.columns].$row = cc.normalized[t][c.columns].$row.add($row);
        -								// add 'hasChild' class name to parent row
        -								if (!$row.prev().hasClass(c.cssChildRow)) {
        -									$row.prev().addClass(ts.css.cssHasChild);
        +					totalRows = ($tb[k] && $tb[k].rows.length) || 0;
        +					for (i = 0; i < totalRows; ++i) {
        +						rowData = {
        +							// order: original row order #
        +							// $row : jQuery Object[]
        +							child: [], // child row text (filter widget)
        +							raw: []    // original row text
        +						};
        +						/** Add the table data to main data array */
        +						$row = $($tb[k].rows[i]);
        +						cols = [];
        +						// if this is a child row, add it to the last row's children and continue to the next row
        +						// ignore child row class, if it is the first row
        +						if ($row.hasClass(c.cssChildRow) && i !== 0) {
        +							t = cc.normalized.length - 1;
        +							cc.normalized[t][c.columns].$row = cc.normalized[t][c.columns].$row.add($row);
        +							// add 'hasChild' class name to parent row
        +							if (!$row.prev().hasClass(c.cssChildRow)) {
        +								$row.prev().addClass(ts.css.cssHasChild);
        +							}
        +							// save child row content (un-parsed!)
        +							rowData.child[t] = $.trim( $row[0].textContent || $row.text() || '' );
        +							// go to the next for loop
        +							continue;
        +						}
        +						rowData.$row = $row;
        +						rowData.order = i; // add original row position to rowCache
        +						for (j = 0; j < c.columns; ++j) {
        +							if (typeof parsers[j] === 'undefined') {
        +								if (c.debug) {
        +									log('No parser found for cell:', $row[0].cells[j], 'does it have a header?');
         								}
        -								// save child row content (un-parsed!)
        -								rowData.child[t] = $.trim( $row[0].textContent || $row.text() || '' );
        -								// go to the next for loop
         								continue;
         							}
        -							rowData.$row = $row;
        -							rowData.order = i; // add original row position to rowCache
        -							for (j = 0; j < c.columns; ++j) {
        -								if (typeof parsers[j] === 'undefined') {
        -									if (c.debug) {
        -										log('No parser found for cell:', $row[0].cells[j], 'does it have a header?');
        -									}
        -									continue;
        -								}
        -								t = ts.getElementText(c, $row[0].cells[j], j);
        -								rowData.raw.push(t); // save original row text
        -								// do extract before parsing if there is one
        -								if (typeof extractors[j].id === 'undefined') {
        -									tx = t;
        -								} else {
        -									tx = extractors[j].format(t, table, $row[0].cells[j], j);
        -								}
        -								// allow parsing if the string is empty, previously parsing would change it to zero,
        -								// in case the parser needs to extract data from the table cell attributes
        -								v = parsers[j].id === 'no-parser' ? '' : parsers[j].format(tx, table, $row[0].cells[j], j);
        -								cols.push( c.ignoreCase && typeof v === 'string' ? v.toLowerCase() : v );
        -								if ((parsers[j].type || '').toLowerCase() === 'numeric') {
        -									// determine column max value (ignore sign)
        -									colMax[j] = Math.max(Math.abs(v) || 0, colMax[j] || 0);
        -								}
        +							t = ts.getElementText(c, $row[0].cells[j], j);
        +							rowData.raw.push(t); // save original row text
        +							// do extract before parsing if there is one
        +							if (typeof extractors[j].id === 'undefined') {
        +								tx = t;
        +							} else {
        +								tx = extractors[j].format(t, table, $row[0].cells[j], j);
        +							}
        +							// allow parsing if the string is empty, previously parsing would change it to zero,
        +							// in case the parser needs to extract data from the table cell attributes
        +							v = parsers[j].id === 'no-parser' ? '' : parsers[j].format(tx, table, $row[0].cells[j], j);
        +							cols.push( c.ignoreCase && typeof v === 'string' ? v.toLowerCase() : v );
        +							if ((parsers[j].type || '').toLowerCase() === 'numeric') {
        +								// determine column max value (ignore sign)
        +								colMax[j] = Math.max(Math.abs(v) || 0, colMax[j] || 0);
         							}
        -							// ensure rowData is always in the same location (after the last column)
        -							cols[c.columns] = rowData;
        -							cc.normalized.push(cols);
         						}
        -						cc.colMax = colMax;
        -						// total up rows, not including child rows
        -						c.totalRows += cc.normalized.length;
        +						// ensure rowData is always in the same location (after the last column)
        +						cols[c.columns] = rowData;
        +						cc.normalized.push(cols);
         					}
        +					cc.colMax = colMax;
        +					// total up rows, not including child rows
        +					c.totalRows += cc.normalized.length;
        +
         				}
         				if (c.showProcessing) {
         					ts.isProcessing(table); // remove processing icon
        @@ -396,7 +392,7 @@
         			function appendToTable(table, init) {
         				var c = table.config,
         					wo = c.widgetOptions,
        -					b = table.tBodies,
        +					b = c.$tbodies,
         					rows = [],
         					cc = c.cache,
         					n, totalRows, $bk, $tb,
        @@ -412,7 +408,7 @@
         				}
         				for (k = 0; k < b.length; k++) {
         					$bk = $(b[k]);
        -					if ($bk.length && !$bk.hasClass(c.cssInfoBlock)) {
        +					if ($bk.length) {
         						// get tbody
         						$tb = ts.processTbody(table, $bk, true);
         						n = cc[k].normalized;
        @@ -754,23 +750,23 @@
         			// sort multiple columns
         			function multisort(table) { /*jshint loopfunc:true */
         				var i, k, num, col, sortTime, colMax,
        -					cache, order, sort, x, y,
        +					rows, order, sort, x, y,
         					dir = 0,
         					c = table.config,
         					cts = c.textSorter || '',
         					sortList = c.sortList,
         					l = sortList.length,
        -					bl = table.tBodies.length;
        +					bl = c.$tbodies.length;
         				if (c.serverSideSorting || isEmptyObject(c.cache)) { // empty table - fixes #206/#346
         					return;
         				}
         				if (c.debug) { sortTime = new Date(); }
         				for (k = 0; k < bl; k++) {
         					colMax = c.cache[k].colMax;
        -					cache = c.cache[k].normalized;
        +					rows = c.cache[k].normalized;
         
        -					cache.sort(function(a, b) {
        -						// cache is undefined here in IE, so don't use it!
        +					rows.sort(function(a, b) {
        +						// rows is undefined here in IE, so don't use it!
         						for (i = 0; i < l; i++) {
         							col = sortList[i][0];
         							order = sortList[i][1];
        @@ -891,7 +887,7 @@
         					$table.find(c.selectorRemove).remove();
         					// get position from the dom
         					var v, t, row, icell,
        -					$tb = $table.find('tbody'),
        +					$tb = c.$tbodies,
         					$cell = $(cell),
         					// update cache - format: function(s, table, cell, cellIndex)
         					// no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
        @@ -940,7 +936,7 @@
         						$row = $($row).attr('role', 'row'); // make sure we're using a jQuery object
         						var i, j, l, t, v, rowData, cells,
         						rows = $row.filter('tr').length,
        -						tbdy = $table.find('tbody').index( $row.parents('tbody').filter(':first') );
        +						tbdy = c.$tbodies.index( $row.parents('tbody').filter(':first') );
         						// fixes adding rows to an empty table - see issue #179
         						if (!(c.parsers && c.parsers.length)) {
         							buildParserCache(table);
        @@ -1200,7 +1196,7 @@
         					colgroup = $('');
         					overallWidth = c.$table.width();
         					// only add col for visible columns - fixes #371
        -					$(table.tBodies).not('.' + c.cssInfoBlock).find('tr:first').children(':visible').each(function() {
        +					c.$tbodies.find('tr:first').children(':visible').each(function() {
         						percent = parseInt( ( $(this).width() / overallWidth ) * 1000, 10 ) / 10 + '%';
         						colgroup.append( $('').css('width', percent) );
         					});
        diff --git a/js/jquery.tablesorter.widgets.js b/js/jquery.tablesorter.widgets.js
        index a8e7dc5e..9c964390 100644
        --- a/js/jquery.tablesorter.widgets.js
        +++ b/js/jquery.tablesorter.widgets.js
        @@ -1146,13 +1146,12 @@ ts.filter = {
         	},
         	findRows: function(table, filters, combinedFilters) {
         		if (table.config.lastCombinedFilter === combinedFilters || !table.config.widgetOptions.filter_initialized) { return; }
        -		var len, $rows, rowIndex, tbodyIndex, $tbody, $cells, $cell, columnIndex,
        +		var len, norm_rows, $rows, rowIndex, tbodyIndex, $tbody, $cells, $cell, columnIndex,
         			childRow, lastSearch, hasSelect, matches, result, showRow, time, val, indx,
         			notFiltered, searchFiltered, filterMatched, excludeMatch, fxn, ffxn,
         			regex = ts.filter.regex,
         			c = table.config,
         			wo = c.widgetOptions,
        -			$tbodies = c.$table.children('tbody'), // target all tbodies #568
         			// data object passed to filters; anyMatch is a flag for the filters
         			data = { anyMatch: false },
         			// anyMatch really screws up with these types of filters
        @@ -1176,14 +1175,14 @@ ts.filter = {
         		// combindedFilters are undefined on init
         		combinedFilters = (filters || []).join('');
         
        -		for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
        -			if ($tbodies.eq(tbodyIndex).hasClass(c.cssInfoBlock || ts.css.info)) { continue; } // ignore info blocks, issue #264
        -			$tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true);
        +		for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
        +			$tbody = ts.processTbody(table, c.$tbodies.eq(tbodyIndex), true);
         			// skip child rows & widget added (removable) rows - fixes #448 thanks to @hempel!
         			// $rows = $tbody.children('tr').not(c.selectorRemove);
         			columnIndex = c.columns;
         			// convert stored rows into a jQuery object
        -			$rows = $( $.map(c.cache[tbodyIndex].normalized, function(el){ return el[columnIndex].$row.get(); }) );
        +			norm_rows = c.cache[tbodyIndex].normalized;
        +			$rows = $( $.map(norm_rows, function(el){ return el[columnIndex].$row.get(); }) );
         
         			if (combinedFilters === '' || wo.filter_serversideFiltering) {
         				$rows.removeClass(wo.filter_filteredRow).not('.' + c.cssChildRow).show();
        @@ -1240,7 +1239,7 @@ ts.filter = {
         				// loop through the rows
         				for (rowIndex = 0; rowIndex < len; rowIndex++) {
         
        -					data.cacheArray = c.cache[tbodyIndex].normalized[rowIndex];
        +					data.cacheArray = norm_rows[rowIndex];
         
         					childRow = $rows[rowIndex].className;
         					// skip child rows & already filtered rows
        @@ -1488,26 +1487,23 @@ ts.filter = {
         		var rowIndex, tbodyIndex, len, row, cache, cell,
         			c = table.config,
         			wo = c.widgetOptions,
        -			$tbodies = c.$table.children('tbody'),
         			arry = [];
        -		for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
        -			if (!$tbodies.eq(tbodyIndex).hasClass(c.cssInfoBlock)) {
        -				cache = c.cache[tbodyIndex];
        -				len = c.cache[tbodyIndex].normalized.length;
        -				// loop through the rows
        -				for (rowIndex = 0; rowIndex < len; rowIndex++) {
        -					// get cached row from cache.row (old) or row data object (new; last item in normalized array)
        -					row = cache.row ? cache.row[rowIndex] : cache.normalized[rowIndex][c.columns].$row[0];
        -					// check if has class filtered
        -					if (onlyAvail && row.className.match(wo.filter_filteredRow)) { continue; }
        -					// get non-normalized cell content
        -					if (wo.filter_useParsedData || c.parsers[column].parsed || c.$headers.filter('[data-column="' + column + '"]:last').hasClass('filter-parsed')) {
        -						arry.push( '' + cache.normalized[rowIndex][column] );
        -					} else {
        -						cell = row.cells[column];
        -						if (cell) {
        -							arry.push( $.trim( cell.getAttribute( c.textAttribute ) || cell.textContent || $(cell).text() ) );
        -						}
        +		for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
        +			cache = c.cache[tbodyIndex];
        +			len = c.cache[tbodyIndex].normalized.length;
        +			// loop through the rows
        +			for (rowIndex = 0; rowIndex < len; rowIndex++) {
        +				// get cached row from cache.row (old) or row data object (new; last item in normalized array)
        +				row = cache.row ? cache.row[rowIndex] : cache.normalized[rowIndex][c.columns].$row[0];
        +				// check if has class filtered
        +				if (onlyAvail && row.className.match(wo.filter_filteredRow)) { continue; }
        +				// get non-normalized cell content
        +				if (wo.filter_useParsedData || c.parsers[column].parsed || c.$headers.filter('[data-column="' + column + '"]:last').hasClass('filter-parsed')) {
        +					arry.push( '' + cache.normalized[rowIndex][column] );
        +				} else {
        +					cell = row.cells[column];
        +					if (cell) {
        +						arry.push( $.trim( cell.getAttribute( c.textAttribute ) || cell.textContent || $(cell).text() ) );
         					}
         				}
         			}
        diff --git a/js/widgets/widget-chart.js b/js/widgets/widget-chart.js
        index 8048ce0d..4ec43700 100644
        --- a/js/widgets/widget-chart.js
        +++ b/js/widgets/widget-chart.js
        @@ -149,14 +149,13 @@
         		},
         
         		getRows: function(c, wo) {
        -			// the cache may not have a zero index if there are any "info-only" tbodies above the main tbody
        -			var cache = c.cache[0].normalized,
        +			var norm_rows = c.cache[0].normalized,
         				rows = [];
         			chart_rows = [];
         			chart_categories = [];
         			chart_category = [];
         
        -			$.each(cache, function(indx, rowVal) {
        +			$.each(norm_rows, function(indx, rowVal) {
         				var i, txt,
         					$tr = rowVal[c.columns].$row,
         					$cells = $tr.children('th,td'),
        diff --git a/js/widgets/widget-grouping.js b/js/widgets/widget-grouping.js
        index bdfa28e6..546743dc 100644
        --- a/js/widgets/widget-grouping.js
        +++ b/js/widgets/widget-grouping.js
        @@ -54,7 +54,7 @@ ts.grouping = {
         
         	update : function(table, c, wo){
         		if ($.isEmptyObject(c.cache)) { return; }
        -		var rowIndex, tbodyIndex, currentGroup, $rows, groupClass, grouping, cache, saveName, direction,
        +		var rowIndex, tbodyIndex, currentGroup, $rows, groupClass, grouping, norm_rows, saveName, direction,
         			lang = wo.grouping_language,
         			group = '',
         			savedGroup = false,
        @@ -89,15 +89,15 @@ ts.grouping = {
         				}
         			}
         			for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++) {
        -				cache = c.cache[tbodyIndex].normalized;
        +				norm_rows = c.cache[tbodyIndex].normalized;
         				group = ''; // clear grouping across tbodies
         				$rows = c.$tbodies.eq(tbodyIndex).children('tr').not('.' + c.cssChildRow);
         				for (rowIndex = 0; rowIndex < $rows.length; rowIndex++) {
         					if ( $rows.eq(rowIndex).is(':visible') ) {
         						// fixes #438
         						if (ts.grouping.types[grouping[1]]) {
        -							currentGroup = cache[rowIndex] ? 
        -								ts.grouping.types[grouping[1]]( c, c.$headers.filter('[data-column="' + column + '"]:last'), cache[rowIndex][column], /date/.test(groupClass) ?
        +							currentGroup = norm_rows[rowIndex] ? 
        +								ts.grouping.types[grouping[1]]( c, c.$headers.filter('[data-column="' + column + '"]:last'), norm_rows[rowIndex][column], /date/.test(groupClass) ?
         								grouping[2] : parseInt(grouping[2] || 1, 10) || 1, group, lang ) : currentGroup;
         							if (group !== currentGroup) {
         								group = currentGroup;
        diff --git a/js/widgets/widget-pager.js b/js/widgets/widget-pager.js
        index 838ca5fb..6c4f968d 100644
        --- a/js/widgets/widget-pager.js
        +++ b/js/widgets/widget-pager.js
        @@ -371,8 +371,7 @@ tsp = ts.pager = {
         	},
         
         	calcFilters: function(table, c) {
        -		var tbodyIndex,
        -			wo = c.widgetOptions,
        +		var wo = c.widgetOptions,
         			p = c.pager,
         			hasFilters = c.$table.hasClass('hasFilters');
         		if (hasFilters && !wo.pager_ajaxUrl) {
        @@ -380,10 +379,8 @@ tsp = ts.pager = {
         				// delayInit: true so nothing is in the cache
         				p.filteredRows = p.totalRows = c.$tbodies.eq(0).children('tr').not( wo.pager_countChildRows ? '' : '.' + c.cssChildRow ).length;
         			} else {
        -				// just in case the pager tbody isn't the first tbody
        -				tbodyIndex = c.$table.children('tbody').index( c.$tbodies.eq(0) );
         				p.filteredRows = 0;
        -				$.each(c.cache[tbodyIndex].normalized, function(i, el) {
        +				$.each(c.cache[0].normalized, function(i, el) {
         					p.filteredRows += p.regexRows.test(el[c.columns].$row[0].className) ? 0 : 1;
         				});
         			}
        @@ -898,8 +895,7 @@ tsp = ts.pager = {
         			if ( !$.isEmptyObject(table.config.cache) ) {
         				var i,
         					rows = [],
        -					tbodyIndex = c.$table.children('tbody').index( c.$tbodies.eq(0) ),
        -					n = table.config.cache[tbodyIndex].normalized;
        +					n = table.config.cache[0].normalized;
         				p.totalRows = n.length;
         				for (i = 0; i < p.totalRows; i++) {
         					rows.push(n[i][c.columns].$row);
        diff --git a/testing/testing.js b/testing/testing.js
        index 1ef509e5..3ee8322e 100644
        --- a/testing/testing.js
        +++ b/testing/testing.js
        @@ -31,7 +31,7 @@ var tester = {
         		var i, j = 0, k, l,
         			c = table.config,
         			result = [],
        -			b = table.tBodies,
        +			b = c.$tbodies,
         			l2 = c.columns;
         		for (k = 0; k < b.length; k++){
         			l = b[k].rows.length;
        @@ -423,7 +423,8 @@ $(function(){
         		expect(4);
         
         		$table1.trigger('sorton', [[[ 0,0 ]]]);
        -		tester.cacheCompare( table1, 0, [ 'test1', 'test2', 'test3', '', 'testa', 'testb', 'testc' ], 'from data-attribute' );
        +		tester.cacheCompare( table1, 0, [ 'test1', 'test2', 'test3',
        +		                                  'testa', 'testb', 'testc' ], 'from data-attribute' );
         
         		$table3.trigger('sorton', [[[ 0,1 ]]]);
         		tester.cacheCompare( table3, 0, [ '', 'a255', 'a102', 'a87', 'a55', 'a43', 'a33', 'a10', 'a02', 'a1' ], 'ignore data-attribute' );
        @@ -442,13 +443,16 @@ $(function(){
         		$table1.trigger('sortReset');
         
         		// lower case because table was parsed before c1.ignoreCase was changed
        -		tester.cacheCompare( table1, 'all', [ 'test2', 'x2', 'test1', 'x3', 'test3', 'x1', '', '', 'testb', 'x5', 'testc', 'x4', 'testa', 'x6' ], 'unsorted' );
        +		tester.cacheCompare( table1, 'all', [ 'test2', 'x2', 'test1', 'x3', 'test3', 'x1',
        +		                                      'testb', 'x5', 'testc', 'x4', 'testa', 'x6' ], 'unsorted' );
         
         		$table1.trigger('sorton', [[[ 0,0 ]]]);
        -		tester.cacheCompare( table1, 'all', [ 'test1', 'x3', 'test2', 'x2', 'test3', 'x1', '', '', 'testa', 'x6', 'testb', 'x5', 'testc', 'x4' ], 'ascending sort' );
        +		tester.cacheCompare( table1, 'all', [ 'test1', 'x3', 'test2', 'x2', 'test3', 'x1',
        +		                                      'testa', 'x6', 'testb', 'x5', 'testc', 'x4' ], 'ascending sort' );
         
         		$table1.trigger('sorton', [[[ 0,1 ]]]);
        -		tester.cacheCompare( table1, 'all', [ 'test3', 'x1', 'test2', 'x2', 'test1', 'x3', '', '', 'testc', 'x4', 'testb', 'x5', 'testa', 'x6' ], 'descending sort' );
        +		tester.cacheCompare( table1, 'all', [ 'test3', 'x1', 'test2', 'x2', 'test1', 'x3',
        +		                                      'testc', 'x4', 'testb', 'x5', 'testa', 'x6' ], 'descending sort' );
         
         		// empty cell position
         		$table3.trigger('sorton', [[[ 0,0 ]]]);
        @@ -569,36 +573,40 @@ $(function(){
         				hl = c1.headerList[1] === nw,
         				p1 = c1.parsers[1].id === 'digit';
         			equal(hc && hd && hl && p1, true, 'testing header cache: updateAll - thead');
        -			tester.cacheCompare( table1, 'all', [ 'test3', 1, 'test2', 2, 'test1', 3, '', '', 'testc', 4, 'testb', 5, 'testa', 6 ], 'updateAll - tbody' );
        +			tester.cacheCompare( table1, 'all', [ 'test3', 1, 'test2', 2, 'test1', 3,
        +			                                      'testc', 4, 'testb', 5, 'testa', 6 ], 'updateAll - tbody' );
         		}]);
         
         		// addRows
         		t = $('testd7');
         		$table1.find('tbody:last').prepend(t);
        -		oldColMax = c1.cache[2].colMax[1];
        +		oldColMax = c1.cache[1].colMax[1];
         		$table1.trigger('addRows', [t, true, function(){
         			updateCallback++;
        -			equal( oldColMax === 6 && c1.cache[2].colMax[1] === 7, true, 'addRows includes updating colMax value');
        -			tester.cacheCompare( table1, 'all', [ 'test3', 1, 'test2', 2, 'test1', 3, '', '', 'testd', 7, 'testc', 4, 'testb', 5, 'testa', 6 ], 'addRows method' );
        +			equal( oldColMax === 6 && c1.cache[1].colMax[1] === 7, true, 'addRows includes updating colMax value');
        +			tester.cacheCompare( table1, 'all', [ 'test3', 1, 'test2', 2, 'test1', 3,
        +			                                      'testd', 7, 'testc', 4, 'testb', 5, 'testa', 6 ], 'addRows method' );
         		}]);
         
         		// updateCell
         		t = $table1.find('td:contains("7")');
         		t.html('-8');
        -		oldColMax = c1.cache[2].colMax[1];
        +		oldColMax = c1.cache[1].colMax[1];
         		$table1.trigger('updateCell', [t[0], true, function(){
         			updateCallback++;
        -			equal( oldColMax === 7 && c1.cache[2].colMax[1] === 8, true, 'updateCell includes updating colMax value');
        -			tester.cacheCompare( table1, 'all', [ 'test3', 1, 'test2', 2, 'test1', 3, '', '', 'testd', -8, 'testc', 4, 'testb', 5, 'testa', 6 ], 'updateCell method' );
        +			equal( oldColMax === 7 && c1.cache[1].colMax[1] === 8, true, 'updateCell includes updating colMax value');
        +			tester.cacheCompare( table1, 'all', [ 'test3', 1, 'test2', 2, 'test1', 3,
        +			                                      'testd', -8, 'testc', 4, 'testb', 5, 'testa', 6 ], 'updateCell method' );
         		}]);
         
         		// update
         		$table1.find('tr.temp').remove();
        -		oldColMax = c1.cache[2].colMax[1];
        +		oldColMax = c1.cache[1].colMax[1];
         		$table1.trigger('update', [true, function(){
         			updateCallback++;
        -			equal( oldColMax === 8 && c1.cache[2].colMax[1] === 6, true, 'update includes updating colMax value');
        -			tester.cacheCompare( table1, 'all', [ 'test3', 1, 'test2', 2, 'test1', 3, '', '', 'testc', 4, 'testb', 5, 'testa', 6 ], 'update method' );
        +			equal( oldColMax === 8 && c1.cache[1].colMax[1] === 6, true, 'update includes updating colMax value');
        +			tester.cacheCompare( table1, 'all', [ 'test3', 1, 'test2', 2, 'test1', 3,
        +			                                      'testc', 4, 'testb', 5, 'testa', 6 ], 'update method' );
         		}]);
         
         		$table5