Bubble menu javascript or playing with YUI’s event delegation

bubble
Image courtesy of jobee59’s photostream

Event delegation refers to the use of a single event listener on a parent object to listen for events happening on its children (or deeper descendants). Event delegation allows developers to be sparse in their application of event listeners while still reacting to events as they happen on highly specific targets. This proves to be a key strategy for maintaining high performance in event-rich web projects, where the creation of hundreds of event listeners can quickly degrade performance. More on event delegation including examples.

This post illustrates the use of event delegation to create a “bubble” menu, inspired by Bedrich Rios’ post on Nettuts.

The HTML

We’ll need the following components from the YUI library:

  • yahoo-dom-event – for DOM/Event handling
  • animation – for sliding effects

So our basic HTML page is like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<!-- Combo-handled YUI JS files: -->
		<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.6.0/build/yahoo-dom-event/yahoo-dom-event.js&2.6.0/build/animation/animation-min.js"></script>
	</head>
	<body>

	</body>
</html>

Next we’ll add some structural markup, a simple list, with “menu” as class name and with 5 items.

<ul class="menu">
	<li>Menu item 1</li>
	<li>Menu item 2</li>
	<li>Menu item 3</li>
	<li>Menu item 4</li>
	<li>Menu item 5</li>
</ul>

Updated HTML page:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<!-- Combo-handled YUI JS files: -->
		<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.6.0/build/yahoo-dom-event/yahoo-dom-event.js&2.6.0/build/animation/animation-min.js"></script>
	</head>
	<body>
		<ul class="menu">
			<li>Menu item 1</li>
			<li>Menu item 2</li>
			<li>Menu item 3</li>
			<li>Menu item 4</li>
			<li>Menu item 5</li>
		</ul>
	</body>
</html>

The Javascript

Then we just have to add the bubble.js javascript file to our page and it’s done, we’ll have a nice “bubble” menu.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<!-- Combo-handled YUI JS files: -->
		<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.6.0/build/yahoo-dom-event/yahoo-dom-event.js&2.6.0/build/animation/animation-min.js"></script>
		<!-- our bubble file -->
		<script type="text/javascript" src="bubble.js"></script>
	</head>
	<body>
		<ul class="menu">
			<li>Menu item 1</li>
			<li>Menu item 2</li>
			<li>Menu item 3</li>
			<li>Menu item 4</li>
			<li>Menu item 5</li>
		</ul>
	</body>
</html>

bubble.js

/**
*	Bubble Menu
*	@author Asvin Balloo (https://htmlblog.net)
*/
bubbleMenu = {
	// private variables
	// class name of our menu
	menuClassName: null,
	// by how much we are sliding
	moveToLength : 10,
	
	/**
	*	Init the whole process here
	*	@method init
	*	@params menuClassName {string} class name of our menus
	*/
	init: function(menuClassName){
		// set our class name to be used later one
		bubbleMenu.menuClassName = menuClassName;
		
		// get all the lists with the class name
		var list = YAHOO.util.Dom.getElementsByClassName(menuClassName);
		
		// loop through them
		for(var i=0; i<list.length; i++) {
			// add the onmouseover event to it, call the mouseOverHandler function
			YAHOO.util.Event.on(list[i], "mouseover", bubbleMenu.mouseOverHandler);
			
			// get the first child and its padding left for future reference. Useful when using onmouseout to 

restore the item to original state
			var listItems = list[i].getElementsByTagName('li');
			var startingPadding = parseInt(YAHOO.util.Dom.getStyle(listItems[0], 'paddingLeft'));
			
			// add the onmouseout event, call the mouseOutHandler function
			YAHOO.util.Event.on(list[i], "mouseout", bubbleMenu.mouseOutHandler, {'list':list[i], "padding" : 

startingPadding});
		}
	},
	
	/**
	*	Mouse over handler
	*	@params e {Event} the event
	*/
	mouseOverHandler: function(e){
		// get the target
		var elTarget = YAHOO.util.Event.getTarget(e);
		
		// calculate the current padding left
		var paddingLeft = parseInt(YAHOO.util.Dom.getStyle(elTarget, 'paddingLeft'));
		
		// call the delegate method, with the target and the padding left value
		bubbleMenu.delegate(elTarget, paddingLeft);
	},
	
	/**
	*	Mouse out handler
	*	@params e {Event} the event
	*	@params o {JSON} JSON data containing the list + padding value, eg. {'list': mylist, 'padding': 10} 
	*/
	mouseOutHandler: function(e, o){
		// get the target
		var elTarget = YAHOO.util.Event.getTarget(e);
		
		// calculate the left padding, the original padding value - by how much we have padded
		var paddingLeft = o.padding - bubbleMenu.moveToLength;
		
		// call the delegate method with the target and padding value
		bubbleMenu.delegate(elTarget, paddingLeft);
	},
	
	/**
	*	Cool things go here, like animation
	*	@params elTarget {Target} our target
	*	@params padding {Int} by how much we are going to padd
	*/
	delegate: function(elTarget, padding){
		//walk up the DOM tree looking for an <li> in the target's ancestry; desist when you reach the container with our class name
		while (elTarget.className != bubbleMenu.menuClassName) {
			// are you an LI?
			if(elTarget.nodeName.toUpperCase() == "LI") {
				// if so then animate, set the attributes, in our case the left padding
				var attributes = { paddingLeft: {"to": [padding + bubbleMenu.moveToLength] } };
				
				// create our animation
				var anim = new YAHOO.util.Motion(elTarget, attributes, 0.6, YAHOO.util.Easing.bounceOut);
				
				// then animate
				anim.animate();
				break;
			}
			else {
				// it's not an li, so we keep looking and looking
	            elTarget = elTarget.parentNode; 
	        } 
		}
	}
};

