Fixed table header

It does not work in Netscape 4, Opera 5, Safari and Ice Browser.

The scaling works in iCab, but the fixing doesn't.

Known bug in all browsers: when the content table width exceeds the width of the page, the script doesn't work any more. I found no easy solution.

This script allows you to size a table so that the width of its TDs becomes the same as the width of the TDs of another table, so that the first table can become a header.

This script consists of two parts. First of all, we need to size the header table to the actual sizes of the TD's in the large table. Thus you can change the large table without changing the header table: it will always take the correct width.
When that is done we 'fix' the header table by means of the sticky menu script.

The example is below on this page because it needs a large table.

The script

This is the script:

var stuff = new Array('name','tel','function');
var old,theTop,menu,temp;

function scale()
{
	if (temp) clearTimeout(temp);
	if (!document.all && !document.getElementById)
	{
		alert('The script on this page does\'t work in your browser');
		return;
	}
	var extra = 0;
	if (document.all) extra = 2;
	extra +=4; // +2 per point of cellpadding
	var a = new getObj('fixedtable');
	var b = new getObj('normaltable');
	for (i=0;i<stuff.length;i++)
	{
		y = new getObj(stuff[i]+'body');
		z = new getObj(stuff[i]+'head');
		x = y.obj.offsetWidth - extra;
		z.style.width = x;
	}
	theTop = b.obj.offsetTop - a.obj.offsetHeight;
	a.style.left = b.obj.offsetLeft;
	old=theTop;
	menu = new getObj('fixedtable');
	movemenu();
}

window.onresize = scale;

You also need my sticky menu script and the DHTML micro-API. All these scripts work on these two tables:

<DIV ID="fixedtable">
<TABLE border=1 cellpadding=2 cellspacing=0>
	<TR>
		<TD ID="namehead">Name</TD>
		<TD ID="telhead">Telephone</TD>
		<TD ID="functionhead">Function</TD>
	</TR>
</TABLE>
</DIV>

<DIV ID="normaltable">
<TABLE border=1 cellpadding=2 cellspacing=0>
	<TR>
		<TD ID="namebody">Ms. T.</TD>
		<TD ID="telbody">secret</TD>
		<TD ID="functionbody">Senior Capital Venturer</TD>
	</TR>
	<TR>lots more TRs</TR>
</TABLE>
</DIV>

which have the following initial style sheets:

div#fixedtable {position: relative;
	padding: 0px;
	z-index: 5;
}

div#normaltable {position: relative;
	margin-left: 10%
}

Explanation

There are two separate tables in your HTML: the header table and the content table. I put the tables inside DIVs because it turns out that Netscape 6 has a bug that makes it impossible to change the top and left of tables, something we'll want to do later on. Using DIVs evades that problem. We give both DIVs an ID. and give them both a relative position. This is because we want to influence the position of the tables later on. Also, the header table should have a higher z-index than the content table because it should be shown on top of it.

Names

Both the header table and the content table should have IDs for individual cells. As usual, I've chosen a definite naming system. There are three columns in both tables, name, tel and function. The IDs of the header table get head behind these names, those of the content table get body behind it. We also store the three names in a JavaScript array and define some more variables we'll need:

var stuff = new Array('name','tel','function');
var old,theTop,menu,temp;

Then to the function scale(). It is for correctly scaling the widths of the TDs of the header table to the widths of the TDs of the content table. I call it in a link but you could also call it onLoad.

