// @preserve jQuery.floatThead 1.4.1 - http://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2016 Misha Koryak
// @license MIT
/* @author Misha Koryak
* @projectDescription lock a table header in place while scrolling - without breaking styles or events bound to the header
*
* Dependencies:
* jquery 1.9.0 + [required] OR jquery 1.7.0 + jquery UI core
*
* http://mkoryak.github.io/floatThead/
*
* Tested on FF13+, Chrome 21+, IE8, IE9, IE10, IE11
*
*/
(function( $ ) {
/**
* provides a default config object. You can modify this after including this script if you want to change the init defaults
* @type {Object}
*/
$.floatThead = $.floatThead || {};
$.floatThead.defaults = {
headerCellSelector: 'tr:visible:first>*:visible', //thead cells are this.
zIndex: 1001, //zindex of the floating thead (actually a container div)
position: 'auto', // 'fixed', 'absolute', 'auto'. auto picks the best for your table scrolling type.
top: 0, //String or function($table) - offset from top of window where the header should not pass above
bottom: 0, //String or function($table) - offset from the bottom of the table where the header should stop scrolling
scrollContainer: function($table) { // or boolean 'true' (use offsetParent) | function -> if the table has horizontal scroll bars then this is the container that has overflow:auto and causes those scroll bars
return $([]);
},
responsiveContainer: function($table) { // only valid if scrollContainer is not used (ie window scrolling). this is the container which will control y scrolling at some mobile breakpoints
return $([]);
},
getSizingRow: function($table, $cols, $fthCells){ // this is only called when using IE,
// override it if the first row of the table is going to contain colgroups (any cell spans greater than one col)
// it should return a jquery object containing a wrapped set of table cells comprising a row that contains no col spans and is visible
return $table.find('tbody tr:visible:first>*:visible');
},
floatTableClass: 'floatThead-table',
floatWrapperClass: 'floatThead-wrapper',
floatContainerClass: 'floatThead-container',
copyTableClass: true, //copy 'class' attribute from table into the floated table so that the styles match.
enableAria: false, //will copy header text from the floated header back into the table for screen readers. Might cause the css styling to be off. beware!
autoReflow: false, //(undocumented) - use MutationObserver api to reflow automatically when internal table DOM changes
debug: false, //print possible issues (that don't prevent script loading) to console, if console exists.
support: { //should we bind events that expect these frameworks to be present and/or check for them?
bootstrap: true,
datatables: true,
jqueryUI: true,
perfectScrollbar: true
}
};
var util = window._;
var canObserveMutations = typeof MutationObserver !== 'undefined';
//browser stuff
var ieVersion = function(){for(var a=3,b=document.createElement("b"),c=b.all||[];a = 1+a,b.innerHTML="",c[0];);return 4
'
+ '
'
);
$('body').append($div);
var w1 = $div.innerWidth();
var w2 = $('div', $div).innerWidth();
$div.remove();
return w1 - w2;
}
/**
* Check if a given table has been datatableized (http://datatables.net)
* @param $table
* @return {Boolean}
*/
function isDatatable($table){
if($table.dataTableSettings){
for(var i = 0; i < $table.dataTableSettings.length; i++){
var table = $table.dataTableSettings[i].nTable;
if($table[0] == table){
return true;
}
}
}
return false;
}
function tableWidth($table, $fthCells, isOuter){
// see: https://github.com/mkoryak/floatThead/issues/108
var fn = isOuter ? "outerWidth": "width";
if(isTableWidthBug && $table.css("max-width")){
var w = 0;
if(isOuter) {
w += parseInt($table.css("borderLeft"), 10);
w += parseInt($table.css("borderRight"), 10);
}
for(var i=0; i < $fthCells.length; i++){
w += $fthCells.get(i).offsetWidth;
}
return w;
} else {
return $table[fn]();
}
}
$.fn.floatThead = function(map){
map = map || {};
if(!util){ //may have been included after the script? lets try to grab it again.
util = window._ || $.floatThead._;
if(!util){
throw new Error("jquery.floatThead-slim.js requires underscore. You should use the non-lite version since you do not have underscore.");
}
}
if(ieVersion < 8){
return this; //no more crappy browser support.
}
var mObs = null; //mutation observer lives in here if we can use it / make it
if(util.isFunction(isTableWidthBug)) {
isTableWidthBug = isTableWidthBug();
}
if(util.isString(map)){
var command = map;
var ret = this;
this.filter('table').each(function(){
var $this = $(this);
var opts = $this.data('floatThead-lazy');
if(opts){
$this.floatThead(opts);
}
var obj = $this.data('floatThead-attached');
if(obj && util.isFunction(obj[command])){
var r = obj[command]();
if(typeof r !== 'undefined'){
ret = r;
}
}
});
return ret;
}
var opts = $.extend({}, $.floatThead.defaults || {}, map);
$.each(map, function(key, val){
if((!(key in $.floatThead.defaults)) && opts.debug){
debug("Used ["+key+"] key to init plugin, but that param is not an option for the plugin. Valid options are: "+ (util.keys($.floatThead.defaults)).join(', '));
}
});
if(opts.debug){
var v = $.fn.jquery.split(".");
if(parseInt(v[0], 10) == 1 && parseInt(v[1], 10) <= 7){
debug("jQuery version "+$.fn.jquery+" detected! This plugin supports 1.8 or better, or 1.7.x with jQuery UI 1.8.24 -> http://jqueryui.com/resources/download/jquery-ui-1.8.24.zip")
}
}
this.filter(':not(.'+opts.floatTableClass+')').each(function(){
var floatTheadId = util.uniqueId();
var $table = $(this);
if($table.data('floatThead-attached')){
return true; //continue the each loop
}
if(!$table.is('table')){
throw new Error('jQuery.floatThead must be run on a table element. ex: $("table").floatThead();');
}
canObserveMutations = opts.autoReflow && canObserveMutations; //option defaults to false!
var $header = $table.children('thead:first');
var $tbody = $table.children('tbody:first');
if($header.length == 0 || $tbody.length == 0){
$table.data('floatThead-lazy', opts);
$table.unbind("reflow").one('reflow', function(){
$table.floatThead(opts);
});
return;
}
if($table.data('floatThead-lazy')){
$table.unbind("reflow");
}
$table.data('floatThead-lazy', false);
var headerFloated = true;
var scrollingTop, scrollingBottom;
var scrollbarOffset = {vertical: 0, horizontal: 0};
var scWidth = scrollbarWidth();
var lastColumnCount = 0; //used by columnNum()
if(opts.scrollContainer === true){
opts.scrollContainer = getClosestScrollContainer;
}
var $scrollContainer = opts.scrollContainer($table) || $([]); //guard against returned nulls
var locked = $scrollContainer.length > 0;
var $responsiveContainer = locked ? $([]) : opts.responsiveContainer($table) || $([]);
var responsive = isResponsiveContainerActive();
var useAbsolutePositioning = null;
if(typeof opts.useAbsolutePositioning !== 'undefined'){
opts.position = 'auto';
if(opts.useAbsolutePositioning){
opts.position = opts.useAbsolutePositioning ? 'absolute' : 'fixed';
}
debug("option 'useAbsolutePositioning' has been removed in v1.3.0, use `position:'"+opts.position+"'` instead. See docs for more info: http://mkoryak.github.io/floatThead/#options")
}
if(typeof opts.scrollingTop !== 'undefined'){
opts.top = opts.scrollingTop;
debug("option 'scrollingTop' has been renamed to 'top' in v1.3.0. See docs for more info: http://mkoryak.github.io/floatThead/#options");
}
if(typeof opts.scrollingBottom !== 'undefined'){
opts.bottom = opts.scrollingBottom;
debug("option 'scrollingBottom' has been renamed to 'bottom' in v1.3.0. See docs for more info: http://mkoryak.github.io/floatThead/#options");
}
if (opts.position == 'auto') {
useAbsolutePositioning = null;
} else if (opts.position == 'fixed') {
useAbsolutePositioning = false;
} else if (opts.position == 'absolute'){
useAbsolutePositioning = true;
} else if (opts.debug) {
debug('Invalid value given to "position" option, valid is "fixed", "absolute" and "auto". You passed: ', opts.position);
}
if(useAbsolutePositioning == null){ //defaults: locked=true, !locked=false
useAbsolutePositioning = locked;
}
var $caption = $table.find("caption");
var haveCaption = $caption.length == 1;
if(haveCaption){
var captionAlignTop = ($caption.css("caption-side") || $caption.attr("align") || "top") === "top";
}
var $fthGrp = $('
');
var wrappedContainer = false; //used with absolute positioning enabled. did we need to wrap the scrollContainer/table with a relative div?
var $wrapper = $([]); //used when absolute positioning enabled - wraps the table and the float container
var absoluteToFixedOnScroll = ieVersion <= 9 && !locked && useAbsolutePositioning; //on IE using absolute positioning doesn't look good with window scrolling, so we change position to fixed on scroll, and then change it back to absolute when done.
var $floatTable = $("
");
var $floatColGroup = $("
");
var $tableColGroup = $table.children('colgroup:first');
var existingColGroup = true;
if($tableColGroup.length == 0){
$tableColGroup = $("
");
existingColGroup = false;
}
var $fthRow = $('
'); //created unstyled elements (used for sizing the table because chrome can't read
width)
var $floatContainer = $('
');
var floatTableHidden = false; //this happens when the table is hidden and we do magic when making it visible
var $newHeader = $("
");
var $sizerRow = $('
|
');
var $sizerCells = $([]);
var $tableCells = $([]); //used for sizing - either $sizerCells or $tableColGroup cols. $tableColGroup cols are only created in chrome for borderCollapse:collapse because of a chrome bug.
var $headerCells = $([]);
var $fthCells = $([]); //created elements
$newHeader.append($sizerRow);
$table.prepend($tableColGroup);
if(createElements){
$fthGrp.append($fthRow);
$table.append($fthGrp);
}
$floatTable.append($floatColGroup);
$floatContainer.append($floatTable);
if(opts.copyTableClass){
$floatTable.attr('class', $table.attr('class'));
}
$floatTable.attr({ //copy over some deprecated table attributes that people still like to use. Good thing people don't use colgroups...
'cellpadding': $table.attr('cellpadding'),
'cellspacing': $table.attr('cellspacing'),
'border': $table.attr('border')
});
var tableDisplayCss = $table.css('display');
$floatTable.css({
'borderCollapse': $table.css('borderCollapse'),
'border': $table.css('border'),
'display': tableDisplayCss
});
if(!locked){
$floatTable.css('width', 'auto');
}
if(tableDisplayCss == 'none'){
floatTableHidden = true;
}
$floatTable.addClass(opts.floatTableClass).css({'margin': 0, 'border-bottom-width': 0}); //must have no margins or you won't be able to click on things under floating table
if(useAbsolutePositioning){
var makeRelative = function($container, alwaysWrap){
var positionCss = $container.css('position');
var relativeToScrollContainer = (positionCss == "relative" || positionCss == "absolute");
var $containerWrap = $container;
if(!relativeToScrollContainer || alwaysWrap){
var css = {"paddingLeft": $container.css('paddingLeft'), "paddingRight": $container.css('paddingRight')};
$floatContainer.css(css);
$containerWrap = $container.data('floatThead-containerWrap') || $container.wrap("
").parent();
$container.data('floatThead-containerWrap', $containerWrap); //multiple tables inside one scrolling container - #242
wrappedContainer = true;
}
return $containerWrap;
};
if(locked){
$wrapper = makeRelative($scrollContainer, true);
$wrapper.prepend($floatContainer);
} else {
$wrapper = makeRelative($table);
$table.before($floatContainer);
}
} else {
$table.before($floatContainer);
}
$floatContainer.css({
position: useAbsolutePositioning ? 'absolute' : 'fixed',
marginTop: 0,
top: useAbsolutePositioning ? 0 : 'auto',
zIndex: opts.zIndex
});
$floatContainer.addClass(opts.floatContainerClass);
updateScrollingOffsets();
var layoutFixed = {'table-layout': 'fixed'};
var layoutAuto = {'table-layout': $table.css('tableLayout') || 'auto'};
var originalTableWidth = $table[0].style.width || ""; //setting this to auto is bad: #70
var originalTableMinWidth = $table.css('minWidth') || "";
function eventName(name){
return name+'.fth-'+floatTheadId+'.floatTHead'
}
function setHeaderHeight(){
var headerHeight = 0;
$header.children("tr:visible").each(function(){
headerHeight += $(this).outerHeight(true);
});
if($table.css('border-collapse') == 'collapse') {
var tableBorderTopHeight = parseInt($table.css('border-top-width'), 10);
var cellBorderTopHeight = parseInt($table.find("thead tr:first").find(">*:first").css('border-top-width'), 10);
if(tableBorderTopHeight > cellBorderTopHeight) {
headerHeight -= (tableBorderTopHeight / 2); //id love to see some docs where this magic recipe is found..
}
}
$sizerRow.outerHeight(headerHeight);
$sizerCells.outerHeight(headerHeight);
}
function setFloatWidth(){
var tw = tableWidth($table, $fthCells, true);
var $container = responsive ? $responsiveContainer : $scrollContainer;
var width = $container.width() || tw;
var floatContainerWidth = $container.css("overflow-y") != 'hidden' ? width - scrollbarOffset.vertical : width;
$floatContainer.width(floatContainerWidth);
if(locked){
var percent = 100 * tw / (floatContainerWidth);
$floatTable.css('width', percent+'%');
} else {
$floatTable.outerWidth(tw);
}
}
function updateScrollingOffsets(){
scrollingTop = (util.isFunction(opts.top) ? opts.top($table) : opts.top) || 0;
scrollingBottom = (util.isFunction(opts.bottom) ? opts.bottom($table) : opts.bottom) || 0;
}
/**
* get the number of columns and also rebuild resizer rows if the count is different than the last count
*/
function columnNum(){
var count;
var $headerColumns = $header.find(opts.headerCellSelector);
if(existingColGroup){
count = $tableColGroup.find('col').length;
} else {
count = 0;
$headerColumns.each(function () {
count += parseInt(($(this).attr('colspan') || 1), 10);
});
}
if(count != lastColumnCount){
lastColumnCount = count;
var cells = [], cols = [], psuedo = [], content;
for(var x = 0; x < count; x++){
if (opts.enableAria && (content = $headerColumns.eq(x).text()) ) {
cells.push('
' + content + ' | ');
} else {
cells.push('
| ');
}
cols.push('
');
psuedo.push("
");
}
cols = cols.join('');
cells = cells.join('');
if(createElements){
psuedo = psuedo.join('');
$fthRow.html(psuedo);
$fthCells = $fthRow.find('fthtd');
}
$sizerRow.html(cells);
$sizerCells = $sizerRow.find("th");
if(!existingColGroup){
$tableColGroup.html(cols);
}
$tableCells = $tableColGroup.find('col');
$floatColGroup.html(cols);
$headerCells = $floatColGroup.find("col");
}
return count;
}
function refloat(){ //make the thing float
if(!headerFloated){
headerFloated = true;
if(useAbsolutePositioning){ //#53, #56
var tw = tableWidth($table, $fthCells, true);
var wrapperWidth = $wrapper.width();
if(tw > wrapperWidth){
$table.css('minWidth', tw);
}
}
$table.css(layoutFixed);
$floatTable.css(layoutFixed);
$floatTable.append($header); //append because colgroup must go first in chrome
$tbody.before($newHeader);
setHeaderHeight();
}
}
function unfloat(){ //put the header back into the table
if(headerFloated){
headerFloated = false;
if(useAbsolutePositioning){ //#53, #56
$table.width(originalTableWidth);
}
$newHeader.detach();
$table.prepend($header);
$table.css(layoutAuto);
$floatTable.css(layoutAuto);
$table.css('minWidth', originalTableMinWidth); //this looks weird, but it's not a bug. Think about it!!
$table.css('minWidth', tableWidth($table, $fthCells)); //#121
}
}
var isHeaderFloatingLogical = false; //for the purpose of this event, the header is/isnt floating, even though the element
//might be in some other state. this is what the header looks like to the user
function triggerFloatEvent(isFloating){
if(isHeaderFloatingLogical != isFloating){
isHeaderFloatingLogical = isFloating;
$table.triggerHandler("floatThead", [isFloating, $floatContainer])
}
}
function changePositioning(isAbsolute){
if(useAbsolutePositioning != isAbsolute){
useAbsolutePositioning = isAbsolute;
$floatContainer.css({
position: useAbsolutePositioning ? 'absolute' : 'fixed'
});
}
}
function getSizingRow($table, $cols, $fthCells, ieVersion){
if(createElements){
return $fthCells;
} else if(ieVersion) {
return opts.getSizingRow($table, $cols, $fthCells);
} else {
return $cols;
}
}
/**
* returns a function that updates the floating header's cell widths.
* @return {Function}
*/
function reflow(){
var i;
var numCols = columnNum(); //if the tables columns changed dynamically since last time (datatables), rebuild the sizer rows and get a new count
return function(){
//Cache the current scrollLeft value so that it can be reset post reflow
var scrollLeft = $floatContainer.scrollLeft();
$tableCells = $tableColGroup.find('col');
var $rowCells = getSizingRow($table, $tableCells, $fthCells, ieVersion);
if($rowCells.length == numCols && numCols > 0){
if(!existingColGroup){
for(i=0; i < numCols; i++){
$tableCells.eq(i).css('width', '');
}
}
unfloat();
var widths = [];
for(i=0; i < numCols; i++){
widths[i] = getOffsetWidth($rowCells.get(i));
}
for(i=0; i < numCols; i++){
$headerCells.eq(i).width(widths[i]);
$tableCells.eq(i).width(widths[i]);
}
refloat();
} else {
$floatTable.append($header);
$table.css(layoutAuto);
$floatTable.css(layoutAuto);
setHeaderHeight();
}
//Set back the current scrollLeft value on floatContainer
$floatContainer.scrollLeft(scrollLeft);
$table.triggerHandler("reflowed", [$floatContainer]);
};
}
function floatContainerBorderWidth(side){
var border = $scrollContainer.css("border-"+side+"-width");
var w = 0;
if (border && ~border.indexOf('px')) {
w = parseInt(border, 10);
}
return w;
}
function isResponsiveContainerActive(){
return $responsiveContainer.css("overflow-x") == 'auto';
}
/**
* first performs initial calculations that we expect to not change when the table, window, or scrolling container are scrolled.
* returns a function that calculates the floating container's top and left coords. takes into account if we are using page scrolling or inner scrolling
* @return {Function}
*/
function calculateFloatContainerPosFn(){
var scrollingContainerTop = $scrollContainer.scrollTop();
//this floatEnd calc was moved out of the returned function because we assume the table height doesn't change (otherwise we must reinit by calling calculateFloatContainerPosFn)
var floatEnd;
var tableContainerGap = 0;
var captionHeight = haveCaption ? $caption.outerHeight(true) : 0;
var captionScrollOffset = captionAlignTop ? captionHeight : -captionHeight;
var floatContainerHeight = $floatContainer.height();
var tableOffset = $table.offset();
var tableLeftGap = 0; //can be caused by border on container (only in locked mode)
var tableTopGap = 0;
if(locked){
var containerOffset = $scrollContainer.offset();
tableContainerGap = tableOffset.top - containerOffset.top + scrollingContainerTop;
if(haveCaption && captionAlignTop){
tableContainerGap += captionHeight;
}
tableLeftGap = floatContainerBorderWidth('left');
tableTopGap = floatContainerBorderWidth('top');
tableContainerGap -= tableTopGap;
} else {
floatEnd = tableOffset.top - scrollingTop - floatContainerHeight + scrollingBottom + scrollbarOffset.horizontal;
}
var windowTop = $window.scrollTop();
var windowLeft = $window.scrollLeft();
var scrollContainerLeft = (isResponsiveContainerActive() ? $responsiveContainer : $scrollContainer).scrollLeft();
return function(eventType){
responsive = isResponsiveContainerActive();
var isTableHidden = $table[0].offsetWidth <= 0 && $table[0].offsetHeight <= 0;
if(!isTableHidden && floatTableHidden) {
floatTableHidden = false;
setTimeout(function(){
$table.triggerHandler("reflow");
}, 1);
return null;
}
if(isTableHidden){ //it's hidden
floatTableHidden = true;
if(!useAbsolutePositioning){
return null;
}
}
if(eventType == 'windowScroll'){
windowTop = $window.scrollTop();
windowLeft = $window.scrollLeft();
} else if(eventType == 'containerScroll'){
if($responsiveContainer.length){
if(!responsive){
return; //we dont care about the event if we arent responsive right now
}
scrollContainerLeft = $responsiveContainer.scrollLeft();
} else {
scrollingContainerTop = $scrollContainer.scrollTop();
scrollContainerLeft = $scrollContainer.scrollLeft();
}
} else if(eventType != 'init') {
windowTop = $window.scrollTop();
windowLeft = $window.scrollLeft();
scrollingContainerTop = $scrollContainer.scrollTop();
scrollContainerLeft = (responsive ? $responsiveContainer : $scrollContainer).scrollLeft();
}
if(isWebkit && (windowTop < 0 || windowLeft < 0)){ //chrome overscroll effect at the top of the page - breaks fixed positioned floated headers
return;
}
if(absoluteToFixedOnScroll){
if(eventType == 'windowScrollDone'){
changePositioning(true); //change to absolute
} else {
changePositioning(false); //change to fixed
}
} else if(eventType == 'windowScrollDone'){
return null; //event is fired when they stop scrolling. ignore it if not 'absoluteToFixedOnScroll'
}
tableOffset = $table.offset();
if(haveCaption && captionAlignTop){
tableOffset.top += captionHeight;
}
var top, left;
var tableHeight = $table.outerHeight();
if(locked && useAbsolutePositioning){ //inner scrolling, absolute positioning
if (tableContainerGap >= scrollingContainerTop) {
var gap = tableContainerGap - scrollingContainerTop + tableTopGap;
top = gap > 0 ? gap : 0;
triggerFloatEvent(false);
} else {
top = wrappedContainer ? tableTopGap : scrollingContainerTop;
//headers stop at the top of the viewport
triggerFloatEvent(true);
}
left = tableLeftGap;
} else if(!locked && useAbsolutePositioning) { //window scrolling, absolute positioning
if(windowTop > floatEnd + tableHeight + captionScrollOffset){
top = tableHeight - floatContainerHeight + captionScrollOffset; //scrolled past table
} else if (tableOffset.top >= windowTop + scrollingTop) {
top = 0; //scrolling to table
unfloat();
triggerFloatEvent(false);
} else {
top = scrollingTop + windowTop - tableOffset.top + tableContainerGap + (captionAlignTop ? captionHeight : 0);
refloat(); //scrolling within table. header floated
triggerFloatEvent(true);
}
left = scrollContainerLeft;
} else if(locked && !useAbsolutePositioning){ //inner scrolling, fixed positioning
if (tableContainerGap > scrollingContainerTop || scrollingContainerTop - tableContainerGap > tableHeight) {
top = tableOffset.top - windowTop;
unfloat();
triggerFloatEvent(false);
} else {
top = tableOffset.top + scrollingContainerTop - windowTop - tableContainerGap;
refloat();
triggerFloatEvent(true);
//headers stop at the top of the viewport
}
left = tableOffset.left + scrollContainerLeft - windowLeft;
} else if(!locked && !useAbsolutePositioning) { //window scrolling, fixed positioning
if(windowTop > floatEnd + tableHeight + captionScrollOffset){
top = tableHeight + scrollingTop - windowTop + floatEnd + captionScrollOffset;
//scrolled past the bottom of the table
} else if (tableOffset.top > windowTop + scrollingTop) {
top = tableOffset.top - windowTop;
refloat();
triggerFloatEvent(false); //this is a weird case, the header never gets unfloated and i have no no way to know
//scrolled past the top of the table
} else {
//scrolling within the table
top = scrollingTop;
triggerFloatEvent(true);
}
left = tableOffset.left + scrollContainerLeft - windowLeft;
}
return {top: top, left: left};
};
}
/**
* returns a function that caches old floating container position and only updates css when the position changes
* @return {Function}
*/
function repositionFloatContainerFn(){
var oldTop = null;
var oldLeft = null;
var oldScrollLeft = null;
return function(pos, setWidth, setHeight){
if(pos != null && (oldTop != pos.top || oldLeft != pos.left)){
$floatContainer.css({
top: pos.top,
left: pos.left
});
oldTop = pos.top;
oldLeft = pos.left;
}
if(setWidth){
setFloatWidth();
}
if(setHeight){
setHeaderHeight();
}
var scrollLeft = (responsive ? $responsiveContainer : $scrollContainer).scrollLeft();
if(!useAbsolutePositioning || oldScrollLeft != scrollLeft){
$floatContainer.scrollLeft(scrollLeft);
oldScrollLeft = scrollLeft;
}
}
}
/**
* checks if THIS table has scrollbars, and finds their widths
*/
function calculateScrollBarSize(){ //this should happen after the floating table has been positioned
if($scrollContainer.length){
if(opts.support && opts.support.perfectScrollbar && $scrollContainer.data().perfectScrollbar){
scrollbarOffset = {horizontal:0, vertical:0};
} else {
if($scrollContainer.css('overflow-x') == 'scroll'){
scrollbarOffset.horizontal = scWidth;
} else {
var sw = $scrollContainer.width(), tw = tableWidth($table, $fthCells);
var offsetv = sh < th ? scWidth : 0;
scrollbarOffset.horizontal = sw - offsetv < tw ? scWidth : 0;
}
if($scrollContainer.css('overflow-y') == 'scroll'){
scrollbarOffset.vertical = scWidth;
} else {
var sh = $scrollContainer.height(), th = $table.height();
var offseth = sw < tw ? scWidth : 0;
scrollbarOffset.vertical = sh - offseth < th ? scWidth : 0;
}
}
}
}
//finish up. create all calculation functions and bind them to events
calculateScrollBarSize();
var flow;
var ensureReflow = function(){
flow = reflow();
flow();
};
ensureReflow();
var calculateFloatContainerPos = calculateFloatContainerPosFn();
var repositionFloatContainer = repositionFloatContainerFn();
repositionFloatContainer(calculateFloatContainerPos('init'), true); //this must come after reflow because reflow changes scrollLeft back to 0 when it rips out the thead
var windowScrollDoneEvent = util.debounce(function(){
repositionFloatContainer(calculateFloatContainerPos('windowScrollDone'), false);
}, 1);
var windowScrollEvent = function(){
repositionFloatContainer(calculateFloatContainerPos('windowScroll'), false);
if(absoluteToFixedOnScroll){
windowScrollDoneEvent();
}
};
var containerScrollEvent = function(){
repositionFloatContainer(calculateFloatContainerPos('containerScroll'), false);
};
var windowResizeEvent = function(){
if($table.is(":hidden")){
return;
}
updateScrollingOffsets();
calculateScrollBarSize();
ensureReflow();
calculateFloatContainerPos = calculateFloatContainerPosFn();
repositionFloatContainer = repositionFloatContainerFn();
repositionFloatContainer(calculateFloatContainerPos('resize'), true, true);
};
var reflowEvent = util.debounce(function(){
if($table.is(":hidden")){
return;
}
calculateScrollBarSize();
updateScrollingOffsets();
ensureReflow();
calculateFloatContainerPos = calculateFloatContainerPosFn();
repositionFloatContainer(calculateFloatContainerPos('reflow'), true);
}, 1);
/////// printing stuff
var beforePrint = function(){
$table.floatThead('destroy', [true]);
};
var afterPrint = function(){
$table.floatThead(opts);
};
var printEvent = function(mql){
//make printing the table work properly on IE10+
if(mql.matches) {
beforePrint();
} else {
afterPrint();
}
};
if(window.matchMedia){
window.matchMedia("print").addListener(printEvent);
} else {
$window.on('beforeprint', beforePrint);
$window.on('afterprint', afterPrint);
}
////// end printing stuff
if(locked){ //internal scrolling
if(useAbsolutePositioning){
$scrollContainer.on(eventName('scroll'), containerScrollEvent);
} else {
$scrollContainer.on(eventName('scroll'), containerScrollEvent);
$window.on(eventName('scroll'), windowScrollEvent);
}
} else { //window scrolling
$responsiveContainer.on(eventName('scroll'), containerScrollEvent);
$window.on(eventName('scroll'), windowScrollEvent);
}
$window.on(eventName('load'), reflowEvent); //for tables with images
windowResize(eventName('resize'), windowResizeEvent);
$table.on('reflow', reflowEvent);
if(opts.support && opts.support.datatables && isDatatable($table)){
$table
.on('filter', reflowEvent)
.on('sort', reflowEvent)
.on('page', reflowEvent);
}
if(opts.support && opts.support.bootstrap) {
$window.on(eventName('shown.bs.tab'), reflowEvent); // people cant seem to figure out how to use this plugin with bs3 tabs... so this :P
}
if(opts.support && opts.support.jqueryUI) {
$window.on(eventName('tabsactivate'), reflowEvent); // same thing for jqueryui
}
if (canObserveMutations) {
var mutationElement = null;
if(util.isFunction(opts.autoReflow)){
mutationElement = opts.autoReflow($table, $scrollContainer)
}
if(!mutationElement) {
mutationElement = $scrollContainer.length ? $scrollContainer[0] : $table[0]
}
mObs = new MutationObserver(function(e){
var wasTableRelated = function(nodes){
return nodes && nodes[0] && (nodes[0].nodeName == "THEAD" || nodes[0].nodeName == "TD"|| nodes[0].nodeName == "TH");
};
for(var i=0; i < e.length; i++){
if(!(wasTableRelated(e[i].addedNodes) || wasTableRelated(e[i].removedNodes))){
reflowEvent();
break;
}
}
});
mObs.observe(mutationElement, {
childList: true,
subtree: true
});
}
//attach some useful functions to the table.
$table.data('floatThead-attached', {
destroy: function(e, isPrintEvent){
var ns = '.fth-'+floatTheadId;
unfloat();
$table.css(layoutAuto);
$tableColGroup.remove();
createElements && $fthGrp.remove();
if($newHeader.parent().length){ //only if it's in the DOM
$newHeader.replaceWith($header);
}
triggerFloatEvent(false);
if(canObserveMutations){
mObs.disconnect();
mObs = null;
}
$table.off('reflow reflowed');
$scrollContainer.off(ns);
$responsiveContainer.off(ns);
if (wrappedContainer) {
if ($scrollContainer.length) {
$scrollContainer.unwrap();
}
else {
$table.unwrap();
}
}
if(locked){
$scrollContainer.data('floatThead-containerWrap', false);
} else {
$table.data('floatThead-containerWrap', false);
}
$table.css('minWidth', originalTableMinWidth);
$floatContainer.remove();
$table.data('floatThead-attached', false);
$window.off(ns);
if(!isPrintEvent){
//if we are in the middle of printing, we want this event to re-create the plugin
window.matchMedia && window.matchMedia("print").removeListener(printEvent);
beforePrint = afterPrint = function(){};
}
},
reflow: function(){
reflowEvent();
},
setHeaderHeight: function(){
setHeaderHeight();
},
getFloatContainer: function(){
return $floatContainer;
},
getRowGroups: function(){
if(headerFloated){
return $floatContainer.find('>table>thead').add($table.children("tbody,tfoot"));
} else {
return $table.children("thead,tbody,tfoot");
}
}
});
});
return this;
};
})(jQuery);
/* jQuery.floatThead.utils - http://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2016 Misha Koryak
* License: MIT
*
* This file is required if you do not use underscore in your project and you want to use floatThead.
* It contains functions from underscore that the plugin uses.
*
* YOU DON'T NEED TO INCLUDE THIS IF YOU ALREADY INCLUDE UNDERSCORE!
*
*/
(function($){
$.floatThead = $.floatThead || {};
$.floatThead._ = window._ || (function(){
var that = {};
var hasOwnProperty = Object.prototype.hasOwnProperty, isThings = ['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'];
that.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
that.keys = function(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object');
var keys = [];
for (var key in obj) if (that.has(obj, key)) keys.push(key);
return keys;
};
var idCounter = 0;
that.uniqueId = function(prefix) {
var id = ++idCounter + '';
return prefix ? prefix + id : id;
};
$.each(isThings, function(){
var name = this;
that['is' + name] = function(obj) {
return Object.prototype.toString.call(obj) == '[object ' + name + ']';
};
});
that.debounce = function(func, wait, immediate) {
var timeout, args, context, timestamp, result;
return function() {
context = this;
args = arguments;
timestamp = new Date();
var later = function() {
var last = (new Date()) - timestamp;
if (last < wait) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) result = func.apply(context, args);
}
};
var callNow = immediate && !timeout;
if (!timeout) {
timeout = setTimeout(later, wait);
}
if (callNow) result = func.apply(context, args);
return result;
};
};
return that;
})();
})(jQuery);