skip navigation

JavaScript: Building Menus using DHTML

You might have noticed the navigation menu that appears on the top right of pages on The Art of Web. This menu is dynamically generated using JavaScript (DHTML) after the page has loaded, based on the headings present on each page. Even the numbers for the headings and the anchors for the links are added dynamically.

How does it work?

We have a single function buildMenu that is called by the window.onload event. It accepts two parameters: the id of the element that is to contain the generated menu and the HTML tag used to mark up headings on the page.

The code for the buildMenu function is as follows:

// Original JavaScript code by Chirp Internet: www.chirp.com.au // Please acknowledge use of this code by including this header. function buildMenu(target_id, heading_tag) { // abort if using MSIE (Mac) or DHTML functions not present if(navigator.userAgent.indexOf("Mac_PowerPC") != -1) return; if(!document.createElement) return; // the element identified by target_id will contain the menu var target = document.getElementById(target_id); // scan the DOM for tags matching heading_tag (eg. 'H2') var headings = document.getElementsByTagName(heading_tag); if(headings.length > 1) { // found two or more headings - start building ordered list var menuList = document.createElement('OL'); // for each heading on the page for(var i=0; i < headings.length; i++) { // use exising id or add new id to heading to use as anchor var anchorName = "section_" + i; if(headings[i].id == '') { headings[i].setAttribute('id', anchorName); } else { anchorName = headings[i].id; } // extract text from heading var headingText = headings[i].firstChild.nodeValue // prefix heading with number headings[i].firstChild.nodeValue = (i+1) + ". " + headingText; // create link to heading var menuLink = document.createElement('A'); menuLink.setAttribute('href', '#' + anchorName); menuLink.appendChild(document.createTextNode(headingText)); // create list item to contain link var listItem = document.createElement('LI'); listItem.appendChild(menuLink); // append list item to list menuList.appendChild(listItem); } // remove contents of target element while(target.hasChildNodes()) target.removeChild(target.firstChild); // insert list into target element target.appendChild(menuList); } else { // no headings found - remove target element from page target.parentNode.removeChild(target); } }

You can copy the above code or download it as a javascript file using the link below.

Instructions for implementation

There are three steps to implement this sytem on your website:

The first step is to include the external JavaScript file that contains the function. The best place for this is in the HEAD of your document but elsewhere on the page will also work:

<script type="text/javascript" src="buildmenu.js"></script>

Then you need to have this function called by an onLoad event. You have at least two options for this. You can use the BODY tag:

<body onLoad="buildMenu('target_id', 'h2');">

or you can use JavaScript:

<script type="text/javascript"> window.onload = function() { buildMenu('target_id', 'h2'); } </script>

The latter is an altogether better solution as it's compatible with XHTML and allows for easier integration with other JavaScript applications that might want to use the same onLoad event.

Note: for more information on this subject, including his extremely useful addLoadEvent function, see the link to Simon Willison's Weblog under References below.

Finally you need to have add an element (usually a DIV) that will contain the menu:

<div id="target_id">building menu...</div>

You can change the appearance (size, colour, ...) of the menu using CSS rather than by adding styles to the HTML.

The reason for using the onLoad (or window.onload) event is that for the script to work it has to be able to read the entire page, and thats only possible once the page has finished loading.

In most cases people won't see the menu placeholder as it's replaced with the menu within a fraction of a second, or, if there are no headings found on the page, removed from the DOM completely.

Known Limitations

  • This code will not work at all in Internet Explorer 5 (Mac), but does work as advertised in MSIE 6.0 (Windows) and possibly in earlier versions;
  • There may be errors if your headings contain other HTML tags such as links instead of just plain text; and
  • If you're already using the onLoad event or wish to add other functions to that event, you will need to adjust the code.

Working examples

For anyone who's stuck, here's the bare minimum code needed to get this working:

<div id="submenu" style="border: 2px solid blue;">building menu...</div> <script type="text/javascript" src="/buildmenu.js"></script> <script type="text/javascript"> window.onload = function() { buildMenu('submenu', 'h2'); } </script>

This placed a DIV with an id of submenu, which will be populated after the page has fully loaded with a menu linking to all H2 elements on the page. Please don't ask how to remove the blue border - you should be able to work that out yourself.

And in the case that you already have an onload event you need to first preserve the old function and then add the call to buildMenu():

<div id="submenu" style="border: 2px solid blue;">building menu...</div> <script type="text/javascript" src="/buildmenu.js"></script> <script type="text/javascript"> function buildmenu_init() { buildMenu('submenu', 'h2'); } var oldonload = window.onload; if(typeof window.onload != 'function') { window.onload = buildmenu_init; } else { window.onload = function() { oldonload(); buildmenu_init(); } } </script>

Really you should always use this more advanced version of the code, even if you don't have other onload events, just in case you want to add one in the future, or you include some third-party code that does.

As always, please report and problems or errors through the Feedback link below.

References

< Back to JavaScript


Bookmark and Share

[top]