Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

DMenu, DHTML Menu

0.00/5 (No votes)
24 Oct 2002 1  
A DHTML menu illustrating seperation of structure, presentation and function

Summary

A strictly structure, presentation and function seperated n depth DHTML menu. The power of HTML coupled with CSS and JavaScript is illustrated.

DMenu in action

Requirements

To view the demo and run the source: Internet Explorer 6 (no ASP or any other server side support required.)

To follow this article: Basic HTML and medium to expert CSS and JavaScript skills.

Updates

I prefer notifying people of updates at the top of the article

  • (2002/10/25) Mozilla 1.0 support:

    Many thanks to Nikhil Dabas for providing the W3C DOM compliant JavaScript code which means the menu now works in Mozilla and Internet Explorer. Also amazingly the CSS itself required just one tweak to get the menu displaying in Mozilla almost perfectly. The menu can be a touch finicky in Mozilla (sometimes the sub-menu does not close, if you can figure it out, then a browny point for you. I suspect it has to do with event bubbling) but it is usable enough to be useful. For those interested the CSS tweak involved simply telling the menu-item LI elements to have a width of 100% (actually 98% because 100% overwrites the border of the containing UL element.) Also I moved the sub-menus slightly up and to the right, to not overwrite the containing menu (good idea Nihkil.)

Introduction

There is an endless list of DHTML menu samples on the world wide web. From intricate cross browser samples to extravagant DirectX enabled wonders. This article demonstrates a DHTML menu that is none of these, what it does demonstrate is how to create a DHTML menu which has strict seperation of structure, presentation and function.

Seperation of these three areas is important for the following reasons:

  • Forward and backward compatibility

    By not tightly coupling the three areas together future browsers are guaranteed at the very least to render the content and structure well enough to be usable. Also in older browsers if the browser does not support advanced CSS or JavaScript, it at least supports the content and structure, so once again ensuring the menu is usable.

  • Accessibility

    By not mixing the content and structure up with presentation and function accesibility devices (like text-readers for the blind) can easily access and understand the content. Search engines will also index well structured content better than mixed content.

  • Maintenance

    Adding or removing menu items from this kind of setup is dead simple. Other setups may require extra CSS classes, further JavaScript and complex structural changes (not too mention understanding it all.)

  • Skill use

    Much like ASP.NET encourages developers to focus on what they are good at, so this setup does as well. UI specialists can modify the CSS file without interfering with a developer working on the JavaScript file or a content specialist working on the HTML file.

  • Information Access

    The web is all about providing information to all. Whatever device you are using, a web page should be at least accesible enough to read the content and navigate the website. The seperation shown in this article is perfect for multiple devices in that it will degrade gracefully depending on what device is accessing the page. e.g. A handheld browser will ignore the JavaScript and CSS and just render the menu as a list, making it usable and accesable. A smidgen of device detection will also improve things as some devices attempt to render beyond their capibilities.

The Menu

The actual menu is very simple. It consists of three parts: The HTML (structure and content), the CSS file (presentation or style) and the JavaScript file (function or workings.)

The menu can display as much depth (sub-menus) as you need, though of course screen real estate can become a practical concern if you go too deep. Also each menu list can display as many menu items as you need, with as many sub-menus in one list as is needed.

The function of the menu (the onmouseover and onmouseout events) are only applied onload of the BODY. This is done to ensure that all structure is loaded before the JavaScript attempts to do it's thing.

You do not need to define a CSS style for each sub-menu. The position of the sub-menus is automatic (and explained in the CSS section below.)

The HTML

The complete HTML is in the source file.

...
<body onload="initialiseMenu();">
        <h1>DMenu: Sample Simple</h1>
        <ul id="mainmenu">
            <li><a href="about/">About Us</a></li>
            <li><a href="about/">Articles</a>
                <ul>
                    <li><a href="articles/">C#</a>
                        <ul>
                            <li><a href="articles/">C# Reference</a></li>
                            <li><a href="articles/">Tutorials</a></li>
                            <li><a href="articles/">Samples</a></li>
                            <li><a href="articles/">FAQ</a></li>
                        </ul>
                    </li>...
                ...</ul>
            </li>
            <li><a href="about/">Tools</a>
                <ul>
                    <li><a href="articles/">Assemblers</a></li>
                    <li><a href="articles/">Disassemblers</a></li>
                    <li><a href="articles/">CP Utilities</a>
                </ul>
            </li>
            <li><a href="contactus/">Contact Us</a></li>
        </ul>
