# 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...

- HFT Online
- Opera Newsgroup
- WebmasterWorld

## Version History

Menu Version...

- v1.5 30Oct02 - bug fix for when label with no box is the last label
- v1.4 29Oct02 - added support for main labels with no boxes
- v1.3 01Oct02 - utilizes the update to CBE v4.15 which allows object methods to be used as event listeners
- v1.2 27Aug02 - now licensed under LGPL
- v1.1 23Aug02 - added optional parameters to the paint() method, for re-painting on win resize
- v1.0 17Aug02 - initial release

Page Version...

- 29Oct02 - added a label with no box
- 03Oct02 - added » as submenu indicator
- 27Aug02 - detect browser in menu9.js so this file only loads menu9.js and default css
- 25Aug02 - converted back to html 4.01
- 24Aug02 - css fixes
- 23Aug02 - converted page to xhtml 1.0
- 17Aug02 - initial release

## 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...

- Win95 OSR1: Opera 5.12, 6.0, 6.02, 6.04, and 6.05
- Win98: IE 6.0, Opera 6.04, Mozilla 1.1b
- Win2K: IE 5.0, Opera 6.0, Mozilla 1.1b
- WinXP Corporate: Opera 6.05, Mozilla 1.1b
- iMac (OS X v.10.1): Netscape 6.2.3
- FreeBSD (4.6-stable): Konqueror 3.0.2

The downgrade menu has been tested with...

- Win95 OSR1: Opera 3.21, 3.51, and 3.62, Opera 6.05 (css user mode)
- Win98: Netscape 4.75, Opera 6.04 (js disabled)
- Win2K: Netscape 4.79, Opera 6.0 (js disabled)

## 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 Item 11 Item 12 Item 13 Sub Label» Item 141 Item 142 Item 143 Item 144 Item 15 Sub Label» Item 161 Item 162 Item 163 Item 164 Item 17 | Label 2 Item 21 Item 22 Item 23 Item 24 Sub Label» Item 251 Item 252 Sub Label» Item 2531 Item 2532 Item 2533 Item 2534 Item 254 Item 26 Item 27 | Label 3 | Label 4 Item 41 Item 42 Item 43 Item 44 Sub Label» Item 451 Item 452 Sub Label» Item 4531 Sub Label» Item 45321 Item 45322 Item 45323 Item 45324 Sub Label» Item 45331 Item 45332 Item 45333 Item 45334 Item 4534 Item 454 Item 46 Item 47