Fixed Position Table Headers
Wouldn't it be great if there was an easy way to make massive HTML tables with scrollable content and headers that stayed put? Why would you want this? Say you have a table with many rows and columns. Typically what happens when you scroll to the bottom of the table is you've forgotten the context of the cell you're viewing because the table header has scrolled out of view.
Theoretically, tables already have the correct markup to achieve fixed positioned headers without the need for elaborate CSS markup or javascript. Many have suggested to simply put a fixed height on the <tbody/> element and set the CSS to overflow:auto. The problem is that tables are rendered differently from browser to browser. I tried many different iterations of that idea before I gave up, it just seemed the most obvious method choked in IE6 & 7 every time.
I did come across another solution that achieves the desired effect by creating two tables and stacking them on top of each other, one for the thead and the other for the tbody. At first I really didn’t like that direction because it requires a bunch of additional non-semantic HTML markup and could be impractical to implement when dealing with dynamic data sources. Regardless of the extra markup required, this approach seemed to work the most reliably across all browsers. So I worked out a way to do it that would rely on Jquery to do most of the heavy lifting for the extra HTML markup and generating the correct cell dimensions so that the thead would line up properly with the tbody with dynamic content. The rest would rely on some light CSS markup to define the table dimensions and positioning.
First let's take a look at the beginning HTML structure:
<table class="scrolling" width="100%" border="0" cellspacing="0" cellpadding="0"> <thead> <tr> <th></th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td></td> <td></td> <td></td> </tr> </tbody> </table>
The important things you should notice is the class name "scrolling" added to the table, as well as the use of the thead and tbody elements within the table,
Now let's take a look at the Jquery code you'll use to transform your regular table into a scrolling table with a header that stays put:
$(document).ready(function(){
// creates structural elements to act as CSS helpers
$(".scrolling").wrap('<div class="scrolling_outer"><div class="scrolling_inner"></div></div>');
});
The first line uses the Jquery function wrap() to add an outer and inner div to the scrolling table.
$(document).ready(function(){
// creates structural elements to act as CSS helpers
$(".scrolling").wrap('<div class="scrolling_outer"><div class="scrolling_inner"></div></div>');
// clone an exact copy of the scrolling table and add to DOM before the original table
// replace old class with new to differentiate between the two
$('.scrolling').before($('.scrolling').clone().removeClass("scrolling").addClass("_thead"));
});
Next we take advantage of clone() to make an exact copy of the original scrolling table and call before() to add the new table directly above the original within the DOM. Chained wihtin that statement is the replacement of the "scrolling" class with the new "_thead" class, this will help distinguish one from the other.
$(document).ready(function(){
// creates structural elements to act as CSS helpers
$(".scrolling").wrap('<div class="scrolling_outer"><div class="scrolling_inner"></div></div>');
// clone an exact copy of the scrolling table and add to DOM before the original table
// replace old class with new to differentiate between the two
$('.scrolling').before($('.scrolling').clone().removeClass("scrolling").addClass("_thead"));
// remove all children within the cloned table after the thead element
$('._thead').children('tbody').remove();
});
Now we'll need to strip out all of the child nodes in the new table directly after the thead element so that we're left with a table with only the thead remaining.
$(document).ready(function(){
// creates structural elements to act as CSS helpers
$(".scrolling").wrap('<div class="scrolling_outer"><div class="scrolling_inner"></div></div>');
// clone an exact copy of the scrolling table and add to DOM before the original table
// replace old class with new to differentiate between the two
$('.scrolling').before($('.scrolling').clone().removeClass("scrolling").addClass("_thead"));
// remove all children within the cloned table after the thead element
$('._thead').children('tbody').remove();
// Copy the cell widths across from the original table
$("thead:eq(0)>tr th", '.scrolling').each( function (i) {
$("thead:eq(0)>tr th:eq("+i+")", '._thead').width( $(this).width() );
} );
$("thead:eq(0)>tr td", '.scrolling').each( function (i) {
$("thead:eq(0)>tr th:eq("+i+")", '._thead')[0].style.width( $(this).width() );
} );
});
The next step is a little more complicated but necessary to make this work. We need to define the cell widths in the new table because we've stripped out all of the tbody cells that were doing that for us. What we're doing is basically copying all of the th and td widths from the orginal table and pasting them back to the new.
The final step is to include a few blocks of CSS markup to create the table scroll by implementing overflow
<style>
body {
margin:40px;
}
table {
width:600px;
}
table thead {
background-color:#EEE;
}
table td, table th{
border:1px solid #000;
padding:6px;
text-align:left;
}
._thead {
position:absolute;
}
.scrolling_inner {
height:300px;
overflow-x:hidden;
overflow-y:auto;
padding-right:17px;
float:left;
}
.scrolling_outer {
position:relative;
}
</style>
Try adding all of these elements to your HTML page and you should end up with something that looks like this:
| Item No. | Menu Item | Order |
|---|---|---|
| 10831 | Please Label Each Item By Food Name!! | Add to order |
| 11255 | Buffalo Chicken Salad | Add to order |
| 17000 | Extra Bacon | Add to order |
| 32396 | Extra Sour Cream | Add to order |
| 10831 | Please Label Each Item By Food Name!! | Add to order |
| 11255 | Buffalo Chicken Salad | Add to order |
| 17000 | Extra Bacon | Add to order |
| 32396 | Extra Sour Cream | Add to order |
| 10831 | Please Label Each Item By Food Name!! | Add to order |
| 11255 | Buffalo Chicken Salad | Add to order |
| 17000 | Extra Bacon | Add to order |
| 32396 | Extra Sour Cream | Add to order |


6 Comments
Doesnt work in IE6 !
Nice work... but question remains... is so much of scripting is required just for fixed header table. It will hit the performance, isn't it better to create two table, one for header and other for body...
Regards,
Kuldeep
There are some issues with that approach. First, it adds a lot of additional markup to your page. Second, you would need to include a width dimension to each of your table cells in order for the header to line up exactly with the table body. The above script does all of the tedious cell dimensions for you - which saves a lot of time in the long run.
DO we need the thead tag, and tbody?
In order for the above script to work properly, you do indeed need to add the thead and tbody markup. You could easily modify the script to make it work without, but I found it was easier to separate and reference the content with the markup included.
Wow..
This great. You are a genius.
Tx
Post new comment