Web DevCenter    
 Published on Web DevCenter (http://www.oreillynet.com/javascript/)
 See this if you're having trouble printing code examples


Designing with Javascript.

Hierarchical Menus with the Underrated style.display Object

by Bill Pena, coauthor of Designing with JavaScript, 2nd Edition
02/22/2002

One of the most common DHTML requests I get is for a Windows Explorer-style hierarchical menu, where there's a list of topics or "folders" that a user can click on to reveal subtopics, or "files," within that folder. It's a common desktop metaphor that seems ever more necessary on the Web, especially as we see navigation bars incorporating larger and more complex content while still trying to fit on the screen. Hierarchical menus are a solution to the common problem of having too many links in too small a space.

But, I figured, there are several Web sites where you can find large, complex scripts to create large, complex hierarchical menus. Instead, I'll show you how to make your own menus, of any length or complexity, with the use of an extremely useful and widely unknown DOM CSS property, style.display.visibility. Most DHTML coders know this as the default CSS property, used to show and hide a layer; it's a pain to use when the visibility of a layer affects the position of other layers because visibility only affects whether an HTML element is visible to the user, and not whether the element is rendered on the page.

When you set an element to visibility="hidden", it simply disappears while still taking up space on the page, almost as if it were transparent. However, if by hiding an element you intend to have another one move up and fill the empty space, like you would expect if you close a folder in a menu, you would have to explicitly reposition each element that is affected by hiding that first element, which is a very tedious task. display lets you show and hide an element as well, but rather than just making it transparent, like visibility does, it completely pulls the element from the page, so the Web browser will render and display the page as if the element didn't exist, filling in gaps and all. It seems like a subtle difference, but it makes a huge difference in scripting time and complexity.

Unfortunately, not every browser supports the style.display object. Only Netscape 6 and IE5.5 and above support this object, but as a standard CSS2 property, other browser makers are working toward their own support. So before you go ahead and use this object in your scripts, make sure you start your script by using browser detection to redirect users with non-compliant browsers. To learn more about browser detection, check out Chapter 6, "Too Many Browsers? Not Really," in Designing with JavaScript.

Houdini with an index

With a little clever design, we can build ourselves a standard, left-hand navigation bar that we can make dynamic with only a few lines of JavaScript. The secret is understanding how layers and tables act like containers for content, and how they automatically resize themselves to fit the content they contain. If you've spent much time designing Web sites, you've noticed how important the content in a table is to the size of the table; the "transparent, one-pixel GIF" hack developed as Web designers realized that some browsers would ignore specified widths and heights of tables if the content of the table didn't fill the entire space.

We can now use this property of content containers to our advantage, nesting tables and layers that will automatically resize to create our hierarchical menu. In a nutshell, we'll create a layer that lists a couple of topic areas, "Projects" and "Interests," from my Web site. The layer also contains tables, which contain sub-listings of each area, that are initially set to style.display="none" with JavaScript. This causes the Web browser to only display the two topic links, making the containing layer take up only the height necessary to list those links.

If we change the display value of a table with JavaScript once the page has loaded, the containing layer will resize to incorporate the table, and the links will appear as if from nowhere. Voila! That's the basis of our menus, and many other DHTML tricks! This simple manipulation of one DOM CSS object, in this case style.display, combined with a little ingenuity lets you do some pretty wonderful things.

Let's take a look now at the menu we'll be creating, so you can get a better picture of how this fits together:

Screen Shot.

As you can see, first we have a layer with two links, "Projects" and "Interests." When "Projects" is clicked, the menu expands and displays a list of sublinks below "Projects." In HTML, the layout is quite simple, but by adding id attributes in the right places, we give ourselves the power to display and hide these links individually with JavaScript:

<div>
	Projects
	<table id="projectlinks">
		Designing with JavaScript
		Hypercubes
		...
	</table>
	Interests
	<table id="interestlinks">
		...
	</table>
</div>

We can extend this menu another level just as easily, and show chapters below the "Designing with JavaScript" link, by nesting another table:

<div>
	Projects
	<table id="projectlinks">
		Designing with JavaScript
		<table id="chapters">
			Chapter 1
			Chapter 2
			Chapter 3
			...
		</table>
		Hypercubes
		...
	</table>
	...
</div>

And we get this:

Screen shot.


Enough, show me the code!

This script requires just a couple of complementary functions: hideLevel() and showLevel(). Each of them takes the id of the menu "level" they're acting on, like "projectlinks" and "chapters," and uses it to flip that level's display setting.

function hideLevel( _levelId) {
	var thisLevel = document.getElementById( _levelId );
	thisLevel.style.display = "none";
	}

function showLevel( _levelId) {
	var thisLevel = document.getElementById( _levelId );
	if ( thisLevel.style.display == "none") {
		thisLevel.style.display = "block";
		}
	else {
		hideLevel( _levelId);
		}
	}

In order to reduce clutter, I decided to make showLevel() take care of both circumstances, hiding and showing the level, by including an if statement that checks whether the level is already displayed or not. This way, we can make the links always call showLevel(), but have the script know which of the two functions is appropriate and run the right one. If the list of chapters below "Designing with JavaScript" is already visible, for example, clicking on that link will hide the chapters instead of revealing them.

Designing with JavaScript

Related Reading

Designing with JavaScript
Creating Dynamic Web Pages
By Nick Heinle, Bill Pena

To be honest, the script is slightly more complicated than this. As you may have noticed in the screenshots above, I've also included plus- and minus-sign images beside the links, indicating the state of that level. This visual aid completes the hierarchical menu metaphor, basically mimicking the "Folders View" in Windows Explorer. It's also trivial to add, and if you've ever made an image rollover, you already know how.

First we create a couple of image objects and load our images into the script:

var plusImg = new Image();
	plusImg.src = "./images/plus.png"
var minusImg = new Image();
	minusImg.src = "./images/minus.png"

Then, we just modify our previous hideLevel() and showLevel() functions a little to make them switch the plus or minus image beside the clicked link:

function hideLevel( _levelId, _imgId ) {
	var thisLevel = document.getElementById( _levelId );
	var thisImg = document.getElementById( _imgId );
	thisLevel.style.display = "none";
	thisImg.src = plusImg.src;
	}

function showLevel( _levelId, _imgId ) {
	var thisLevel = document.getElementById( _levelId );
	var thisImg = document.getElementById( _imgId );
	if ( thisLevel.style.display == "none") {
		thisLevel.style.display = "block";
		thisImg.src = minusImg.src;
		}
	else {
		hideLevel( _levelId, _imgId);
		}
	}

Whereas before we passed the function just one id, the level being manipulated, we now need to pass it the id of the image next to the link. So our links now look something like this:

<a href="javascript:showLevel( 'projectlinks', 'projImg');">Projects</a>

Last but not least, we want the menu to be completely collapsed when the page loads. We don't want to open with all links displayed, because that would defeat the point of making a menu. So we want to create one more function, hideAll(), that gets called once when the page loads and hides all of the links at once. All we have to do is create a function that calls hideLevel() multiple times, once for each level of the menu.

function hideAll() {
	hideLevel("chapters", "chapImg");
	hideLevel("projectlinks", "projImg");
	hideLevel("interestlinks", "intsImg");
	}

Then add a call to hideAll() when the page loads, like so:

<body onLoad="javascript:hideAll();">

It's as simple as that.

Now that you've seen a breakdown of how this script works, take a look at the complete product, and feel free to adapt it for your own needs. Remember to use browser detection! Once you've explored using the style.display object, write and tell me about your experiences, and let me know if you've found it useful.

<script language="JavaScript">
<!--

var plusImg = new Image();
	plusImg.src = "./images/plus.png"
var minusImg = new Image();
	minusImg.src = "./images/minus.png"

function hideLevel( _levelId, _imgId ) {
	var thisLevel = document.getElementById( _levelId );
	var thisImg = document.getElementById( _imgId );
	thisLevel.style.display = "none";
	thisImg.src = plusImg.src;
	}
	
function hideAll() {
	hideLevel("chapters", "chapImg");
	hideLevel("projectlinks", "projImg");
	hideLevel("interestlinks", "intsImg");
	}
	
function showLevel( _levelId, _imgId ) {
	var thisLevel = document.getElementById( _levelId );
	var thisImg = document.getElementById( _imgId );
	if ( thisLevel.style.display == "none") {
		thisLevel.style.display = "block";
		thisImg.src = minusImg.src;
		}
	else {
		hideLevel( _levelId, _imgId);
		}
	}
// -->
</script>

Bill Pena is a freelance Web/information designer and writer. He was also the designer for Safari: Tech Books Online, O'Reilly's online books service.


O'Reilly & Associates recently released (November 2001) Designing with JavaScript, 2nd Edition.

Return to the JavaScript and CSS DevCenter.

Copyright © 2009 O'Reilly Media, Inc.