Creating Menus in JavaScript
Now that we've learned about how to create layers in CSS, and how to create and manipulate tag properties with JavaScript.
At this point we can start putting this knowledge together - we're going to create a menu system.
We're not going to overengineer this menu such that we demonstrate every possible function in JavaScript, but we will
make a useable, flexible menu system that you'll be able to easily use in your own websites.
Once you understand how this menu works, feel free to tinker around with it and improve it!
Step 1 - Defining the menu structure
We're not going to code our menu in HTML in the normal way. We're going to create a JavaScript function which generates
the HTML necessary, based upon a database of information containing the structure of the menu.
This will allow us to create a flexible menu system that can be used on multiple pages with only a small amount of extra
code on each page, which will be more robust. It will also mean that if we update the menu structure, the menus on all our HTML pages
will be updated automatically.
To start with we'll create the menu structure database. In this case it will be an array of links. JavaScript supports multidimensional
arrays (arrays within arrays), which allow us to define our menus easily.
var MENU_ITEMS = [
['Home', 'http://www.intelligent-web.co.uk', null],
['Tutorials', '../index.html', [
['1. Layers in CSS', '../css_layers.html', null],
['2. Basic JavaScript', '../basic_javascript.html', null],
['3. Menus in JavaScript', '../javascript_menus.html', null],
['4. Clever Form Validation', '../javascript_forms.html', null],
['5. Javascript Games', '../javascript_games.html', null],
['6. AJAX', '../javascript_ajax.html', null],
['7. Transparency Effects', '../css_transparency.html', null],
['8. Paypal Integration', '../paypal_integration.html', null]
]],
['Links', null, [
['W3C', 'http://www.w3.org/', null],
['Essex University', 'http://www.essex.ac.uk', null],
['Google UK', 'http://www.google.co.uk', null],
['B3TA', 'http://www.b3ta.com', null]
]]
];
|
As you can see, the array we defined contains a number of smaller arrays. We've allocated three positions in each array
the first position is for the name of the menu, the second for the actual URL to navigate to, and the third allows us to insert
a sub menu, which in turn is another array of arrays. If the menu does not have a submenu, we use the null
keyword to indicate this.
Take a moment to familiarise yourself with the code (the multiple brackets may be confusing at first!). It helps to arrange
your code with tabs, so that it's in an easily readable fashion.
Since we want this file to be accessible to all pages, we can't define it within a particular page, otherwise the other pages
would not be able to access it. This time we're going to save it as a separate Javascript file, called menu_items.js. To access it from our
HTML page, we'll take advantage of the src attribute provided by the <script> tag:
<script type="text/javascript" src="menu_items.js" ></script>
|
Step 2 - Parsing the array to build the menu
We'll now start to build our JavaScript function to read this array and turn it into a nice looking menu that our visitors will
appreciate using.
The basic idea behind our JavaScript is shown below:
readMenu(MENU_ITEMS);
function readMenu(array) {
// loop through the items in this menu
// extract the information we need from the array
// print out the item
// if the item has a sub menu {
// call readMenu() again to print that out too.
// }
// end loop
}
|
Step 3 - Writing the first function
We'll start putting this into practice step by step. First of all, we'll create a for loop
to iterate through the items in the array. We can then extract each array in turn and get out the information we need.
// loop through the items in this menu
for (i = 0; i < array.length; i++) {
// Get out one menu at a time
var menu = array[i];
// extract the information we want from the array
var name = menu[0];
var url = menu[1];
var submenu = menu[2];
}
|
Step 4 - Creating the top level menus
OK. We have an idea of how to read the array and extract the data. But what we need to do is actually convert this information
into HTML that the user can interact with.
We'll start by printing out the top level menus. Each top level menu will be a table. We'll use CSS absolute positioning
to put the tables exactly where we want them.
Using absolute positioning means that we need to remember the different X and Y positions so that we can put each menu in the right place.
To do this, we'll pass X and Y parameters to our method, and each time we add a new top level menu, we'll increase the value of the X variable
so that the next menu is further to the right.
function readMenu(array, x, y) {
...
// print out the item
document.write("<table border='1'
style='position: absolute; left: " + x + "; top: " + y + ";'>");
document.write("<tr><td>");
document.write(name);
document.write("</td></tr>");
document.write("</table>");
x = x + menuWidth + xGap;
...
}
|
You'll notice we've also created a couple of variables, called menuWidth and xGap, these are defined at the
top of our JavaScript function. We'll be able to change these later to tweak our menu's size and shape.
Thus far we've managed to print out (in a very ugly manner - but we'll fix that later!) the top level elements within our menu structure. Now we'll
concentrate on printing out the sub menus of each of these.
Since the sub menus are different in format to the top menus (they list downwards as opposed to across), we'll create a different
function to print them out.
As we proceed through each top level menu, we can check to see if it contains a sub menu. If this is the case we call
the readSubMenu() function to create that menu as another layer.
Step 5 - Adding the sub menus
// if the item has a sub menu, call readMenu() again to print that out too.
if (submenu != null) {
readSubmenu(submenu, x, y);
}
|
The readSubMenu function is very similar to the readMenu function. (Indeed they could both be written using a single recursive function, but that would add additional complexity)
We supply a submenu name to the menu so that we can refer to it using the document.getElementById() function later. This will
be important once we want to show and hide our menus.
function readSubMenu(submenu, x, y, id) {
// first we create a container object for this entire menu.
// This allows us to hide the whole menu at once.
document.write("<table border='1' id='" + id + "'
style='position: absolute; left: " + x +
"; top: " + (y + menuHeight + yGap) + ";'>");
// loop through the items in this menu
for (var j = 0; j < submenu.length; j++) {
// Get out one menu at a time
var menu = submenu[j];
// extract the information we want from the array
var name = menu[0];
var url = menu[1];
document.write("<tr><td>");
document.write(name);
document.write("</td></tr>");
}
document.write("</table>");
}
|
Step 6 - Making the menu work
The readSubMenu function creates a layer (in this case a table which will contain each individual menu item on separate rows.
If you run this example (menu5.html) you'll see that we have now printed out all the items on the list in the correct order.
We now need to start making the menu dynamic - submenus should appear only when necessary, and the links should take us to places.
We'll start by changing the menus so that when we click on them we are taken to the appropriate URL:
// create the onClick event.
var onClick;
if (url != null) { onClick = "self.location.href=\"" + url + "\";" };
document.write("<tr><td onClick='" + onClick + "'>");
document.write(name);
document.write("</td></tr>");
|
Step 7 - Styling our menu
At this stage we should start to think about styles for our objects. We need to make our submenus invisible at first,
and we need to change the cursor when the user hovers on each menu. Additionally, it would be nice to make our menu look a little more presentable!
Below is a basic stylesheet that we'll use for our menus. Once we've completed our menu system, we'll have a play around with the stylesheet so
that our menu looks good.
td.topMenu_Over {
cursor: hand;
background: blue;
color: white;
}
td.topMenu_Out {
}
td.subMenu_Over {
cursor: hand;
background: blue;
color: white;
}
td.subMenu_Out {
}
table.subMenu {
visibility: hidden;
background: white;
border: 1px solid gray;
}
|
We can write in the class="" attributes easily into our JavaScript:
document.write("<tr><td class='topMenu_Out' onClick='" + onClick + "'>");
|
Step 8. Showing and Hiding Submenus
Now that we've set our submenu tables to be invisible by default, we can no longer see them. What we need is for the relevant submenu to appear
when the user puts his or her mouse over a top level menu.
We'll create another couple of events for our top level menus to highlight the menu and display the submenu.
var onClick;
if (url != null) { onClick = "self.location.href=\"" + url + "\";" };
var onMouseOver;
if (submenu != null) { onMouseOver = "showSubMenu(\"" + subMenuName + "\");";
document.write("<tr><td class='topMenu_Out' onMouseOver='" + onMouseOver +
"' onClick='" + onClick + "'>");
document.write(name);
document.write("</td></tr>");
|
We've asked the menu to run the showSubMenu method when the mouse moves over it. The showSubMenu makes the menu visible:
function showSubMenu(id) {
document.getElementById(id).style.visibility = "visible";
}
|
This is reasonable, but has the problem that more than one submenu can be visible at once. What would be better is to have a
function to close the previous menu before showing the next one. This only requires a few extra lines of code.
// remember the id of the previously opened menu.
var previousMenuId;
function showSubMenu(id) {
// if this menu is a new menu being opened...
if (id != previousMenuId) {
// hide the other one
hidePreviousMenu();
// display the new one...
document.getElementById(id).style.visibility = "visible";
previousMenuId = id;
}
}
// hides the currently open menu
function hidePreviousMenu() {
if (previousMenuId != null) {
document.getElementById(previousMenuId).style.visibility = "hidden";
previousMenuId = null;
}
}
|
Well, maybe more than just a couple of additional lines, but the meaning should hopefully be clear. What we do is remember the id of a menu as we show it, so once
we open a new submenu, we know which other menu to hide first.
If we run our menu now, it's starting to feel a little bit more like a proper menu system.
There are just a couple of extra things we need to do.
Step 9 - Hiding menus automatically.
So that the sub menus hide themselves automatically, we'll create a new method hideMe and we'll have the submenus call it
upon themselves once the user moves the mouse away from them, with a little extra code onMouseOut='hideMe(this);'.
Our hideMe() function could hide the menu straightaway simply by changing the visibility property to "hidden", like so:
// hides a sub menu
function hideMe(submenu) {
submenu.style.visibility = "hidden";
previousMenuId = null;
}
|
But unfortunately, this code wouldn't work very well. As soon as the user moves the mouse outside the menu, the menu would close. If there is a gap
between the top menu and the sub menu, the sub menu would hide itself before the user could move the mouse over the submenu!
What we'll do instead is introduce a delay so that a menu only dissapears after the user moves the mouse away for a certain time.
var aboutToHide;
// hides a sub menu
function hideMe(submenu) {
if (aboutToHide == null) {
// remember the object we're going to hide
aboutToHide = submenu;
// call the hide immediately function in 500ms.
// if the cancelHide() function is called inbetween times,
// the hideImmediately() function will not work.
setTimeout("hideImmediately();", 500);
}
}
// prevents the hideImmediately function from hiding the menu.
function cancelHide() {
aboutToHide = null;
}
// hides a menu immediately.
function hideImmediately() {
if (aboutToHide != null) {
aboutToHide.style.visibility = "hidden";
previousMenuId = null;
}
}
|
Essentially, this code updates the hideMe() function so that it does the hiding process after a short delay. In this instance the delay is 500ms, or half a second.
We use the setTimeout() function in JavaScript which allows us to call a function after a given delay. We then add the ability to cancel the hide with the
cancelHide() function. We add another event to the submenu, this time we'll add the cancelHide() function to the onMouseOver event.
This means that if the user moves the mouse out, the hideMe() function will hide the submenu in half a second. However, if the user moves the mouse back onto the submenu,
the cancelHide() function is run, which stops the hide function from working once the half second is up.
We now have a menu system which works and behaves like a menu should. In the last section, we'll add a few finishing touches so that our menu
really looks the part too.
Step 10: Finishing Touches
All that remains to do now, is to make the menus light up as the mouse moves over them. This we will do by changing the class of each menu as the mouse rolls over and out.
We've already added an onClick function to each menu item, we'll simply add a onMouseOver and onMouseOut function to each, and make use of the other CSS
classes that we made earlier.
var onMouseOver = "this.className=\"subMenu_Over\"";
var onMouseOut = "this.className=\"subMenu_Out\"";
document.write("<tr><td class='subMenu_Out' onMouseOver='" + onMouseOver +
"' onMouseOut='" + onMouseOut + "' onClick='" + onClick + "'>");
|
And that's it! You can view the completed menu example here.
Now that you've the basics of the menus, you can put all the javascript code into a js file, and link it from your program, allowing you
to add the menus to your page with only one or two lines of boilerplate code.
You may also like to try making your menu look a bit more interesting. Here's another one
|