Creating a Dynamic Navigation Menu
I’ve gotten a few emails recently asking how I created the dynamic menu in the color selection component of the Durable Wordpress theme I released a few weeks back.
Instead of answering questions individually, I thought I would run through how I created the menu, so others can benefit.
Here is a step by step guide. You should have an HTML page ready to add code into, and have a blank external javascript file linked in the header to add the javascript functions to.
Step 1: The HTML
The menu is just a simple unordered list with the ID set as “menu”. Each sub-level of the menu is another unordered list within the parents list item. You can create as many levels as your heart desires, as the javascript we’ll write in the next step can handle this.
Here is the HTML I’ll use for the example:
<ul id="menu">
<li><a href="fruit.htm" title="Fruit">Fruit</a>
<ul>
<li><a href="apple.htm" title="Apple">Apple</a>
<ul>
<li><a href="grannysmith.htm" title="Granny Smith">Granny Smith</a></li>
</ul>
</li>
<li><a href="orange.htm" title="Orange">Orange</a></li>
<li><a href="banana.htm" title="Banana">Banana</a></li>
</ul>
</li>
<li><a href="veg.htm" title="Fruit">Vegetables</a>
<ul>
<li><a href="carrot.htm" title="Carrot">Carrot</a></li>
<li><a href="onion.htm" title="Onion">Onion</a></li>
<li><a href="peas.htm" title="Peas">Peas</a></li>
</ul>
</li>
</ul>
This will produce a vanilla HTML list which you can check out in example 1.
Step 2: Collapsing the Menu
The next step is to make that vanilla HTML list into something a little less rigid. This is where we’ll dip into some fancy DOM scripting.
The first thing we want to do is collapse the menu once it’s been loaded. We’ll do this by writing a recursive function called collapseMenu(), to traverse through each nested list in your menu, and hide it.
function collapseMenu(node) {
if (!document.getElementById) return false;
if (!document.getElementById("menu")) return false;
if (!node) node = document.getElementById("menu");
if (node.childNodes.length > 0) {
for (var i=0; i<node.childNodes.length; i++) {
var child = node.childNodes[i];
if (child.nodeName == “UL”) {
child.style.display = “none”;
}
collapseMenu(child);
}
}
}
For those who’re interested, a recursive function is one that calls upon itself.
In this function, we start at the very first node which is the <ul id=”menu”> node. We then count how many child nodes there are, and if there’s 1 or more, we check to see if any of the children are <ul> nodes. If so, then we hide them, by setting child.style.display = “none”.
Then there’s that recursive part, we call the same function again, but this time pass in the child as the starting node. This will go through and check all the grandchildren. The recursion will continue until there are no remaining child nodes to cycle through and everything is hidden.
Step 3: Bringing Those Links to Life
As it stands right now, the HTML we created in step one has no javascript associated with it. By removing the javascript completely from the HTML and placing it in an external file we are paving the way for graceful degradation. This means that if any of your users have javascript turned off, the menu will not collapse, and they will be able to use your navigation as though it was a static list.
So, we want to create a function called prepareMenu() that will go through and find every anchor within the menu, and add an “onclick” event to each one.
function prepareMenu() {
if (!document.getElementById || !document.getElementsByTagName) return false;
if (!document.getElementById("menu")) return false;
if (!menu.getElementsByTagName("a")) return false;
var links = document.getElementById("menu").getElementsByTagName("a");
for (var i=0; i<links.length; i++) {
links[i].onclick = function() {
toggleMenu(this.parentNode.getElementsByTagName(”UL”)[0], this.href);
return false;
}
}
}
For every link within the menu, we have added an “onclick” event, which calls the function toggleMenu(). To this function, we will pass the nested list which represents the sub menu items, and also the “href” (destination) of the link.
Here is the code for toggleMenu() which will handle the hiding and displaying of sub-menus when a link is clicked:
function toggleMenu(node, link) {
if (!document.getElementById) return false;
if (!link) return false;
if (!node) location.href = link.href;
if (node.style.display == "") {
node.style.display = "none";
} else {
node.style.display = "";
}
}
This is pretty straightforward, the function will check the display property of the node, if it is hidden then display it, if not then hide it. One small detail however is that if the node passed to this function is null, then the links href is followed. This means that if the link clicked has no submenu, then the function will direct the user to the links location rather than trying to display a non-exisistent sub-menu.
Step 4: Add the OnLoad Events
Right now we have our functions ready to go, but they are not being called at all by our code. This means they will quite happily sit in your javascript file doing absolutely nothing. We need to add two “onload” events so that when the page has finished loading, both collapseMenu() and prepareMenu() are executed.
We can add multiple “onload” events using the excellent addLoadEvent() function written by Simon Willison:
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
oldonload();
func();
}
}
}
You can add this function directly into your main javascript file, or you can create a separate javascript file with just this function, and include it with a <script> tag in the header of your HTML.
Now that we have Simon’s function to use, we can add two lines to the top of our javascript file:
addLoadEvent(collapseMenu);
addLoadEvent(prepareMenu);
This will ensure that when the page has finished loading, both the collapse and prepare menu functions are executed.
Now that we have most of the javascript and HTML in place, you should see something like example 2 when you load your page and click around the menu.
Although this is a perfectly adequate menu, it’s a little boring. Now is the time to snazz up the menu with a little extra javascript and some CSS love.
Step 5: Bring on the Goodies
For the menu I made for the Durable Wordpress Theme, whenever you clicked a top level node of the menu, all the other levels would collapse. This meant that the menu wouldn’t ever get too big, and would only allow the user to see one level at a time.
To collapse the top level nodes when any top level node is clicked, we need to add a check to toggleMenu() and create another function hideTopLevels():
function hideTopLevels() {
if (!document.getElementById) return false;
if (!(node = document.getElementById("menu"))) return false;
if (node.childNodes.length > 0) {
for (var i=0; i<node.childNodes.length; i++) {
var child = node.childNodes[i];
for(var j=0; j<child.childNodes.length; j++) {
var grandchild = child.childNodes[j];
if (grandchild.nodeName == “UL”) {
if (grandchild.style.display == ”) {
grandchild.style.display = “none”;
}
}
}
}
}
}
Now we must add a quick check in toggleMenu() that will call this function if we have clicked a top level node:
Add in these lines after if (!node) return false;
if (node.parentNode.parentNode.id == "menu") {
hideTopLevels();
}
Now you should have something like example 3 where top level nodes will collapse when another is clicked.
Finally, we can really spice up the menu by adding transitional effects using the Script.aculo.us javascript library and adding some nice CSS styles.
Download Script.aculo.us and link the javascript files in the <head> section of your HTML. If you are unsure how to do this, follow the Scriptaculous installation instructions.
Once you’ve got Scriptaculous linked and ready for use, we can change a couple of lines in toggleMenu() and hideTopLevels() so that the effects are used:
In toggleMenu() change:
if (node.style.display == "") {
node.style.display = "none";
} else {
node.style.display = "";
}
To this:
if (node.style.display == "") {
Effect.BlindUp(node, {duration: 0.2});
} else {
Effect.BlindDown(node, {duration: 0.2});
}
Then in hideTopLevels() change:
grandchild.style.display = "none";
To this:
Effect.BlindUp(grandchild, {duration: 0.2});
Your list should now be displaying some pretty cool looking transitions, rather than jumping from hiding to displaying.
I’ve added some CSS to my final example, but you are free to style the list any way you like to suit your own taste. Here is the final version with the transitions added, some new content, and my own example CSS linked above.
You are free to edit whatever you like and take this code away to use on your own site. I hope you enjoyed the walk through!
A zipped up version of the menu is availiable for download.
UPDATE: Now includes latest version of Prototype and Scriptaculous which fixes the drop down effects bug mentioned in the comments below.
47 People Added Their Comments
Hello Andy. Can you incorporate this dynamic menu on your next release of Durable as an optional sidebar? Currently, I have tweaked the Durable theme so that the original postmeta box ono the front page now displays some sort of asides. I was searching for a simple dynamic menu sidebar to replace it. I did find some but they were too complicated to implement.
Anyway, this is just a suggestion. I’d like the dynamic sidebar menu to house the various links and categories so that I can use the original drop-down “drawer” for some other stuff.
What do you think? Durable would become the coolest theme ever!
I absolutely love it. Awesome work and I’m definitely going to be having fun with this.
you can always add the delicious links to the sidebar.
i did, and it is very easy to do with the delicious plugin and editing the sidebar.php page.
nice theme. thanks tons. or tonnes.
patrick
Dynamische Navigationen…
Andy Peatling von cssdev zeigt in seinem Artikel Creating a Dynamic Navigation Menu wie man recht einfach mit html und Javascript (neudeutsch: AJAX) dynamisch Menü erstellt. Die übersichtliche Schritt für Schritt Erklärung animiert …
Hi,
Love the dynamic menu, one thing I’ve noticed when using Firefox
1.5 is that if you repeatedly open and close the same sub navigation,
for example the “United Kingdom” each time the sub menu opens a little less is shown, I cant figure out why this is happening.
Simon: This seems to be a bug with the Scriptaculous library. If you remove the transitions and just have the menu items show and hide then the problem goes away.
I’ve reported the problem, as I’ve seen this problem in other places on the web.
Great thing, this dynamic menu. i would like to use it, but I’ve got one question: Is there a way to make the menu horizontal ?
Do u mine sharing the theme u using now?
it is so pretty especially the menu!
I keep getting an error saying: Error: Effect is not defined
Source File: http://127.0.0.1/sites/roaiptv/jscript/global.js
Line: 53
Fixed it! There’s an error in the script.aculo.us file:
Replace:
// inserting via DOM fails in Safari 2.0, so brute force approach
document.write(”);
With:
// inserting via DOM fails in Safari 2.0, so brute force approach
document.write(”);
Im impressed from your design and ajax knowledge.
I have surfed much time on internet and see more ajax example, your website is doing very well.
Compliment ! bravo !
I also have a problem with menu showing less and less, when I repeatedly open and close the same sub navigation.
I see that this is not happening on main navigation on this site. How did you fix it ?
Hi
I too notice the problem with less and less of the drop down showing
What file and part of code do I have to edit to stop this from happening.
Thanks, and would like to say what a great menu!
Hi guys, yes the problems you are coming across are to do with the script.aculo.us library and is a known bug. If you want it to stop happening, follow the examples up to the point where the effects are added.
Hi Andy, first off great job on the menu!
I am looking to replace a flash based menu that I created. Anyways the one feature that I would like to add is for the menu to be expanded properly based on the page it is being displayed on. So if I click a nested link and go to that page the category that the page is in is still expanded. You can see what I’m talking about here: http://dsdial.com. Do you have any advice on ways to implement this?
I already have the PHP code to pass the filename so at least the first step is not an issue.
Thanks!
-Scott
hm there is a small bug, i think:
klick: europe > uk > spain > europe > europe > uk > spain
the li doesnt get back again
Question. I click “fruit”. Then when I click “apple” i get a “style is null or not an object”. I have narrowed it down to it being in the function toggleMenu(node, link). Probably more specifically this line….if (node.style.display == “”). When i click “apple” or a child of the parent the node is “undefined”. I could really use some help/guidance here…
Thank you very much,
Patrick
Correction, I click “carrot” or “granny smith” and it gives me the error. Help with this one anyone??
instead of
if (node.style.display == “”) {
Effect.BlindUp(node, {duration: 0.2});
} else {
Effect.BlindDown(node, {duration: 0.2});
}
you can try
if (node.style.display == “”) {
Effect.Fade(node, {duration: 0.2});
} else {
Effect.Appear(node, {duration: 0.2});
}
it still looks pretty good and gets rid of the double click truncating issue.
That still doesn’t fix my problem. It is because the node does not exist and when it try to set the style.display line it gives the js error…..still looking for some help here….much appreciated though
Cheers!
Is there a way to close second level menus when a sibling is clicked? So, in you example, if I first clicked “United Kingdom” then clicked “Spain” the “United Kingdom” menu would close?
Thanks!
Also, I noticed an interesting thing. If you use suckerfish for drop downs on the same page, in IE7 only, after the user activates the dynamic menu the dropdowns don’t hide. In IE6 this problem does not occur.
Another bug
I was about to implement it, when I found a little bug.
If you click many times in the same menu, the sub-menus get smaller till disapear.
IE6 / Firefox2.0
AlbertoMarlboro : if u read the previous comments, u can see that Andy already talked 2 times of the “sub menu gettin smaller” bug
He says it’s somethin’ related to script.aculo.us library.
Personally, I managed to fix it by changin’ some code into Andy’s source, so that I use jQuery standard fxmodule’s slide effect instead of script.aculo.us. Give it a try, it works and it’s quite simple. Cheers.
Indeed there is a problem with the Scriptaculous library when using the SlideUp/SlideDown or the Blind effects however, you can use most likely any other effect shown here:
http://wiki.script.aculo.us/scriptaculous/show/CombinationEffectsDemo
Just view source for a how-to..
Right now I am using the Effect.Fade / Effect.Appear and there is no problem..
Excellent write-up!
Cheers.
Just a note, the “interesting thing” I noticed had nothing to with a conflict between libraries and suckerfish as I first thought, it is in fact an irritating IE7 bug.
I’d still really like to know how to only have one sub menu open at a time in the same way that only one section is open at a time… please?
Hi All, Im having a problem with Ie7 when you click the first link the sub nav aligns further left than the actual surounding tag. Then the second time I click the link it sorts itself.
Can someone please help
This is a great tutorial, exactly what I was looking for. Thanks very much for posting it.
Anyone know how to implement the JQuery code to fix the disappearing problem?
How would one keep the menu from collapsing if you click through to a page? I want a user to be able to see where they are in the menu. If it collapses each time, this will not occur.
It looks like a few people out there want to know this solution as well.
Hello Andy,
Thanks for sharing this menu. It is very nice and exactly what I was looking for. I just want to ask you how I can enable the “click” function to the main nodes. As an example, I would like that whenever I click on EUROPE or AFRICA etc, the browser can move to “Europe.htm” or “Africa.htm”.
Thanks for your hint.
Regards
Ivan
I am working on same type of menu,but my menu is dynamic,whenever mouse goes on main menu item its childs are displayed and on mouse out it is removed,can you give some idea for working on cascading menu(like in windows start menu works).
Hi there, thx for your work, i used it to make my own menu, but i have a little problem. With Firefox 2.01 my menu doesnt expand, but your “final version” runs well in my browser. I tried to “copy-paste” your functions – that doesn’t fix it.
Anyone have a little hint for me ?
Thx
Any fix to the main nodes working?? If fruit could link, this would work for me. As it is I can only run the menu on one page and not the secondary pages as the main nodes don’t work to get back up the tree. Overall a good first attempt.
Thank you so much , Andy, for sharing this menu.
How would one keep the menu from collapsing if you click through to a page? I want a user to be able to see where they are in the menu. If it collapses each time, this will not occur.
Anybody?
Thank you in advance.
If I wish to allocate current section at opening page how to realize it in this script
Wow, thanks for this! My javascript skills are pathetic, so this is a huge help (and it looks awesome!)
Naturally, I had the same problem as everybody else with the sub-menus appearing a little less each time. I find that just removing the duration fixes it (so Effect.BlindUp(node, {duration: 0.2}) becomes just Effect.BlindUp(node), etc.) After that it works perfectly (although the effects are a little slow!) Still, I hope scriptaculous fixes that bug soon…
Just change the duration to 0.3 and everything will work fine.
Thanks for this great menu! I am creating a horizontal menu using the above but I’m having a problem.
To explain, let’s say I have some tabs across – Tab1, Tab2, Tab3, and Tab4. Each have a dropdown menu.
When Tab1’s menu drops down on mouseover, it doesn’t extend under Tab2 but rather pushes Tab2 over.
It’s as if it’s in a table with columns and doesn’t overlap – although it’s not in a table. I hope this makes sense.
Has anyone had a similar issue and know a solution? Thanks.
I would also like some help keeping the menu from collapsing when you click a submenu item.
This is a great menu. Thank you for your work and your help implementing it.
Hello there,
i have written an additional function which “unfolds” the current menu item (when the list for the menu is created with the wordpress wp_list_pages(‘title_li=’); function) using the classNames this wordpress-function assigns to the list-items.
it may need some adjustment for your page. works for me in safari, opera, ie (version 6 on win) and firefox.
it seems this is what some people in the comments were asking for.
cheers
tobi
you have to add an additional
addLoadEvent(unfoldMenuItem);
and this function to your script
—-
function unfoldMenuItem(node){
if (!document.getElementById) return false;
if (!document.getElementById(“menu”)) return false;
if (!node) node = document.getElementById(“menu”);
li_s = node.getElementsByTagName(“li”);
for (var i=0; i < li_s.length; i++) {
classNames = li_s[i].className;
if (classNames.indexOf(“current_page_item”) != -1) {
current_li = li_s[i];
}
}
if(current_li){
parent_ul = current_li.parentNode;
parent_ul.style.display = ‘block’;
while(parent_ul.parentNode.nodeName == “LI”){
parent_ul = parent_ul.parentNode.parentNode;
parent_ul.style.display = ‘block’;
}
}
}
—-
what i forgot to mention in my previous comment.
great work and thanks for sharing!
tobi
Heh, this is very buggy in the latest version of Firefox :S
You say:
Add in these lines after if (!node) return false;
But there is no such line in the toggleMenu function!
Aside from that, can’t get this to work at all – doesn’t even get past the onload bits in FF.
I added a small code change in the two functions to resolve this.
In toggleMenu, change:
hideTopLevels();
to
hideTopLevels(node);
In hideTopLevels, add an if check before the effect:
Effect.BlindUp(grandchild, {duration: 0.2});
to
if (grandchild != currentNode) {
Effect.BlindUp(grandchild, {duration: 0.2});
}
I forgot to mention in my last comment, add currentNode in the function input:
function hideTopLevels(currentNode) {
…
Thank you, thank you, thank you! Thank you for writing the additional function to unfold the current menu item. That is just what I needed to make this menu usable for me. This menu has been a blessing!
Tobias posted a great starting point for persistent display. However, because the display of the node is being set to “block”, it breaks the blinding when another level is expanded. The fix for this is simple:
Change ONLY the first instance of
parent_ul.style.display = ‘block’;
To
Effect.BlindDown(parent_ul, {duration: 0.2});
Thanks Tobias!
2 Trackbacks
[...] Creating a dynamic navigation menuStep by step guide Submitted: 3 days ago Category: Technology Submitter: RssFeed Website: blazenewmedia.com Report this link: Click here to report Comments: 0 [...]
[...] Blaze New Media » » Creating a Dynamic Navigation Menu Blaze New Media is a boutique interface design and development studio based in Vancouver, BC. We make web interfaces that are simple yet striking, elegant yet usable. (tags: css html javascript menu menus navigation scriptaculous tradesinfo) [...]