Developer's manual

Javascript development

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.

Using the API

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.

Receiving data

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);
}

Hooking into multiple events

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.

Using anonymous functions

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 practical example - changing the text in a box onmouseover

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.

Breakdown of event codes

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.

The implications of initializing before window.onload

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.

Public methods

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.

um.refresh

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

um.closeAllMenus

A basic reset function, it closes all open menus and clears all highlighted links. There is no return value:

//reset the menus
um.closeAllMenus();

um.createElement

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.

um.getRealPosition

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;
	}

um.getWindowDimensions

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);

um.getScrollAmount

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);

Feature support

The following two properties declare overall feature support:

um.d
DOM support - the browser fully supports the menu script. The following browsers are included in this group, but it will also catch future browsers which support dynamic element creation:
  • Opera 7 or later for Windows, Mac and Linux
  • Mozilla Gecko browsers, for Windows, Mac and Linux
  • Safari and OmniWeb 4.5 or later, for Mac OS X
  • Konqueror 3.2 or later, for Linux/KDE
  • Internet Explorer 5 or later, for Windows and Mac
um.b
Basic support - the browser supports enough scripting and CSS to render the navigation bar, but not enough to have stable menus. The following browsers are included in this group:
  • Opera 5 and 6, for Windows, Mac and Linux
  • Konqueror 2.2 to 3.1, for Linux/KDE

Event-handling attributes

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.

Adding handlers in the DOM

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.


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.

-