Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

html thead is always visible at the top of the screen not when scrolling down #232

Closed
ghost opened this issue Aug 12, 2015 · 44 comments
Closed

Comments

@ghost
Copy link

ghost commented Aug 12, 2015

I am working in a rails app. I have a table on my screen and the table header always stays at the top doesn't move when i am scrolling down. I downloaded JQuery.floatThead plugin and required it in application.js script by doing //= require jquery_floatThead and then in the application.js file i called floatThead function but its not working. I am also using bootstrap. Here is my application.js file:

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
//
// this brings in jquery, bootstrap, and all the defaults

//
// Note that usually, Rails uses require_tree . here. That requires
// everything in the current directory tree. I don't think we reallg
// want to do that for this app, though, because there will be a bid
// variety of things deployed.
//
// However, you probably DO want to require almost everything here, so
// it gets compiled into the one giant cached JS file. That is better
// than separate files for every page, usually.
//
//

PNotify.prototype.options.styling = "fontawesome";

// Set the auth token on all ajax requests, if we have one
$(document).ready(function() {
if (window.bpub.authToken) {
var token = encodeURIComponent(window.bpub.authToken);
$.ajaxSetup({
headers: {'Authorization' : 'Token token="' + token + '"'},
});
}
});
/*
Global error handler for all ajax problems. If you don't want this,
because you want to handle it yourself, then add the
global: false option as follows:

$.ajax({
url: url,
data: data,
global: false,
...
})

*/
$( document ).ajaxError(function( event, jqxhr, settings, thrownError ) {
var message = jqxhr.responseText;
if (jqxhr.responseJSON.error && jqxhr.responseJSON.error.message) {
message = jqxhr.responseJSON.error.message;
}

new PNotify({
    type: 'error',
    title: 'Error',
    text: 'Error: ' + message,
});

});

 var $table = $(".queue-display");

 $table.floatThead({

    //absuoltePositioning is better for highly dynamic site
    useAbsolutePositioning: false


 });

$(document).ready(function() {

// used to make a simple popup -- put data-popup-name="whatever"
// in your <a>, and clicking on the a will open a window with that
// name

$(document).on("click", 'a[data-popup-name]', function(event) {
    event.preventDefault();

    var name = $(this).data('popup-name');
    var win = window.open($(this).attr("href"), name, "width=600,height=600,scrollbars=yes");
    win.focus();
});

// helper for adding functionality that doesn't really exist yet
$(document).on("click", ".not-rly", function() {
    alert("Not implemented yet");
});

// TODO: better way to handle these tool tips?
$('[data-toggle="tooltip"]').tooltip();
$(document).bind("ajaxComplete", function(){
    $('[data-toggle="tooltip"]').tooltip();
});

});

// clean up all our yamm windows -- if they are long then they won't
// fit on the page properly.

$(document).ready(function() {
var resize_yamm = function() {
// 150 is somewhat arbitrary, want to fill up most of screen
// but still look like a dropdown, and don't overlap toolbars

    var height = $(window).height() - 150;

    $('.yamm-content').css('max-height', height + 'px');
}

resize_yamm();

$(window).resize(resize_yamm);

});

// this will catch any error and email it to support
$(document).ready(function() {
window.onerror = function(errorMsg, url, lineNumber) {
var postUrl = window.bpub.queue_web_services_base + "/errors/send_to_support";

    var message = "Error in bpub-web: " + errorMsg;

    message = message + "\n\n" + url + "\nline: " + lineNumber;

// disabling for now -- this is spamming
// $.ajax(
// { url: postUrl,
// data: { "message" : message },
// method: 'post',
// }
// );
};
});

$(document).ready(function() {

// some crazy hack to stop IE from selecting text when you do a
// 'shift-click'
$('.unselectable').mousedown(function(e) {
    if (e.ctrlKey || e.shiftKey) {
        e.preventDefault();

        this.onselectstart = function () { return false; };
        var self = this;
        window.setTimeout(function() { self.onselectstart = null;}, 0);
    }
});

});

