diff --git a/libraries/tablesorter/css/bootstrap.less b/libraries/tablesorter/css/bootstrap.less
index 32264fcd4f3d19679f1eadd6228c079699c6b16d..d71e22084521477e62d102d9cc458dbb55f4e9cd 100644
--- a/libraries/tablesorter/css/bootstrap.less
+++ b/libraries/tablesorter/css/bootstrap.less
@@ -1,4 +1,4 @@
-/* Tablesorter Custom Bootstrap LESS Theme by Rob Garrison
+/* Tablesorter Custom Bootstrap v3 LESS Theme by Rob Garrison
 
 To create your own theme, modify the code below and run it through
 a LESS compiler, like this one: http://leafo.net/lessphp/editor.html
diff --git a/libraries/tablesorter/css/theme.bootstrap.css b/libraries/tablesorter/css/theme.bootstrap.css
index 78bfc7d07da8b079ad7d5c7c4e6478f408bb1e7b..10e911ddfab8a32065975cc15f8b473d396f9604 100644
--- a/libraries/tablesorter/css/theme.bootstrap.css
+++ b/libraries/tablesorter/css/theme.bootstrap.css
@@ -56,8 +56,8 @@
 	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAOCAYAAAD5YeaVAAAA20lEQVR4AWJABpKSkoxALCstLb0aUAsZaCAMhVEY6B0amx8YZWDDEDSBa2AGe7XeIiAAClYwVGBvsAcIllsf/mvcC9DgOOd8h90fxWvngVEUbZIkuWRZZlE8eQjcisgZMM9zi+LJ6ZfwegmWZflZDugdHMfxTcGqql7TNBlUB/QObtv2VBSFrev6OY7jngzFk9OT/fn73fWYpqnlXNyXDMWT0zuYx/Bvel9ej+LJ6R08DMOu67q7DkTkrSA5vYPneV71fX/QASdTkJwezhs0TfMARn0wMDDGXEPgF4oijqwM5YjNAAAAAElFTkSuQmCC);
 }
 
-/* white unsorted icon */
-.tablesorter-bootstrap .icon-white.bootstrap-icon-unsorted {
+/* white unsorted icon; updated to use bootstrap-icon-white - see #1432 */
+.tablesorter-bootstrap .bootstrap-icon-white.bootstrap-icon-unsorted {
 	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAOCAYAAAD5YeaVAAAAe0lEQVR4AbXQoRWDMBiF0Sh2QLAAQ8SxJGugWSA6A2STW1PxTsnB9cnkfuYvv8OGC1t5G3Y0QMP+Bm857keAdQIzWBP3+Bw4MADQE18B6/etRnCV/w9nnGuLezfAmXhABGtAGIkruvk6auIFRwQJDywllsEAjCecB20GP59BQQ+gtlRLAAAAAElFTkSuQmCC);
 }
 
diff --git a/libraries/tablesorter/js/jquery.tablesorter.combined.js b/libraries/tablesorter/js/jquery.tablesorter.combined.js
index 2df96a0e136e032878251bd726cb84dedfdb5f5c..b098001ff23425c0355d605d270c16a4e436c244 100644
--- a/libraries/tablesorter/js/jquery.tablesorter.combined.js
+++ b/libraries/tablesorter/js/jquery.tablesorter.combined.js
@@ -4,7 +4,7 @@
 ██  ██ ██  ██   ██  ██ ██  ██   ██     ██ ██ ██ ██  ██ ██  ██ ██ ██▀▀    ▀▀▀██
 █████▀ ▀████▀   ██  ██ ▀████▀   ██     ██ ██ ██ ▀████▀ █████▀ ██ ██     █████▀
 */
