Cascading Drop-down Menu

CBE Menu Example 9
The menu has downgraded. It is at the bottom of this page.

Intro

This is my best menu yet. It works in IE4up, Gecko, Konquerer, and Opera. It downgrades in anything else.

Here's a template for this menu.

There are several current discussions on this menu. I welcome your comments, bug-reports, browser-tests, etc...

Version History

Menu Version...

Page Version...

Features

The number of nested menus is unlimited.

Almost no javascript editing is required to adapt the menu to your page.

The HTML consists of nested DIV's so the menu downgrades very well.

The menu is very quick and responsive. It is also very extensible.

Browser Testing

Special thanks to everyone who helped with testing.

The dynamic menu has been tested with...

The downgrade menu has been tested with...

Javascript

In the file menu9.js you'll see...

document.write("<script type='text/javascript' src='../cbe_core.js'></script>");
document.write("<script type='text/javascript' src='../cbe_event.js'></script>");
If you have the '.js' files in the same folder as the '.html' file, then change the above to the following:
document.write("<script type='text/javascript' src='cbe_core.js'></script>");
document.write("<script type='text/javascript' src='cbe_event.js'></script>");

Also in menu9.js you'll see...

cbeMenu = new cbeDropdownMenu(
  mnuMarker.pageX(), mnuMarker.pageY(), // coord of first label
  75, 20,                               // label width and height
  120,                                  // box width
  18,                                   // item height
  2,                                    // item left padding
  '#336699',                            // background color
  '#00cccc',                            // text color
  '#00cccc',                            // hover background color
  '#336699'                             // hover text color
);

This code instantiates the cbeDropdownMenu object. The arguments provide for the position, sizes, and colors of the menu. That's it - that's the only javascript that has to be edited.

CSS

There are two CSS files:

menu9_def.css - This is the default CSS which provides styling for the page and the menu.

menu9_abs.css - This CSS is only included if the menu does not downgrade. It adds positioning properties to the menu's css classes.

There are 3 CSS classes: mLabel, mBox, and mItem. They have CSS rules in both files.

HTML

The only HTML requirement is that you follow a few simple naming and structural conventions.

Every label, box, and item is a DIV. The main labels must have id's like this... label1, label2, label3, etc. That's the only id naming requirement. Of course the other DIVs must have unique id's - but it doesn't matter what they are.

There are 3 CSS classes: mLabel, mBox, and mItem. The following example defines one mLabel, one mBox, and two mItems.

<div id='label1' class='mLabel'>Label 1</div>
<div id='b1' class='mBox'>
  <div id='i11' class='mItem'><a class="m" href="#">Item 11</a></div>
  <div id='i12' class='mItem'><a class="m" href="#">Item 12</a></div>
</div>

The following example defines two mLabels which will appear as the two main labels on a horizontal menubar. Each main label has its associated mBox. The first box has two mItems. Following the first mItem is a mBox - a sub-menu. The mItem immediately before a mBox becomes the sub-label for the nested box.

<div id='label1' class='mLabel'>Label 1</div>
<div id='b1' class='mBox'>
  <div id='i11' class='mItem'><a class="m" href="#">Sub Label</a></div>
  <div id='b11' class='mBox'>
    <div id='i111' class='mItem'><a class="m" href="#">Item 111</a></div>
    <div id='i112' class='mItem'><a class="m" href="#">Item 112</a></div>
  </div>
  <div id='i12' class='mItem'><a class="m" href="#">Item 12</a></div>
</div>

<div id='label2' class='mLabel'>Label 2</div>
<div id='b2' class='mBox'>
  <div id='i21' class='mItem'><a class="m" href="#">Item 21</a></div>
  <div id='i22' class='mItem'><a class="m" href="#">Item 22</a></div>
</div>

Only the main labels (the labels that will appear as on a menubar) will be of class mLabel. All sub-labels will be of class mItem, because they are items contained in a box.

The id's for the main labels in the above example are 'label1' and 'label2'. All the remaining DIV's must have unique id's but it doesn't matter what names you use.

