Developer's manual

Using popup menus

With popup menus you can open and position menu-branches using events from other page elements. You can use it to add menus to your existing page or navigation structure, without having to change it.

You can also use it to make layouts which would otherwise not be possible - such as menus that appear to cross frames, or which come from other structures such as inline-links or even Flash movies.

Popup menus are enabled by setting the navbar alignment to "popup". In this mode the standard navigation bar is not visible at all, but it should still have content, as we'll see when we look at Accessibility considerations a little later. But first the mechanics:

Assigning IDs

Each branch of menus will be identified by the id of its parent list-item. You may as well add one to every top-level item, for example:

<li id="home"><a href="/">Home</a>
	<ul> ... </ul>
</li>
<li id="about"><a href="/menu/">About</a>
	<ul> ... </ul>
</li>

and so on.

Menu methods

There are two methods - one for opening a branch and one for closing it. The general syntax looks like this:

//activate menu
um.activateMenu('id', 'left', 'top');

//de-activate menu
um.deactivateMenu('id');
For um.activateMenu
you pass the id of the branch you want to open, then its left and top position. You cannot omit the unit unless the value is "0" - for example "100px", "5em" and "25%" are all okay, but "100" is not.
For um.deactivateMenu
you simply pass the branch id.

Both methods are automatically delayed, to implement open and closer timers.

Remember that the computed value of a scaleable unit is contextual - "25%" is 25% of the width of the trigger element's parent, not [necessarily the same as] 25% of the document width. If you use variable units they may not produce the positions you initially expect.

Variable replacement

You can also set the values programatically - for example, the position of a menu could be relative to the element that triggered it, by getting the element's co-ordinates and passing them to um.activateMenu. We'll see an example of this in a moment, using a position-finding method built into the script.

In fact there's a number of public methods you might find useful when developing with popup menus - for more about that please see Public methods.

um.ready

We're almost ready to look at some examples, but first a note about the um.ready variable, which declares the ready-state of the menu code as a boolean (true or false). You MUST NOT call either of the menu methods without first checking that they're ready, or you may get errors due to incomplete loading. So wrap all calls in this condition:

if(um.ready) { ... }

Do it as a matter of course, because errors due to incomplete loading are seldom apparent in local development. It also serves the secondary purpose of protecting unsupported browsers, because for them it's never true.

Examples

Now for some examples, and what I'm going to show you in each case is some javascript menu-handling code, and the HTML that triggers it. I've done it this way because it's much neater and more re-useable than writing calls to um.activateMenu directly inside the event-handlers.

But in practise it would probably be better (cleaner and more encapsulated) not to use inline event handlers at all, and add the scripting using handlers in the DOM.

Absolutely positioned

Absolute positioning is the simplest to use - you simply pass x and y co-ordinates:

//open menu with given ID and co-ordinates
function openMenu(menuID,menuX,menuY)
{
	//if the menu code is ready
	if(um.ready)
	{
		//activate menu
		um.activateMenu(menuID, menuX + 'px', menuY + 'px');
	}
}

//close menu with given ID
function closeMenu(menuID)
{
	//if the menu code is ready
	if(um.ready)
	{
		//deactive menu
		um.deactivateMenu(menuID);
	}
}
<ul>
	<li><a href="/" title="UDM Home page">
		Home</a></li>
	<li><a href="/menu/"
		onmouseover="openMenu('about',200,155)"
		onmouseout="closeMenu('about')">
		About UDM ...</a>
	</li>
	<li><a href="/demos/"
		onmouseover="openMenu('demos',220,205)"
		onmouseout="closeMenu('demos')">
		Demos ...</a>
	</li>
	<li><a href="/licensing/">
		Purchase</a>
	</li>
	<li><a href="/licensing/download/">
		Download</a>
	</li>
	<li><a href="/menu/support/" title="Help and support"
		onmouseover="openMenu('support',180,255)"
		onmouseout="closeMenu('support')">
		Support ...</a>
	</li>
	<li><a href="/contact/" title="Contact the author">Contact</a>
	</li>
	<li><a href="/" title="Brothercake home page">Brothercake</a>
	</li>
</ul>

Demo of popup menus, absolutely positioned

Relative to a link

For this example I'm using the script's built-in position-finding method - um.getRealPosition - which returns a number for the triggering object's x or y co-ordinate. These numbers, plus the object's height and a small margin, make the menu open nicely below the link:

//open menu with given ID
function openMenu(menuID,linkObj)
{
	//if the menu code is ready
	if(um.ready)
	{
		//find co-ordinates of link object
		var coords = {
			'x' : um.getRealPosition(linkObj,'x'),
			'y' : um.getRealPosition(linkObj,'y')
			};

		//increase y-position to place it below the link
		coords.y += (linkObj.offsetHeight + 4);

		//activate menu at returned co-ordinates
		um.activateMenu(menuID, coords.x + 'px', coords.y + 'px');
	}
}

//close menu with given ID
function closeMenu(menuID)
{
	//if the menu code is ready
	if(um.ready)
	{
		//deactive menu
		um.deactivateMenu(menuID);
	}
}
<p>
	Move your mouse over the links in this paragraph to
	see menus opening below them. Like this link to the
	<a href="/menu/"
		onmouseover="openMenu('about',this)"
		onmouseout="closeMenu('about')">
		About UDM</a> section, or this one to the
	<a href="/menu/support/"
		onmouseover="openMenu('support',this)"
		onmouseout="closeMenu('support')">
		Help and support</a> page.  Finally, here's a link to the
	<a href="/menu/demos/"
		onmouseover="openMenu('demos',this)"
		onmouseout="closeMenu('demos')">
		Demos</a> page.
</p>

Demo of popup menus, relative to a link

Across frames