...

As you can see it looks just like a normal UL (un-ordered list) arrangement. Inside each LI element is an A element which controls the hyperlinking.

According to the W3C the proper way to nest lists is as above, e.g. the sub-UL is within the LI element and not outside of it. This is critical also for the JavaScript to work properly (i.e. if you don't nest the lists properly then the JavaScript will not work.)

The only setup note you need to know about really is the onload="initialiseMenu();" declaration in the BODY element. If you do know how to attach the initialiseMenu method to the onload event from within the JavaScript file then please email me, thanks.

The JavaScript

The HTML is dead simple and nothing new. But the JavaScript is quite fun and interesting. I am going to step through it line by line:

function showSubMenu(){
    var objThis = this;	
    
    for(var i = 0; i  < objThis.childNodes.length; i++)
    {
        if(objThis.childNodes.item(i).nodeName == "UL")			
        {							
            objThis.childNodes.item(i).style.display = "block";
            break;            
        }		
    }	
}

Very simply this is the method that is called on mouse over of an LI element. As you will see below the onmouseover event is assigned but no parameter is passed indicating what element to show or what element fired the event. Luckily when you do this assigning and the event fires the browser keeps a reference of what element fired the event and that is accessed via the this object. Very handy indeed.

At this point you have this and all you need to do is loop through the child elements until the UL element is found. Once found the style.display attribute value is changed to block.

On an aside the childNodes method returns all child elements of an element, including literals, so be careful when using an index to reference a child element (thanks for that Nikhil!.)

function hideSubMenu()
{								
    var objThis = this;	
    
    for(var i = 0; i  < objThis.childNodes.length; i++)			
    {
        if(objThis.childNodes.item(i).nodeName == "UL")
        {				
            objThis.childNodes.item(i).style.display = "none";			
            break;
        }			
    }	
}

Virtually identical to the showSubMenu method, except it changes the style.display attribute to none which effectively hides the element.

function initialiseMenu()
{
    var objLICollection = document.body.getElementsByTagName("LI");        
    for(var i = 0; i < objLICollection.length; i++)
    {        
        var objLI = objLICollection[i];        
        for(var j = 0; j  < objLI.childNodes.length; j++)
        {
            if(objLI.childNodes.item(j).nodeName == "UL")
            {
                objLI.onmouseover=showSubMenu;
                objLI.onmouseout=hideSubMenu;
                
                for(var j = 0; j  < objLI.childNodes.length; j++)
                {
                    if(objLI.childNodes.item(j).nodeName == "A")
                    {                    
                        objLI.childNodes.item(j).className = "hassubmenu";                                
                    }
                }
            }
        }
    }
}

initialiseMenu is the fun method. It is called onload of the documents body.

First it gets a complete collection of all LI elements in the documents DOM tree. getElementsByTagName is a very handy method for returning a collection of nodes of any element type. It can also be used off of almost any element (i.e. not just body) allowing you to contain the scope of the get. For instance it would probably be better in this article to use document.body.all.mainmenu.getElementsByTagName("LI") so that no other non-menu LI elements are returned.

Next the code loops through the collection by getting the length of the collection objLICollection.length.

For each node in the collection it then loops through it's child elements looking for a UL element.

If one is found it assigns the showSubMenu method to the onmouseover event and the hideSubMenu method to the onmouseout event of the current LI element. Ensure when you do this assigning that you do not end off the assignment with () as is usually done for methods. If you do this then the method is run on the actual assignment (another hard lesson learnt.)

The last thing to do is to once again loop through the child elements and assign the hasssubmenu CSS class to the child A element. This simply displays the arrow image to indicate to the user that the menu item has a sub-menu.

The CSS

The CSS is short but sometimes tricky. I will step through each relevant class:

...
ul, li
{
    font-size: small;
    margin-top: 0px;
    margin-right: 0px;
    margin-bottom: 0px;
    display: block;
}
...

Just give all UL and LI elements no margins, a font-size of small and a display type of block.

...
ul
{
    width: 130px;
    border: solid 1px #333333;
    border-top: solid 5px #333333;
    border-right: solid 2px #333333;
}
...

Give all UL elements a width of 130px. Then assign a solid, 1px dark grey border to the UL, but override the right and top borders to have a width of 5px. You could define each border attribute individually, but this is just a quicker way.

...
ul li ul
{
    display: none;
    position: absolute;
}
...

ul li ul is a CSS parent/child selector. Basically it is saying "apply the following styles to any UL which is a child of an LI which is a child of a UL." This way you prevent the top level menu UL being given the style (which is important otherwise the top level menu UL would be hidden from the start.)

display: none; effectively says not to display the element in any way and that no space is taken up by it. visibility: hidden for instance does not do the same thing. It makes the elment invisible, but the element still takes up space.

position: absolute; takes the element out of the normal flow of the document and "floats" it above other relative or statically position elements. This way when the element is made visible, it will not "bump" other elements downwards in the flow of the document. Also it is important to note that this absolute value is still relevant to the elements container. This is what allows the sub-menus to automatically position themselves.

...
li a
{
    padding: 2px;
    text-decoration: none;
    color: #000000;
    background-color: #ffffee;
    width: 100%;
    display: block;
    border-bottom: dashed 1px #333333;
    text-indent: 2px;
    font-size: small;
}
...

Most of this is dedicated to making the menu items look nice. e.g. the dashed bottom border, the background colour etc. Once again a parent/child selector is used: li a. So the style will only be applied to A elements which are children of an LI element.

The only important part is the width: 100%; attribute which tells the A element to fill up, width ways, all available space of the parent element. This ensures the mouseover effect happens on the whole menu-item and not just the letters of the A element.

Also the text-indent attribute is not often used but is quite useful when you want to ensure just text is padded away from the borders (padding can also be used, but does not always work in some cases.)

...
li a:hover
{
    background-color: #ffcc00;
    font-weight: bold;
    border-bottom: solid 1px #333333;
}
...

This just defines the style of the hover state (when the mouse is over the element.) One day (XHTML 2.0) this kind of functionality (and putting HREF on any element!) is going to be supported for all elements, which will be just great.

...
li
{
    float: left;
    width: 98%;
}
...

This one seems simple, but is critical. float can be your best friend and your worst enemy. What it does is allow elements to literally float alongside each other, without a line break. You can float left and right. The problem comes when you want other elements to stop floating alongside a certain element. In that case you need to use the clear: both attribute and value, which tells the element not to allow any other elements to float alongside it on either side.

width: 98%; tells the LI element to fill up, width ways, all available space. 100% though unfortunatley overwrites the containing UL's border style in Mozilla. 98% does the trick though and works in Internet Explorer quite fine.

We are almost at the end.

...
a.hassubmenu
{
    background-image: url(../img/lay_dmnuhassub.gif);
    background-repeat: no-repeat;
    background-position: 120px 7px;
}
...

Remember the className assignment in the JavaScript? Well this is the class that is assigned to LI elements which have sub-menus.

All it really does is display the lay_dmnuhassub.gif graphic in the background of the element. background-repeat: no-repeat; is quite useful in that you can control on which plane the background repeats (hence the name.) You can specify repeat-x, repeat-y, repeat and no-repeat as valid values.

background-position: 120px 7px; is also useful and lets you positon the start of a background graphic. It is an x, y co-ord.

Possible Improvements & Bugs

  • Pre-load the hassubmenu arrow graphic to prevent it from re-loading each time a sub-menu is displayed. If anyone has some quick code to do this, please feel free to share.
  • Try and fix the hiding of sub-menus bug in Mozilla. I assume it has something to do with event bubbling.
  • Seperate the important CSS style attributes and values from the purely presentational attributes and values. This way a "base" DHTML menu could be provided and then styling (colours, borders, backgrounds etc.) applied without fear of breaking the menu.
  • Figure out how to attach the onload event handler to the event from within the JS file but without having to move the script block out of the head area (While perfectly valid, I am picky about where my script blocks go. Mainly for maintenance and understanding purposes.)
  • Instantly upgrade every single users browser to either Inernet Explorer 6 or Mozilla 1.0. ;)

Conclusion

And that folks is a wrap. All in all quite simple and a very elegant solution to producing DHTML menus.

For now the DHTML menu is perfect for a forwards thinking website content with not having a DHTML menu on older browsers or on company intranets where you can control what browser users use. In the future with the above tweaks this kind of web code is going to become more and more popular.

Overall though the concept I am driving at is seperation of structure/content, presentation/style and function. This is critical to grasp for the web to move forward and in creating efficient development and maintenance teams. Without it we remain with our hard to maintain and non-forwards-compatible websites.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here