$(document).ready(function() {
tw_attach_data_link_clicks(window.bpub.queue_web_services_base,
window.bpub.current_user);
});

// This handles automatically saving a field. Stick an attribute on
// an input, and when it changes this will automatically store it to
// localstorage. When the page is loaded, then we update the field
// with the data from local storage
$(document).ready(function() {

// first restore them all
$('[data-auto-save]').each(function(i, item) {
    var saveName = $(item).data('auto-save');
    var content =localStorage.getItem(saveName);

    if (content) {
        $(item).val(content);
    }
});

$('[data-auto-save]').on('change', function() {
    var saveName = $(this).data('auto-save');
    localStorage.setItem(saveName, $(this).val());
});

});

Here is my jquery.float plugin:

// @preserve jQuery.floatThead 1.2.12 - http://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2015 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 = {
    cellTag: null, // DEPRECATED - use headerCellSelector instead
    headerCellSelector: 'tr:visible:first>
    :visible', //thead cells are this.
    zIndex: 1001, //zindex of the floating thead (actually a container div)
    debounceResizeMs: 10, //Deprecated!
    useAbsolutePositioning: true, //if set to NULL - defaults: has scrollContainer=true, doesn't have scrollContainer=false
    scrollingTop: 0, //String or function($table) - offset from top of window where the header should not pass above
    scrollingBottom: 0, //String or function($table) - offset from the bottom of the table where the header should stop scrolling
    scrollContainer: function($table){
    return $([]); //if the table has horizontal scroll bars then this is the container that has overflow:auto and causes those scroll bars
    },
    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="",c[0];);return 4<a?a:document.documentMode}();
    var isFF = /Gecko//.test(navigator.userAgent);
    var isWebkit = /WebKit//.test(navigator.userAgent);

    //safari 7 (and perhaps others) reports table width to be parent container's width if max-width is set on table. see: Safari Bug - max-width:100% and tiny viewport #108
    var isTableWidthBug = function(){
    if(isWebkit) {
    var $test = $('

    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 elements, but webkit cannot

    var $window = $(window);

    /**

  • @param debounceMs

  • @param cb
    */
    function windowResize(debounceMs, eventName, cb){
    if(ieVersion == 8){ //ie8 is crap: ie8 feedback loop in resize() #65
    var winWidth = $window.width();
    var debouncedCb = util.debounce(function(){
    var winWidthNew = $window.width();
    if(winWidth != winWidthNew){
    winWidth = winWidthNew;
    cb();
    }
    }, debounceMs);
    $window.on(eventName, debouncedCb);
    } else {
    $window.on(eventName, util.debounce(cb, debounceMs));
    }
    }

    function debug(str){
    window && window.console && window.console.log && window.console.log("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
    '

    '
    + '
    '
    );
    $('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: Safari Bug - max-width:100% and tiny viewport #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 $tablefn;
    }
    }
    $.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 = objcommand;
    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.one('reflow', function(){
    $table.floatThead(opts);
    });
    return;
    }
    if($table.data('floatThead-lazy')){
    $table.unbind("reflow");
    }
    $table.data('floatThead-lazy', false);

    var headerFloated = false;
    var scrollingTop, scrollingBottom;
    var scrollbarOffset = {vertical: 0, horizontal: 0};
    var scWidth = scrollbarWidth();
    var lastColumnCount = 0; //used by columnNum()
    var $scrollContainer = opts.scrollContainer($table) || $([]); //guard against returned nulls
    var locked = $scrollContainer.length > 0;
    
    var useAbsolutePositioning = opts.useAbsolutePositioning;
    if(useAbsolutePositioning == null){ //defaults: locked=true, !locked=false
      useAbsolutePositioning = locked;
    }
    if(!useAbsolutePositioning){
      headerFloated = true; //#127
    }
    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;"/>');
    
    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" class="floatThead-floatContainer"></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");
        if(!relativeToScrollContainer || alwaysWrap){
          var css = {"paddingLeft": $container.css('paddingLeft'), "paddingRight": $container.css('paddingRight')};
          $floatContainer.css(css);
          $container = $container.wrap("<div class='"+opts.floatWrapperClass+"' style='position: relative; clear:both;'></div>").parent();
          wrappedContainer = true;
        }
        return $container;
      };
      if(locked){
        $wrapper = makeRelative($scrollContainer, true);
        $wrapper.append($floatContainer);
      } else {
        $wrapper = makeRelative($table);
        $table.after($floatContainer);
      }
    } else {
      $table.after($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 width = $scrollContainer.width() || tw;
      var floatContainerWidth = $scrollContainer.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.scrollingTop) ? opts.scrollingTop($table) : opts.scrollingTop) || 0;
      scrollingBottom = (util.isFunction(opts.scrollingBottom) ? opts.scrollingBottom($table) : opts.scrollingBottom) || 0;
    }
    
    /**
     * get the number of columns and also rebuild resizer rows if the count is different than the last count
     */
    function columnNum(){
      var count, $headerColumns;
      if(existingColGroup){
        count = $tableColGroup.find('col').length;
      } else {
        var selector;
        if(opts.cellTag == null && opts.headerCellSelector){ //TODO: once cellTag option is removed, remove this conditional
          selector = opts.headerCellSelector;
        } else {
          selector = 'tr:first>'+opts.cellTag;
        }
        if(util.isNumber(selector)){
          //it's actually a row count. (undocumented, might be removed!)
          return selector;
        }
        $headerColumns = $header.find(selector);
        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);
          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();
        }
      };
    }
    
    function floatContainerBorderWidth(side){
      var border = $scrollContainer.css("border-"+side+"-width");
      var w = 0;
      if (border && ~border.indexOf('px')) {
        w = parseInt(border, 10);
      }
      return w;
    }
    /**
     * 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 =  $scrollContainer.scrollLeft();
    
      return function(eventType){
        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'){
          scrollingContainerTop = $scrollContainer.scrollTop();
          scrollContainerLeft =  $scrollContainer.scrollLeft();
        } else if(eventType != 'init') {
          windowTop = $window.scrollTop();
          windowLeft = $window.scrollLeft();
          scrollingContainerTop = $scrollContainer.scrollTop();
          scrollContainerLeft =  $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 =  0;
        } 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 - 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 = $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(){
      updateScrollingOffsets();
      calculateScrollBarSize();
      ensureReflow();
      calculateFloatContainerPos = calculateFloatContainerPosFn();
      repositionFloatContainer = repositionFloatContainerFn();
      repositionFloatContainer(calculateFloatContainerPos('resize'), true, true);
    };
    var reflowEvent = util.debounce(function(){
      calculateScrollBarSize();
      updateScrollingOffsets();
      ensureReflow();
      calculateFloatContainerPos = calculateFloatContainerPosFn();
      repositionFloatContainer(calculateFloatContainerPos('reflow'), true);
    }, 1);
    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
      $window.on(eventName('scroll'), windowScrollEvent);
    }
    
    $window.on(eventName('load'), reflowEvent); //for tables with images
    
    windowResize(opts.debounceResizeMs, eventName('resize'), windowResizeEvent);
    $table.on('reflow', reflowEvent);
    if(isDatatable($table)){
      $table
        .on('filter', reflowEvent)
        .on('sort',   reflowEvent)
        .on('page',   reflowEvent);
    }
    
    
    if (canObserveMutations) {
      var mutationElement = $scrollContainer.length ? $scrollContainer[0] : $table[0];
      mObs = new MutationObserver(function(e){
        var wasThead = function(nodes){
          return nodes && nodes[0] && nodes[0].nodeName == "THEAD";
        };
        for(var i=0; i < e.length; i++){
          if(!(wasThead(e[i].addedNodes) || wasThead(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);
        }
        if(canObserveMutations){
          mObs.disconnect();
          mObs = null;
        }
        $table.off('reflow');
        $scrollContainer.off(ns);
        if (wrappedContainer) {
          if ($scrollContainer.length) {
            $scrollContainer.unwrap();
          }
          else {
            $table.unwrap();
          }
        }
        $table.css('minWidth', originalTableMinWidth);
        $floatContainer.remove();
        $table.data('floatThead-attached', false);
        $window.off(ns);
      },
      reflow: function(){
        reflowEvent();
      },
      setHeaderHeight: function(){
        setHeaderHeight();
      },
      getFloatContainer: function(){
        return $floatContainer;
      },
      getRowGroups: function(){
        if(headerFloated){
          return $floatContainer.children("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);

It worked for me JSfiddle. Here is the link for that: http://jsfiddle.net/833eLyod/18/

@mkoryak
Copy link
Owner

mkoryak commented Aug 12, 2015

seriously?
you want me to read all this shit?

@ghost
Copy link
Author

ghost commented Aug 12, 2015

well i didn't know how else to explain the problem. I don't think you have to read the plugin part.

@ghost
Copy link
Author

ghost commented Aug 12, 2015

Also i mean you can use this because it works here http://jsfiddle.net/833eLyod/18/

@mkoryak
Copy link
Owner

mkoryak commented Aug 12, 2015

i can help you fix a broke jsfiddle because I can run the code there. I cant run this code. Also, I dont know rails

@ghost
Copy link
Author

ghost commented Aug 12, 2015

so whats broken and what do i need to fix.

@mkoryak
Copy link
Owner

mkoryak commented Aug 12, 2015

there is nothing broken here: http://jsfiddle.net/833eLyod/18/

@ghost
Copy link
Author

ghost commented Aug 12, 2015

okay I found what the problem is there is yum bar at the top and that scrolls which is causing the table header not to scroll is there a way to have both of them scroll the yum bar and the table header

@ghost ghost closed this as completed Aug 12, 2015
@mkoryak
Copy link
Owner

mkoryak commented Aug 12, 2015

🎉

@ghost ghost reopened this Aug 12, 2015
@ghost
Copy link
Author

ghost commented Aug 12, 2015

hey i am having an issue again finally got it to work, but there is a little gap between my table header and the table but when i resize the window everything gets back to the position. Let me attach a screen shot for you. before re sizing the window:
image

after re sizing the window:

image

same thing happens when my table changes:

image

After re sizing the window

image

Can you please help with this.

@ghost
Copy link
Author

ghost commented Aug 12, 2015

here is what i have for the javascript and css: $(document).ready(function(){

 var $table = $(".queue-display");

 $table.floatThead({useAbsolutePositioning: false});

});

.yamm.navbar.navbar-inverse.navbar-fixed-top.navbar-at-top.queue-top-bar {

position:absolute;

}

table.floatThead-table {
border-top: none;
border-bottom: none;
background-color: gray;

}

@mkoryak
Copy link
Owner

mkoryak commented Aug 12, 2015

this issue makes absolutely no sense to me

@mkoryak mkoryak closed this as completed Aug 12, 2015
@ghost
Copy link
Author

ghost commented Aug 12, 2015

well here is the problem it works now but there is a little problem if you look at the first screen shot the table header doesn't align with the table unless i re size my browser window. I am trying to figure out why even after i used $table.floatThead({useAbsolutePositioning: false});

@mkoryak
Copy link
Owner

mkoryak commented Aug 13, 2015

look, i can help you if you provide a jsfiddle that reproduces your problem. i cant help you by looking at code or screenshots. if you cant reproduce your problem on jsfiddle, then you dont know what your problem is, and you are in no position to ask anyone to help you solve it.

@mkoryak
Copy link
Owner

mkoryak commented Aug 13, 2015

did you read the docs?

Events
$table.trigger('reflow')

Given a table element $table that has been widgetized, a reflow event causes the header to realign itself to the correct position. This event must be triggered if the DOM is modifed in such a way that the widgetized table's location changes.

@ghost
Copy link
Author

ghost commented Aug 13, 2015

Yeah i did read the docs.

I used useAbsolutePositioning: false and $table.trigger('reflow') it didn't work for me still when i scroll the table header doesn't alight with the table.

image

If you can't help then i understand but i pretty much did everything on the documentation. Even when you look at jsfiddle the table header doesn't align with the table.

@mkoryak
Copy link
Owner

mkoryak commented Aug 13, 2015

ok,
you need to provide a jsfiddle that reproduces your problem in order for me to help you any further.

@ghost
Copy link
Author

ghost commented Aug 13, 2015

You can look at this one even though it scrolls i don't think it aligns properly: http://jsfiddle.net/833eLyod/18/

@ghost
Copy link
Author

ghost commented Aug 13, 2015

hey so i finally got it to work for one view by adding this setTimeout(function(){$('.queue-display').trigger('reflow');},1000); but when my column gets bigger it doesn't align and i have to re size the window again for the table header to align. For something like this i have to re size the browser every time: http://jsfiddle.net/suj7ws01/

@mkoryak
Copy link
Owner

mkoryak commented Aug 14, 2015

there is not javascript in that fiddle

@ghost
Copy link
Author

ghost commented Aug 14, 2015

I am running the same javascript code: http://jsfiddle.net/833eLyod/18/. I think i know what the problem is. Your plugin is not working with dynamic table that uses javascript knockout to build the table. I am not sure if you can fix that .

@ghost
Copy link
Author

ghost commented Aug 14, 2015

Here is a perfect example of jsfiddle: http://jsfiddle.net/6tpp23dd/. Like i said the rows are being dynamically created by knockout javascript. one the real application the table header scrolls but it's all bunched up together.

@mkoryak
Copy link
Owner

mkoryak commented Aug 14, 2015

yes, the plugin will not work with knockout because it does not know when knockout is done rendering. you need to tell it.

see:
http://stackoverflow.com/questions/14254317/success-callback-after-knockout-js-finishes-rendering-all-the-elements

knockout/knockout#1475

maybe those are helpful. you basically need to setup some callback that gets called after knockout renders the table, and when that happens call $table.trigger('reflow')

I dont know knockout, so thats the best I can do here.

@mkoryak mkoryak closed this as completed Aug 14, 2015
@ghost
Copy link
Author

ghost commented Aug 14, 2015

if it;s something to do with the knockout why does it work when i resize the window even just a little bit. Is there a way to resize the window just even by a percent which could be a work around as of right now

@mkoryak
Copy link
Owner

mkoryak commented Aug 14, 2015

it works when you resize the window because this plugin will reflow itself when you do that. resizing the window is the same as calling $table.trigger('reflow').
https://github.com/mkoryak/floatThead/blob/master/jquery.floatThead.js#L757

you probably resize the window AFTER knockout finishes rendering, so it appears to fix the issue. the real fix is to have a callback when knockout finishes rendering

@ghost
Copy link
Author

ghost commented Aug 14, 2015

okay I did exactly what you asked and i still have to resize my window for it work.

tbody data-bind="foreach: {
data: visibleQueueFiles,
beforeRemove: hideQueueFileElement,
afterAdd: showQueueFileElement, afterRender: showAfterRender
}"

this.showAfterRender = function () {

      var $table = $('.queue-display');
       $table.floatThead({useAbsolutePositioning: false});
      $table.trigger('reflow');

   }

@mkoryak
Copy link
Owner

mkoryak commented Aug 14, 2015

ok, it looks like I cant help you. Sorry.

@ghost
Copy link
Author

ghost commented Aug 14, 2015

I mean obviously something is broken with the plugin so i am understanding how can't not help me.

@mkoryak
Copy link
Owner

mkoryak commented Aug 14, 2015

Yes, which is why it works on the demo site

@ghost
Copy link
Author

ghost commented Aug 14, 2015

I already tried that man. I read the stuff that provided too.

@mkoryak
Copy link
Owner

mkoryak commented Aug 14, 2015

either way, if you can provide a jsfiddle that runs, and reproduces your issue, i will look at it. otherwise this is a waste of time for both of us. I need a jsfiddle that has javascript css and html and when you run it, the same thing happens as on your site. I dont know how else to say this. You will probably need to learn how to use jsfiddle external resources, since you have not done this and it would be much easier to make one.

it will be harder to make a jsfiddle with knockout, but that is what I require. I cannot solve your problem in my imagination, i need to work with real code.
There is nothing wrong with the plugin, you just need to use it correctly.

@mkoryak
Copy link
Owner

mkoryak commented Aug 14, 2015

@ghost
Copy link
Author

ghost commented Aug 14, 2015

well like you said it's gonna be hard to me build a jsfiddle for you with all the knockout that i am running. I will just look around see what i can find. Is there something in css that i can do to align this properly:
image

@ghost
Copy link
Author

ghost commented Aug 14, 2015

hey i got it to work by adding: setTimeout(function(){$('.queue-display').trigger('reflow');},1000); just in case if someone else is having the same problem.

@mkoryak
Copy link
Owner

mkoryak commented Aug 15, 2015

try adding this to the options: autoReflow: true

i think the problem is that your callback showAfterRender is not the right one, because obviously the html is still being updated after it. there seem to be multiple components that render in knockout, can you move it up to a parent component?

also you can try

this.showAfterRender = function () {
     setTimeout(function(){ 
       var $table = $('.queue-display');
       $table.floatThead({useAbsolutePositioning: false});
       $table.trigger('reflow');
    }, 1000);
 }

good luck

@ghost
Copy link
Author

ghost commented Aug 17, 2015

I take it back it works for some of the table but not the others. What you mean by moving it up to the parent component? do you mean parent component of .queue-display which is a div class name is queue-app. and i tried replace the .queue-display with queue-app that didn't work at all unless there is another way to do it.

@mkoryak
Copy link
Owner

mkoryak commented Aug 17, 2015

I dont know knockout, but since triggering reflow after rendering the table component does not work (but resizing window works) that tells me that your table isnt actually done rendering. Perhaps there is a parent component that you can use that will ensure your table is rendered when the callback gets called.

@ghost
Copy link
Author

ghost commented Aug 17, 2015

what you mean by parent component? do you mean parent component of .queue-display which is a div class name is queue-app?

@mkoryak
Copy link
Owner

mkoryak commented Aug 17, 2015

this.showAfterRender is a method on a component? is there a component that includes the one with the table? Like I said, I dont know knockout, I am assuming that it is like other frameworks I do know.

@ghost
Copy link
Author

ghost commented Aug 17, 2015

there is this but this is for the columns:

    <th data-bind="atrr: {'class': 'queue-row-' + $data}">
      <!-- ko if: $parent.isSortableColumn($data) -->
        <i class="fa fa-sort"
           data-bind="click: function() { $parent.toggleSortBy($data) }"></i>
      <!-- /ko -->

@mkoryak
Copy link
Owner

mkoryak commented Aug 17, 2015

I don't know knockout, so I don't know what you want me to do with that.

@ghost
Copy link
Author

ghost commented Aug 18, 2015

Here is a jsfiddle: https://jsfiddle.net/9d38dek4/

@ghost
Copy link
Author

ghost commented Aug 18, 2015

This one doesn't align at all

@ghost
Copy link
Author

ghost commented Aug 18, 2015

I think i am pretty sure i figure out what the problem is this time: Sometime it takes a while for the columns to build because it's striping data from the file and the table header already gets build before the column does. so if you set the times to 10 second or something it works but anything less than that for big view doesn't. Is there a way to tell it not to build the table header until the column is ready.

@mkoryak
Copy link
Owner

mkoryak commented Aug 18, 2015

The point of a jsfiddle is so that i can run the javascript code on it and see what happens. your fiddle contains the html generated by my plugin. This doesnt help me in any way. Include the html before the plugin runs.

I have spent too much time on this issue. If you want further help, please consider donating to floatThead.

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant