Section H. Dropdown Menu, mouseouts, and the related target


H. Dropdown Menu, mouseouts, and the related target

Dropdown Menu will show you how to work with the mouseover and mouseout events, and will also show you that the last event, especially, can cause a lot of trouble if you don't use it correctly. We'll also discuss the relatedTarget and toElement properties that are vital to scripts that use mouseout a lot.

The problem is that these events can fire a lot of times even when the user makes a small mouse movement, so you have to manually distinguish between important and unimportant mouseovers and mouseouts.

Besides, neither the event target nor the this keyword is worth much in mouseout events. They refer to the element the mouse moves out of, and as we'll see, that's usually not very interesting information.

Mouseover

Let's delve into Dropdown Menu's bowels a bit. As we saw, the script defines general onmouseover and onmouseout event handlers for the entire menu, and relies on event bubbling to get all the events that take place on its descendants:

<ul > <li><a href="#">News</a>   <ul>     <li><a href="#">Press Releases</a>            <ul>                    <li><a href="#">Release 1</a></li>                    <li><a href="#">Release 2</a></li>                    <li><a href="#">Release 3</a></li>            </ul>     </li>     etc. </ul> 


[Dropdown Menu, lines 7-21}

var lists = document.getElementsByTagName('ul'); for (var i=0;i<lists.length;i++) {     if (lists[i].className != 'menutree') continue;     lists[i].onmouseover = navMouseOver;     lists[i].onmouseout = navMouseOut;     var listItems = lists[i].getElementsByTagName('li');     for (var j=0;j<listItems.length;j++) {            var test = listItems[j].getElementsByTagName('ul')[0];            if (test) {                    listItems[j].firstChild.onfocus = navMouseOver;                    listItems[j].relatedItem = test;            }     } } 


Note the second part of the initialization: it goes through all <li>s of the dropdown menu and finds out if they have a nested <ul>. If they do, the script gives them a custom relatedItem property to point to this <li>. This property serves two purposes:

  • Its presence alerts the script that the <li> has a child menu. (I could search for a nested <ul> every time the user mouses over something, but I find this approach cleaner.)

  • Its value refers to this child menu, and that makes it easy to find the <ul> that should open when the user mouses over a certain <li>.

relatedItem is used in navMouseOver():

[Dropdown Menu, lines 33-36]

if (evtTarget.relatedItem && !evtTarget.relatedItem.opened) {     evtTarget.className = 'highlight';     evtTarget.relatedItem.className = 'foldOut';     evtTarget.relatedItem.opened = true; 


We already discussed Dropdown Menu's targeting. We make very certain evtTarget is an <li>, and then we check if it contains a relatedItem, i.e., a submenu. If it does, the script changes the class names of the <li> and the submenu, and the submenu opens. That's not too convoluted.

Mouseout and its problems

Our problems start when we consider the mouseout event. Sacred tradition requires us to close the menus as soon as the user mouses out of them. But how do we find out if we have to close menus, and which ones?

Suppose the user mouses out of the Area 3 link by moving down. Now the dropdown menu that the link is part of should close.

Figure 7.13. The user mouses out of the link and leaves the entire submenu. The submenu should close.


Suppose the user mouses out of the Area 3 link by moving up. Now the mouse stays within the submenu and it should not close.

Figure 7.14. The user mouses out of the link but stays within the submenu. Nothing should happen.


Mouseleave

Microsoft's proprietary mouseleave event would come in very handy here. If we register it on the submenu, it only fires when the mouse moves out of the entire submenu, while ignoring any internal mouseouts. Unfortunately, we can't use it because of the lack of support in non-Explorer browsers.


Solution

Working with the mouseout event can quickly become horrifically complicated. That's why I decided to use a separate array, currentlyOpenedMenus, which stores all menus that are currently opened. Thus I can easily go through all opened menus and see if they need to be closed. I reached this solution independently in both my 2001 and my 2005 dropdown menu scripts, so I suppose this approach is worthwhile.

The principle is simple. As soon as a menu is opened, a reference to this menu is added to currentlyOpenedMenus:

[Dropdown Menu, lines 34-37]

evtTarget.className = 'highlight'; evtTarget.relatedItem.className = 'foldOut'; evtTarget.relatedItem.opened = true; currentlyOpenedMenus.push(evtTarget.relatedItem); 


When the time comes to close menus, I go through this array and decide which of the currently opened menus should be closed.

How do I decide whether to close a certain menu? I take the element the mouse goes to and see if the menu contains this element. If it does, the menu should not be closed, since the user is still mousing over it. If the menu does not contain the element, the user has moused out of it, and it should be closed:

[Dropdown Menu, lines 47-54, condensed]

function foldMenuIn(targetNode) {        for (var i=0;i<currentlyOpenedMenus.length;i++) {            if ([the menu does NOT contain targetNode]) {                   // close menu            } } 


This function is called onmouseover. When the user mouses over a new item, any menus associated with other items should close. In this case, the element the mouse moves to is easy to findit's the mouseover's event target:

[Dropdown Menu, line 32]

foldMenuIn(evtTarget); 


Obviously, we should also call this function onmouseout, since a mouseout might also trigger menu closures. But how do we find the element the mouse moves to? The event's target is the element the mouse moves from, and that information is useless.

relatedTarget, fromElement, and toElement

Fortunately, both the W3C and the Microsoft models allow you to find out which element the mouse comes from. The W3C model defines the relatedTarget property of the event object. This property refers to the object the mouse goes to (onmouseout) or comes from (onmouseover).

The Microsoft model has two separate properties for this information. fromElement refers to the object the mouse comes from onmouseover, toElement to the object the mouse goes to onmouseout.

This solves our problem. If we read out relatedTarget or toElement, we find the element the mouse moves to, and we can send this information on to foldMenuIn(). Therefore we need the following function:

[Dropdown Menu, lines 41-45]

function navMouseOut(e) {     var evt = e || window.event;     var relatedNode = evt.relatedTarget || evt.toElement;     foldMenuIn(relatedNode); } 


This works, and DropDown Menu is ready.

Nonetheless, you'll agree that working with the mouseout event can be pretty hard sometimes. If you have the chance to avoid it, or to use the Microsoft proprietary mouseleave instead, do it.



ppk on JavaScript. Modern, Accessible, Unobtrusive JavaScript Explained by Means of Eight Real-World Example Scripts2006
ppk on JavaScript. Modern, Accessible, Unobtrusive JavaScript Explained by Means of Eight Real-World Example Scripts2006
ISBN: N/A
EAN: N/A
Year: 2005
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net