-/*! tablesorter (FORK) - updated 06-08-2017 (v2.28.14)*/
+/*! tablesorter (FORK) - updated 07-04-2017 (v2.28.15)*/
 /* Includes widgets ( storage,uitheme,columns,filter,stickyHeaders,resizable,saveSort ) */
 (function(factory) {
 	if (typeof define === 'function' && define.amd) {
@@ -16,7 +16,7 @@
 	}
 }(function(jQuery) {
 
-/*! TableSorter (FORK) v2.28.14 *//*
+/*! TableSorter (FORK) v2.28.15 *//*
 * Client-side table sorting with ease!
 * @requires jQuery v1.2.6+
 *
@@ -40,7 +40,7 @@
 	'use strict';
 	var ts = $.tablesorter = {
 
-		version : '2.28.14',
+		version : '2.28.15',
 
 		parsers : [],
 		widgets : [],
@@ -2258,7 +2258,7 @@
 				cells = $rows[ i ].cells;
 				for ( j = 0; j < cells.length; j++ ) {
 					cell = cells[ j ];
-					rowIndex = cell.parentNode.rowIndex;
+					rowIndex = i;
 					rowSpan = cell.rowSpan || 1;
 					colSpan = cell.colSpan || 1;
 					if ( typeof matrix[ rowIndex ] === 'undefined' ) {
@@ -2317,7 +2317,7 @@
 			if ( !valid ) {
 				$rows.each( function( indx, el ) {
 					var cell = el.parentElement.nodeName;
-					if ( cells.indexOf( cell ) ) {
+					if ( cells.indexOf( cell ) < 0 ) {
 						cells.push( cell );
 					}
 				});
@@ -3016,10 +3016,10 @@
 			active       : '', // applied when column is sorted
 			hover        : '', // custom css required - a defined bootstrap style may not override other classes
 			// icon class names
-			icons        : '', // add 'icon-white' to make them white; this icon class is added to the <i> in the header
+			icons        : '', // add 'bootstrap-icon-white' to make them white; this icon class is added to the <i> in the header
 			iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted
-			iconSortAsc  : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort
-			iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort
+			iconSortAsc  : 'glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort
+			iconSortDesc : 'glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort
 			filterRow    : '', // filter row class
 			footerRow    : '',
 			footerCells  : '',
@@ -3274,7 +3274,7 @@
 
 })(jQuery);
 
-/*! Widget: filter - updated 5/24/2017 (v2.28.11) *//*
+/*! Widget: filter - updated 7/4/2017 (v2.28.15) *//*
  * Requires tablesorter v2.8+ and jQuery 1.7+
  * by Rob Garrison
  */
@@ -3345,8 +3345,10 @@
 			var tbodyIndex, $tbody,
 				$table = c.$table,
 				$tbodies = c.$tbodies,
-				events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterAndSortReset filterEnd search '
-					.split( ' ' ).join( c.namespace + 'filter ' );
+				events = (
+					'addRows updateCell update updateRows updateComplete appendCache filterReset ' +
+					'filterAndSortReset filterFomatterUpdate filterEnd search stickyHeadersInit '
+				).split( ' ' ).join( c.namespace + 'filter ' );
 			$table
 				.removeClass( 'hasFilters' )
 				// add filter namespace to all BUT search
@@ -3875,7 +3877,9 @@
 		// so we have to work with it instead
 		formatterUpdated: function( $cell, column ) {
 			// prevent error if $cell is undefined - see #1056
-			var wo = $cell && $cell.closest( 'table' )[0].config.widgetOptions;
+			var $table = $cell && $cell.closest( 'table' );
+			var config = $table.length && $table[0].config,
+				wo = config && config.widgetOptions;
 			if ( wo && !wo.filter_initialized ) {
 				// add updates by column since this function
 				// may be called numerous times before initialization
diff --git a/libraries/tablesorter/js/jquery.tablesorter.js b/libraries/tablesorter/js/jquery.tablesorter.js
index ae09696822b012a9d7dbe17c6df9a3ad8b88ab05..f83a1d13f497f5685e76ea865ddbaf812876df4b 100644
--- a/libraries/tablesorter/js/jquery.tablesorter.js
+++ b/libraries/tablesorter/js/jquery.tablesorter.js
@@ -1,4 +1,4 @@
-/*! TableSorter (FORK) v2.28.14 *//*
+/*! TableSorter (FORK) v2.28.15 *//*
 * Client-side table sorting with ease!
 * @requires jQuery v1.2.6+
 *
@@ -22,7 +22,7 @@
 	'use strict';
 	var ts = $.tablesorter = {
 
-		version : '2.28.14',
+		version : '2.28.15',
 
 		parsers : [],
 		widgets : [],
@@ -2240,7 +2240,7 @@
 				cells = $rows[ i ].cells;
 				for ( j = 0; j < cells.length; j++ ) {
 					cell = cells[ j ];
-					rowIndex = cell.parentNode.rowIndex;
+					rowIndex = i;
 					rowSpan = cell.rowSpan || 1;
 					colSpan = cell.colSpan || 1;
 					if ( typeof matrix[ rowIndex ] === 'undefined' ) {
@@ -2299,7 +2299,7 @@
 			if ( !valid ) {
 				$rows.each( function( indx, el ) {
 					var cell = el.parentElement.nodeName;
-					if ( cells.indexOf( cell ) ) {
+					if ( cells.indexOf( cell ) < 0 ) {
 						cells.push( cell );
 					}
 				});
diff --git a/libraries/tablesorter/js/jquery.tablesorter.widgets.js b/libraries/tablesorter/js/jquery.tablesorter.widgets.js
index 85715a0d9dc4e87efbf50f41e8ce9441d22d06da..3cf0ac8414371d7f56d5512a435e4d10928d82c9 100644
--- a/libraries/tablesorter/js/jquery.tablesorter.widgets.js
+++ b/libraries/tablesorter/js/jquery.tablesorter.widgets.js
@@ -4,7 +4,7 @@
 ██  ██ ██  ██   ██  ██ ██  ██   ██     ██ ██ ██ ██  ██ ██  ██ ██ ██▀▀    ▀▀▀██
 █████▀ ▀████▀   ██  ██ ▀████▀   ██     ██ ██ ██ ▀████▀ █████▀ ██ ██     █████▀
 */
-/*! tablesorter (FORK) - updated 06-08-2017 (v2.28.14)*/
+/*! tablesorter (FORK) - updated 07-04-2017 (v2.28.15)*/
 /* Includes widgets ( storage,uitheme,columns,filter,stickyHeaders,resizable,saveSort ) */
 (function(factory) {
 	if (typeof define === 'function' && define.amd) {
@@ -150,10 +150,10 @@
 			active       : '', // applied when column is sorted
 			hover        : '', // custom css required - a defined bootstrap style may not override other classes
 			// icon class names
-			icons        : '', // add 'icon-white' to make them white; this icon class is added to the <i> in the header
+			icons        : '', // add 'bootstrap-icon-white' to make them white; this icon class is added to the <i> in the header
 			iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted
-			iconSortAsc  : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort
-			iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort
+			iconSortAsc  : 'glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort
+			iconSortDesc : 'glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort
 			filterRow    : '', // filter row class
 			footerRow    : '',
 			footerCells  : '',
@@ -408,7 +408,7 @@
 
 })(jQuery);
 
-/*! Widget: filter - updated 5/24/2017 (v2.28.11) *//*
+/*! Widget: filter - updated 7/4/2017 (v2.28.15) *//*
  * Requires tablesorter v2.8+ and jQuery 1.7+
  * by Rob Garrison
  */
@@ -479,8 +479,10 @@
 			var tbodyIndex, $tbody,
 				$table = c.$table,
 				$tbodies = c.$tbodies,
-				events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterAndSortReset filterEnd search '
-					.split( ' ' ).join( c.namespace + 'filter ' );
+				events = (
+					'addRows updateCell update updateRows updateComplete appendCache filterReset ' +
+					'filterAndSortReset filterFomatterUpdate filterEnd search stickyHeadersInit '
+				).split( ' ' ).join( c.namespace + 'filter ' );
 			$table
 				.removeClass( 'hasFilters' )
 				// add filter namespace to all BUT search
@@ -1009,7 +1011,9 @@
 		// so we have to work with it instead
 		formatterUpdated: function( $cell, column ) {
 			// prevent error if $cell is undefined - see #1056
-			var wo = $cell && $cell.closest( 'table' )[0].config.widgetOptions;
+			var $table = $cell && $cell.closest( 'table' );
+			var config = $table.length && $table[0].config,
+				wo = config && config.widgetOptions;
 			if ( wo && !wo.filter_initialized ) {
 				// add updates by column since this function
 				// may be called numerous times before initialization
diff --git a/libraries/tablesorter/js/widgets/widget-columnSelector.js b/libraries/tablesorter/js/widgets/widget-columnSelector.js
index 6abdb4110c4df4305a3ab0255d7bf9c48a64750e..c67ff369d1d1b9cb1904430abd992ec0843dbf5f 100644
--- a/libraries/tablesorter/js/widgets/widget-columnSelector.js
+++ b/libraries/tablesorter/js/widgets/widget-columnSelector.js
@@ -1,4 +1,4 @@
-/* Widget: columnSelector (responsive table widget) - updated 5/25/2017 (v2.28.12) *//*
+/* Widget: columnSelector (responsive table widget) - updated 7/4/2017 (v2.28.15) *//*
  * Requires tablesorter v2.8+ and jQuery 1.7+
  * by Justin Hallett & Rob Garrison
  */
@@ -547,6 +547,11 @@
 			csel.$style.remove();
 			csel.$breakpoints.remove();
 			$( c.namespace + 'columnselectorHasSpan' ).removeClass( wo.filter_filteredRow || 'filtered' );
+			c.$table.find('[data-col-span]').each(function(indx, el) {
+				var $el = $(el);
+				console.log($el, $el.attr('data-col-span'));
+				$el.attr('colspan', $el.attr('data-col-span'));
+			});
 			c.$table.off('updateAll' + namespace + ' update' + namespace);
 		}
 
diff --git a/libraries/tablesorter/js/widgets/widget-filter-formatter-jui.js b/libraries/tablesorter/js/widgets/widget-filter-formatter-jui.js
index 78fbbe0b0b7db1f138a3e4b1942f7522927874eb..9b933f04d4557bb3d29778ca3d6b99ec9a44c5bc 100644
--- a/libraries/tablesorter/js/widgets/widget-filter-formatter-jui.js
+++ b/libraries/tablesorter/js/widgets/widget-filter-formatter-jui.js
@@ -89,18 +89,20 @@
 					chkd = $cell.find('.toggle').is(':checked');
 				}
 				state = o.disabled || !chkd ? 'disable' : 'enable';
-				$cell.find('.filter')
-					// add equal to the beginning, so we filter exact numbers
-					.val( chkd ? (compare ? compare : o.exactMatch ? '=' : '') + v : '' )
-					.trigger( notrigger ? '' : 'search', searchType ).end()
-					.find('.spinner').spinner(state).val(v);
-				// update sticky header cell
-				if ($shcell.length) {
-					$shcell
-						.find('.spinner').spinner(state).val(v).end()
-						.find(compareSelect).val( compare );
-					if (o.addToggle) {
-						$shcell.find('.toggle')[0].checked = chkd;
+				if (!ts.isEmptyObject($cell.find('.spinner').data())) {
+					$cell.find('.filter')
+						// add equal to the beginning, so we filter exact numbers
+						.val( chkd ? (compare ? compare : o.exactMatch ? '=' : '') + v : '' )
+						.trigger( notrigger ? '' : 'search', searchType ).end()
+						.find('.spinner').spinner(state).val(v);
+					// update sticky header cell
+					if ($shcell.length) {
+						$shcell
+							.find('.spinner').spinner(state).val(v).end()
+							.find(compareSelect).val( compare );
+						if (o.addToggle) {
+							$shcell.find('.toggle')[0].checked = chkd;
+						}
 					}
 				}
 			};
@@ -137,7 +139,7 @@
 				});
 
 			// update spinner from hidden input, in case of saved filters
-			c.$table.bind('filterFomatterUpdate', function(){
+			c.$table.bind('filterFomatterUpdate' + c.namespace + 'filter', function(){
 				var val = tsff.updateCompare($cell, $input, o)[0];
 				$cell.find('.spinner').val( val );
 				updateSpinner({ value: val }, true);
@@ -153,7 +155,7 @@
 			}
 
 			// has sticky headers?
-			c.$table.bind('stickyHeadersInit', function(){
+			c.$table.bind('stickyHeadersInit' + c.namespace + 'filter', function(){
 				$shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty();
 				if (o.addToggle) {
 					$('<div class="button"><input id="stickyuispinnerbutton' + indx + '" type="checkbox" class="toggle" />' +
@@ -187,7 +189,7 @@
 			});
 
 			// on reset
-			c.$table.bind('filterReset', function(){
+			c.$table.bind('filterReset' + c.namespace + 'filter', function(){
 				if ($.isArray(o.compare)) {
 					$cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] );
 				}
@@ -250,24 +252,26 @@
 					// add values to the handle data-value attribute so the css tooltip will work properly
 					$cell.find('.ui-slider-handle').addClass('value-popup').attr('data-value', result);
 				}
-				// update the hidden input;
-				// ****** ADD AN EQUAL SIGN TO THE BEGINNING! <- this makes the slide exactly match the number ******
-				// when the value is at the minimum, clear the hidden input so all rows will be seen
-
-				$cell.find('.filter')
-					.val( ( compare ? compare + v : v === o.min ? '' : (o.exactMatch ? '=' : '') + v ) )
-					.trigger( notrigger ? '' : 'search', searchType ).end()
-					.find('.slider').slider('value', v);
-
-				// update sticky header cell
-				if ($shcell.length) {
-					$shcell
-						.find(compareSelect).val( compare ).end()
+				// prevent JS error if "resetToLoadState" or filter widget was removed for another reason
+				if (!ts.isEmptyObject($cell.find('.slider').data())) {
+					// update the hidden input;
+					$cell.find('.filter')
+						// ****** ADD AN EQUAL SIGN TO THE BEGINNING! <- this makes the slide exactly match the number ******
+						// when the value is at the minimum, clear the hidden input so all rows will be seen
+						.val( ( compare ? compare + v : v === o.min ? '' : (o.exactMatch ? '=' : '') + v ) )
+						.trigger( notrigger ? '' : 'search', searchType ).end()
 						.find('.slider').slider('value', v);
-					if (o.valueToHeader) {
-						$shcell.closest('thead').find('th[data-column=' + indx + ']').find('.curvalue').html(' (' + result + ')');
-					} else {
-						$shcell.find('.ui-slider-handle').addClass('value-popup').attr('data-value', result);
+
+					// update sticky header cell
+					if ($shcell.length) {
+						$shcell
+							.find(compareSelect).val( compare ).end()
+							.find('.slider').slider('value', v);
+						if (o.valueToHeader) {
+							$shcell.closest('thead').find('th[data-column=' + indx + ']').find('.curvalue').html(' (' + result + ')');
+						} else {
+							$shcell.find('.ui-slider-handle').addClass('value-popup').attr('data-value', result);
+						}
 					}
 				}
 
@@ -296,7 +300,7 @@
 				.slider(o);
 
 			// update slider from hidden input, in case of saved filters
-			c.$table.bind('filterFomatterUpdate', function(){
+			c.$table.bind('filterFomatterUpdate' + c.namespace + 'filter', function(){
 				var val = tsff.updateCompare($cell, $input, o)[0];
 				$cell.find('.slider').slider('value', val );
 				updateSlider({ value: val }, false);
@@ -312,7 +316,7 @@
 			}
 
 			// on reset
-			c.$table.bind('filterReset', function(){
+			c.$table.bind('filterReset' + c.namespace + 'filter', function(){
 				if ($.isArray(o.compare)) {
 					$cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] );
 				}
@@ -322,7 +326,7 @@
 			});
 
 			// has sticky headers?
-			c.$table.bind('stickyHeadersInit', function(){
+			c.$table.bind('stickyHeadersInit' + c.namespace + 'filter', function(){
 				$shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty();
 
 				// add a jQuery UI slider!
@@ -401,20 +405,22 @@
 						.eq(0).attr('data-value', val[0]).end() // adding value to data attribute
 						.eq(1).attr('data-value', val[1]);      // value popup shown via css
 				}
-				// update the hidden input
-				$cell.find('.filter').val(range)
-					.trigger(notrigger ? '' : 'search', searchType).end()
-					.find('.range').slider('values', val);
-				// update sticky header cell
-				if ($shcell.length) {
-					$shcell.find('.range').slider('values', val);
-					if (o.valueToHeader) {
-						$shcell.closest('thead').find('th[data-column=' + indx + ']').find('.currange').html(' (' + result + ')');
-					} else {
-						$shcell.find('.ui-slider-handle')
-						.addClass('value-popup')
-						.eq(0).attr('data-value', val[0]).end() // adding value to data attribute
-						.eq(1).attr('data-value', val[1]);      // value popup shown via css
+				if (!ts.isEmptyObject($cell.find('.range').data())) {
+					// update the hidden input
+					$cell.find('.filter').val(range)
+						.trigger(notrigger ? '' : 'search', searchType).end()
+						.find('.range').slider('values', val);
+					// update sticky header cell
+					if ($shcell.length) {
+						$shcell.find('.range').slider('values', val);
+						if (o.valueToHeader) {
+							$shcell.closest('thead').find('th[data-column=' + indx + ']').find('.currange').html(' (' + result + ')');
+						} else {
+							$shcell.find('.ui-slider-handle')
+							.addClass('value-popup')
+							.eq(0).attr('data-value', val[0]).end() // adding value to data attribute
+							.eq(1).attr('data-value', val[1]);      // value popup shown via css
+						}
 					}
 				}
 
@@ -443,13 +449,13 @@
 				.slider(o);
 
 			// update slider from hidden input, in case of saved filters
-			c.$table.bind('filterFomatterUpdate', function(){
+			c.$table.bind('filterFomatterUpdate' + c.namespace + 'filter', function(){
 				getRange();
 				ts.filter.formatterUpdated($cell, indx);
 			});
 
 			// on reset
-			c.$table.bind('filterReset', function(){
+			c.$table.bind('filterReset' + c.namespace + 'filter', function(){
 				$cell.find('.range').slider('values', o.values);
 				setTimeout(function(){
 					updateUiRange();
@@ -457,7 +463,7 @@
 			});
 
 			// has sticky headers?
-			c.$table.bind('stickyHeadersInit', function(){
+			c.$table.bind('stickyHeadersInit' + c.namespace + 'filter', function(){
 				$shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty();
 
 				// add a jQuery UI slider!
@@ -552,7 +558,7 @@
 			$date.datepicker(o);
 
 			// on reset
-			c.$table.bind('filterReset', function(){
+			c.$table.bind('filterReset' + c.namespace + 'filter', function(){
 				if ($.isArray(o.compare)) {
 					$cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] );
 				}
@@ -563,7 +569,7 @@
 			});
 
 			// update date compare from hidden input, in case of saved filters
-			c.$table.bind('filterFomatterUpdate', function(){
+			c.$table.bind('filterFomatterUpdate' + c.namespace + 'filter', function(){
 				var num, v = $input.val();
 				if (/\s+-\s+/.test(v)) {
 					// date range found; assume an exact match on one day
@@ -591,7 +597,7 @@
 			}
 
 			// has sticky headers?
-			c.$table.bind('stickyHeadersInit', function(){
+			c.$table.bind('stickyHeadersInit' + c.namespace + 'filter', function(){
 				$shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty();
 
 				// add a jQuery datepicker!
@@ -702,7 +708,7 @@
 			$cell.find('.dateTo').datepicker(o);
 
 			// update date compare from hidden input, in case of saved filters
-			c.$table.bind('filterFomatterUpdate', function(){
+			c.$table.bind('filterFomatterUpdate' + c.namespace + 'filter', function(){
 				var val = $input.val() || '',
 					from = '',
 					to = '';
@@ -733,7 +739,7 @@
 			});
 
 			// has sticky headers?
-			c.$table.bind('stickyHeadersInit', function(){
+			c.$table.bind('stickyHeadersInit' + c.namespace + 'filter', function(){
 				$shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty();
 				$shcell.append(t);
 
@@ -747,7 +753,7 @@
 			});
 
 			// on reset
-			$cell.closest('table').bind('filterReset', function(){
+			$cell.closest('table').bind('filterReset' + c.namespace + 'filter', function(){
 				$cell.add($shcell).find('.dateFrom').val('').datepicker('setDate', o.from || null );
 				$cell.add($shcell).find('.dateTo').val('').datepicker('setDate', o.to || null );
 				setTimeout(function(){
diff --git a/libraries/tablesorter/js/widgets/widget-filter-formatter-select2.js b/libraries/tablesorter/js/widgets/widget-filter-formatter-select2.js
index cb517916be35792a8aacdb8825f148b7f66ad6ab..e1eeee0d28b3d4ae6a4309443628e01ecd6e818b 100644
--- a/libraries/tablesorter/js/widgets/widget-filter-formatter-select2.js
+++ b/libraries/tablesorter/js/widgets/widget-filter-formatter-select2.js
@@ -70,18 +70,20 @@
 			if (arry) {
 				v = v.split('\u0000');
 			}
-			$input
-				// add regex, so we filter exact numbers
-				.val(
-					$.isArray(v) && v.length && v.join('') !== '' ?
-						'/(' + matchPrefix + (v || []).join(matchSuffix + '|' + matchPrefix) + matchSuffix + ')/' + flags :
-						''
-				)
-				.trigger('search').end()
-				.find('.select2').select2('val', v);
-			// update sticky header cell
-			if (c.widgetOptions.$sticky) {
-				c.widgetOptions.$sticky.find('.select2col' + indx + ' .select2').select2('val', v);
+			if (!ts.isEmptyObject($input.find('.select2').data())) {
+				$input
+					// add regex, so we filter exact numbers
+					.val(
+						$.isArray(v) && v.length && v.join('') !== '' ?
+							'/(' + matchPrefix + (v || []).join(matchSuffix + '|' + matchPrefix) + matchSuffix + ')/' + flags :
+							''
+					)
+					.trigger('search').end()
+					.find('.select2').select2('val', v);
+				// update sticky header cell
+				if (c.widgetOptions.$sticky) {
+					c.widgetOptions.$sticky.find('.select2col' + indx + ' .select2').select2('val', v);
+				}
 			}
 		},
 
diff --git a/libraries/tablesorter/js/widgets/widget-filter.js b/libraries/tablesorter/js/widgets/widget-filter.js
index 816ce2b601e1faae1adabe1b7ba2393b7781a5b7..5a5b7607db3b0bcaefa605729b1f904f287f156a 100644
--- a/libraries/tablesorter/js/widgets/widget-filter.js
+++ b/libraries/tablesorter/js/widgets/widget-filter.js
@@ -1,4 +1,4 @@
-/*! Widget: filter - updated 5/24/2017 (v2.28.11) *//*
+/*! Widget: filter - updated 7/4/2017 (v2.28.15) *//*
  * Requires tablesorter v2.8+ and jQuery 1.7+
  * by Rob Garrison
  */
@@ -69,8 +69,10 @@
 			var tbodyIndex, $tbody,
 				$table = c.$table,
 				$tbodies = c.$tbodies,
-				events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterAndSortReset filterEnd search '
-					.split( ' ' ).join( c.namespace + 'filter ' );
+				events = (
+					'addRows updateCell update updateRows updateComplete appendCache filterReset ' +
+					'filterAndSortReset filterFomatterUpdate filterEnd search stickyHeadersInit '
+				).split( ' ' ).join( c.namespace + 'filter ' );
 			$table
 				.removeClass( 'hasFilters' )
 				// add filter namespace to all BUT search
@@ -599,7 +601,9 @@
 		// so we have to work with it instead
 		formatterUpdated: function( $cell, column ) {
 			// prevent error if $cell is undefined - see #1056
-			var wo = $cell && $cell.closest( 'table' )[0].config.widgetOptions;
+			var $table = $cell && $cell.closest( 'table' );
+			var config = $table.length && $table[0].config,
+				wo = config && config.widgetOptions;
 			if ( wo && !wo.filter_initialized ) {
 				// add updates by column since this function
 				// may be called numerous times before initialization
diff --git a/libraries/tablesorter/js/widgets/widget-output.js b/libraries/tablesorter/js/widgets/widget-output.js
index 722bd4f9e97314770f255b5790343dbb7cfaf8f8..305d33b239fbf2f96137ed24ee7f5cc3fb001a73 100644
--- a/libraries/tablesorter/js/widgets/widget-output.js
+++ b/libraries/tablesorter/js/widgets/widget-output.js
@@ -1,4 +1,4 @@
-/*! Widget: output - updated 4/2/2017 (v2.28.6) *//*
+/*! Widget: output - updated 7/5/2017 (v2.28.16) *//*
  * Requires tablesorter v2.8+ and jQuery 1.7+
  * Modified from:
  * HTML Table to CSV: http://www.kunalbabre.com/projects/table2CSV.php (License unknown?)
@@ -131,8 +131,10 @@
 			return data;
 		},
 
-		process : function(c, wo) {
-			var mydata, $this, $rows, headers, csvData, len, rowsLen, tmp,
+		// optional vars $rows and dump added by TheSin to make
+		// process callable via callback for ajaxPager
+		process : function(c, wo, $rows, dump) {
+			var mydata, $this, headers, csvData, len, rowsLen, tmp,
 				hasStringify = window.JSON && JSON.hasOwnProperty('stringify'),
 				indx = 0,
 				tmpData = (wo.output_separator || ',').toLowerCase(),
@@ -162,7 +164,8 @@
 			headers = output.processRow(c, $this, true, outputJSON);
 
 			// all tbody rows - do not include widget added rows (e.g. grouping widget headers)
-			$rows = $el.children('tbody').children('tr').not(c.selectorRemove);
+			if ( !$rows )
+				$rows = $el.children('tbody').children('tr').not(c.selectorRemove);
 
 			// check for a filter callback function first! because
 			// /^f/.test(function(){ console.log('test'); }) is TRUE! (function is converted to a string)
@@ -210,10 +213,15 @@
 				mydata = outputArray && hasStringify ? JSON.stringify(tmpData) : tmpData.join('\n');
 			}
 
+			if (dump) {
+				return mydata;
+			}
+
 			// callback; if true returned, continue processing
 			if ($.isFunction(wo.output_callback)) {
 				tmp = wo.output_callback(c, mydata, c.pager && c.pager.ajaxObject.url || null);
 				if ( tmp === false ) {
+					output.busy = false;
 					return;
 				} else if ( typeof tmp === 'string' ) {
 					mydata = tmp;
diff --git a/libraries/tablesorter/js/widgets/widget-sort2Hash.js b/libraries/tablesorter/js/widgets/widget-sort2Hash.js
index 7429bf8a63ee617a15bc8471c9e2bbffb30dd00e..cbb12c6e60cf1abe9fab50fd3869d0951220dc8f 100644
--- a/libraries/tablesorter/js/widgets/widget-sort2Hash.js
+++ b/libraries/tablesorter/js/widgets/widget-sort2Hash.js
@@ -1,4 +1,4 @@
-/*! Widget: sort2Hash (BETA) - updated 4/2/2017 (v2.28.6) */
+/*! Widget: sort2Hash (BETA) - updated 7/4/2017 (v2.28.15) */
 /* Requires tablesorter v2.8+ and jQuery 1.7+
  * by Rob Garrison
  */
@@ -26,10 +26,21 @@
 					filter = filter.split( wo.sort2Hash_separator );
 					c.$table.one( 'tablesorter-ready', function() {
 						setTimeout(function(){
-							c.$table.one( 'filterEnd', function(){
+							c.$table.one( 'filterEnd', function() {
 								$(this).triggerHandler( 'pageAndSize', [ page, size ] );
 							});
-							$.tablesorter.setFilters( table, filter, true );
+							// use the newest filter comparison code
+							if ( ts.filter.equalFilters ) {
+								temp = ts.filter.equalFilters( c, c.lastSearch, filter );
+							} else {
+								// quick n' dirty comparison... it will miss filter changes of
+								// the same value in a different column, see #1363
+								temp = ( c.lastSearch || [] ).join( '' ) !== ( filter || [] ).join( '' );
+							}
+							// don't set filters if they haven't changed
+							if ( !temp ) {
+								$.tablesorter.setFilters( table, filter, true );
+							}
 						}, 100 );
 					});
 				}
diff --git a/libraries/tablesorter/js/widgets/widget-uitheme.js b/libraries/tablesorter/js/widgets/widget-uitheme.js
index 42a945eb7d6dbd442c1ae24fe6042a37369b5c35..4104df867519266704104dc0a3307f1bdb67fc62 100644
--- a/libraries/tablesorter/js/widgets/widget-uitheme.js
+++ b/libraries/tablesorter/js/widgets/widget-uitheme.js
@@ -15,10 +15,10 @@
 			active       : '', // applied when column is sorted
 			hover        : '', // custom css required - a defined bootstrap style may not override other classes
 			// icon class names
-			icons        : '', // add 'icon-white' to make them white; this icon class is added to the <i> in the header
+			icons        : '', // add 'bootstrap-icon-white' to make them white; this icon class is added to the <i> in the header
 			iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted
-			iconSortAsc  : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort
-			iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort
+			iconSortAsc  : 'glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort
+			iconSortDesc : 'glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort
 			filterRow    : '', // filter row class
 			footerRow    : '',
 			footerCells  : '',