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:
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.
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');
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.
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.
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.
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
.
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.
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
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
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
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.
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.
UDM 4 is valid XHTML, and in our judgement, meets the criteria for WAI Triple-A conformance.