Drop Down Menu Tutorial Version 2

dHTML Drop Down Menu Tutorial – Part 1

Aims of the tutorial

This tutorial is intended to get you started on your way to building your own drop down menu system in JavaScript.

It is written for educational purposes, but will hopefully provide a full, comprehensive menu system for download at the end (at least, all the basics will be in place from which to build one).

If you want to download the example menu system for use in your site,   Download Drop down menu example here

The code in the tutorial will be loosely based on an evolution of the code from the drop down menu section (as used at the top of this page) and will include some of the improvements that will be in Version 3 or the menus. It won’t neccesarily include all of the features of these menus.

Contents

Browser Compatibility

I currently test code on the following browsers:

Windows Mac Linux (Red Hat 9)
Microsoft Internet Explorer 4.0
5.0
5.5
6.0
5.0 (OS9)
5.1.7 (OS9)
5.2 (OSX)
n/a
Netscape Navigator / Communicator 4.72
6.01
6.1
6.2
7.1
4.75
6.1
4.72
Opera 5.02
6.0
7.11
6.03 (OS9)
7.5b (OSX)
7.23
Mozilla 1.5 1.3
1.6
1.4.2
Other Safari 1.0.2
Camino 0.7
Konqueror 3.1-15

Details on obtaining browsers for testing can be found here.

The JavaScript API

Along with most of the dHTML on this site, this drop-down menu tutorial will make use of the JavaScript API. The API (Application Programming Interface) is a core set of cross-browser JavaScript functions to make dHTML programming quicker and easier.

The API performs tasks such as moving and hiding page elements (DIVs, layers or images) as well as getting and setting their attributes (such as size/position).

The API includes the following functions:

  • Basic Browser Sniffing - Not a complete browser sniffer, it only highlights those browsers that can cause problems in the menu system.
  • Get Style Object - Returns the Style Object for a given page element.
  • Change Object Visibility - Hides or shows an element.
  • Find Image (NS4 only) - Used to find image objects in NS4.
  • Find object (NS4 only) - Used to find other objects in NS4.
  • Get Element Width/Height - Gets the width or height of any page element (not images in NS4).
  • Get Element Top/Left - Get the page co-ordinates of any element (not images in NS4).
  • Get Image width/Height - Gets the width or height of any image.
  • Get Image Top/Left - Get the page co-ordinates of any image.
  • Move Element - Sets the x and y co-ordinates of an element.
  • Change Style Class - changes the style class of an object (Not NS4 or Opera 5/6).

It’s worth taking a few minutes to familiarise yourself with the API. Click here to go to the API section (opens in a new window).

How the System Will Work

The code for the menus will be perform two main tasks:

1. Building the menus

To make the menus easy to build and maintain, we’ll build a set of functions that will create the html for the menus for us. Not only does this make building the menus simpler, these configuration files will used throughout your site – just change one file and the changes will effect every page.

There will be functions to build the menu bars and the menus themselves. The menu bars are just tables containing links that activate the menus, the actual menus are tables wrapped in a <div> element that can be displayed, hidden and moved around the page.

2. Operating the menus

These functions will show and position the menus when the user activates them and hide them again when necessary.

The positioning of the menus is based on the position of the label they are attached to. We’ll use functions from the JavaScript API to get the page co-ordinates of the label, and then to position the menu relative to it.

Terminology Used in the Tutorial

In the tutorial I’ll be using terms like “menus”, “submenus”, “menu labels”, “submenu labels” and “menu items”.

The figure on the right should help to explain what I mean by these terms. It shows a menu bar along the top of a page with a menu dropping down from it. Hanging off this menu is a submenu.

Menus drop down from the menu bar, Submenus hang from other menus.

We need different names for them in the tutorial because the rules for positioning them are different.

Menus and submenus are made up of Menu Items. Menu items which spawn submenus are referred to as Submenu Labels.

The Bullet Points

This may seem like an odd place to start the tutorial, but it explains a little about object oriented programming (OOP) in JavaScript.

You can find out more about JavaScript OOP at javascriptkit.com.

The Code

The code below creates an object with 3 properties:

  1. An image object for the “off” version of the bullet
  2. An image object for the “on” version of the bullet
  3. A string containing the URL of the “off” version of the bullet (used later in the tutorial)
function bulletPoint(offURL, onURL) {
	this.offImage = new Image();
	this.offImage.src = offURL;
	this.onImage = new Image();
	this.offImage.src = onURL;
	this.menuBulletURL = String(offURL);
}

Usage

The code below creates three bulletPoint objects called menuItemBullet, labelBullet and subMenuBullet. We’ll use these later in the tutorial.

menuItemBullet = new bulletPoint("menu_off.gif", "menu_on.gif");
labelBullet = new bulletPoint("header_off.gif", "header_on.gif");
subMenuBullet = new bulletPoint("sub_header_off.gif", "sub_header_on.gif");

The images in these objects can then be referenced by menuItemBullet.offImage,menuItemBullet.onImage etc. The URL of the “off” image would be menuItemBullet.menuBulletURL.

Some Globals

The menu system will use a number of global variables. Using globals goes against the OOP design, but makes sharing data easier.

The globals are:

// used to hold a timer
var timeOn = null;

// the maximum number of menus in the system
// (can be increased if necessary)
var numMenus = 50;

// used internally to count the menus defined so far
var currentMenuNo = 0;

// used internally to store whether a menu is active
// or not. Speeds up the system
var menuActive = new Array(numMenus);

// stores which tier the menu is in
var tier = new Array(numMenus);

// used to help line up the menus more accurately
// when borders are enabled
var borderMod = new Array(numMenus);

// the "off" class of the corresponding (sub)menu label link
var offClass = new Array(numMenus);

// the "on" class of the corresponding (sub)menu label link
var onClass = new Array(numMenus);

// the "off" colour of the corresponding (sub)menu label background
var offColours = new Array(numMenus);

// the "on" colour of the corresponding (sub)menu label background
var onColours = new Array(numMenus);

// the name of the bullet object (if any) used
// by the corresponding (sub)menu label
var labelBulletName = new Array(numMenus);

// the type of (sub)menu label corresponding to this menu
// currently can be 'defualt' for a normal menu
// or 'blank'
var menuType = new Array(numMenus);

// an array used to hold the menu objects in the system
var menus = new Array(numMenus);

Building the Menu Bar – The “menuBar” Object

The menuBar object does the job of building the HTML for the menu bar.

Below are the properties of the object (the two methods are covered later).

// -- OBJECT ARGUMENTS: --
// barName: an ID for the DIV that surrounds the menu bar
//   which can then be altered using CSS
// barWidth: the width of the menu bar in pixels
// orientation: can be 'horizontal' or 'vertical'
// i_Bor: Inner border colour (between the menu labels)
//   (set as 'null' for no inner border)
// o_Bor: Outer border colour(around the menu bar as a whole)
//   (set as 'null' for no outer border)

function menuBar(barName, barWidth, orientation, i_Bor, o_Bor) {

// --- PRIVATE PROPERTIES --

  // used internally count the number of labels in the menu bar
  this.numLabels = 0;

  // store the border colours and orientation
  this.i_Bor = i_Bor;
  this.o_Bor = o_Bor;
  this.orientation = orientation;

  // used to store the HTML for the menu bar
  this.labelText = new Array();
  // used in vertical menu bars to store the row element
  this.rowText = new Array();

// -- PUBLIC PROPERTIES --
// can be altered BEFORE adding menu labels to the menu bar

// height of the bar in pixels
  this.height = 15;

// the classes for the links
  this.offClass = 'MenuLabelLink';
  this.onClass = 'MenuLabelLinkOn';

// alignment of the bullet points (if any)
// can be 'left' or 'right'
  this.bulletAlign = 'left';

// can be 'self', 'iframe', 'frame', 'new'
  this.targetType = 'self'; 

// '_self', '_blank' or (i)frame name
  this.targetFrame = '_self'; 

// -- METHODS --
// (described later)
  this.addLabel = function(bullet, labelText, menuNo, labelWidth,
    offColour, onColour, labelURL, align) {
    // code displayed later
  }
  this.writeMenuBar = function() {
    // code displayed later
  }
}

Usage

The following creates a 450 pixel wide horizontal menu bar called “myTest”:

myTest = new menuBar('myTest',450, 'horizontal', '#ff0000', '#000000');

Building the Menu Bar – Adding menu labels

Quick Tip: Early revisions of my menu code used multiple document.write statements to write the html one line at a time. This works fine in most browsers except Opera 5.x (and to a lesser extent Opera 6.x) where document.write seems to be very slow. In this system we’ll build fewer, longer strings. This greatly increases the speed in the Opera browsers.

This method builds a string of HTML for a menu label and stores it it in the labelText array. Due to differences in Netscape 4, the code is quite long. Netscape 4 uses a layer nested in an ilayer (mouseover and mouseout events are attached to the layer – NS4 doesn’t offer onclick events for layers). The other browsers use just a div element (the events are attached to the parent td element).

The Code

