DHTML Navigation:
layers

It does not work in iCab.

There are some minor bugs in the style sheets in Explorer 4 on Mac, Netscape 4 and Omniweb. The script itself works fine, though.

The pseudo-mouseover effect does not work in Netscape 4 and Opera 6-.

See this script in real live action at the World Press Photo site I coded.

On this page I explain my version of the ever more popular DHTML navigation script. This is no doubt one of the best things you can do with DHTML, if you design the graphics, interaction and information structure of your site well, this navigation can double as sitemap and users can quickly find their way through complex, multi-layered sites.

As usual the script runs in this page. The links with the arrows > open sub menus. The menu should of course fit into the design of the page (it doesn't fit in mine, but this is only a demo anyway).

After you haven't done anything for 5 seconds I close all navigation layers, because then I assume you've lost interest in the navigation and want to read the actual page. Since the navigation layers divert attention from the page, it's best to close them.

I gave the example 4 levels of navigation, but this script can handle an unlimited number of levels (though your users probably can't).

The code

You'll need specific HTML, style sheets and JavaScript code for this script. You're free to change the style sheets, of course, but you shouldn't change too much in the HTML because this script expects certain HTML structures.

The script


var remember = new Array();
var remember2 = new Array();
var checkIt;
var DHTML = (document.getElementById || document.all || document.layers);

function show(name,lvl,obj)
{
	if (!DHTML) return;
	checkUserInput();
	if (remember[lvl] && remember[lvl] == name) return;
	if (remember[lvl])
	{
		closeAll(lvl);
	}
	if (name)
	{
		var x = getObj(name);
		x.visibility = 'visible';
	}
	remember[lvl] = name;
	if (obj.parentNode) y = obj.parentNode;
	else if (obj.parentElement) y = obj.parentElement;
	else return;
	if (y.className) return;
	y.className = 'over';
	if (remember2[lvl]) remember2[lvl].className = '';
	remember2[lvl] = y;
}

function closeAll(lvl)
{
	for (i=remember.length - 1;i>=lvl;i--)
	{
		if (remember[i])
		{
			var x = getObj(remember[i]);
			x.visibility = 'hidden';
		}
		remember[i] = null;
		if (remember2[i])
		{
			remember2[i].className = '';
			remember2[i] = null;
		}
	}
}

function checkUserInput()
{
	if (checkIt) clearTimeout(checkIt);
	checkIt = setTimeout('closeAll(1)',5000);
}

function getObj(name)
{
  if (document.getElementById)
  {
    return document.getElementById(name).style;
  }
  else if (document.all)
  {
    return document.all[name].style;
  }
  else if (document.layers)
  {
    return document.layers[name];
  }
  else return false;
}

HTML

The navigation menus themselves look like this:

<DIV CLASS="navilayer" ID="base" STYLE="visibility: visible;  left: 20px">
<DIV><A HREF="#"
	onMouseOver="show('',1,this)">Home</A></DIV>
<DIV><A HREF="#"
	onMouseOver="show('about',1,this)">About&nbsp;&gt;</A></DIV>
<DIV><A HREF="#"
	onMouseOver="show('products',1,this)">Products&nbsp;&gt;</A></DIV>
<DIV><A HREF="#"
	onMouseOver="show('',1,this)">Contact</A></DIV>
<DIV>&nbsp;</DIV>
</DIV>

The empty DIV at the bottom is for the last horizontal line. The subsequent menus may also have one or more of these DIVs:

<DIV CLASS="fill"><A>&nbsp;</A></DIV>

This is to make sure that the first link in the menu is at the same height as the link that opened the menu. I give them a separate class because the border color should be the same as the background color. This doesn't work in Netscape 4 for obscure reasons.

Style sheets

Finally the style sheets are the most difficult part of this menu, because you'll find that anything you think of won't work in a certain browser (usually, but not always, Netscape 4). The extra pseudo-mouseover effect (mouseovered link becomes blue bg and white text) does not work in Netscape 4 and Opera 5. Even worse, at first I did

a {display: block}

to make the links blocks on their own. It turned out that Explorer 4 doesn't support this, which forced me to put extra DIV's around each link.

As you see each layer has its own left position, I don't define this in the general style sheet. In addition the first menu has a visibility: visible because it should always be shown when the user comes to the page.

I do not further explain the style sheets on this page, for details please view the source.

Preparations

First of all we define a few variables that we'll need:

var remember = new Array();
var remember2 = new Array();
var checkIt;
var DHTML = (document.getElementById || document.all || document.layers);

The arrays are for storing the current status of the menu: which menus are visible, which links are mouseovered. See below for more information. checkIt is for the timeout, DHTML is to see if the browser supports DHTML.

Furthermore we need our faithful DHTML micro-API page. Then the preparations are finished, we can start using the menu.

The mouseover event handler

Each link in every menu should have a mouseover event handler, for instance:

show('about',1,this)
show('',3,this)

As you see, each call hands three variables to the function show().

  1. First of all it gives the ID of the menu layer to be opened if the user mouses over the link. In the first case, the <DIV ID="about"> should be opened, in the second case nothing should be opened. Nonetheless the function expects a string, so we send it an empty string.
  2. Then the layer level should be passed. The basic navigation which is always visible has level 1, each menu coming from it has level 2, each menu coming from a level 2 menu has level 3 etc. Thus you tell the script on what level it should operate. There's no technical limit to the number of levels.
  3. Finally, pass this which contains the link object itself because the script must access the exact link the user mouses over. (I could have given each link an ID, then passed the ID to the function, but this solution is far simpler).

Main function: show()

These variables are handed to the function show(), which is the central processing function of this script:

function show(name,lvl,obj)
{

First of all, check if the browser supports DHTML. If it doesn't, end the function immediately.

	if (!DHTML) return;

Secondly, we call the function checkUserInput() to tell it that the user has moused over a link. I explain this function below.

	checkUserInput();

Thirdly, we see if a menu is already opened on this level. To do this we use the array remember[]. This array is filled with the names of the menus that are currently opened. remember[1] is the first menu opened (the about menu or the products menu), remember[2] is the second one etc. If no menu is opened on a certain level remeber[lvl] is empty.

Now we check if a menu is opened on the current level and if the name of this menu is the same as the name of the menu that should be opened now. If it is the same, the user has moused over the same link once again. Now nothing should happen, so we end the function.

	if (remember[lvl] && remember[lvl] == name) return;

Opening and closing layers

When we pass this last check we're certain that the user has moused over a new link and things should start to happen. First of all we see again if remember[lvl] contains a name (if a menu is already opened on this level). If it does, this layer and all menus below it should be closed, since the user has un-selected the link that contains these levels. To close everything we use the special function closeAll() which is explained below.

	if (remember[lvl])
	{
		closeAll(lvl);
	}

Then we see if the event handler has passed a name of a new layer to be opened. If it has, we get the object with that name and set its visibility to visible: the menu layer magically opens.

	if (name)
	{
		var x = getObj(name);
		x.visibility = 'visible';
	}

We make note of the opened level in the array remember[]. We also do this when no name is given, then remember[lvl] should become empty.

	remember[lvl] = name;

The pseudo-mouseover

If it's necessary, a new menu layer has now been opened. There is a second effect, however. When the user mouses over a link, it should get a mouseover colour (blue background and white text). Note that I cannot use a a:hover here: I want the mouseover colour to remain when the user goes to a newly opened menu so that the user always knows where he is. What I do is change the CLASS of the DIV around the A (I'd rather have changed the CLASS of the A itself, but as said before the DIVs are necessary for Explorer 4).

The mouseover event handler hands this to the function, so it knows which link the user went over. This link is now stored in obj. However, we want to change the class of the parent node of this link, the DIV that's around it. So first we should search for the parent node. For the Level 1 DOM browsers we search for obj.parentNode and store it in y:

	if (obj.parentNode) y = obj.parentNode;

For Explorer 4 we search for obj.parentElement and store it in y.

	else if (obj.parentElement) y = obj.parentElement;

If the browser supports neither parentNode nor parentElement (Netscape 4) we cannot apply the mouseover and we end the function.

	else return;

If the parent node already has a class, end the function now: nothing needs to change.

	if (y.className) return;

If it hasn't, now change the class to over so it gets the blue background and the white text, as defined in the style sheet. (In Opera 5 the class name is changed but the browser doesn't apply the new styles to the DIV)

	y.className = 'over';

If another link on the same level had the mouseover colours, it should change back now. We keep track of this in the array remember2[] which contains the objects that have class over. If an object is currently in remember2[], empty its class name so that it returns to standard styles.

	if (remember2[lvl]) remember2[lvl].className = '';

Finally we store the new object with class over in remember2[].

	remember2[lvl] = y;
}

