In this section we'll look at some general ways of integrating extra scripting. But first and more specifically, we'll look at the menu's API, which you can use to tie other scripting directly into link and menu events.
The UDM API provides information you can use to program new extensions, or tie other scripting into link and menu events. Have a look at the API receiver demo and the API rollover-text example, for some neat visual demonstrations. You might also like to check out some extensions for more sophisticated examples of the API in use.
In order to receive an event from the menu
you define a receiver function,
using um.addReceiver
. This should go in
the body section, after the main menu script:
//add new receiver function
um.addReceiver(myCustomFunction,'010');
The first argument is a reference to your function; the second argument is the event code you want to trigger it. When that event occurs your function will be called, with the event-object (a reference to the object which generated the event) passed to it as the first argument:
//receiver function
function myCustomFunction(eventObject)
{
//tell me the event object
alert(eventObject);
}
If you specify an empty string instead of an
event code for the second argument in um.addReceiver
,
your function will be called
on every event, so that you can process multiple
events if you want to. Here the
event code will be passed to your function as
a second argument, so you could, for example, use
a switch
statement to call different functions depending
on its value:
//add new receiver function
um.addReceiver(myCustomFunction,'');
//receiver function
function myCustomFunction(eventObject,eventCode)
{
//switch by event code
switch(eventCode)
{
//on link mouseover
case '020' :
//pass the link href to some function
someFunction(eventObject.href);
break;
//on menu open
case '060' :
//pass the menu object to some other function
someOtherFunction(eventObject);
break;
}
}
In the API receiver demo I'm processing every event, compiling the data into a string, and then adding that string to an event log [view the code]. You'll no doubt notice the menus working much more slowly than usual, but don't be concerned - it isn't the API which causes that, it's what I'm doing with the textarea.
In practise you wouldn't do anything so intensive, I just wanted to show you the output in real time - and I thought it would be cool But all the same, running your function on every event is obviously less efficient, so don't do it if you're only using two or three events - save it for when you want most or all of them.
Thanks to javascript's ability to pass almost anything as
a reference, you can write anonymous functions directly inside
um.addReceiver
. This might be convenient if
the scripting you're doing is only minimal,
and particularly helpful if you
wanted to pass additional arguments - simply capture and
pass-on the event object reference, then add whatever else you want:
//add anonymous receiver for menu opening event
um.addReceiver(function(eventObject)
{
myCustomFunction(eventObject, arg2, arg3 ...);
},'060');
A popular effect is to change the text in another
box when mousing over menu links, to give additional information about
the link. To do this we can use the
event code for link mouseover
, which is "020"
,
and use it to call a function that reads the text from the
link's title
attribute, then writes it to the box:
//add receiver for link mouseover
um.addReceiver(changeText,'020');
//change text function
function changeText(linkObj)
{
//get text box object
var textBox = document.getElementById('textbox');
//write link title to text box
textBox.innerHTML = linkObj.title;
}
The text box in this case is just a
<p>
with the id="textbox"
.
To clear the box afterwards we need a function tied to
link mouseout
, which is
event code
"025"
. This only takes one line of code
so I'm just gonna put it inside an anonymous function,
because it's quicker to type (and because I
think closures are inherently cool):
//add anonymous receiver for link mouseout
um.addReceiver(function()
{
//clear text box
document.getElementById('textbox').innerHTML = '';
},'025');
However ... that
won't fire with every link mouseout
- only events
where the to-element is outside the from-element are reported,
meaning that moving to a link in a child menu
will not fire the mouseout
of its parent,
and this ultimately means that
the box will not be cleared if the next link has no
title
text. So what we have to do is modify the original
mouseover
function, to clear
the box before writing a new value (or not):
//change text function
function changeText(linkObj)
{
//get text box object
var textBox = document.getElementById('textbox');
//clear text box
textBox.innerHTML = '';
//write link title to text box if it has one
if(linkObj.title)
{
textBox.innerHTML = linkObj.title;
}
}
And there you go Have a look at the API rollover-text example to see this in action.
Please note that event codes are strings, not numbers.
Code | Description | Object reference |
---|---|---|
000 | Begin initialising | Root <ul> |
001 | Keyboard module initialised | Keyboard module object |
002 | Speech module initialised | Speech module object |
008 | List item initialised | <li> |
009 | Navbar object initialised | Navbar object (um.n ) |
010 | Ready | Root <ul> |
020 | Link mouseover | <a> |
025 | Link mouseout | <a> [only events where the to-element is outside the from-element are reported] |
030 | Link mousedown | <a> |
035 | Link mouseup | <a> |
040 | Link focus | <a> |
058 | Menu displayed, but invisible and unpositioned | <ul> |
060 | Menu open | <ul> |
061 | Start menu open timer | <ul> or null [null = no menu to open] |
065 | Start menu open transition | <ul> |
066 | End menu open transition | <ul> |
067 | Hide select elements | <select> collection |
070 | Menu closed | <ul> |
071 | Start menu close timer | <ul> |
077 | Show select elements | <select> collection |
080 | Keyboard mount command | Root <ul> |
090 | Keyboard unmount command | Root <ul> |
100 | Up key pressed | Current <a> |
101 | Right key pressed | Current <a> |
102 | Down key pressed | Current <a> |
103 | Left key pressed | Current <a> |
110 | Menu repositioned horizontally | <ul> |
120 | Menu repositioned vertically | <ul> |
Some events only occur in certain environments - for example,
"040"
(link focus) will only happen in browsers that support
Keyboard navigation.
Version 4.5 introduces the ability to initialize the menu
before window.onload
, and so bypass
the script's dependency on the loading of images and other embedded media.
This is controlled using the
um.trigger setting
in your configuration file.
However this change means that any scripting you do which is tied into the
"Ready" event
(event "010"
) may need to be
adjusted, or at least looked at, to make sure it still behaves correctly.
The point here is that the script's initialization point is
not a direct and interchangeable replacement for
window.onload
, simply because it's not dependent on
images and other embedded content. But if you're doing any scripting
which relies on information from such content - like the width and
height of a bunch of images, or most significantly, the
document contents of an iframe
or object
-
this information is no longer reliably available from the
"Ready" event,
because these elements may not have finished loading yet.
This was the situation for the Import HTML extension, and to fix that it was necessary to re-implement the initialization code, so that it uses a closure-based onload solution followed by refresh, instead of hooking into API events directly.
A number of methods are available which you might find useful when scripting with the API, or when scripting in general on pages which have access to the menu codebase. They might also be useful if you're using Popup menus.
Re-initialises the navigation tree, as though the page had been reloaded. There is no return value:
//refresh the tree
um.refresh();
This method also takes an optional boolean argument,
which specifies whether
API
initialisation events should fire again. This
defaults to false
if undefined.
For more about this method and its uses, please see:
Refreshing the tree after dynamic changes
A basic reset function, it closes all open menus and clears all highlighted links. There is no return value:
//reset the menus
um.closeAllMenus();
Based on a method by Peter Bailey.
Pass an element name, and a set of attributes in the form of an object literal, and it will create that element using HTML or XHTML methods, as appropriate to the browser. Returns a reference to the newly-created element:
//define attributes in an object literal
var attribs = { 'class':'foo', 'id':'bar', 'text':'Hello world' };
//create the element with those attributes
var mySpan = um.createElement('span',attribs);
//apend it to the page
document.getElementsByTagName('body')[0].appendChild(mySpan);
Using the keyword "text"
inside that object literal
instructs the method to create a text node
rather than an attribute
(you can only pass plain text, not entities or comments).
Otherwise, the method will create an attribute with the given name and value. It doesn't check for validity, and will try to create invalid or non-existent attributes, if you tell it to.
Please note that for overall compactness, this method cannot
create namespaced attributes (such as
xml:lang
) nor the for attribute
of a <label>
element;
you'd have to do those yourself:
//create an "xml:lang" attribute
document.documentElement.setAttributeNS('http://www.w3.org/1999/xhtml','xml:lang','en');
//create a "for" attribute
myLabel.setAttribute('htmlFor','value');
You also shouldn't use it for setting the
src
of an image, because of
mozilla's behavior with empty src values.
Pass an element reference, and whether to
get the x
or y
co-ordinate.
Returns the
true position as an integer offset (in pixels) from the
left or top of the page:
//element reference
var obj = document.getElementById('foobar');
//get the position
var position = {
'x' : um.getRealPosition(obj,'x'),
'y' : um.getRealPosition(obj,'y')
};
//tell me the x
alert(position.x);
In Mac/IE5 these
figures will be incorrect by an inverse of the
body margins (not CSS
margin properties, the leftMargin
and
topMargin
properties set by
<body>
attributes). This doesn't much affect the menu
because for its use
(menu repositioning)
a small margin of error is acceptable.
However if it does affect your own scripting
you can work around it with
CSS margins - set them to 0
so
the problem doesn't manifest:
html,body {
margin:0;
}
Returns the inner dimensions of the window
(the viewport size) as an object
with integer x
and y
properties:
//get the window dimensions
var winSize = um.getWindowDimensions();
//tell me the width
alert(winSize.x);
Returns the scrolling distance from the left or top
of the page as a single integer; the function takes
an optional boolean argument, where true
means
to retrieve the left
value, and false
or no argument means the top
value.
//get the scrolling position
var scroll = {
'x' : um.getScrollAmount(true),
'y' : um.getScrollAmount()
};
//tell me the left value
alert(scroll.x);
The following two properties declare overall feature support:
Even if you're not
using the API
you can still incorporate extra scripting,
by adding HTML
event-handlers directly to links and menus, and they'll
mostly work just as they normally would.
One obvious example would be an onclick
event to open a popup window.
For what it's worth, I generally disapprove of window manipulation. But in the name of pragmatism I'm going to show you a way of opening popups that minimizes any potential accessibility problems, and will allow me to demonstrate the general principles.
The most important thing is that the underlying link should still
do something - never use the javascript:
pseudo-protocol,
but rather, use the onclick
event
and control its return value to cancel the link. For example:
<li><a href="/menu/"
onclick="window.open('/menu/');return false"
>About us</a></li>
The same thing applies if you use a function call - an event-handler like this:
<li><a href="/menu/"
onclick="return openWindow(this)"
>About us</a></li>
would call a function like this:
//open custom window
function openWindow(linkObj)
{
//open with link object href
window.open(linkObj.href);
//cancel the normal link
return false;
}
Please note that while event-handling attributes
on <a>
and <ul>
will
mostly be fine, event-handlers on <li>
will
generally not work, because the script already uses most
of the common events as expando properties of those items. For more about
this please see
Potential javascript conflicts.
Binding an anonymous function to an element in the DOM is generally better than using an attribute event-handler. It's cleaner, easier to maintain, and arguably more correct (since event-handlers do not have universal semantics, maybe they shouldn't really exist as HTML attributes at all).
So for example, this link:
<li><a id="aboutButton" href="/menu/">About us</a></li>
could have a function bound to it like this:
//bind anonymous function to "aboutButton"
document.getElementById('aboutButton').onclick = function()
{
//tell me the link object href
alert(this.href);
}
Remember that you can only do that once the
DOM is ready, so code like that would
either have to be inside a window.onload
or equivalent function,
or if the element in question is inside the menu tree, you could hook into the
"Ready" event of the
UDM API.
UDM 4 is valid XHTML, and in our judgement, meets the criteria for WAI Triple-A conformance.