MediaWiki:Common.js
From Guardians of Ember Wiki
Note: After saving, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Go to Menu → Settings (Opera → Preferences on a Mac) and then to Privacy & security → Clear browsing data → Cached images and files.
/* Any JavaScript here will be loaded for all users on every page load. */
var myTable = jQuery("#myTable");
var thead = myTable.find("thead");
var thRows = myTable.find("tr:has(th)");
if (thead.length===0){
thead = jQuery("<thead></thead>").appendTo(myTable);
}
var copy = thRows.clone(true).appendTo("thead");
thRows.remove();
/** @preserve jQuery.floatThead 2.1.2 - https://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2018 Misha Koryak **/
// @license MIT
/* @author Misha Koryak
* @projectDescription position:fixed on steroids. Lock a table header in place while scrolling.
*
* Dependencies:
* jquery 1.9.0 + [required] OR jquery 1.7.0 + jquery UI core
*
* https://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.
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
},
floatContainerCss: {"overflow-x": "hidden"} // undocumented - css applied to the floatContainer
};
var util = (function underscoreShim(){
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 = Object.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;
})();
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="<!--[if gt IE "+ a +"]><i><![endif]-->",c[0];);return 4<a?a:document.documentMode}();
var isFF = /Gecko\//.test(navigator.userAgent);
var isWebkit = /WebKit\//.test(navigator.userAgent);
if(!(ieVersion || isFF || isWebkit)){
ieVersion = 11; //yey a hack!
}
//safari 7 (and perhaps others) reports table width to be parent container's width if max-width is set on table. see: https://github.com/mkoryak/floatThead/issues/108
var isTableWidthBug = function(){
if(isWebkit) {
var $test = $('<div>').css('width', 0).append(
$('<table>').css('max-width', '100%').append(
$('<tr>').append(
$('<th>').append(
$('<div>').css('min-width', 100).text('X')
)
)
)
);
$("body").append($test);
var ret = ($test.find("table").width() == 0);
$test.remove();
return ret;
}
return false;
};
var createElements = !isFF && !ieVersion; //FF can read width from <col> elements, but webkit cannot
var $window = $(window);
var buggyMatchMedia = isFF && window.matchMedia; // TODO remove when fixed: https://bugzilla.mozilla.org/show_bug.cgi?id=774398
if(!window.matchMedia || buggyMatchMedia) {
var _beforePrint = window.onbeforeprint;
var _afterPrint = window.onafterprint;
window.onbeforeprint = function () {
_beforePrint && _beforePrint();
$window.triggerHandler("fth-beforeprint");
};
window.onafterprint = function () {
_afterPrint && _afterPrint();
$window.triggerHandler("fth-afterprint");
};
}
/**
* @param eventName
* @param cb
*/
function windowResize(eventName, cb){
if(ieVersion == 8){ //ie8 is crap: https://github.com/mkoryak/floatThead/issues/65
var winWidth = $window.width();
var debouncedCb = util.debounce(function(){
var winWidthNew = $window.width();
if(winWidth != winWidthNew){
winWidth = winWidthNew;
cb();
}
}, 1);
$window.on(eventName, debouncedCb);
} else {
$window.on(eventName, util.debounce(cb, 1));
}
}
function getClosestScrollContainer($elem) {
var elem = $elem[0];
var parent = elem.parentElement;
do {
var pos = window
.getComputedStyle(parent)
.getPropertyValue('overflow');
if (pos != 'visible') break;
} while (parent = parent.parentElement);
if(parent == document.body){
return $([]);
}
return $(parent);
}
function debug(str){
window && window.console && window.console.error && window.console.error("jQuery.floatThead: " + str);
}
//returns fractional pixel widths
function getOffsetWidth(el) {
var rect = el.getBoundingClientRect();
return rect.width || rect.right - rect.left;
}
/**
* try to calculate the scrollbar width for your browser/os
* @return {Number}
*/
function scrollbarWidth() {
var d = document.createElement("scrolltester");
d.style.cssText = 'width:100px;height:100px;overflow:scroll!important;position:absolute;top:-9999px;display:block';
document.body.appendChild(d);
var result = d.offsetWidth - d.clientWidth;
document.body.removeChild(d);
return result;
}
/**
* Check if a given table has been datatableized (https://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 += getOffsetWidth($fthCells.get(i));
}
return w;
} else {
return $table[fn]();
}
}
$.fn.floatThead = function(map){
map = map || {};
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 args = Array.prototype.slice.call(arguments, 1);
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].apply(this, args);
if(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){
if(opts.debug) {
if($header.length == 0){
debug('The thead element is missing.');
} else{
debug('The tbody element is missing.');
}
}
$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};
if(util.isFunction(scrollbarWidth)) {
scrollbarWidth = 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 (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 = $('<fthfoot>').css({
'display': 'table-footer-group',
'border-spacing': 0,
'height': 0,
'border-collapse': 'collapse',
'visibility': 'hidden'
});
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 = $("<table/>");
var $floatColGroup = $("<colgroup/>");
var $tableColGroup = $table.children('colgroup:first');
var existingColGroup = true;
if($tableColGroup.length == 0){
$tableColGroup = $("<colgroup/>");
existingColGroup = false;
}
var $fthRow = $('<fthtr>').css({ //created unstyled elements (used for sizing the table because chrome can't read <col> width)
'display': 'table-row',
'border-spacing': 0,
'height': 0,
'border-collapse': 'collapse'
});
var $floatContainer = $('<div>').css(opts.floatContainerCss).attr('aria-hidden', 'true');
var floatTableHidden = false; //this happens when the table is hidden and we do magic when making it visible
var $newHeader = $("<thead/>");
var $sizerRow = $('<tr class="size-row" aria-hidden="true"/>');
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(
$('<div>').addClass(opts.floatWrapperClass).css({
'position': 'relative',
'clear': 'both'
})
).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,
willChange: 'transform'
});
$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.length ? getOffsetWidth($container[0]) : 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.css('width', tw+'px');
}
}
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++){
content = $headerColumns.eq(x).text();
cells.push('<th class="floatThead-col" aria-label="'+content+'"/>');
cols.push('<col/>');
psuedo.push(
$('<fthtd>').css({
'display': 'table-cell',
'height': 0,
'width': 'auto'
})
);
}
cols = cols.join('');
cells = cells.join('');
if(createElements){
$fthRow.empty();
$fthRow.append(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 getScrollContainerLeft = function(){
return (isResponsiveContainerActive() ? $responsiveContainer : $scrollContainer).scrollLeft() || 0;
};
var scrollContainerLeft = getScrollContainerLeft();
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 = getScrollContainerLeft();
}
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 if(scrollingContainerTop - tableContainerGap > tableHeight - floatContainerHeight){
// scrolled past table but there is space in the container under it..
top = tableHeight - floatContainerHeight - scrollingContainerTop - tableContainerGap;
} 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 + scrollingBottom; //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: Math.round(top), left: Math.round(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)){
if(ieVersion === 8){
$floatContainer.css({
top: pos.top,
left: pos.left
});
} else {
var transform = 'translateX(' + pos.left + 'px) translateY(' + pos.top + 'px)';
var cssObj = {
'-webkit-transform' : transform,
'-moz-transform' : transform,
'-ms-transform' : transform,
'-o-transform' : transform,
'transform' : transform,
'top': 0,
};
cssObj[/rtl/i.test(document.documentElement.dir || '') ? 'right': 'left'] = 0;
$floatContainer.css(cssObj);
}
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 = scrollbarWidth;
} else {
var sw = $scrollContainer.width(), tw = tableWidth($table, $fthCells);
var offsetv = sh < th ? scrollbarWidth : 0;
scrollbarOffset.horizontal = sw - offsetv < tw ? scrollbarWidth : 0;
}
if($scrollContainer.css('overflow-y') == 'scroll'){
scrollbarOffset.vertical = scrollbarWidth;
} else {
var sh = $scrollContainer.height(), th = $table.height();
var offseth = sw < tw ? scrollbarWidth : 0;
scrollbarOffset.vertical = sh - offseth < th ? scrollbarWidth : 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, true);
}, 1);
/////// printing stuff
var beforePrint = function(){
unfloat();
};
var afterPrint = function(){
refloat();
};
var printEvent = function(mql){
//make printing the table work properly on IE10+
if(mql.matches) {
beforePrint();
} else {
afterPrint();
}
};
var matchMediaPrint = null;
if(window.matchMedia && window.matchMedia('print').addListener && !buggyMatchMedia){
matchMediaPrint = window.matchMedia("print");
matchMediaPrint.addListener(printEvent);
} else {
$window.on('fth-beforeprint', beforePrint);
$window.on('fth-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(){
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);
$window.off('fth-beforeprint fth-afterprint'); // Not bound with id, so cant use ns.
if (matchMediaPrint) {
matchMediaPrint.removeListener(printEvent);
}
beforePrint = afterPrint = function(){};
return function reinit(){
return $table.floatThead(opts);
}
},
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;
};
})((function(){
var $ = window.jQuery;
if(typeof module !== 'undefined' && module.exports && !$) {
// only use cjs if they dont have a jquery for me to use, and we have commonjs
$ = require('jquery');
}
return $;
})());