Closing layers: function closeAll()

To close layers (make them invisible) I wrote a special function. After all, if you close a certain layer all layers below it should also become invisible.

This function is called from the main function show() and from the special function checkUserInput(). Each time a level is passed to the function as argument. The layer on this level and the layers on all lower levels should be closed.

function closeAll(lvl)
{

We go through the levels that should be closed in reverse order. We start at the level with the highest number and go back in the navigation to the level that's been passed to the function.

	for (i=remember.length - 1;i>=lvl;i--)
	{

If the array remember[] has a value at the current level (if a layer is opened on this level) we close it by taking the correct object and setting its visibility to hidden.

		if (remember[i])
		{
			var x = getObj(remember[i]);
			x.visibility = 'hidden';
		}

After that we empty this level of remember[], after all there's no opened layer anymore at this level.

		remember[i] = null;

Secondly we see if any object on the current level has the mouseover style. If it has, it's stored in remember2[]. We have to switch the special style off, otherwise the user will find one link with the special style when he opens the layer again. This can be mightily confusing, since the style marks the path through the navigation the user chooses.

So if remember2[] is filled at this level, we remove the class of the object and afterwards remove the object from remember2[].

		if (remember2[i])
		{
			remember2[i].className = '';
			remember2[i] = null;
		}

Then on to the next level until we reach the level that has been passed to the function.

	}
}

Closing all layers: function checkUserInput()

Finally a special function for closing all layers. I want to close down the entire navigation if the user does nothing for 5 seconds.

Each time the user mouses over any link the function checkUserInput() is called. This function sets a timeout that calls closeAll() 5 seconds later.

function checkUserInput()
{

This function uses the variable checkIt in which the timeout is stored. First of all we see if checkIt is filled with a previous timeout from the previous mouseover event. If it is, we cancel the timeout. After all the user is still active so the menus shouldn't be closed.

	if (checkIt) clearTimeout(checkIt);

Then we set the timeout once again. After 5 seconds, we call closeAll(1) which closes all navigation layers so that only the topmost starting layer is shown. The navigation is now completely closed.

	checkIt = setTimeout('closeAll(1)',5000);
}

Of course, if the user mouses over another link this timeout is canceled and a new one is set. The net result is that all layers close 5 seconds after the last mouseover event.