| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066 |
- // @preserve jQuery.floatThead 1.4.0 - 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.
- };
- 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="<!--[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 style="width:0px"><table style="max-width:100%"><tr><th><div style="min-width:100px;">X</div></th></tr></table></div>');
- $("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);
- if(!window.matchMedia) {
- //these will be used by the plugin to go into print mode (destroy and remake itself)
- var _beforePrint = window.onbeforeprint;
- var _afterPrint = window.onafterprint;
- window.onbeforeprint = function () {
- _beforePrint && _beforePrint();
- $window.triggerHandler("beforeprint");
- };
- window.onafterprint = function () {
- _afterPrint && _afterPrint();
- $window.triggerHandler("afterprint");
- };
- }
- /**
- * @param debounceMs
- * @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.bind(eventName, debouncedCb);
- } else {
- $window.bind(eventName, util.debounce(cb, 1));
- }
- }
- function getTrueOffsetParent($elem) {
- var elem = $elem[0];
- var parent = elem.offsetParent;
- if (!parent) {
- parent = elem.parentElement;
- do {
- var pos = window
- .getComputedStyle(parent)
- .getPropertyValue('position');
- if (pos != 'static') break;
- if (parent.offsetParent) {
- parent = parent.offsetParent;
- 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 $div = $( //borrowed from anti-scroll
- '<div style="width:50px;height:50px;overflow-y:scroll;'
- + 'position:absolute;top:-200px;left:-200px;"><div style="height:100px;width:100%">'
- + '</div>'
- );
- $('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 = getTrueOffsetParent;
- }
- 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 = $('<fthfoot style="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 style="display:table-row;border-spacing:0;height:0;border-collapse:collapse"/>'); //created unstyled elements (used for sizing the table because chrome can't read <col> width)
- var $floatContainer = $('<div style="overflow: hidden;" aria-hidden="true"></div>');
- 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"/>');
- 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(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 class='"+opts.floatWrapperClass+"' style='position: relative; clear:both;'></div>").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('<th scope="col" class="floatThead-col">' + content + '</th>');
- } else {
- cells.push('<th class="floatThead-col"/>');
- }
- cols.push('<col/>');
- psuedo.push("<fthtd style='display:table-cell;height:0;width:auto;'/>");
- }
- 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(){
- $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();
- }
- $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($scrollContainer.data().perfectScrollbar){
- scrollbarOffset = {horizontal:0, vertical:0};
- } else {
- var sw = $scrollContainer.width(), sh = $scrollContainer.height(), th = $table.height(), tw = tableWidth($table, $fthCells);
- var offseth = sw < tw ? scWidth : 0;
- var offsetv = sh < th ? scWidth : 0;
- scrollbarOffset.horizontal = sw - offsetv < 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.bind('beforeprint', beforePrint);
- $window.bind('afterprint', afterPrint);
- }
- ////// end printing stuff
- if(locked){ //internal scrolling
- if(useAbsolutePositioning){
- $scrollContainer.bind(eventName('scroll'), containerScrollEvent);
- } else {
- $scrollContainer.bind(eventName('scroll'), containerScrollEvent);
- $window.bind(eventName('scroll'), windowScrollEvent);
- }
- } else { //window scrolling
- $responsiveContainer.bind(eventName('scroll'), containerScrollEvent);
- $window.bind(eventName('scroll'), windowScrollEvent);
- }
- $window.bind(eventName('load'), reflowEvent); //for tables with images
- windowResize(eventName('resize'), windowResizeEvent);
- $table.bind('reflow', reflowEvent);
- if(isDatatable($table)){
- $table
- .bind('filter', reflowEvent)
- .bind('sort', reflowEvent)
- .bind('page', reflowEvent);
- }
- $window.bind(eventName('shown.bs.tab'), reflowEvent); // people cant seem to figure out how to use this plugin with bs3 tabs... so this :P
- $window.bind(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.unbind('reflow reflowed');
- $scrollContainer.unbind(ns);
- $responsiveContainer.unbind(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.unbind(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 - 2014 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);
|