Setting up a menu system is a task that most web-developers / designers have to perform on nearly every project they complete. Choosing the right menu system can have a profound impact on the usability, accessibility and appearance of the project.
Most of the JavaScript based menu systems floating around the web have some major failing. Many of them push a particular look and feel on their users. Most of them force you to use an invalid HTML layout or worse yet, define the menu structure in JavaScript (this means that search engines and people using screen-reader won't be able to access the menu!).
After trying a multitude of different solutions I decided that I should write my own system and implement my recommendations along the way.
This menu system has a number of advantages over the competition:
Upload the script: First things first, you will need to upload the script at the bottom of the page to your webserver, and link to it using a <script> tag just before the </body> tag on your website like so:
... <script src="hover_menu.js type="text/javascript"></script> </body> </html>
Define your menu structure: Simply setup an (X)HTML structure similar to the one below. Assigning an item the class 'has_submenu' tells the menu system that the item contains a submenu. The classes 'no_submenu' and 'is_submenu' simply provide hooks for your CSS.
<ul id="main_menu"> <li class="no_submenu"><a href="http://www.google.com/">Google</a></li> <li class="no_submenu"><a href="http://www.yahoo.com">Yahoo!</a></li> <li class="has_submenu"><span>This has a submenu</span> <ul class="is_submenu"> <li><a href="#">Submenu Item 1</a></li> <li><a href="#">Submenu Item 2</a></li> <li><a href="#">Submenu Item 2</a></li> </ul> </li> <li class="has_submenu"><span>This has a submenu too!</span> <ul class="is_submenu"> <li><a href="#">Submenu Item 2:1</a></li> <li><a href="#">Submenu Item 2:2</a></li> <li><a href="#">Submenu Item 2:3</a></li> <li><a href="#">Submenu Item 2:4</a></li> </ul> </li> </ul>
Style your menus: Using CSS you can present the menu horizontally, vertically or even as an accordian style! All without having to change a single line of HTML or JavaScript! Below is a simple style sheet to get you started.
/* This stylesheet sets up the menu in a horizontal layout */
#main_menu{
margin: 0;
padding: 0;
list-style: none;
height: 35px;
}
/* setup the styling for the top level menu items */
.no_submenu, .has_submenu{
display: block;
float: left;
height: 35px;
position: relative;
}
.no_submenu a, .has_submenu span{
display: block;
height: 35px;
line-height: 35px;
color: white;
text-decoration: none;
font-size: 14px;
width: 136px;
}
.no_submenu a:hover, .has_submenu span:hover{
color: silver;
}
.has_submenu span{
cursor: pointer;
_cursor: hand; /* For IE 6 */
}
/* position the submenus at the bottom of their
respective parent menu items */
.is_submenu{
position: absolute;
left: 0px;
top: 35px;
background: black;
margin: 0;
padding: 0;
list-style: none;
z-index: 9999;
}
/* style the individual submenu items */
.is_submenu li a{
display: block;
height: 25px;
line-height: 25px;
text-align: left;
padding-left: 10px;
width: 119px;
padding-right: 10px;
color: white;
text-decoration: none;
}
.is_submenu li a:hover{
background: #c5e7e7;
color: #19bcb9;
}
// Coding and Concept by Aaron Gough http://www.aarongough.com
// This work is licensed under the Creative Commons Attribution-Share Alike 3.0
// Unported License. To view a copy of this license please visit:
// http://creativecommons.org/licenses/by-sa/3.0/
// This object sets-up a simple hover activated menuing system
// that associates itself with the Element ID as supplied in menuID
function dropdownMenu( menuID)
{
// This function shows the submenu to the user
this.showMenu = function()
{
thisObj.menuList.style.display = "block";
}
// This function hides the submenu from the user
this.hideMenu = function()
{
thisObj.menuList.style.display = "none";
}
// INITIALIZE THE OBJECT:
// declare a static, local instance of THIS for later use
var thisObj = this;
// grab the element that contains the menu and store it as a property of this object
this.menuElement = document.getElementById( menuID );
// find the UL that contains the actual drop-down and assign it to a property of this object
for( x2 = 0; x2 < this.menuElement.childNodes.length; x2++ )
{
if( this.menuElement.childNodes[x2].tagName && this.menuElement.childNodes[x2].tagName == "UL" )
{
this.menuList = this.menuElement.childNodes[x2];
}
}
// hide the dropdown menus
this.hideMenu();
// assign the hover event handler
this.menuElement.onmouseover = this.showMenu;
// assign the mouseout event handler
this.menuElement.onmouseout = this.hideMenu;
}
// This function returns an array of all the elements with a classname that matches the one supplied
function getElementsByClassName( classname, node)
{
if( !node ) node = document.getElementsByTagName("body")[0];
var a = [];
var re = new RegExp('\\b' + classname + '\\b');
var els = node.getElementsByTagName("*");
for( var i=0,j=els.length; i<j; i++ )
{
if( re.test(els[i].className ))
{
a.push(els[i]);
}
}
return a;
}
// This function attaches a menu object to every element in the DOM
// that has a class of "has_submenu"
function associateDropdownMenus()
{
menus = getElementsByClassName( "has_submenu" );
for( x = 0, array_length = menus.length; x < array_length; x++ )
{
// Check to see if the menu has no ID, if not we assign one to it
if( menus[x].id == "" )
{
menus[x].id = "dropdownMenu_id" + x;
}
// Create a new menu object and associate it
menus[x].dropdownMenu = new dropdownMenu( menus[x].id );
}
}
// Setup the menus
associateDropdownMenus();
When the page loads the user may see a flicker as the menu system hides the sub-menus. This is because the menu system is only loaded and run after the rest of the content has been loaded. this can be fixed two ways:
The simple, incorrect way: By adding a CSS rule to your stylesheet that hides the submenus by default you can eliminate the flicker. The issue with this solution is that if a user does not have JavaScript enabled then they won't be able to access the menu! By default the menus would simply remain visible, while this is a bit unsightly at least the menus can still be used properly. A CSS rule that would do the trick is shown below:
.is_submenu{
display: none;
}
The correct way: If we move the script to the header of our document and then register the 'associateDropdownMenus()' call to the 'document.ready' event (using jQuery or another JavaScript library) we can eliminate the flicker. This solves the issue by allowing the menu system to load and work it's magic before the user ever sees a rendered page. An example of this using jQuery is shown below:
// OLD WAY: // Setup the menus associateDropdownMenus(); // REPLACE WITH: // Setup the menus jQuery( document ).ready( associateDropdownMenus );