Creating a Hover Menu with HTML5 and Simpli5

A More Usable Application

I decided to build my own version of a contextual hover menu to make my applications more usable. It is meant to appear when you select a piece of data and give you quick access to all the actions you might perform on it. Forget long toolbars and hidden right-click menus. I wanted something that a user didn’t have to dig around to find, that wouldn’t be hard to navigate, and that wasn’t hidden (a right-click on the web is not common enough for users to rely on).

I’ll walk you through the beginning process I took to create the HoverMenu component using Simpli5 and then I’ll cover at a higher level the UX considerations that went into making it even better. You can check out the component in action first (using Safari, Chrome, or Firefox).

Base Simpli5 Component

To start, Simpli5 encourages using tags that provide good semantics for the application, ignoring whether or not they are valid HTML tags. It makes your application easier to read and understand, and since Simpli5 was made for creating full web applications, SEO and other content-based concerns are thrown out the window. I will start with the component definition in HoverMenu.js. I have the base component, menu containers, and menu items that I’ll need. There will also be separators, but that has no code or logic and so can just be added in the stylesheet.

var HoverMenu = new Component({
	extend: Component, // the base when using custom tags
	template: new Template('<hover-menu></hover-menu>'), // component template used when creating from code
	register: 'hover-menu', // css selector to convert matching elements into a HoverMenu

	constructor: function() {
		// the constructor
		this.submenu = this.find(simpli5.selector('menu'));
		if (this.submenu) this.submenu.hide();
	}
});

var HoverMenuSubmenu = new Component({
	extend: Component,
	template: new Template('<menu></menu>'),
	register: 'hover-menu menu'
});

var HoverMenuItem = new Component({
	extend: Component,
	template: new Template('<menu-item></menu-item>'),
	register: 'hover-menu menu-item',

	constructor: function() {

	}
});

I’ll give it a stylesheet HoverMenu.css to make it look good.

What I want is when the user hovers over the button, the menu pops up.

constructor: function() {
	...
	this.on('rollover', this.open.boundTo(this));
	this.on('rollout', this.close.boundTo(this));
},

open: function() {
	var rect = this.rect();
	this.submenu.show(true);

	this.addClass('open');
	this.submenu.rect({left: rect.right, top: rect.top});
},

close: function() {
	this.submenu.close();
}

Of course, sometimes I might want to have the user click to popup the menu, for ones that are used less often or in the case that there are many on the screen (don’t want to have popups all over the place by moving your mouse around).

var HoverMenu = new Component({
	extend: Component,
	template: new Template('<hover-menu></hover-menu>'),
	register: 'hover-menu',
	properties: ['click-only'], // add attributes that translate to properties in this array

	constructor: function() {
		...
		this.on('click', this.open.boundTo(this)); // click will always open it
		this.clickOnly = false;
	},

	get clickOnly() {
		return this._clickOnly;
	},
	set clickOnly(value) {
		if (this._clickOnly == value) return;
		this._clickOnly = value;
		if (this.submenu) {
			value ? this.un('rollover', this.open.boundTo(this)) : this.on('rollover', this.open.boundTo(this));
		}
	},
});

Here I added an implicit getter/setter that by default is false so hovering will open the menu. But if hoverMenu.clickOnly = false or <hover-menu click-only=”false”>…</hover-menu> then you’ll have to click the button to open the menu.

I’ve also added other settings for customization: autoClose to close the menu automatically when the mouse moves off of it for a few milliseconds, menuDelay to turn off the delay menus take to close (I talk about this later), and openBelow to cause the menu to open up beneath the button instead of to the side of it.

Next we need to allow menu items to hold submenus and to dispatch events when the user selects them. It would be nice if these can be triggered by code too.

// HoverMenuItem
events: ['select'], // add custom events that can be listened to via onevent attributes

