Creating a Drop-down Menu
We had an article on MSDN for creating a drop-down menu, but after repeated attempts, I, like many of our customers, couldn't make the code work, so we're in the process of removing it. We will (mostly likely) eventually replace it. In the meantime, though, I promised that I would provide an alternative. I have working code for a drop-down menu that works in IE, Mozilla, and Netscape Navigator (sort of -- I've noticed some funky onmouseout behavior in Mozilla and Netscape Navigator that I haven't been able to resolve).
The example is comprised of HTML, JavaScript, and CSS and is contained in the following two files:
Here's a simple explanation of the menu table and the accompanying script.
Menu Table
The menu table is simple, but to make it to be usable on any ... uh, almost ... page, I surrounded the entire table with a DIV element. The following code shows a shortened version of the menu table with the DIV element. The DIV element has an id attribute value so that the JavaScript code can access the child elements. This was necessary in order to allow anyone who might use this code to have <div> tags elsewhere on the page without interfering with the expand/collapse functions of the JavaScript.
<div id="menu">
<table width="100%">
<tr>
<td>
... (missing code)
</td>
</tr>
</table>
</div>
The table has a width of 100%, but if you wanted to specify an exact pixel measurement, you could easily do that without messing up the menus. The table is comprised of one row (TR element) with multiple columns (TD elements). In the live version, there are five columns with drop-down menus. Each drop-down menu lives within its own cell in the table. The code within this TD element is somewhat complex to provide for the proper expand/collapse funtionality of the drop-down menu. The following shows the code for the first TD element. Each of the TD elements are almost exactly the same with a few exceptions, which I'll explain in a moment.
<td class="button" width="20%" valign="top" id="item1"
onmouseover="expand(this.id, 'menu1');"
onmouseout="collapse(this.id, 'menu1');">
<p><b>Menu 1</b></p>
... (missing code)
</td>
The class attribute names the style in the CSS that defines the table cells appearance when a user first displays the page. The width attribute is set to 20% so that each of the five drop-down menus will be of equal space. Again this is an easy thing to change if you want to modify the code to suit your own needs. The id attribute is necessary as the scripts use these to access the TD element.
The missing code is the nested DIV element that contains the items in the drop-down menu. The DIV element for the first menu item is shown below.
<div id="menu1" class="exp" onmouseout="collapse('item1', this.id);">
<p class="button"><a href="page.htm" class="menuitem">Item 1</a></p>
<p class="button"><a href="page.htm" class="menuitem">Item 2</a></p>
<p class="button"><a href="page.htm" class="menuitem">Item 3</a></p>
<p class="button"><a href="page.htm" class="menuitem">Item 4</a></p>
</div>
The nested DIV element also provides a value for the id attribute. Again, the class attribute specifies the style to use when displaying the menu. Within the DIV element are paragraphs (P elements), with a class attribute value of "button", a style defined in the attached CSS file, and links (A elements), with a class attribute value of "menuitem", another style defined in the attached CSS. The menuitem style defines link, visited, hover, and active link styles.
Then, of course, there are the onmouseover and onmouseout events. The top-level menu item, contained in the TD element, specifies both the onmouseover and onmouseout events. However, the drop-down menu, contained in the nested DIV element, specifies only the onmouseout event. The JavaScript functions that these events call require both the id attribute for the TD element and the nested DIV element.
If you modify this script, adding or removing drop-down menus, you will want to change the second argument for these events. The following code shows the parts that you will need to change as you add and remove menus. The red code shows the id attribute for the top-level menu item (TD element) and all the places where you would need to change this in the event function calls, and the blue code shows the id attribute for the drop-down menu (DIV element) and all the places where you would need to change this in the event functions calls. Basically, all the code in red must match and all the code in blue must match.
<td class="button" width="20%" valign="top" id="item1"
onmouseover="expand(this.id, 'menu1');"
onmouseout="collapse(this.id, 'menu1');"><p><b>Menu 1</b></p>
<div id="menu1" class="exp" onmouseout="collapse('item1', this.id);">
<p class="button"><a href="page.htm" class="menuitem">Item 1</a></p>
<p class="button"><a href="page.htm" class="menuitem">Item 2</a></p>
<p class="button"><a href="page.htm" class="menuitem">Item 3</a></p>
<p class="button"><a href="page.htm" class="menuitem">Item 4</a></p>
</div>
</td>
Notes
- Because the event function calls use
this.id
, there's no need to specify the id attribute values for these. - The code is purple above is code that you can safely change without affecting how the menus function.
If you look at the full code in the page (see Drop-down menu example above), the only difference between each of the TD/DIV element pairs is the id attribute value. You'll notice that each menu is numbered consecutively (i.e., item1/menu1, item2/menu2, item3/menu3, etc.). I did this for ease of use. You can, of course, provide any id attribute values you want, as long as they're not the same as another element on the page and as long as you change the appropriate arguments in the event function calls so that they match as shown above.
JavaScript Functions
Now for the functions. There are three functions: one for collapsing all drop-down menus, one for expanding a single menu, and one for collapsing a single menu.
The first one, called collapseAll, collapses all menus. The collapseAll function takes no arguments and is called once when the page loads in the onload event for the BODY. This code is relatively simple and is shown in the following example:
function collapseAll()
{
var menuDiv = document.getElementById("menu");
var divs = menuDiv.getElementsByTagName("div");
var div;
for (i = 0; i < divs.length; i++)
{
div = divs[i];
div.style.visibility = "hidden";
div.style.display = "none";
}
}
The second function, called expand, is where most of the action happens ... uh, at least half of it. The expand function has two parameters: Then, the function is basically a bunch of scripted CSS that is used to show the DIV element and change the appearance of the top-level menu. The expand function is shown below:
function expand(s, m)
{
var browser = window.navigator.appName;
var dif = 0;
if (browser != "Microsoft Internet Explorer") { dif = 12; }
var d = document.getElementById(m);
var td = document.getElementById(s);
var left = td.offsetLeft;
var top = td.offsetTop + 58;
var width = td.offsetWidth - dif;
td.style.color = "#FFFFFF";
td.style.backgroundColor = "#686496";
d.style.top = top;
d.style.left = left;
d.style.width = width;
d.style.position = "absolute";
d.style.visibility = "visible";
d.style.display = "block";
}
There are a few things you should know about this code.
- First, there is a difference between how IE displays the menu and how Mozilla and Netscape Navigator display the menu. (Note that I haven't tested this code in Opera.) In Moz and NN, the width doesn't show up properly, so I had to specify a value that would account for the difference between the offsetWidth and the actual width of the table in these browsers. The number isn't arbitrary; I determined it by trial and error, and this was the only number than worked. I have no idea why this is different only that it is and this number fixes it. If you make huge changes to the menu, this number may need to be changed also.
- Second, the number added to the offsetTop value accounts for the height of the first line in the top-level menu as well as the top heading paragraph. Again, this number is not arbitrary and accounts for the space between the top of the browser and the top of the drop-down menu. I arrived at this number by a trial and error process. If your top-level menu text runs onto multiple lines and/or you have a large graphic heading or other things above the menu bar, this number will likely change.
- Third, if you want to change the appearance of the menu, you can change any of the values in purple in the code above to appropriate and desired values without affecting how the script functions.
And finally, the third function, called collapse, collapses a single drop-down menu. This is where the other half of the action happens. The collapse function has two parameters: the first is the id attribute value for the TD element (the top-level menu item), the second is is the id attribute value for the nested DIV element (the drop-down menu). The collapse function is shown in the following code:
function collapse(s, m)
{
var d = document.getElementById(m);
var td = document.getElementById(s);
td.style.color = "black";
td.style.backgroundColor = "#EEEEEE";
d.style.position = "static";
d.style.visibility = "hidden";
d.style.display = "none";
}
There's absolutely nothing major about this code. It changes the appearance of the TD element back to the original formatting, and hides the DIV element. Ideally, this formatting would match what is in the attached CSS, which in this case, it does. You can change any of the values in purple in the code above to appropriate and desired values without affecting how the script functions.
Conclusion
And that's all there is to it. Have fun using this, and if you need help, please let me know. If you are looking for a drop-down menu, feel free to use it, or if you do an Internet search, you will find many more examples and just as many ways to create drop-down menus, and many of them much cooler than my litte example here.
Have fun, and enjoy playing with the code, and if you can tell me what's up with the Mozilla/Netscape issue, please do. I've tried all types of arrangements for the code to see if order made a difference, if putting the event function call in a different place made a difference, and I've been relatively unsuccessful getting it to be completely functional in these browsers. Sometimes the onmouseout event just doesn't fire.
Anyway, as usual, my only real caveat is that the code won't work at all if the user has scripting turned off in their browser, but IMHO, that's not a reason to not use it or any other script.
Comments
- Anonymous
October 05, 2004
Fails miserably in IE 5.2 Mac...which I don't use except for testing but I'm just sayin'. - Anonymous
October 06, 2004
Any advanced css, valid or not, will fail in IE 5.2 Mac in my experience. My experience hasn't been all that great, but it has been enough to frustrate the heck out of me.
I think Mac users who use IE don't deserve properly rendered css. :-) - Anonymous
October 06, 2004
Can this be adapted to use nested dropdown menus? (i.e. click file, see a bunch of menu items, one of which is Send, which when clicked displays Email, Contact, etc.) - Anonymous
October 06, 2004
Lerch - To be honest, I'm not sure. At first guess, I would say yes, but I will need to do some testing first. - Lisa - Anonymous
October 06, 2004
Some more lightweight and standards complaint approches can be found here http://xkr.us/js/menus - most, if not all, suppost submenus, too :) - Anonymous
October 06, 2004
Oops, wrong link in my previous comment: http://xkr.us/js/menu - Anonymous
October 06, 2004
The comment has been removed - Anonymous
October 06, 2004
I played with it some, Lisa, and I run into the same thing I've seen other code deal with: there has to be a way to figure out if a menuitem has a submenu (so you can put a little arrow or something), and you have to figure out where to display the submenu (i.e. ideally to the right of the menuitem that spawns the submenu).
Thanks for that link, b.gr...that Son of Suckerfish Dropdown article was amazing...I've been looking at dropdown menu strategies off and on for a while (which led me here, Lisa), and that's got to be one of the most amazing, lightest, most cross-browser compliant ones I've seen. I love the idea that it's coming from a heirarchical list...that makes it really easy to populate the menu items from a database! - Anonymous
October 06, 2004
I took a look at the link b.gr provided, but I haven't had a chance yet to read through it. Great recommendation; I'll have to find some time to do so.
I also haven't had a chance yet this morning to futz with my code, but my thought after a brief glance at it is that the P elements inside would need to change to a nested table with TD elements inside the nested DIV element, and then you could nest TABLE and DIV elements to your hearts content to create as many submenus as you want.
The only thing I'm not sure about is the offsetTop value. I'm not sure how this would change with submenus, but my gut tells me it would change, which means that it may need to be a value that gets passed in, depending on the placement of the submenu.
Again, without a great deal more looking at this, I can't say for sure what would need to be done to make it allow for submenus, but when we publish the article perhaps I'll fix the code to allow for this. - Anonymous
October 22, 2004
hi people - good page, i'm a bit of newbie still picking this example apart and understanding it nearly...
i was wondering how difficult it would be to mod this code to create a rollover image button for the menus contents? - Anonymous
February 09, 2006
The comment has been removed - Anonymous
February 09, 2006
http://www.htmldog.com/articles/suckerfish/example/ - Anonymous
May 03, 2006
The site is really good and it stand by its name.It's very helpful.
The doubt that I have right now is how can i set or increase the width of a drop down menu in HTML.
For eg what should in include inside <select> tag.
Thank you. - Anonymous
September 16, 2006
Why didn't you place the whole data thing in the right order to copy and past it.
When it is past in page view of MS-Frontpage you have a better sight on the whole picture and easier to play with, I think?
In this lay-out it takes for ever to get it right.
Chris.