// init the whole thing here
YAHOO.util.Event.on(window, "load", function(){
	bubbleMenu.init("menu");
});

You can have unlimited (don’t abuse) number of bubble menus by just adding the “menu” class to each one of them and including the bubble.js file.
If you’re not happy with the menu class or you found another cool class name, then you’ll have to update the bubble.js file towards the end:

// init the whole thing here
YAHOO.util.Event.on(window, "load", function(){
	bubbleMenu.init("mycoolclass");
});

Also you can modify by how much an item slides by modifying the moveToLength variable (default 10):

// by how much we are sliding
moveToLength : 30,

Note that the demo is using reset-fonts-grids CSS and the page has been styled with the following:

body {
	margin: 0;
	padding: 0;
	background: #1d1d1d;
	font-family: "Lucida Grande", Verdana, sans-serif;
	font-size: 100%;
}
			
.menu li{
	padding-left: 20px;
	line-height: 2em;
	width: 150px;
	color: #999;

	background: #222;
	border: 1px solid #1a1a1a;
	-moz-border-radius: 10px;
}

Expanding/collapsing javascript menu

In this post I’ll show you how to create an expanding/collapsing menu like Slashdot left menu used to be, using the YUI library and Dav GlassYUI effects widget.


The HTML markup for our menu is :

<div id="menu">
			<h1 id="menu-title">Menu</h1>
			<div id="menu-links">
				<ul>
					<li>Home</li>
					<li>About</li>
					<li>CSS</li>
				</ul>
			</div>
		</div>

We use 2 div tags, one for holding the menu itself and the other one which contains the links in an unordered list and 1 h1 tag for the title.

The CSS for the menu :

/** width of the menu */
#menu{
				width: 180px;
			}
			

/** the main title */
			#menu h1{
				line-height: 35px;
				color: #fff;
				/** simulate link */
				cursor: pointer;
				font-weight: bold;
				/** background image will change when menu expands/collapses */
				background: #666 url(images/block-arrow-expanded.gif) no-repeat scroll 30px 13px;
			}
			
/** the links */
			#menu-links li{
				line-height: 35px;
				border-bottom:1px solid #ddd;
				background-color: #eee;
			}

Please pay attention to the background for the #menu h1. It contains the image, which will change when the menu collapses or expands. When the menu collapses we’ll be using the block-arrow-collapsed.gif image and when the menu expands we’ll be using the block-arrow-expanded.gif image. You can find the images in the sample demo file in the images directory.

To do the collapsing/expanding effects, we need to include the following javascript files beforehand :

<!-- js -->
<script type="text/javascript"  src="http://yui.yahooapis.com/combo?2.5.2/build/yahoo-dom-event/yahoo-dom-event.js&2.5.2/build/animation/animation-min.js"></script> 
<script type="text/javascript" src="js/tools-min.js"></script>
<script type="text/javascript" src="js/effects-min.js"></script>

We use Dav Glass YAHOO.Tools package and his effects widget along with yahoo-dom-event-animation aggregate file which are hosted on Yahoo servers.

Finally the javascript to do the magical stuff goes like that :

<script type="text/javascript">
			YAHOO.util.Event.addListener('menu-title', 'click', function(){
				if(YAHOO.util.Dom.getStyle('menu-links', 'display') == 'block'){
					new YAHOO.widget.Effects.BlindUp('menu-links', {seconds: 0.2});
					YAHOO.util.Dom.setStyle('menu-title', 'background-image', 'url(images/block-arrow-collapsed.gif)');
				}
				else{
					new YAHOO.widget.Effects.BlindDown('menu-links', {seconds: 0.2});
					YAHOO.util.Dom.setStyle('menu-title', 'background-image', 'url(images/block-arrow-expanded.gif)');
				}
			});
		</script>

In line 3 of the above listing, we wait when the user clicks on the menu-title container and we check the display attribute of our links. If the links are not currently displayed then we display them by using Dav’s BlindDown effect with a duration of 0.2 seconds ;-).

new YAHOO.widget.Effects.BlindDown('menu-links', {seconds: 0.2});

We also change the background image for the menu title by using the YAHOO.util.Dom.setStyle method:

YAHOO.util.Dom.setStyle('menu-title', 'background-image', 'url(images/block-arrow-expanded.gif)');

If the links are visible then we do the contrary, i.e we use the BlindUp effect and use the collapsed gif as background image.