YUI MenuCreator : generic show/hide code

At work I frequently encounter designs that utilize a mouseover show/mouseout hide menu that usually consists of a trigger (often an A tag, or LI element) and a menu (often a DIV, or a UL). Due to the browser inconsistencies between event handling and mouseout/mouseover DOM detection, it can be cumbersome to create a menu that is quick to implement (thus cost effective) and most importantly, stable. It also has support for a delay threshold, and the ability to animate show and hide events.



Althought I use javascript-free solutions for the majority of drop-downs I make, some menus simply warrant javascript and there’s no way around it. Perhaps the content inside the menu is interactive, or if javascript support has previously been established, or maybe the DOM structure requires two oddly placed elements to interact with each other like a normal drop-down would. Given that some advanced menus require javascript (sorry Stu Nichols), this method uses javascript. All debates and javascript taboos aside, the code below is capable of creating a consistently stable show/hide relationship between a trigger element and a menu element.

My idea of a menu (it could be a ‘drop-down’ or a ‘flyout’ menu) usually consists of a trigger (the element that onmouseover reveals the menu) and a menu (the element to be shown). The code below is capable of creating menus out of nested “semantic” lists (UL, OL, DL) OR from an array of trigger/menu ID pairs depending on what you pass in as the constructor menu array.

This code requires YAHOO! User Interface Library yahoo-dom-event.js.

This article assumes you already have an HTML/CSS menu in mind. This article does not teach how to write CSS. This code simply provides an outlet for controlling show/hide functionality with javascript. The MenuCreator code basically creates a show/hide relationship between a trigger and a menu element. It’s purpose is to eliminate the nuances of mouse event handling when creating simple show/hide menus.

Examples:

  1. Example 1 (div menu AND a nested list menu)
  2. Example 2 (complex example of a div menu)
  3. Example 3 (used on a live site)

The usage is simple:

  1. Once you have your CSS and HTML setup to look the way you want, add the MenuCreator code to your page:
    var MenuCreator = function(obj) {
    	for (var oMenu in obj.menus) {
    		(function(index) {
    			var trigger = (obj.menus[index].trigger) ? YAHOO.util.Dom.get(obj.menus[index].trigger) : YAHOO.util.Dom.get(obj.menus[index]);
    			var menu = (obj.menus[index].id) ? YAHOO.util.Dom.get(obj.menus[index].id) : YAHOO.util.Dom.get(obj.menus[index]).lastChild;
    
    			if (!trigger || !menu || !obj.show || !obj.hide) return;
    
    			function enterLink(e) {
    				obj.show(trigger,menu);
    				YAHOO.util.Event.stopEvent(e);
    			}
    
    			function exitLink(e) {
    				if (!e) var e = window.event;
    				if (e.stopPropagation) e.stopPropagation();
    				else e.cancelBubble = true;
    				var relTarg = e.relatedTarget || e.toElement;
    				if (relTarg != menu) {
    					obj.hide(trigger, menu);
    					return false;
    				}
    				YAHOO.util.Event.stopEvent(e);
    			}
    
    			function exitField(e) {
    				if (!e) var e = window.event;
    				var tg = (window.event) ? e.srcElement : e.target;
    				if (e.stopPropagation) e.stopPropagation();
    				else e.cancelBubble = true;
    				if (tg.nodeName != 'DIV' && tg.nodeName != 'UL') return;
    				var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
    				while (reltg != tg && reltg.nodeName != 'BODY' && reltg.nodeName != 'HTML') {
    					if (reltg == this) return;
    					reltg = reltg.parentNode
    				}
    				if (reltg == tg) return;
    				obj.hide(trigger, menu);
    				YAHOO.util.Event.stopEvent(e);
    			}
    
    			YAHOO.util.Event.on(trigger, 'mouseover', enterLink);
    			YAHOO.util.Event.on(trigger, 'mouseout', exitLink);
    			YAHOO.util.Event.on(menu, 'mouseout', exitField);
    		}(oMenu));
    	}
    };
  2. Create the menu by passing in an array of menu/trigger ID pairs and a show function and a hide function.
    var divMenu = new MenuCreator({
    	menus: [{
    		id: 'box1',
    		trigger: 'tab1' },
    	{
    		id: 'box2',
    		trigger: 'tab2'},
    	{
    		id: 'box3',
    		trigger: 'tab3'
    	}],
    	show:function(trigger,menu) {
    		YAHOO.util.Dom.addClass(trigger, 'hover');
    		menu.style.display = "block";
    	},
    	hide:function(trigger,menu) {
    		YAHOO.util.Dom.removeClass(trigger, 'hover');
    		menu.style.display = "none";
    	}
    });

Requires
yahoo-dom-event.js
Include this file by placing this above the MenuCreator code.

FAQ
Why provide a custom show/hide function?
Often the specific things that need to happen on a successful show/hide differ from menu to menu, so abstracting them from the MenuCreator seemed more logical.

<script type="text/javascript" src="http://yui.yahooapis.com/2.5.2/build/yahoo-dom-event/yahoo-dom-event.js"></script>

Why is a bulky menu creator solution like this (after yahoo-dom-event.js) useful?
Most programmers prefer to use one JavaScript library (if any) for a web site. This code is most useful for a web site that is already using YUI (like all Pint web sites, where YUI is included by default).

Why not use something like suckerfish drop downs?
Suckerfish drop downs only support nested lists with links in them. Suckerfish code is not a robust solution for complex HTML menus. MenuCreator offers a convenient way to deal with multiple javascript hover interfaces.

Assumptions

  1. Assumes the menus are hidden by default (using display:none, visibility:hidden, or however else you wish).
  2. Assumes the show/hide events are mouseover/mouseout events.

Special Thanks
Peter Paul-Koch (quirksmode) for his mouse event DOM solutions.

Diana Chan who wrote the prototype for this entire snippet.

Tags: , , , ,

Leave a Reply