skip to content

JavaScript: Building an automatic submenu

 Tweet1 Share0 Tweets

You might have noticed the navigation menu that appears on the top right of the article pages on The Art of Web. This menu is dynamically generated using JavaScript (DHTML) when the page is loaded so we don't have to adjust it every time the page changes.

All second level (H2) headings on a page are included, and numbers and anchor links for the headings generated automatically.

How does it work?

We use a "self-executing anonymous function" to create the menu. It accepts two parameters: the id of the element that is to contain the generated menu; and the HTML tag used to identify headings on the page (defaults to H2 if not supplied).

All you need to do is include a placeholder where you want the menu to be inserted: <div id="submenu">building menu...</div>

and then include the following code at the bottom of your HTML page. The parameters are specifed in the last line:

<script type="text/javascript"> // Original JavaScript code by Chirp Internet: www.chirp.com.au // Please acknowledge use of this code by including this header. (function(targetId, headingTag) { var target = document.getElementById(targetId); var headings = document.getElementsByTagName(headingTag || "h2"); if(headings.length > 1) { // construct an ordered list of links var menuList = document.createElement("OL"); for(var i=0; i < headings.length; i++) { var anchorName = ""; if(headings[i].id) { anchorName = headings[i].id; } else { anchorName = "section_" + i; headings[i].setAttribute("id", anchorName); } var headingText = headings[i].firstChild.nodeValue headings[i].firstChild.nodeValue = (i+1) + ". " + headingText; var menuLink = document.createElement("A"); menuLink.setAttribute("href", "#" + anchorName); menuLink.appendChild(document.createTextNode(headingText)); var listItem = document.createElement("LI"); listItem.appendChild(menuLink); menuList.appendChild(listItem); } // remove all nodes from inside target element while(target.hasChildNodes()) target.removeChild(target.firstChild); // insert our generated menu into the target element target.appendChild(menuList); } else { // remove the target element from the DOM target.parentNode.removeChild(target); } })("submenu", "h2"); </script>

You can copy the above code or download it as a separate javascript file using the link further down the page.

Instructions for implementation

First you need to include a placeholder for the submenu. Normally this would be a DIV element. The submenu will replace any existing contents of the selected element with an unordered list (OL) listing and linking to all the headings on the page.

<div id="submenu"><!-- --></div>

The submenu can be positioned and styled using CSS on the #submenu element (the name can change, but must match that in the function call).

Then all you need to to is include the above code at the end of your HTML page (or anywhere after the last heading that you want to be included in the submenu).

You can also include it as a separate file, again at the bottom of the page:

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

Or you can create an onload event that triggers the script after the DOM has finished loading.

That's all. The script will run as the page loads, scan the page for H2 headings (or other tags if specified), add numbers and anchors to them and populate the submenu with links. Normally this happens in a fraction of a second once the page has loaded.

If there are no headings found, or only one, the submenu element is removed from the DOM.

How to style the submenu

On page load the script dynamically replaces the contents of the #submenu element as follows:

Before:

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

After:

<div id="submenu"> <ol> <li><a href="#section_0">How does it work?</a></li> <li><a href="#section_1">Instructions for implementation</a></li> <li><a href="#section_2">Sample CSS styles</a></li> <li><a href="#section_3">References</a></li> <li><a href="#send_feedback">Send Feedback</a></li></ol> </ol> </div>

You can see here that when an id has already been assigned to one of the headings, it is used automatically as the anchor link. Otherwise a new, numbered, id is assigned to the headings.

So all we need to do now is apply some styles to the DIV and OL elements. The CSS styles we're currently using are:

<style type="text/css"> #submenu { float: right; margin: 1em 0; padding: 4px; background: #fcfaf0; border: 2px solid #E0D8B7; border-radius: 1em; font-size: 11px; } #submenu ol { margin: 0; padding: 0 0 0 30px; } </style>

This places the menu at the top right of the page as you can see. On The Art of Web we've also added a transition and hover effect using CSS transforms to make the menu zoom out.

Other options could be used to place the menu inline at the top of the page as a "Table of Contents" or to have it instead displayed as a dropdown menu - especially useful for mobile devices.

Using an onload event

The 'correct' way to fire this kind of JavaScript function is to attach a handler to the DOMContentLoaded event. This is not supported in Internet Explorer 8, so a fallback is included using attachEvent.

Only minor changes to the code are required as you can see below:

<script type="text/javascript"> // Original JavaScript code by Chirp Internet: www.chirp.com.au // Please acknowledge use of this code by including this header. var buildMenu = function(targetId, headingTag) { var target = document.getElementById(targetId); var headings = document.getElementsByTagName(headingTag || "h2"); if(headings.length > 1) { // construct an ordered list of links var menuList = document.createElement("OL"); for(var i=0; i < headings.length; i++) { var anchorName = ""; if(headings[i].id) { anchorName = headings[i].id; } else { anchorName = "section_" + i; headings[i].setAttribute("id", anchorName); } var headingText = headings[i].firstChild.nodeValue headings[i].firstChild.nodeValue = (i+1) + ". " + headingText; var menuLink = document.createElement("A"); menuLink.setAttribute("href", "#" + anchorName); menuLink.appendChild(document.createTextNode(headingText)); var listItem = document.createElement("LI"); listItem.appendChild(menuLink); menuList.appendChild(listItem); } // remove all nodes from inside target element while(target.hasChildNodes()) target.removeChild(target.firstChild); // insert our generated menu into the target element target.appendChild(menuList); } else { // remove the target element from the DOM target.parentNode.removeChild(target); } }; if(document.addEventListener) { document.addEventListener("DOMContentLoaded", function() { buildMenu("submenu", "h2"); }, false); } else { window.attachEvent("onload", function() { buildMenu("submenu", "h2"); }); } </script>

The difference is that the script will execute only after all the DOM elements have been rendered rather than during the page load when the script is encountered.

References

< JavaScript

Send a message to The Art of Web:


used only for us to reply, and to display your gravatar.

<- copy the digits from the image into this box

press <Esc> or click outside this box to close

User Comments

Post your comment or question

22 October, 2015

This code seems to be what I am looking for. Actually I want to be able create menus and sub sub menus and the labeling must come from database. currently I did it where on load the menus are filled with data from database. But I want to be able to update menu list prior to changes in the database without having to code again. Good job and I count on you, thanks.

top