The Blog

ADxMenu2 — the usability script

There are situations when the drop-down multilevel menu will go outside the visible area of the page. It is either too long or has two or three sub levels (I certainly don’t recommend more than that).

Such menu is highly unusable and there are two solutions:

  • Re-arrange the layout elements so that menu fits on the page in reasonable space
  • Apply the script that will reposition the menu
An example of bad and good user experience

Script dependencies

Calculating element position on the page is still in the art-form stage. Browser line in use today is not always up to the task. Sad thing is that you can’t check for certain element or property and be sure that script will work. Buggy or partial implementation of certain elements is the real problem. A problem that leads to ugly browser detection.

For example, Gecko browsers have complete (in terms of what I needed so far) positioning support from version 1.5 (Firebird 0.7). Previous version were fine when you used CSS-P purely. If you look at this example you will see that sub menus appear where they are supposed to.

But when you want to set the position through DOM, you have problems as illustrated in the following figure. 1.4 version of Gecko core is used by the last Netscape-branded browser (7.1) so this problem is here to stay. :(

positioning problem

Apple web kits prior to version 101 (I think) also have this problem, so Safari 1.0 and Omniweb suffer from it. Safari 1.2 is fine and I hope that final version of Omniweb 5 will fix this as well. Additionaly, it does not work in Opera 7.23.

Problem is that, when you do object.style.left = "0px", that 0 is not the (0,0) point of the positioned parent, but that of the whole page (of body).

So, this script must check the browser version to make sure that everything would work as expected. That is one major dependence.

Another is technical. Previous version of ADxMenu was dependent on three other scripts. In this version, I removed all of them.

Onload execution

In the previous version, I used AttachEvent script to do necessary work on page load. I like that script because I know the order of onload-scripts execution. ADxMenu is not dependent on the order anymore, as the basic menu functionality is achieved using only CSS.

This entry is updated (May 6th) to be compatible with latest ADxMenu.

Therefore, I could safely use event listeners already existing in the browsers (you’ll shortly see what ADXM is).

if (window.addEventListener) {
	window.addEventListener("load", ADXM.Init, false);
} else if (window.attachEvent) {
	window.attachEvent("onload", ADXM.Init);
}
if (window.addEventListener) {
	window.addEventListener("resize", ADXM.Viewport, false);
} else if (window.attachEvent) {
	window.attachEvent("onresize", ADXM.Viewport);
}

It also acts as yet another browser detect.

DOM library

As before, I use X library from Mike Foster. Only this time I moved the functions I use to the ADxMenu script. If you use X-lib for anything else and have it already included in the page, then just delete that part of the script.

Drop-down field problem in IE/Win

You are certainly aware of the problem with form drop-down fields showing through the layers. I have recoded the menu script so that it checks for existence of WCH functions before it calls them. That way, you can include or not include the WCH script – menu script will work either way. It is good not to include it if you don’t have drop-downs or other windowed controls on the page (saves one HTTP request and some download time).

Usage

I have simplified the way script works (or so I believe). During page loading, it will create an object, ADXM, that has certain properties and methods.

How to use it?

Include the script in the page, and add your menus with lines such as this:

ADXM.Add( "menuList", "H" );

menuList is the ID of the main menu UL element. Second parameter defines whether main menu is horizontal (“H”) or vertical (“V”).

Update: If you are using WCH along with menu script, each UL in the menu must have an unique ID. Without the IDs, WCH will not work as expected. Kudos to Koen Calliauw for reporting this back.

How it works

All added menus will be processed on page load. Viewport size will be saved (this is also done on each resize). Then script process all menu items and adds appropriate handlers to sub menu actuators.

On hover, it calculates the position where menu should be, and then checks for available space until the bottom and right viewport edge. If there is not enough space, it moves the submenu in the opposite direction.

Take a look at the examples – follow the “full script” links. Please report any positioning problems you encounter.

Banca

Banca

Beautiful and functional currency converter, supports just about any currency in the world.

Go Couch to 5k

Go Couch to 5k

The most popular starter running program in beautiful feature-rich app (GPS tracking, charts, detailed history etc)

Quickie to do

Quickie to do

The fastest short-term task-list / check-list app on the App Store. Really.

Guerrilla Cardio

Guerrilla Cardio

The most challenging high-impulse interval training in the world.

Run Mate

Run Mate

A versatile running coach app, with unlimited number of running programs. Perfect for casual runners.

30 Comments

Feel free to chime in, looking forward to it. Leave a Comment

  1. Norbert Martinez says:

    Dear Alek,

    Thanks for the menu. I have been looking for a menu that a) allows me to arrange my menu items via structured ‘s and ‘s, b) works well in most modern browsers, c) does not involve too much scripting.

    After checking a lot of menus, I was quite happy to find yours as I have quite a lot of forms in my pages and I need my menu to cover windowed elements.

    The only problem I have found is that in IE5.5 the submenu width is not fixed as in, say, IE6 or Mozilla. Instead, the text in each submenu item occupies as much horizontal space as it needs (does not wrap into different lines).

    Although I could live with this, sometimes this problem makes the menu unreadable:

    Imagine the following menu with the WinXP css:

    AAA AAA AAA

    BBB

    CCC
    DDD

    EEE
    FFF
    GGG
    HHH HHH HHH HHH HHH HHH HHH HHH HHH
    III
    JJJ

    KKK KKK KKK KKK KKK KKK KKK KKK KKK KKK
    LLL LLL LLL LLL

    In the above example, the KKK and the LLL items are longer than the DDD item and when the DDD submenu is shown, it is placed over the KKK and LLL text.

    Unfortunately, for some strange reason, the background of submenu DDD does not cover the text in submenu BBB and the KKK and LLL texts are mixed with the EEE, FFF and GGG texts (making them unreadable).

    I am not sure whether this is a IE5.5 bug or whether it has a solution, but I wanted you to be aware of it so that you can solve it if you want.

    Once again, thanks for the menu and good luck with your development!

  2. aleck says:

    Norbert, unless I misunderstood you, menu is behaving as expected.
    Menu did work as you said, only background was there. I tried with clean Win98/IE5.5 install. Of course, such long item with no brakes are rather rare (except maybe in German language).

    Let me know if I should’ve tried something else.

    Thanks2, I found and fixed small error in WCH, that caused it to fail in IE5.5.

  3. Peppe says:

    Thanks Aleck, i like this script ;)

  4. Tim Connor says:

    Aleck, I don’t think you should have to reimplement getElementByTagName with ADXM_GetChildsByTagName(oNode, sNodeName). Haven’t tested in a while, but it should work as oNode.getElementByTagName(sNodeName)

    It’s often referenced for document. but I think you can call it from any element, to return the children

    http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/getelementsbytagname.asp

    http://www.mozilla.org/docs/dom/technote/tn-dom-table/#fundamental

    http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247 (scroll down to Interface element, or just search forward (I love “find as you type” in FF) for getElementByTagName)

  5. Tim Connor says:

    Edit: Of course, I don’t remember if it will limit itself to children, or do a full traversal.

  6. aleck says:

    Tim, the reason I created this function is that DOM function returns all children, not just from first level. This way I got only the first level.

  7. Tim Connor says:

    That’s what I realized AFTER I posted. ;) I just saw the code comment about “missing something” so I thought I would check.

  8. Bjrn says:

    On the usability topic; the script is really nice and lightweight, just wondering if there’s a way to set some timeout when the menus are about to close?

  9. Aleksandar says:


    Previous version of the menu had that functionality (because I didn’t know then how to do it with CSS only) so you can review eariler entries.

  10. Norbert says:

    Dear Aleck,

    After writing the comment above I noticed that all the HTML code that I had inserted in the comment disapeared, so I imagine it must have been a bit difficult for you to realise what I was talking about.

    The example you included, though, did reproduce the scenario in which I encountered one of the problem I was trying to explain: an item (B) that is so long that it is covered by the children items of another item (A) (item A is at the same level as item B but just above it).

    Your example, however, did work as expected as item B was indeed covered by the children of item A (in my tests item B was shown together with the children of item A as though the children of item A had a transparent background).

    I noticed you mentioned you had discovered a bug regarding WCH in IE5.5. I don’t know if this bug is what I was talking about, but I downloaded all the ADxMenu code again, paid a lot of attention to the way you implement it and have finally managed to have a fully working example (have tested it in IE5.5, IE6.0 and Mozilla 1.6).

    The other problem I was talking about (long items not wrapping around in IE5.5) has also been solved (I was probably doing something wrong before). In your example you include a very long item that is made up of a single long word (yes, only Germans would have such words). However, the problem I was encountering was related to items that were not being wrapped in spite of having many short words with spaces in between. However, as I said, that doesn’t happen any more, so I wouldn’t worry.

    Once again, thanks for the menu! I will send you my web site’s address once I put it online so that you can check the result!

  11. Bjrn says:

    Regarding the onClose timeout:
    Doesn’t the menu consist of two different versions, one CSS only and one “full script”?

    Since I need to use the WCH in any way, just wondered if its possible to somehow add the setTimeout there somewhere?

    sorry, I never seem to understand exactly how to implement a timeOut, otherwise I’d give it a try myself… If you have a menu consisting of more than two sublevels, a timeout would be very valuable…

  12. aleck says:

    Nope, full script version is an add-on. It still uses CSS for menu open/close, but adds additional stuff, like WCH for IE and repositioning into viewport for larger menus.

    But, recent developments I did showed one side of csshover.htc I did not notice on the examples: it is slow. If you use fairly large stylesheets, parsing each one of them is so painfully slow that it freezes IE for 5-15s. I tried some hacks to shorten the number of rules that are parsed, but no luck.
    Therefore, I will probably revert to some sort of CSS+script functionality. Will have to find time to do it, since I have so much work I did not visited this blog for almost two days.

    However, I doubt I will implement setTimeout, as I want to use as minimum javascript as possible for basic menu functionality.

  13. Tim Connor says:

    I have the same problem — csshover.htc is too slow on page load, if linked with WCH, particularly in IE6. I too have tried tweaking thngs — I even have changed it to only parse one stylesheet (hover.css) minimized my use of non-anchor hover, and put those few styles for my menu only, in hover.css, and only had it bind WCH if the hasHider class is found. Still to slow.

    Oddly enough, it’s only IE6 that really has a problem.

    I haven’t yet tried hiding the whole page (display:none) and then only showing it at the end of the .htc. That may speed up rendering. I’ll let you know if I come up with anything that performs.

  14. Tim Connor says:

    Heh. Actually my problems where entirely unrelated (well related to my standards obsession, and trying to do everything as minimally as possible, but not the hover.htc). The hover actually performs just fine for me, it seems. Even with my grafting on the WHC.

    I was using a 2 pixel gif to make a striped background. I know IE doesn’t handle caching of css backgrounds, but it didn’t occur to me that even it tiling one would cause a problem. Having to recreate that image hundreds and hundreds of times was taking IE far more work than just diplaying a version with some redundancy. Just by stretching it out to 100 pixels horizontally IE suddely had no more problems on render, and I can go back to not worring about it.

    I may still have some occasionally points where I could streamline my usage more, but its good enough for now. Just try to avoid overusing it in glee (for example I am probably going to switch all the appearance changes back to a:hover, which causes no problems since IE, can handle it natively and just us li:hover for showing submenus), and it should work.

  15. Tim Connor says:

    Changed my mind again — it really does seem like the .htc is bogging things down too much on initial render. If I get a chance, in the next couple weeks, I’ll test the performance of some alternatives for whatever:hover behavior in a menu, while trying to keep the code as clean as possible.

    There are two real issues, the actual :hover declaration and the event handling.

    1) If we figure we are going to handle the event handling another way, let’s look at just the :hover selector. The choices, have a script (htc) change them all or have the developer have to just duplicate his styles (having a . and a : ).

    Need to see how much of a load the barebones css parsing and adding new rules script is, but from my quick testing, that looks like where a lot of the intial delay is coming from, without even having to deal with the actual hover event assiging or the figuring out which element needs the events assigned.

    If not this bears more looking into, if that is truly the case, though, then performance needs may require the developer adding in .hover rules that duplicate the :hover rules– not that big of a loss.

    2) The actual event handling that adds/remove .hover. This is the ugly part, in my opinion, that would be nice to keep out of sight as much as possible for the end-developer. There are a number of possibilites I want to evaluate, that I have come up with, so far. Some of these reflect back on 1).

    Adding a single pair of on/out handlers to any element that may contain a :hover (the top container of the menu). This will check the srcElement and add/remove the .hover class, back up the DOM to itself (to get nested menus right). Perhaps adding some type of timeout on mouseout here might be nice for performance and usability — maybe not just for testing, though.

    Do some sort of Dom walk. Have an onload callable function that walks the top of the menu down, adding onmouse events to all li’s.

    Use an htc as a behavior — bind to the elements, not run something on the body itself. Any selector that will require a :hover psuedoclass, must first have a hover.htc bound so

    ul#topmenu li (behavior:url(hover.htc))
    ul#topmenu li(. and/or: issues here)hover ul {visible}.

    This one would require more research, but if the performance is acceptable, and I correctly understand how behaviors work, this may be the optimal solution, in my view. This may also be the cleanest way to bind in something additional (say like WCH, or edge detection).

    If I find any time to do the research, I’ll let you know how it comes out, and if I found a workable solution you may wish to consider for AD xMenu.

  16. Aleksandar says:

    Had no chance to respond properly to your comments (this is just to let you know I’m reading them).

    I am positive that csshover.htc is the problem — I experienced it on StanJames web site when I tried to implement the latest scripts. I removed it and went for duplicate style rules, then changed the ADxMenu to work similarly as 1.x versions, while keeping the CSS from 2.x.

    When I’m free from current work (which might be several days and possibly few weeks), I will take a very close look to your suggestions and see what will come up.

  17. Tim Connor says:

    It’s really really easy to make a behavior that adds in proper hover, and that functions exactly as fast as the js mouseover, because it is just an sbastracted version of that. Right now I just have it bound onto all and it works fine.

    I want to see if I can figure out the best way to hide the behavior for validation — as that is only non-validating line of code I have. I’m going to look into it more, to see about adding in the WCH call, as needed, also.

    I’ll let you know the final results.

    Tim

  18. Tim Connor says:

    To give you an idea how easy: I just have my :hover rules duped, added this

    li {behavior:url(“scripts/hover.htc”); }

    and the entirety of my hover.htc is below.

    <PUBLIC:COMPONENT>
    <PUBLIC:ATTACH EVENT=“onmouseover” ONEVENT=“hover()” />
    <PUBLIC:ATTACH EVENT=“onmouseout” ONEVENT=“unhover()” />
    <SCRIPT LANGUAGE=“JScript”>
    var hoverClass = “hover”;
    function hover(){
    if(className.indexOf(hoverClass)!= –1){return;}
    className = className == “”?hoverClass:className + ” ” + hoverClass
    }

    function unhover(){
    if(className.indexOf(hoverClass)== –1){return}
    var replaceString = new RegExp(“\s*” + hoverClass + “|” + hoverClass + “\s+”);
    className = className.replace(replaceString,””)
    }
    </SCRIPT>
    </PUBLIC:COMPONENT>

    and my menus work perfectly. Now I am going to look at adding WCH (you wouldn’t happen to have your newest cleanest version on hand, would you?) and then hiding the behavior from the validator.

  19. Tim Connor says:

    SWEET! This works:

    var hoverClass = “hover”;
    function hover(){
    if(className.indexOf(hoverClass)!= –1){return;}
    className = className == “”?hoverClass:className + ” ” + hoverClass;
    if(className.indexOf(‘hasHider’)!=-1){
    window.WCH.Apply(id+‘Hider’,this);
    }
    }

    function unhover(){
    if(className.indexOf(hoverClass)== –1){return}
    var replaceString = new RegExp(“\s*” + hoverClass + “|” + hoverClass + “\s+”);
    className = className.replace(replaceString,””);
    if(className.indexOf(‘hasHider’)!=-1){
    window.WCH.Discard(id+‘Hider’,this);
    }
    }

    where I have put this in my body onload

    window.WCH = new WindowControlHider()

    I have WCH appropriately, and I have choosen to use the ‘hasHider’ (could be subMneu) class and forced a specifc id scheme. You could do it however, of course.

  20. Aleksandar says:

    Thanks for sharing the code. I barely have time to check on the blog, as I have a deadline this weekend.

    However, last night, while working on that project (CMS that uses latest ADxMenu) I figured that I was wrong about csshover.htc.

    It’s not the size of CSS used, but it’s the size of HTML document. When used on short page, there is no freeze. But when used on a page around 100kB, then it have noticeable freeze. It seems that IE is checking the whole document tree on each rule creation, thus freezing display until processing is done.

  21. K.C. Baltz says:

    Sorry to take this discussion in a different direction, but I have a different question about ADxMenu2. I notice that it appears to add a large amount (50-100px) of whitespace below the menu. I’d like to use it in my app with 0px of whitespace beneath it, so it’s a bit of problem for me. I tried placing a border around the menu div, but it doesn’t include the whitespace, so I’m not really sure what’s causing it to appear. Any ideas?

  22. Aleksandar says:

    Can you give me an example page, as I’m not sure I understand what do you mean by “whitespace”?
    Also, which browser are you using?

  23. K.C. Baltz says:

    Isn’t it frequently the case that as soon as you ask for help, you spot the solution yourself? I took another look at the problem and realized that all of your examples have a 30px margin at the bottom added for appearance sake. Disabling this (and setting the margin-top to 0px for my content below the menu) fixed the problem.

  24. Aaron says:

    does this work with asp pages? i am having a hardtime getting it to work. Works fine until i upload to server and is rendered dynamiclay to asp.

  25. Aleksandar says:

    Yes it does, Aaron. As long as you output proper list structure, style sheet and (if needed) script file, it will work. Care to provide me a link to the problematic page?

  26. Richard says:

    Hi there, I love this menu script it is brilliant. I’m a newbie so I’m just getting the hang of CSS. I need to add one more level to the example and I need some help. I can manage the code for the list, but I am not sure what needs to be added in the style sheet. Please could you help me or give me a clue. Thankyou very much in advance.

  27. Aleksandar says:

    Richard, any browser with native li:hover support can have unlimited amount of menus. The part:

    #menu ul#menuList li:hover > ul {
    	visibility: visible;
    }
    

    takes care of that. For IE, you need to add enough levels, that will cover the depth of the submenus. Consider this example:

    #menu li:hover ul {
    	visibility: visible;
    }
    #menu li:hover li ul {
    	visibility: hidden;
    }
    

    Look at the HTML code for the list and work it out, down the nesting. #menu starts it up. Then goes the li:hover which selects the the item in the main menu and ul nested inside of that item, and that is submenu. This rule means: find all nested submenus and make them visible.
    The other rule has one more li before the ul. This will fetch all deeper nested submenus and hide them, thus leaving only the first found submenu.

    To be even more clear, using the list from examples. When you load the page, only main menu is shown. When you mouse over Products item, first rule will show all possible submenus — for Products, Categories and Hardware. What we actually need is just Products submenu to appear. Second rule makes sure that Categories and Hardware submenus are hidden.

    For deeper levels, you need to repeat this rules pair and each time add one more li:hover. Here is how it looks for one level down (Categories).

    #menu li:hover li:hover ul {
    	visibility: visible;
    }
    #menu li:hover li:hover li ul {
    	visibility: hidden;
    }
    

    Since I have 3 submenus (main menu is not counted), in all .css files I have 3 pairs of rules.

    Or, to cut the long story short — number of li:hover is identical to submenu level.

  28. Richard says:

    That’s helped. Thanks alot for answering so quickly.

  29. Margo says:

    I am trying to setup your menu. Style wise it was very easy and looks great. However, I’m not sure what I’ve done (or whether it’s my computer) but the menu is still going behind the drop down list underneath.

    example:
    http://www.passthetoast.com/example.php

    I hope you can help. the header is a separwte php include.

  30. Margo says:

    Never mind… conflicting ul styles in my original stylesheet..

    Thank you for a wonderful menu.

Comments are now closed for this article.