// -- METHOD ARGUMENTS --
// bullet: the name of a bullet object or null
// labelText: the text to appear in the menu label
// menuNo: the number of the menu which pops from this menu label
//   (can be a blank menu - i.e. one with no menu items)
// labelWidth: width of the label in pixels
// offColour: the background colour of the label's "off" state
// onColour: the background colour of the label's "on" state
// labelURL: the URL of the page the menu label links to
// align: the text alignment can be 'left', 'right', or 'center'
//   (note American spelling in this case)

this.addLabel = function(bullet, labelText, menuNo,
  labelWidth, offColour, onColour, labelURL, align) {

  // increment the counter of menu labels in the menu bar
  this.numLabels += 1;

  // set the tier for the menu hanging from this label
  tier[menuNo] = 0;		

  // this is an offset for placing the menu due to the outer border
  if (this.o_Bor != null) borderMod[menuNo] = 1;
  else borderMod[menuNo] = 0;

  // set the globals for the menu label
  if (menuNo != null) {
    onColours[menuNo] = onColour;
    offColours[menuNo] = offColour;
    onClass[menuNo] = this.onClass;
    offClass[menuNo] = this.offClass;
    labelBulletName[menuNo] = bullet;
  }

  // create a temporary string object to hold
  // the html for the menu label
  temp = new String('');

  // create a string object to hold the row tag
  // for the label (if necessary)
  this.rowText[this.numLabels] = new String('');

  if (this.orientation == 'vertical')
    this.rowText[this.numLabels] += '<tr id="labelRow'+ menuNo + '">';

  // create the td tag
  temp += '<td id="labelCell' + menuNo + '" width="'+ labelWidth + '" bgcolor="' +
    offColour + '" valign="middle" height="' + this.height + '" ';

  // if the browser ISN'T Netscape 4...
  if (!ns4) {
    // ...attach events to the td tag
    temp += ' onmouseout="menuOut(); "onmouseover="menuOver(); ';

    if (this.orientation == 'vertical')
      temp += 'return !showMenuSide('+ menuNo+', event, tier['+menuNo+']);" ';
    else
      temp += 'return !showMenu(' + menuNo + ', event);" ';

    if (this.targetType=='self')
      temp += ' onclick="document.location.href=\'' + labelURL + '\';" ';
    if (this.targetType=='new')
      temp += ' onclick="openMe(\'' + labelURL + '\'); return false;" ';
    if (this.targetType=='frame')
      temp += ' onclick="parent.' + this.targetFrame +
        '.document.location.href=\'' + labelURL + '\';" ';
    if (this.targetType=='iframe')
      temp += ' onclick="' + this.targetFrame + '.location.href=\'' +
        labelURL + '\';" ';
  }
  // close the td tag
  temp +='>';

  // if the browser IS Netscape 4...
  if (ns4) {
    // ...create layer within an ilayer and attach the events to this
    temp +='<ilayer><layer onmouseout="menuOut();" onmouseover="menuOver(); ';
    if (this.orientation == 'vertical')
      temp +='return !showMenuSide('+menuNo+', event, tier['+menuNo+']);" ';
    else
      temp +='return !showMenu(' + menuNo + ', event);" ';
  } else {
    // if NOT Netscape 4 we use a div tag instead
    temp +='<div ';
  }

  // give the layer/div an ID and start the link tag
  temp += ' width="' + labelWidth +
    '"  id="menuLabel' + menuNo +'"><a href="' + labelURL +'"
    target="' + this.targetFrame + '"
    id="menuLink' + menuNo +'">';

  // if bullet points are enabled...
  if (bullet != null)
    // put in the bullet point
    temp += '<img src="' + eval(bullet + ".URL") + '" border="0"
      align="' + this.bulletAlign + '"
      id="menuBullet' + menuNo + '" name="menuBullet' + menuNo + '">';

  // close the link tag
  temp += labelText + '</a>';

  // close the layer or div (depending on browser)
  if (ns4) temp += '</layer></ilayer>';
  else temp += '</div>';

  // close the td tag
  temp += '</td>';	

  // create a string object and store the HTML
  // from the temporary string in it for later
  this.labelText[this.numLabels] = new String(temp);
}

Usage

The following Code adds three menu labels to the menu bar:

myTest.addLabel('menuItemBullet', 'test1', 1, 150, '#cccccc',
  '#ffffff', 'test1.asp', 'left');
myTest.addLabel('labelBullet', 'test2', 2, 150, '#cccccc',
  '#ffffff', 'test2.asp', 'center');
myTest.addLabel('subMenuBullet', 'test3', 3, 150, '#cccccc',
  '#ffffff', 'test3.asp', 'right');


Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>