I worked on a frameworkless prototype I want to share with you.
It's working in Firefox 4+, IE7-8 (haven't checked 6 or 9) Safari and Chrome.
The biggest unsolved problem is what to do when the static headers run out of room to shrink before the data columns do.
For the actual project I was working on frankly I backed out of the scrolling tbody and went with pagination. but I was dealing with 25+ columns with sorting and potentially 1000s of rows.
Anyway this is where I got to before I abandoned it. Please post your additions if you improve it:
Hope this helps.
Link to an example
<!DOCTYPE html>
<html>
<head>
<title>Table with Fixed Header Prototype</title>
<script type="text/javascript">
</script>
<style type="text/css">
.tableContainer{
background:#eee;
width:80%;
}
.scrollContainer{
width:100%;
height:500px; /** The only thing required */
}
.staticHeaderTable{
-moz-box-shadow: 1px 4px 5px #cccccc;
-webkit-box-shadow:1px 4px 5px #cccccc;
box-shadow: 1px 4px 5px #cccccc;
position: relative;
z-index: 10;
}
/*****************************************************/
/**** Generic styles outside of problem scope ****/
table{border-collapse: collapse;font:normal 12px sans-serif;}
th,td{padding:2px; height:20px;border:1px solid #dddddd;}
th{background:#ffcc00;}
</style>
</head>
<body>
<script>
function makeStaticTableHeaders(tableId){
// Overall Container : wraps everything
var tableContainer = document.createElement("div");
tableContainer.className = "tableContainer";
// Scrolling Container : must have a height.(Set in CSSS in this sample) this contains the table without the head or foot
var scrollContainer = document.createElement("div");
scrollContainer.className = "scrollContainer";
scrollContainer.style.overflowY = "auto"; // just Y since IE7 doesn't add the scrollbar to the width.
scrollContainer.style.overflowX = "hidden"; // IE7 override. consider a CSS fix
scrollContainer.style.width = "100%";
// Identifies the actual table to wrap from the dom. exits if it can't be found
var dataTable = document.getElementById(tableId);
if(typeof(dataTable) == "undefined"){
return false;
}
dataTable.style.width = "100%";
// Identify the header. If there is none, there is no point in this so exit.
var header = dataTable.getElementsByTagName("thead");
if (header.length == 0){
return false;
}
for (var i = 0; i < header.length; i++)
{
if(header[i].className.indexOf("static") != -1){
header = header[i];
break;
}
if(i == header.length - 1){
header = header[i];// failsafe
}
}
// If we are still here, we begin the process of altering the DOM elements
// 1. Insert the tableContainer in front of the dataTable
dataTable.parentNode.insertBefore(tableContainer, dataTable);
// 2. Insert the scrollContainer into the table container
tableContainer.appendChild(scrollContainer);
// 3. get the thead tr and create staticHeaderTable with the tr
var headerRow = header.getElementsByTagName("tr")[header.getElementsByTagName("tr").length - 1]; // gets last tr in case there is more than one
var staticHeaderTable = document.createElement("table");
staticHeaderTable.className = "staticHeaderTable";
staticHeaderTable.appendChild(header);
staticHeaderTable.style.width = "100%";
// 4. Put the staticHeaderTable in the scrollContanier
tableContainer.insertBefore(staticHeaderTable, scrollContainer);
// 5. take the datatable out of the dom and put it in the scrollContainer
scrollContainer.appendChild(dataTable);
// 6. footer(optional)
var footer = dataTable.getElementsByTagName("tfoot");
if (footer.length > 0){
for (var i = 0; i < footer.length; i++)
{
if(footer[i].className.indexOf("static") != -1){
footer = footer[i];
break;
}
if(i == footer.length - 1){
footer = footer[i];// failsafe
}
}
// TODO: footer assumes columns are not linked to the data columns.
var staticFooterTable = document.createElement("table");
staticFooterTable.className = "staticFooterTable";
staticFooterTable.appendChild(footer);
staticFooterTable.style.width = "100%";
tableContainer.appendChild(staticFooterTable);
}
function tableResize(){
var firsttableRow = dataTable.getElementsByTagName("tbody")[0].getElementsByTagName("tr")[0];
var extra = headerRow.offsetWidth - firsttableRow.offsetWidth;
var headerCells = (headerRow.getElementsByTagName("td").length > 0 )? headerRow.getElementsByTagName("td") : headerRow.getElementsByTagName("th");
for(var i=0; i <headerCells.length;i++){
headerCells[i].style.width = firsttableRow.getElementsByTagName("td")[i].offsetWidth + ((i==headerCells.length-1)? extra: 0) + "px";
}
};
if(window.addEventListener) {window.addEventListener("resize", tableResize, false);}
else{window.attachEvent("onresize", tableResize);}
tableResize();
}
window.onload = function(){
var staticTable = makeStaticTableHeaders("dataTable");
}
</script>
<h2>Datatable</h2>
<table id="dataTable" class="dataTable">
<thead class="static">
<tr>
<th>Header 1 A long One</th>
<th>Header 2</th>
<th>Header 3</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cell Content 1 All the good Stuff is in here</td>
<td>Cell Content 2</td>
<td>Cell Content 3</td>
</tr>
<tr>
<td>More Cell Content 1</td>
<td>More Cell Content 2</td>
<td>More Cell Content 3</td>
</tr>
<tr>
<td>Even More Cell Content 1</td>
<td>Even More Cell Content 2</td>
<td>Even More Cell Content 3</td>
</tr>
<tr>
<td>And Repeat 1</td>
<td>And Repeat 2</td>
<td>And Repeat 3</td>
</tr>
<tr>
<td>Cell Content 1</td>
<td>Cell Content 2</td>
<td>Cell Content 3</td>
</tr>
<tr>
<td>More Cell Content 1</td>
<td>More Cell Content 2</td>
<td>More Cell Content 3</td>
</tr>
<tr>
<td>Even More Cell Content 1</td>
<td>Even More Cell Content 2</td>
<td>Even More Cell Content 3</td>
</tr>
<tr>
<td>And Repeat 1</td>
<td>And Repeat 2</td>
<td>And Repeat 3</td>
</tr>
<tr>
<td>Cell Content 1</td>
<td>Cell Content 2</td>
<td>Cell Content 3</td>
</tr>
<tr>
<td>More Cell Content 1</td>
<td>More Cell Content 2</td>
<td>More Cell Content 3</td>
</tr>
<tr>
<td>Even More Cell Content 1</td>
<td>Even More Cell Content 2</td>
<td>Even More Cell Content 3</td>
</tr>
<tr>
<td>And Repeat 1</td>
<td>And Repeat 2</td>
<td>And Repeat 3</td>
</tr>
<tr>
<td>Cell Content 1</td>
<td>Cell Content 2</td>
<td>Cell Content 3</td>
</tr>
<tr>
<td>More Cell Content 1</td>
<td>More Cell Content 2</td>
<td>More Cell Content 3</td>
</tr>
<tr>
<td>Even More Cell Content 1</td>
<td>Even More Cell Content 2</td>
<td>Even More Cell Content 3</td>
</tr>
<tr>
<td>And Repeat 1</td>
<td>And Repeat 2</td>
<td>And Repeat 3</td>
</tr>
<tr>
<td>Cell Content 1</td>
<td>Cell Content 2</td>
<td>Cell Content 3</td>
</tr>
<tr>
<td>More Cell Content 1</td>
<td>More Cell Content 2</td>
<td>More Cell Content 3</td>
</tr>
<tr>
<td>Even More Cell Content 1</td>
<td>Even More Cell Content 2</td>
<td>Even More Cell Content 3</td>
</tr>
<tr>
<td>And Repeat 1</td>
<td>And Repeat 2</td>
<td>And Repeat 3</td>
</tr>
<tr>
<td>Cell Content 1</td>
<td>Cell Content 2</td>
<td>Cell Content 3</td>
</tr>
<tr>
<td>More Cell Content 1</td>
<td>More Cell Content 2</td>
<td>More Cell Content 3</td>
</tr>
<tr>
<td>Even More Cell Content 1</td>
<td>Even More Cell Content 2</td>
<td>Even More Cell Content 3</td>
</tr>
<tr>
<td>And Repeat 1</td>
<td>And Repeat 2</td>
<td>And Repeat 3</td>
</tr>
<tr>
<td>Cell Content 1</td>
<td>Cell Content 2</td>
<td>Cell Content 3</td>
</tr>
<tr>
<td>More Cell Content 1</td>
<td>More Cell Content 2</td>
<td>More Cell Content 3</td>
</tr>
<tr>
<td>Even More Cell Content 1</td>
<td>Even More Cell Content 2</td>
<td>Even More Cell Content 3</td>
</tr>
<tr>
<td>And Repeat 1</td>
<td>And Repeat 2</td>
<td>And Repeat 3</td>
</tr>
<tr>
<td>Cell Content 1</td>
<td>Cell Content 2</td>
<td>Cell Content 3</td>
</tr>
<tr>
<td>More Cell Content 1</td>
<td>More Cell Content 2</td>
<td>More Cell Content 3</td>
</tr>
<tr>
<td>Even More Cell Content 1</td>
<td>Even More Cell Content 2</td>
<td>Even More Cell Content 3</td>
</tr>
<tr>
<td>And Repeat 1</td>
<td>And Repeat 2</td>
<td>And Repeat 3</td>
</tr>
<tr>
<td>Cell Content 1</td>
<td>Cell Content 2</td>
<td>Cell Content 3</td>
</tr>
<tr>
<td>More Cell Content 1</td>
<td>More Cell Content 2</td>
<td>More Cell Content 3</td>
</tr>
<tr>
<td>Even More Cell Content 1</td>
<td>Even More Cell Content 2</td>
<td>Even More Cell Content 3</td>
</tr>
<tr>
<td>And Repeat 1</td>
<td>And Repeat 2</td>
<td>And Repeat 3</td>
</tr>
<tr>
<td>Cell Content 1</td>
<td>Cell Content 2</td>
<td>Cell Content 3</td>
</tr>
<tr>
<td>More Cell Content 1</td>
<td>More Cell Content 2</td>
<td>More Cell Content 3</td>
</tr>
<tr>
<td>Even More Cell Content 1</td>
<td>Even More Cell Content 2</td>
<td>Even More Cell Content 3</td>
</tr>
<tr>
<td>And Repeat 1</td>
<td>And Repeat 2</td>
<td>And Repeat 3</td>
</tr>
<tr>
<td>Cell Content 1</td>
<td>Cell Content 2</td>
<td>Cell Content 3</td>
</tr>
<tr>
<td>More Cell Content 1</td>
<td>More Cell Content 2</td>
<td>More Cell Content 3</td>
</tr>
<tr>
<td>Even More Cell Content 1</td>
<td>Even More Cell Content 2</td>
<td>Even More Cell Content 3</td>
</tr>
<tr>
<td>End of Cell Content 1</td>
<td>End of Cell Content 2</td>
<td>End of Cell Content 3</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3">Footer Is Here</td>
</tr>
</tfoot>
</table>
</body>
</html>