constructor: function() {
	this.on('click', this.select.boundTo(this));
	this.on('rollover', this.hovered.boundTo(this));
	this.submenu = this.find('menu');
	if (this.submenu) {
		this.submenu.hide();
		this.addClass('submenu');
	}
},

open: HoverMenu.prototype.open, // use the same function from HoverMenu

close: function() {
	if (this.submenu) {
		this.submenu.close();
		this.removeClass('open');
		this.parentNode.hoveredItem = null;
	}
},

select: function() {
	if (this.disabled || this.submenu) return;
	this.dispatchEvent(new CustomEvent('select', true)); // this is our own event and we will bubble it
	this.menu.close(); // once selected, close the whole menu
},

hovered: function() {
	if (this.disabled) return;
	if (this.parentNode.hoveredItem && this.parentNode.hoveredItem != this) {
		this.parentNode.hoveredItem.close();
	}

	if (this.submenu) {
		this.open();
	}
}

Hopefully you can understand the logic by reading through the code, but there are a couple of things I want to point out. The “events” property holds a list of custom events for the component to look for when initializing its attributes. Because I specified the select event there you can add onselect=”alert(‘item selected’)” to the tag and it will work. Also, our first usability tidbit for the menu, don’t close submenus until the user moves their mouse to a sibling menu item. That wraps up our basic-component-building-101-in-Simpli5 overview and brings us to our user experience in using this component. Now I realize that UX encompasses so much more than a component, but the usability and experience the user has with this component is what I am referring to when I say UX.

Making it Shine with Usability

Most of these things were added because I tried using the menu and noticed spots of frustration. Some were added from suggestions of others.

The first thing I did to enhance the usability of the menu was to keep the entire menu and submenus from closing immediately. The less accurate a user has to be with their mouse, the quicker they can get things done and the easier it is to use an application. If the menu closes because a user accidentally moves the mouse a little beyond the menu then they have to start all over again opening the menu up from the beginning. When the hover menu’s autoClose is true, the menu waits 600 milliseconds before closing. This allows a user to make mouse mistakes and recover from them before having to reopen the menu.

The next usability piece came from testing with longer submenus. I noticed that if I wanted to click the last item in a submenu and I moved my mouse strait to it, the mouse path went over the edge of the next sibling menu item, closing the previous item’s submenu. In order to select that last submenu item I had to alter my mouse path to a 7 shape, moving across to the submenu first, then down to the desired item. In order to allow some forgiveness in the mouse movement while trying not to hamper the speed at opening the next submenu if that is the real desired action, I added a 150ms delay in opening and closing submenus. This seemed to be enough time for a quick mouse movement down across sibling menu items into the submenu, while not being too much time if you wanted to open the sibling submenu. I also added the menuDelay option that defaults to true, but can be set to false if you want to get rid of this 150ms delay.

I added an alternate element style in the stylesheet for elements called <menu-content> which is an alternative to holding menu items in a submenu and allows robust components like color pickers or lists of images to be used, adding to the overall UX of the UI.

I added positioning support for them menus to popup above or to the left of their parent if they are near the edge of the screen. I made the menus append to the body of the document so that they wouldn’t get cut off by any overflow auto/hidden elements. There were also other small things I added too, such as a style on items with open submenus so they can be seen easier and allowing the menu to be closed by clicking elsewhere or pressing Esc.

Overall the component turned out quite well. Here is a demo page of it in action (view source to see the markup): http://jacwright.github.com/simpli5/demos/hover-menu.html

4 Responses to “Creating a Hover Menu with HTML5 and Simpli5”

  1. Dustin Woodard Says:

    Jac I’m excited about your interest in HTML5. Can’t wait to see more.

  2. daniel Says:

    any idea why this doesn’t work in the IE9 preview? (http://ietestdrive.com/)

  3. Jacob Wright Says:

    Nope! Never tested in IE9. That is actually on my list of things to do today. :)

  4. Ladida Cafe Says:

    wow, nice tutorial . .
    thanks