You can put the menu code in one frame and trigger it from links in another frame. If you do this you'll be able to mix non-HTML content within your frameset, such as text-documents or PDF - obviously the menus won't appear on those pages, but you won't get any errors either, and they'll start working again once you're back on a page containing the menu code.

But communicating across frames is slightly complicated, because frames load asynchronously in an unpredictable order. We have to check for the existence of the menu object, before we can ask if it's ready, or use it.

So, using the initial reference parent.frames['main'] we're saying: if the um object is defined, and it has a property called um.ready, and the value of that property is true ... then call um.activateMenu with an absolute left position, and a top position derived from the scrolling distance, plus a fixed margin. I've used another built-in method there - um.getScrollAmount - which returns the scrolling distance from the top of the page:

//set a reference to the main frame
var mainFrame = parent.frames['main'];

//open menu with given ID and left position
function openMenu(menuID,menuX)
{
	if(
		//if the "um" object exists in the main frame
		typeof mainFrame.um != 'undefined'
		&&
		//and the ready state variable exists
		typeof mainFrame.um.ready!='undefined'
		&&
		//and it has a value of true
		mainFrame.um.ready == true
		)
	{
		//find y-scrolling amount plus a top margin
		var scrollY = (mainFrame.um.getScrollAmount() + 17);

		//activate menu at given left position and scroll-top position
		mainFrame.um.activateMenu(menuID, menuX, scrollY + 'px');
	}
}

//close menu with given ID
function closeMenu(menuID)
{
	if(
		//if the "um" object exists in the main frame
		typeof mainFrame.um != 'undefined'
		&&
		//and the ready state variable exists
		typeof mainFrame.um.ready!='undefined'
		&&
		//and it has a value of true
		mainFrame.um.ready == true
		)
	{
		//deactivate menu
		mainFrame.um.deactivateMenu(menuID);
	}
}
<ul id="topList">
	<li><a href="/" title="UDM Home page" target="_top">
		Home</a>
	</li>
	<li><a href="/menu/" target="_top"
		onmouseover="openMenu('about','4.7em')"
		onmouseout="closeMenu('about')">
		About UDM ..</a>
	</li>
	<li><a href="/demos/" target="_top"
		onmouseover="openMenu('demos','11.2em')"
		onmouseout="closeMenu('demos')">
		Demos ..</a>
	</li>
	<li><a href="/licensing/download" target="_top">
		Download</a>
	</li>
	<li><a href="/licensing/" target="_top">
		Purchase</a>
	</li>
	<li><a href="/menu/support/" title="Help and support" target="_top"
		onmouseover="openMenu('support','25.6em')"
		onmouseout="closeMenu('support')">
		Support ..</a>
	</li>
	<li><a href="/contact/" title="Contact the author" target="_top">Contact</a>
	</li>
	<li><a href="/" title="Brothercake home page" target="_top">Brothercake</a>
	</li>
</ul>

When used across frames, the gap between the bottom of your navbar and the top of each menu is inevitably larger than it would normally be. The gap is only practicable because of the menu timers, so if you use this configuration I recommend having a slower close-timer to compensate.

Demo of popup menus, across frames

Triggered from Flash

There's a method in ActionScript called getURL which can be used to call a javascript function using the javascript: pseudo-protocol. In this example we're passing the menu trigger id through a rollOver event:

on(rollOver)
{
	getURL("javascript:openMenu('about')");
}

The javascript function receives that id and passes it to um.activateMenu, along with predefined positions:

//menu positions indexed by trigger ID
var positions = {
	'about' : ['133px','34px'],
	'demos' : ['133px','63px'],
	'support' : ['133px','150px']
	}

//open menu
function openMenu(menuID)
{
	//if the menu is ready
	if(um.ready)
	{
		//activate menu at given co-ordinates
		um.activateMenu(menuID,positions[menuID][0],positions[menuID][1]);
	}
}

//close all menus
function closeAllMenus()
{
	//for each item in the object
	for(var i in positions)
	{
		//deactive menu with its ID
		um.deactivateMenu(i);
	}
}

The items which have a menu call the openMenu function, while the items which don't have a menu call an iterative closing method, closeAllMenus:

on(rollOver)
{
	getURL("javascript:closeAllMenus()");
}

It's done like that, rather than as a rollOut from the trigger items, to ensure that there are no synchronisation issues - I'm not 100% sure why this is necessary, but my observations suggest to me that getURL is asynchronous - it happens after a non-predictable pause. That being the case, other javascript processes may be assuming events to have occured before they actually have occured; ultimately, the overlap of events can cause menus to receive close commands while you're still using them. Closing the menus from rollovers on other items avoids this problem.

The menus in this configuration may be rather slow to open in Mac/IE5.

Demo of popup menus, triggered from Flash

Accessibility considerations

When you're using popup menus the top-level list items still exist, even though you can't see them - they're positioned offscreen, but nonetheless accessible to screenreaders, text-browsers and other non-CSS user-agents.

You can use this to good advantage, to provide an extra level of navigation for people who use assistive devices, and to search-engine robots. I think it makes sense have each invisible link pointing to the same place as the visible link which triggers that branch. But if your trigger is not itself a link, then point to a relevant index or sub-index page - for example, if you've used a product-image as the trigger, link to a page in your catalogue with details of that product.

However, if you're sure that in your particular situation there's really no need to have redundent content, the minimum definition is empty anchors - you musn't exclude the anchors or the menus won't work at all:

<li id="about"><a></a>
	<ul> ...
</li>

One final thing - Keyboard navigation is not supported when using popup menus. This is certainly unfortunate, and something I hope to rectify in a future update.


Search

We would like your feedback! Take the UDM4 Survey!

UDM 4 is valid XHTML, and in our judgement, meets the criteria for WAI Triple-A conformance.

-