The JavaScript Menu component
provides a user- and developer-friendly
way for you to build easily customized, floating menus
made up of pure, cross-browser Dynamic HTML (DHTML) and object-oriented JavaScript. In this article
I'll show how you can use the (free to the public)
Menu component to build
DHTML menus in your own web site or web application.
(Note that only version 4.0 browsers will display the DHTML menus; 3.0 browsers will simply ignore them.)
Part 2 of this article focuses on advanced features, such as
drag-and-drop menus, static menus, and tracking menus.
The Menu component provides
the pull-down menu element that's common in most graphical user interfaces (GUIs).
Using a familiar GUI element will reduce the interface learning curve of your web site or
application for new users,
as well as help all users more easily find what they're looking for.
Having menus that contain links to sections at various levels in your web site
can improve both the navigation of
the site and the real estate of your web pages.
I'll show how easy yet useful it can be to take advantage of DHTML
in your web site or application.
Using optimized, pre-built DHTML widgets that are portable like the Menu
component can save you a lot of time and effort,
because the hard part has already been done for you.
You only need to know how to copy the JavaScript code into
your web page and how to customize it.
You can try the sample scripts provided in this article
and visit some of the public web sites that use the
Menu component as a navigation utility
(for example,
the Netscape Web Building site).
I'll assume you're familiar with the basic syntax of the JavaScript language.
If you're new to JavaScript, this is a great place to learn a lot about it quickly.
If you already know JavaScript, you should soon enjoy modifying and customizing your own menu objects
(through an object-oriented architecture that resembles JavaBeans, or the
Dialog API
in the DevEdge Online sample code library).
Getting Started
Adding menus to your web site is easy.
To begin, you need to add the following line of code into your web page:
Here the standard HTML <SCRIPT> tag contains a SRC attribute
for including the menu.js JavaScript library in your page source.
This library contains the code for the Menu component constructor and functions that you'll need for building menus.
It's best to place this tag in the HEAD section of your page source.
As you may know, one of the many benefits of using JavaScript libraries is that they're
kept in memory via Navigator's cache,
so the library code doesn't have to be downloaded each time.
Also, the library code can be shared by multiple pages and by different sites;
when a change is made to the library file, it applies to all the pages that are sharing
that code.
Adding the above <SCRIPT> tag shouldn't affect the performance of your web page.
If it does in any considerable way, please
report the problem.
Note that you can alternatively download menu.js onto your hard drive, and point to it locally from within your code or host it on your server.
Creating Menus
The syntax for creating your own menus isn't very different from that of
regular client-side JavaScript code.
The Menu component provides an object-oriented application programming interface (API)
which includes a collection of member functions, procedures, and variables for creating new "menu objects" -- instances of the Menu prototype object.
You create a menu object by using the JavaScript new operator
and the Menu() constructor, as follows:
var myMenu = new Menu();
This new expression creates a new menu object called myMenu.
The template-based Menu() constructor implements prototype-based inheritance and shared properties for new menu objects.
Using the new operator and a constructor is how you create most new objects in JavaScript.
You can add "menu items" to your newly created menu object with
the addMenuItem() method, which is built into the Menu component.
For example:
myMenu.addMenuItem("my item");
If you're familiar with the Java language, you may notice that the syntax of the
Menu API is very similar to the way menus are applied to Java applications with the JDK
java.awt.Menu class; the addMenuItem() method in the Menu component is analogous to the
java.awt.Menu.add() method. You can add a menu item to your menu
object by passing any text string
as a "label" parameter to addMenuItem().
To continue adding menu items, keep calling addMenuItem(), as shown
in the sample script in Example 1.
Example 1
<SCRIPT LANGUAGE="JavaScript1.2">
<!--
var myMenu = new Menu();
myMenu.addMenuItem("my menu item A");
myMenu.addMenuItem("my menu item B");
myMenu.addMenuItem("my menu item C");
myMenu.addMenuItem("my menu item D");
myMenu.writeMenus();
//-->
</SCRIPT>
This script creates a new menu object with four menu items, each with its own string label:
"my menu item A", "my menu item B", and so on.
To see what this menu object displays, click the following link, which calls the showMenu()
method to display the menu:
I should also explain the writeMenus() method, which is called at the end of the script
in Example 1.
This method writes the menu GUI into the menu container of your page.
The menu container is a CSS-P-based object that the Menu component creates for writing
and storing your menus.
The purpose of the menu container is to enable you to create and write,
or change and rewrite, your menus at any time, on the fly.
You need to call writeMenus() at the end of your menu script.
If you instead use the write() method to write your menus at the
regular window.document level after the page is loaded,
you'll wind up rewriting the entire web page document.
(Later the section Creating Multiple Menus
illustrates using writeMenus() when you've created more than one menu.)
Moving on, Example 2 creates another, more useful menu.
This example is similar to Example 1, but it passes a second parameter to addMenuItem().
I've actually defined addMenuItem() to have two parameters: a "label" and an "action" parameter.
The action parameter is for setting the JavaScript action or function that should be called or performed when
the user clicks the menu item.
In Example 2, I set all the menu item actions so that the browser will change the location of the page to
the specified URL.
To try this out, click the following link:
Notice that like the label parameter, the action parameter is a text string,
but in this case a string of code.
The action string is registered by the addMenuItem() method for an event handler in
the menu object, and the menu item gets a copy of the event data.
The event handler performs the action at run time, when the user clicks the menu item.
(The action string evaluation is similar to that performed by the JavaScript eval() method.)
Wrapping the action code in a string makes it possible
to pass practically any kind of action, including multiple JavaScript statements and
expressions, without having the action performed until the event takes place.
You can even pass variables or functions before they're defined, because they
aren't evaluated until run time.
For example, instead of passing a string of code like
"location='http://developer.netscape.com'" for the item action
(as in Example 2),
we could have passed
"document.bgColor='red'" to change the document's bgColor property to
red. For an example script that does this,
see Example 3.
Example 3
<SCRIPT LANGUAGE="JavaScript1.2">
<!--
var myMenu3 = new Menu();
myMenu3.addMenuItem("Red", "document.bgColor='red'");
myMenu3.addMenuItem("Green", "document.bgColor='green'");
myMenu3.addMenuItem("Blue", "document.bgColor='blue'");
myMenu3.addMenuItem("White", "document.bgColor='white'");
myMenu3.writeMenus();
//-->
</SCRIPT>
Now that you know how easy it is to add a menu to your web page, let's look at how to customize menus.
You can change the sizes, fonts, and colors of your menus by changing the settings of the Menu prototype properties
from their default values.
Example 4 creates a menu similar to the one in Example 2, but a bit more customized.
This sample script includes two extra lines of code that override two of the default property values, by
setting the menu's fontColor property to "blue" and its menuItemBgColor property
to "yellow".
You can copy this script and put in your own colors.
There are several other properties that you can customize in the Menu prototype object,
such as the font size, the font family, and the highlight color for your menu items.
Here's a list of the properties (and their default settings) that you can change:
This time the menu is brick-red, with a larger, bolder font.
As you can see, the font characteristics are determined by the menu's fontWeight,
fontSize, and fontColor properties.
(These are the same as the regular JavaScript font styles.)
Take advantage of any or all of the customizable Menu prototype properties to make your menus match
the look and feel of your web site.
Creating Multiple Menus
You can add multiple menu objects and scripts to your page in various ways and display them whenever you want.
Compare Example 6 to the earlier examples.
Notice that the three new menu objects in Example 6 are contained inside a function called
loadMenus().
Placing your menu code in a function enables you to load your menus whenever you want,
because the menu objects aren't actually created until the function is called.
For example, you can call this function after your page is loaded, allowing the main content of your
page to load the menus.
Although there are three menus, this script contains only one
call to the writeMenus() method.
That's a feature of the Menu component:
you only need to call writeMenus()once,
because every time you create a menu object for your page,
the new object gets registered and stored in an array called window.menus,
which contains all the menus you've created for that window.
Whenever you call writeMenus() (which in turn calls document.writeln()),
it writes all the menus stored in the window.menus array.
This way your script calls document.writeln() only once for all the menus,
rather than calling it several times, which can be much slower.
For the Open Studio web site,
writeMenus() takes less than one second to load its menus,
and this article quickly loads about 20 example menus
(hopefully you'll have far fewer menus than that!).
The window.menus array is a regular JavaScript array,
so you can access the elements of it the same way you access other JavaScript arrays that you create,
or the window.images or window.forms array.
The window.menus array is readable and writeable; you can modify it
or access the objects it contains at any time.
Also notice that Example 6 doesn't use the JavaScript
var keyword like the earlier examples.
Instead of using
var myMenu6 = new Menu();
this example creates a new window object property, with the following syntax:
window.myMenu6 = new Menu();
If I had instead used the var keyword, the myMenu6 object
would have been declared only as a local variable of the loadMenus() function.
However, I want myMenu6 to be a "global" variable (or window property),
so I declared it as a property of the window object.
This is one of the many powerful features of the JavaScript language.
Creating Child Menus
In this section, I'll show how you can add "child" menus, or submenus, into
your regular menus to display a deeper hierarchical menu tree.
Creating a child menu is exactly the same as creating a regular menu.
(Again, it's similar to using the Java Menu class in the JDK.)
Example 7 shows how to create a single child menu hosted inside a regular "parent" menu.
As in the Java language, once you create a menu object, you can pass it as a child menu
for another menu; Example 7 does this by passing the mySubMenu7 object as the label parameter
in one of the addMenuItem() calls for the myMenu7 menu.
Thus the mySubMenu7 menu object becomes the child of the parent (myMenu7) menu.
You can also share the same child menu or menus with several different
parent menus, and vice versa.
Example 8 shows how to create a menu that serves as the "root" parent menu for several
different child menus. This script is quite short, because
it uses the prescripted menu objects from the previous examples as the child menus.
Example 8
<SCRIPT LANGUAGE="JavaScript1.2">
<!--
function loadMenus ()
{
window.myMenu8 = new Menu("my menu tree");
myMenu8.addMenuItem(myMenu2);
myMenu8.addMenuItem(myMenu3);
myMenu8.addMenuItem(myMenu4);
myMenu8.addMenuItem(myMenu5);
myMenu8.addMenuItem(myMenu7);
myMenu8.writeMenus();
}
//-->
</SCRIPT>
Notice that the labels displayed in myMenu8 for the submenus myMenu2
through myMenu5 have the default labels "menuLabel0" through "menuLabel4". These
default labels were used because no label was passed when each of those menu objects was created
in the earlier examples. Compare
this to Example 7, where I passed a label when creating myMenu7,
by setting
window.myMenu7 = new Menu("Technologies");
Consequently, the myMenu8 menu displays "Technologies" as the label for the submenu
myMenu7. If you're going to be creating submenus or adding your menus to a menu bar
(as in
Visual DHTML ),
you should get into the habit of always passing a label to all your menu objects.
Although you can logically add as many child menus into as many parent menus as you want
(to as many levels deep as you want), you should try not to use too many child menus,
to avoid annoying or confusing the user.
In fact, I recommend avoiding the use of child menus altogether whenever possible, although
having one or two of them once in a while is usually OK.
Just keep it simple.
Prototyping Menu Styles
You've seen how to customize the font and color styles of a menu object by changing the default property values,
and how to create multiple customized and child menus.
Usually when creating multiple customized menus for your web page,
you'll want to use the same font and color styles for all your menus.
For this reason, I added style prototyping into the architecture of the Menu object.
Example 9 illustrates what you would have to do to customize
the font and color styles for three sample menu objects
without prototyping.
To change the font and color styles,
I added four new lines of code to Example 6 for
each of the three menu objects, for a total of 12 new lines.
Imagine if there had been ten menu objects: this could add up to a lot of new code.
If you want these style properties to be different for each menu object, you have no alternative;
however, if you want all your menus to have the same customized look and feel,
using the menu prototyping feature is faster, as well as easier to maintain.
Example 10 accomplishes the same thing as Example 9
but with prototyping.
Compared to Example 9, this example uses only five lines of code rather than 12; the more
menus there are, the more lines of code saved.
This is done via the prototypeStyles property, which you set to the menu object
whose styles you want to be used as the prototype for your other menu objects.
Displaying Menus
Remember that your menus are CSS-P-based objects
that you can show and hide using the visibility property.
To make life easier for you, I've built showMenu() and
hideMenu() functions into the Menu component.
The showMenu() method is what I've been using all along to show the example menus.
Here's how to use showMenu() as an event handler:
As you can see, I set the onClick attribute for the <A> tag
to call showMenu() with the mozilla menu object as its argument.
This displays the mozilla menu I created earlier.
Passing a menu object as an argument to showMenu()
is one of the simplest ways to show a menu.
By default, the menu will be displayed wherever your mouse was when showMenu() was called:
the top left corner of the menu falls at that mouse position.
You can override the default menu placement as follows:
I've defined showMenu() to have two more parameters, for setting
the x and y coordinates on the page (for the left and top positions) where you want the menu to be displayed.
The above example shows how to override the default x position: passing 150 as the x coordinate
displays the mozilla menu
exactly 150 pixels from the left page border, rather than where the mouse pageX position was.
An onClick attribute of "showMenu(mozilla, 150, 150)" would set the x and y coordinates equally.
You can also use other event handlers and actions to show and hide menus.
Here's how to use the familiar onMouseOver event handler to display menus:
This is the same as the previous example, except that it uses onMouseOver instead of onClick
to show the menu.
Run your mouse over the following link to see how this works:
You may prefer using onMouseOver instead of the onClick style for
displaying menus on your web page.
I recommend implementing the onClick style for application interfaces,
because most application GUIs display menus based on user clicks.
However, for web pages the onMouseOver style is well accepted in most cases,
as in the Open Studio site. Again, just remember to keep it simple.
You can implement the onMouseOver or onClick interface with an image,
too, or use other event handlers, or capture any of the new JavaScript
1.2 events, such as
DblClick,
DragDrop,
KeyDown,
KeyPress,
KeyUp,
MouseDown,
MouseMove,
MouseOut,
MouseOver,
MouseUp,
Move,
or
Resize.
You can also pass the
menu's label string or CSS-P object ID
to showMenu()
-- the Menu component looks for these variables, too.
Compare the following with the previous example:
See the difference?
The argument passed to showMenu() is the "Mozilla" label (a string type)
rather than the mozilla object type.
This flexibility enables you to create event handlers more easily
(especially if you're not so familiar with object-oriented syntax)
and in a greater variety of ways.
On the subject of hiding menus:
Usually you don't have to worry about doing this, because the Menu component sets up the
hideMenu() method to hide the menus for you, by default.
That is, when the user moves the mouse out of a menu, hideMenu() gets called,
because I set the onMouseOut event handler to hideMenu() for the menu objects.
I also overrode the window.onMouseUp event handler
with hideMenu() to hide the menu if the user clicks elsewhere in the window.
This reflects the menu behavior that's familiar on most operating systems.
If you want to change any of this default behavior, you can override these event handlers
with your own functions.
Sharing Menus Across Domains
You can really take advantage of the basic features of the Internet with JavaScript,
especially when you use JavaScript libraries.
Storing your menu code in JavaScript .js libraries allows you not only to share code
on your own domain, but also to safely share it across different domains, similar to how folks have shared
GIF images since the beginning of the web's existence.
In other words, you can view the prototypical Menu library
as a real Internet component or shared object.
Web Building Studio is a perfect example of this.
It not only uses all the Menu component features that I've discussed so far,
but it also distributes the Menu component and several customized menu objects across the
Netscape developer domain,
as well as other external domains such as the
C|NET,
Wired,
Adobe,
Apple, and
Macromedia domains, just to mention a few.
The way Open Studio shares its menu objects is by storing them in a JavaScript library
named openstudio.js.
(This library also contains other code that both Open Studio's pages
and other external sites share, such as some links and MouseOver code.)
As illustrated in previous examples, the openstudio.js library
wraps the menu objects inside a function called loadMenus(),
so that the menu objects can be created after the main web page content is laid out.
Like the scripts in this article, the openstudio.js library
depends on the menu.js library for accessing the Menu code.
Sharing the code of these two libraries
allows Open Studio to share its menu objects (and other code) across the various web
pages and domains.
If you like menus, you really should use Open Studio as a reference.
It shows how useful menu objects can be, and how well they work on a busy web site
-- and they're very customized (white letters on a black background, with blue as the highlight color,
to match the site).
Cross-Browser Compatibility
Netscape Communicator 4 and Microsoft Internet Explorer 4 support different document object models (DOMs).
The Menu component accommodates for these kind of differences.
Look at the Menusource to see exactly how this is done.
Notice that I didn't use any of the traditional "browser sniffing" techniques.
Instead of looking for numerous browser versions and names, I used "object sniffing," which is a lot simpler.
Let's look at how object sniffing works in the Menu component.
For situations where I know there is (or could be) a difference in the browser DOMs,
such as with the document.layers vs. document.all array objects, I use something
like the following code:
if (document.layers)
{
document.layers["myObjectID"].top = 100;
}
else if (document.all)
{
document.all["myObjectID"].styles.pixelTop = 100;
}
Object sniffing can be more reliable than browser sniffing.
However, if you're using a browser sniffer, don't make common mistakes
like if (version == 4.0) or even if (version == 4), in case
a 4.1 or 5.0 browser comes out a few months later.
Use if (version >= 4) instead to allow for future versions.
An
earlier (mid-1997) version of the
Menu component is even more flexible and robust
(see also other objects for
Visual DHTML).
However, this earlier version is more for web applications than web sites,
and it doesn't run on Internet Explorer 4, since that IE version wasn't around at the time.
Known Issues
One of the main features of the Menu component is cross-browser compatibility;
however, both Communicator 4 and Internet Explorer 4 are known to sometimes fall short
in different areas regarding
their new DOMs and the DHTML "draft standards."
The IE implementation of JScript is getting better,
but it's still virtually light years behind the
standard implementation of JavaScript 1.2 and LiveConnect in Communicator.
On the other hand, IE4 seems to hold more water regarding the HTML 4.0 draft,
because it came out later than Communicator; however, so far it's still less stable.
Keep in mind that the upcoming 5.0 browsers will support DHTML much better and will
conform to the W3C final specs, which until now have been a moving target
(Netscape, at least, is committed to supporting all the W3C standards).
Currently, a
weaker area for Communicator 4.0 is the layout: when you resize the window it sometimes
loses some of the properties for CSS-P objects (the <LAYER> tag is still more robust).
To work around this problem,
you can capture the Resize event and call a function to reload the menu or page.
IE4 has a layout bug, too: you'll need to load your menus before the
window's onLoad event handler is called,
because IE4 will most often crash when the Menu component tries to use the JScript
innerHTML property for CSS-P objects after the page is laid out,
especially with multiple menus. Load your menus early and you should be fine.
You'll also often need to specify the width of your menus for IE4,
because IE4 doesn't explicitly assign values to CSS-P object properties.
When you create CSS-P objects the default behavior usually isn't
what you think it should be
(especially the pixelWidth property), so you'll need to
specifically set each property.
To work around the menu width problem in particular, I set the default
pixelWidth in the Menu component to 200 pixels for IE4;
otherwise, your menus would always be the width of your whole web page document in IE4.
The DOM in Communicator handles default behavior much better, because it assigns
default values to virtually all CSS-P properties for your objects.
In this case your menus will always be the width of your largest menu item.
Note that the Menu component allows you to specify the width of your menus for both browsers
by setting the menuItemWidth property.
View Source wants your feedback! Write to us and let us know
what you think of this article.
Many thanks to Paul Dreyfus, Andres Espineira, and Richard Hall for the opportunity to write this article,
and special thanks to Caroline Rose for the excellent editing job.