Download

Everything you need to implement this menu is included in the CBE download package.

The Menu Source-Code

// begin class cbeDropdownMenu

function cbeDropdownMenu(mnuX, mnuY, lblW, lblH, boxW, itmH, itmPad, bgColor, txtColor, hvrBColor, hvrTColor) {

  // Properties

  this.mnuX = mnuX;
  this.mnuY = mnuY;
  this.lblW = lblW;
  this.lblH = lblH;
  this.boxW = boxW;
  this.itmH = itmH;
  this.itmPad = itmPad;
  this.bgColor = bgColor;  
  this.txtColor = txtColor; 
  this.hvrBColor = hvrBColor;
  this.hvrTColor = hvrTColor;
  this.lblCount = 0;
  this.lblActive = null;
  
  // Methods

  this.paint = function(mnuX, mnuY) { // this is the only public method
    if (arguments.length > 0) this.mnuX = mnuX;
    if (arguments.length > 1) this.mnuY = mnuY;
    var lbl = null; // of type Element
    var box = null; // of type CBE
    var mX = this.mnuX;
    this.lblCount = 0;
    do {
      ++this.lblCount;
      lbl = cbeGetElementById('label' + this.lblCount)
      if (lbl) {
        with (lbl.cbe) {
          color(this.txtColor);    
          background(this.bgColor);
          zIndex(2002);
          resizeTo(this.lblW, this.lblH);
          moveTo(mX, this.mnuY);
          show();
        }
        if (lbl.cbe.nextSibling && lbl.cbe.nextSibling.id.indexOf('label')==-1) box = lbl.cbe.nextSibling;
        else box = null;
        lbl.cbe.childBox = box;
        lbl.cbe.parentLabel = null;
        if (box) this.paintBox(box, lbl.cbe, mX, this.mnuY + lbl.cbe.height());
        mX += lbl.cbe.width();
      }
    } while(lbl);
    --this.lblCount;
  }

  this.paintBox = function(box, parent, x, y) {
    var mx=0, my=4, itmCount=0;
    box.background(this.bgColor);
    box.width(this.boxW);
    box.moveTo(x, y);
    box.zIndex(2002);
    var itm = box.firstChild;
    while (itm) {
      if (itm.id.indexOf('i') != -1) {
        itm.color(this.txtColor);
        itm.background(this.bgColor);
        itm.resizeTo(this.boxW - 6, this.itmH);
        itm.moveTo(mx + this.itmPad, my);
        itm.show();
        my += itm.height();
        ++itmCount;
      }
      else {
        itm.previousSibling.childBox = itm;
        itm.previousSibling.parentLabel = parent;
        this.paintBox(itm, itm.previousSibling, mx + itm.parentNode.width() - 4, my - itm.previousSibling.height());
      }
      itm = itm.nextSibling;
    }
    box.height(itmCount * this.itmH + 8);
  }

  this.mousemoveListener = function(e) {
    if (
      this.lblActive &&
      (e.cbeTarget != this.lblActive.childBox &&
      e.cbeTarget != this.lblActive &&
      e.cbeTarget.parentNode != this.lblActive.childBox)
    ) {
      if (this.lblActive.childBox) this.lblActive.childBox.hide();
      this.lblActive.color(this.txtColor);
      this.lblActive.background(this.bgColor);
      this.lblActive = this.lblActive.parentLabel;
    }
    else if (e.cbeTarget.childBox || e.cbeTarget.id.indexOf('label')!=-1) {
      e.cbeTarget.color(this.hvrTColor);
      e.cbeTarget.background(this.hvrBColor);
      this.lblActive = e.cbeTarget;
      if (this.lblActive.childBox) this.lblActive.childBox.show();
    }
  }
  
  // Constructor Code

  this.paint();
  document.cbe.addEventListener('mousemove', this.mousemoveListener, false, this);

} // end class cbeDropdownMenu

 

Label 1
Label 2
Label 3
Label 4