Thursday, December 01, 2011

Floating/Fixed Table Header in HTML Page or Inside DIV control

I’ve looked a lot for finding a solution were we can float a header of a table so that the header always appears on the top no matter how down do I scroll.
All the ‘floating table header’ solution ‘mostly’ worked were full HTML body is getting the scroll but none of them worked which can work inside scrolling div. None!
So I though I can create one. The concept was simple, get the ‘onScroll’ even of Div control and change CSS “top” property of table header to current scrolled position of Div. 
Unfortunately, there are many browser issues. Like mozilla doesn’t like when table cells change their position. Chrome rendering issues shows two headers rows when scrolled up.
At last, after storming through the problems, I arrived on this complex but simplified solution which is mentioned below. Copy the content in a blank HTML file and see the fun.

<html> 
    <head> 
        <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript" > </script> 
        <script  type="text/javascript"> 
            $(function() { 
                if($.browser.mozilla) 
                    //table row doean't float in firefox, div floats 
                    $(".floatingHeader" + " tr th div") 
                            .addClass("floatingStyle"); 
                else 
                    //table row can float in IE and Chrome 
                    $(".floatingHeader"+ " tr th") 
                            .addClass("floatingStyle"); 
            }); 
            function changeFloatingHeaderPosition(container, headerId) { 
                    if($.browser.webkit) //chrome rendering bug fix 
                        $("#"+headerId + " tr th") 
                            .css("visibility", "hidden"); 
                    if($.browser.mozilla) 
                        $("#"+headerId + " tr th div") 
                            .css("top", container.scrollTop); 
                    else 
                        $("#"+headerId + " tr th") 
                            .css("top", container.scrollTop); 
                    if($.browser.webkit) //chrome rendering bug fix 
                        $("#"+headerId + " tr th") 
                            .css("visibility", "visible"); 
            } 
     
      // Remove this method if you are using Jqeury earlier than 1.9
     (function() {
      var matched, browser;

      // Use of jQuery.browser is frowned upon.
      // More details: http://api.jquery.com/jQuery.browser
      // jQuery.uaMatch maintained for back-compat
      jQuery.uaMatch = function( ua ) {
   ua = ua.toLowerCase();

   var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
       /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
       /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
       /(msie) ([\w.]+)/.exec( ua ) ||
       ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
       [];

   return {
       browser: match[ 1 ] || "",
       version: match[ 2 ] || "0"
   };
      };

      matched = jQuery.uaMatch( navigator.userAgent );
      browser = {};

      if ( matched.browser ) {
   browser[ matched.browser ] = true;
   browser.version = matched.version;
      }

      // Chrome is Webkit, but Webkit is also Safari.
      if ( browser.chrome ) {
   browser.webkit = true;
      } else if ( browser.webkit ) {
   browser.safari = true;
      }

      jQuery.browser = browser;
  })();
        </script> 
        <style> 
            .floatingStyle 
            { 
                position:relative; 
                background-color:#829DC0; 
                top:0px; 
            } 
        </style> 
    </head> 
<body> 
    <div class="floatingContainer"     onscroll="changeFloatingHeaderPosition(this, 'idHeader' );"      style="height:150px; width: 100px;overflow:auto;"> 
        <table cellspacing=0 cellpadding=0> 
            <thead class="floatingHeader" id="idHeader"> 
                <tr> 
                    <th><div>Col1</div></th>                     <th ><div>Col2<div></th> 
                    <th ><div>Col3<div></th> 
                </tr> 
            </thead> 
            <tbody> 
                <tr><td>first_row</td><td>first_row</td><td>first_row</td></tr> 
                <tr><td>data</td><td>data</td><td>data</td></tr> 
                <tr><td>data</td><td>data</td><td>data</td></tr> 
                <tr><td>data</td><td>data</td><td>data</td></tr> 
                <tr><td>data</td><td>data</td><td>data</td></tr> 
                <tr><td>data</td><td>data</td><td>data</td></tr> 
                <tr><td>data</td><td>data</td><td>data</td></tr> 
                <tr><td>data</td><td>data</td><td>data</td></tr>             </tbody> 
        </table> 
</div></body><html>
Note: Dan/Kenji has written plugging for the same which might be useful to you. I've not verified if it works in all case but worth having a look. You can find it: here: http://www.redkitetechnologies.com/2013/03/floatingsticky-headers-for-visualforce-pageblocktable/

17 comments:

Clayton said...

Fantastic solution! Well done.

I have a question: Do you think there's any way to ensure that the header scrolls smoothly instead of "jumping"?

Anonymous said...

Yes. You can use ".animate" method instead of ".css" for smooth transitions.
Try: http://api.jquery.com/animate/

Let me know how it goes.

Clayton said...

Thanks for the feedback, but I'm unfortunately not winning. There seems to be a lag in the animation...

Anonymous said...

Can you assist with an example?

Anonymous said...

Excellent script. It seems though that the floating div does not sit flush with the top of the div container. How would this be recitfied?

Anonymous said...

This is latest place to get jquery:

http://code.jquery.com/jquery-latest.min.js

Anonymous said...

Great solution, thank you!

@Anonymous - modify .floatingStyle.top to -2px and modify function changeFloatingHeaderPosition to subtract 2 from scrollTop.

i.e.: .css("top", container.scrollTop -2);

Zach said...

This is great. I've been looking for solution for a long time. I haven't been able to get the animate to work yet so it doesn't jump but that is next.

I have a quick question though. I've tried to turn the borders back on through the "style" script that was added but they don't stay. I basically added the line "border: 1px solid black;" to the style.

Could you tell me how I can get border to stay around the cells?

Thanks...

Yogee said...

Zach,
I've added this:
$("td").css("border", "1px solid black");
in load function and it applied border to all td. Is this what you wanted?

Zach said...

Yogee,
That's close but I only want the grid that is having the fixed header applied to have the border change. I have several other tables on the page loading various items that need to keep their respective "0" borders.

When I applied your new css, it created a border around all of the tables.

Thank you.

Yogee said...

You mean something like this?
$(".floatingContainer td").css("border", "1px solid black");

Yohoho said...

Just tried it in Chrome, doesn't work. Header just scrolls with the rest.

Yogee said...


Yohoho,
This used to work on chrome but need to check on latest chrome. It is unlikely that something is broken in new chrome for such a simple and popular scripts. So, can you see if any Javascript fails or something fishy?
You help in improving this article will be appreciated.

Yogee said...

Yohoho,
As suspected, Javascript error - the bug is found and fixed.
Browser detection is removed from jquery 1.9 onward (and note that I am using latest Jqeury version). So I've added a method from Jquery 1.8 to detect browser version. DON'T FORGET TO REMOVE THAT METHOD IF YOU ARE USING JQUERY VERSION EARLIER THAN 1.9.
(http://stackoverflow.com/questions/18277773/jquery-1-9-how-to-migrate-from-browser-msie/18278164#18278164)

Anonymous said...

Not working now. Any updates?

Anonymous said...

Hi, I have used your code it is working fine but if i apply background colour for tr and scroll the table the background is not staying at the top in mozilla. It is working fine in chrome. Can any one help me out

Yogee said...

Please don't just say something is not working. Share your code somewhere to get help.