function scale()
{

First of all, if there is already a timeout for fixing the table, cancel it. I explain the timeout below.

	if (temp) clearTimeout(temp);

Then support detection. If neither document.all nor document.getElementById is supported (= if the browser is Netscape 4 or worse), the browser is too old and the script should end.

	if (!document.all && !document.getElementById)
	{
		alert('The script on this page does\'t work in your browser');
		return;
	}

extra

Now we can start. First of all, we need a variable extra to subtract from the TD width later on. Initially it is 0.

	var extra = 0;

However, if the browser is Explorer on Windows, it needs to be 2. (Why? I have no idea, I determined this value experimentally)

	if (document.all &&
		navigator.userAgent.indexOf('Win') != -1) extra = 2;

Furthermore, for every point of CELLPADDING you give to the tables, 2 should be added to extra. Since in my example the tables have a CELLPADDING of 2, we add 4:

	extra +=4; // +2 per point of cellpadding

Setting the width

Then we create new objects for both tables by using the DHTML micro-API. a now contains the header table, b the content table.

	var a = new getObj('fixedtable');
	var b = new getObj('normaltable');

Then we're ready for the actual scaling. We go through the array stuff which contains the basic names of the TDs.

	for (i=0;i<stuff.length;i++)
	{

We take one TD in the content table (by adding body to the name) and the corresponding TD in the header table (by adding head to the name) and put them in y and z.

		y = new getObj(stuff[i]+'body');
		z = new getObj(stuff[i]+'head');

Now we calculate the width the TD in the header table should become. This is the offsetWidth of the content table TD minus extra. We set the width of the header table TD to this value. (For offset, see the Get styles page).

		x = y.obj.offsetWidth - extra;
		z.style.width = x;
	}

Moving the tables

When all TDs have the correct width, we still have to move the tables themselved a little bit. Both tables should remain at the same place they are, but the header table should get an absolute position (so we can move it later on). When this is done, the content table moves up a bit to fill the gap that the header table has left in the natural flow of the page. We don't want that, we want the table to remain where it was.

So first of all we calculate where the header table should come. We take the current top of the content table (b.obj.offsetTop) and subtract from it the height of the header table (a.obj.offsetHeight). This is the top of the header table.

	theTop = b.obj.offsetTop - a.obj.offsetHeight;

We set the left of the header table to the same amount as the left of the content table, so it respects the margins

	a.style.left = b.obj.offsetLeft;

Getting ready

Now that both tables are in position, we have to turn to the sticky menu script for keeping the header table in its correct position. I don't use exactly the same script: the checkbox is missing because it's not necessary.

To pave the way for this function we set some variables that it expects. It uses theTop, which we've also used in this script. old becomes the current top of the header table:

	old=theTop;

menu becomes the header table itself:

	menu = new getObj('fixedtable');

and we call movemenu()

	movemenu();
}

Resize

Finally, we have to make sure that the widths of the TDs are recalculated when the user resizes the browser window.

window.onresize = scale;

That's it, you have a working fixed table header.

Example

Back to the top.

Below is the table with phone numbers of all our people. Scale the header table and fix it.

Name Telephone Function
The Bo$$ (to be reached through his secretary) Our gracious leader, informed, well-spoken, judicious, He Who Guides, Strong Arm of the Business, Offender of the Faithful, Protractor of the Useless, mystical, mythical, arcane, may his working days be long and his wealth increase
Dr. Markbrand ++46.32.191883 Customer Care Server
'Manuel' ++.89.23844.37222 Cousin of Pedro
'Pedro' ++.89.23844.37221 Cousin of Manuel
Helly Marshdown 525.9910 Incompatibility Manager
The Archimandrite 914-20339 Religious leader, competitor of The Primate
The Primate 915-20339 Religious leader, competitor of The Archimandrite
Lord Faintheart 1904 Chairman of the Vague Board
Maxwell 8838 (home) or 7402 (mistress) Co-worker
John Street 8302 Snooker referee
Dr. Grävestein 0 Fruitologist
Ms. T. secret Senior Capital Venturer
Augustus 2714 Dead Roman Emperor
Claude 4154 Claims to be a dead Roman Emperor.
Mohammed al-Jawri to be ascertained Currently asleep, function to be determined onWakeup
flock of geese Can be reached through The Capitol, Rome Security
Kiwulu Asampa Doesn't have one
(religous reasons)
He's the guy sitting on the ground in the disused office on the fifth floor
Albert Einstein 194024205 Inventor of relations
Ernst Schrödinger 234239420 Inventor of cat abuse
Niels Bohr 3948234 Inventor of the dice
Sir Isaac Newton q14892048 Inventor of the apple
Steve Jobs 03948-3 Inventor of the Apple
Mongo none Pawn in game of life


One problem with the relatively positioned table: it may go over the paragraph below it, which is positioned as if the table weren't slightly moved. I solved this by inserting two <BR>'s below the table.

Back to the top.