Building Content Management Systems
& e-Business Solutions

Code Snippets: Drop-down Navigation

This module allows the owner of the website to build and manage a hierarchal menu system.  The hierarchal menu system is intended to function as the main navigation for the site and will most likely appear on every page.  Therefore, when the menu system is updated by the site owner (or some other logged in user) the entire site is updated.  For more information please see the tutorial on dynamic navigation.

Features

  • Works for (IE 5.5+ PC), (Mozilla 1+ Mac and PC), (Opera 7+)
  • Add, remove, rename links.
  • Links can lead to new pages, existing pages, external URLs, or uploaded files

Limit

Unfortunately Microsoft IE can only handle ten items per menu level.  (Like most programers who don't work for Microsoft, I'm a real fan of their work.)  So, you can have ten top level items, ten items under each top level items, and ten items swinging off of each second level item — and so on, up to 10,000,000,000 items.  While this may seem like a lot of pages, you can not have a single level with more than 10 items. (Firefox can handle an infinite number of items within each level.)

User Documentation

Deemed unnecessary.

Code

Applicable Styling code

This code may appear in the head of your html, or in an attached style sheet.

/* Below is a set of CSS rules that will allow you to style the drop down menu bar. */
 
/* note: the term 'dropdown' refers to the second level menus, which drop down vertically. */
/* note: the term 'flyout' refers to the third level menus, which stick out from the second level drop down menus. */
/* note: you need to change #menubar to reflect the name of the menubar you are styling.
    if the menubar is called 'henry', then change #menubar to #henry
*/
 
/* use this to style the main menu */
#menubar1 ul.horizontal li.level1 {
  padding: 0px;
  padding: 2px 10px 2px 10px;
border: 1px #C1C1C1 solid;
background-color: #FFFFFF;
}
 
/* controls the root menu's list item links, use this to style the main menus links */
#menubar1 ul.horizontal li.level1 a {
  font-size : 14px;
  font-family : Arial, Helvetica;
  font-weight : bold;
  font-style : normal;
  color: #6666FF;
  text-decoration: none;
}
 
/* controls the root menu's list item links when a cursor is over them (mouseover) */
#menubar1 ul.horizontal li.level1 a:hover {
  color: #9B9B9B;
}
 
/* this controls the style of the dropdown and flyout menus, A great deal of the styling will be done here, probably. */
/* note that the comma needs to be there, don't remove it. */
#menubar1 ul.dropdown,
#menubar1 ul.flyout {
  width: 135px;
  border: 1px solid #C1C1C1;
  background-color: #FFFFFF;
}
 
/* this controls the styles of the list items that are in the dropdown and flyout menus.
/* note that the comma needs to be there, don't remove it. */
#menubar1 ul.dropdown li.level2,
#menubar1 ul.flyout li.level3 {
  border-bottom: 1px #C1C1C1 solid;
}
 
/* this controls the styles of the list item's links that are in the dropdown and flyout menus */
/* note that the comma needs to be there, don't remove it. */
#menubar1 ul.dropdown li.level2 a,
#menubar1 ul.flyout li.level3 a {
  font-family: Arial;
  font-size: 11px;
  font-weight: normal;
  color: #6666FF;
  padding: 2px 4px 2px 5px;
}
 
/* this controls the style of the list item's links that are in the dropdown and flyout menus when a cursor is over them. (mouseover) */
/* note that the comma needs to be there, don't remove it. */
#menubar1 ul.dropdown li.level2 a:hover,
#menubar1 ul.flyout li.level3 a:hover {
  border: 1px #C1C1C1 solid;
  color: #9b9b9b;
  background-color: #DAE0D2;
}

Clear Site Tag Code

Position this code where you want the menubar to appear.

<clearsite:hidden module="menubar" name="menubar1" >
<script type="text/javascript">
userAgent = navigator.userAgent.toLowerCase();
isIE     = ((userAgent.indexOf("msie") != -1) && (userAgent.indexOf("opera") == -1));
isOpera  = (userAgent.indexOf("opera") != -1);
isMac     = (userAgent.indexOf("mac") != -1);
isMacIE = (isIE && isMac);
isWinIE = (isIE && !isMac);
isSafari = (userAgent.indexOf("safari") != -1);
isGecko  = (navigator.product == "Gecko" && !isSafari);
 

function Menu(idOrElement, name, customConfigFunction) {
  this.author = "Matthias Platzer AT knallgrau.at";
  this.copyright = "Copyright (c) 2004 Knallgrau New Medias Solutions GmbH, Vienna - Austria";
  this.license = "distributed under a BSD-Style license";
 
  this.lastUpdate = "2004-11-08";
  this.version = "0.4";
 
  this.name = name;
  this.type = "menu";
  this.closeDelayTimer = null;
  this.closingMenuItem = null;
 
  this.config();
  if (typeof customConfigFunction == "function") {
    this.customConfig = customConfigFunction;
    this.customConfig();
  }
 
  this.rootContainer = new MenuContainer(idOrElement, this);
}
 
Menu.prototype.config = function() {
  this.collapseBorders = true;
  this.quickCollapse = true;
  this.closeDelayTime = 500;
};
 
function MenuContainer(idOrElement, parent) {
  this.type = "menuContainer";
  this.menuItems = [];
  this.init(idOrElement, parent);
}
 
MenuContainer.prototype.init = function(idOrElement, parent) {
  this.element = (typeof idOrElement == "string") ? document.getElementById(idOrElement) : idOrElement;
  this.parent = parent;
  this.parentMenu = (this.type == "menuContainer") ? ((parent) ? parent.parent : null) : parent;
  this.root = parent instanceof Menu ? parent : parent.root;
  this.id = this.element.id;
 
  if (this.type == "menuContainer") {
    if (this.hasClass("dropdown")) this.menuType = "dropdown";
    else if (this.hasClass("flyout")) this.menuType = "flyout";
    else if (this.hasClass("horizontal")) this.menuType = "horizontal";
    else this.menuType = "standard";
    if (this.menuType == "flyout" || this.menuType == "dropdown") {
      this.isOpen = false;
      this.element.style.position = "absolute";
      this.element.style.top = "0px";
      this.element.style.left = "0px";
      this.element.style.visibility = "hidden";
    } else {
      this.isOpen = true;
    }
  } else {
    this.isOpen = this.parentMenu.isOpen;
  }
 
  var childNodes = this.element.childNodes;
  if (childNodes == null) return;
 
  for (var i = 0; i < childNodes.length; i++) {
    var node = childNodes[i];
    if (node.nodeType == 1) {
      if (this.type == "menuContainer") {
        if (node.tagName.toLowerCase() == "li") {
          this.menuItems.push(new MenuItem(node, this));
        }
 
      } else {
        if (node.tagName.toLowerCase() == "ul") {
          this.subMenu = new MenuContainer(node, this);
        }
      }
    }
  }
};
 
MenuContainer.prototype.getAbsOffsetTop = function() {
  var offset = 0;
  var ele = this.element;
  var sl = (window.pageYOffset ? window.pageYOffset : document.body.scrollTop);
  do { offset += ele.offsetTop; ele = ele.offsetParent; } while (ele!=null && ele.style.position !="relative" && ele.style.position!="absolute" && (ele.style.position!="fixed"))
  return offset;
};
 
MenuContainer.prototype.getAbsOffsetLeft = function() {
  var offset = 0;
  var ele = this.element;
  var sl = (window.pageXOffset ? window.pageXOffset : document.body.scrollLeft);
  do { offset += ele.offsetLeft; if (ele.style.position=='fixed') { offset+=sl }; ele = ele.offsetParent; } while (ele!=null)
  return offset;
};
 
MenuContainer.prototype.hasClass = function(className) {
  return (" " + this.element.className + " ").indexOf(className) > -1;
};
 
MenuContainer.prototype.getBorders = function(element) {
  var ltrb = ["Left","Top","Right","Bottom"];
  var result = {};
  for (var i in ltrb) {
    if (this.element.currentStyle)
      var value = parseInt(this.element.currentStyle["border"+ltrb[i]+"Width"]);
    else if (window.getComputedStyle)
      var value = parseInt(window.getComputedStyle(this.element, "").getPropertyValue("border-"+ltrb[i].toLowerCase()+"-width"));
    else
      var value = parseInt(this.element.style["border"+ltrb[i]]);
    result[ltrb[i].toLowerCase()] = isNaN(value) ? 0 : value;
  }
  return result;
};
 
MenuContainer.prototype.open = function() {
  if (this.root.closeDelayTimer) window.clearTimeout(this.root.closeDelayTimer);
  this.parentMenu.closeAll(this);
  this.element.style.visibility = "visible";
  this.isOpen = true;
  if (this.menuType == "dropdown") {
    //this.element.style.top = (this.parent.getAbsOffsetTop() + this.parent.element.offsetHeight) + "px";
    this.element.style.top = (this.parent.element.offsetHeight) + "px";
    this.element.style.left = (this.parent.element.offsetLeft) + "px";
  } else if (this.menuType == "flyout") {
    var parentMenuBorders = this.parentMenu ? this.parentMenu.getBorders() : new Object();
    var thisBorders = this.getBorders();
    if (
      (this.parentMenu.getAbsOffsetLeft() + this.parentMenu.element.offsetWidth + this.element.offsetWidth + 20) >
      (window.innerWidth ? window.innerWidth : document.body.offsetWidth)
    ) {
      this.element.style.left = (- this.element.offsetWidth - (this.root.collapseBorders ?  0 : parentMenuBorders["left"]) + 3) + "px";
    } else {
      this.element.style.left = (this.parentMenu.element.offsetWidth - parentMenuBorders["left"] - (this.root.collapseBorders ?  Math.min(parentMenuBorders["right"], thisBorders["left"]) : 0) -3) + "px";     
    }
    //this.element.style.top = (this.parent.element.offsetTop - parentMenuBorders["top"] - this.menuItems[0].element.offsetTop + 3) + "px";
    this.element.style.top = (this.parent.element.offsetTop - parentMenuBorders["top"] - this.menuItems[0].element.offsetTop + 3) + "px";
  }
};
 
MenuContainer.prototype.close = function() {
  this.element.style.visibility = "hidden";
  this.isOpen = false;
  this.closeAll();
};
 
MenuContainer.prototype.closeAll = function(trigger) {
  for (var i in this.menuItems) {
    this.menuItems[i].closeItem(trigger);
  }
};
 
MenuItem.prototype = MenuContainer.prototype;
function MenuItem(idOrElement, parent) {
  var menuItem = this;
  this.type = "menuItem";
  this.subMenu;
  this.init(idOrElement, parent);
  if (this.subMenu) {
    this.element.onmouseover = function() {
      menuItem.subMenu.open();
    }
  } else {
    if (this.root.quickCollapse) {
      this.element.onmouseover = function() {
        menuItem.parentMenu.closeAll();
      }
    }
  }
  var linkTag = this.element.getElementsByTagName("A")[0];
  if (linkTag) {
     linkTag.onfocus = this.element.onmouseover;
     this.link = linkTag;
     this.text = linkTag.text;
  }
  if (this.subMenu) {
    this.element.onmouseout = function() {
      if (menuItem.root.openDelayTimer) window.clearTimeout(menuItem.root.openDelayTimer);
      if (menuItem.root.closeDelayTimer) window.clearTimeout(menuItem.root.closeDelayTimer);
      eval(menuItem.root.name + ".closingMenuItem = menuItem");
      menuItem.root.closeDelayTimer = window.setTimeout(menuItem.root.name + ".closingMenuItem.subMenu.close()", menuItem.root.closeDelayTime);
    }
  }
}
 
MenuItem.prototype.openItem = function() {
  this.isOpen = true;
  if (this.subMenu) { this.subMenu.open(); }
};
 
MenuItem.prototype.closeItem = function(trigger) {
  this.isOpen = false;
  if (this.subMenu) {
    if (this.subMenu != trigger) this.subMenu.close();
  }
};
 
// DEBUG
 
Menu.prototype.toString = function() {
   return "Menu";
}
 
Menu.prototype.trace = function() {
   return "root";
}
 
MenuContainer.prototype.toString = function() {
   return "y:"+this.getAbsOffsetTop();
}
 
MenuContainer.prototype.trace = function() {
   // return this.element.outerHTML.substring(0,40);
   return this;
   return (this.parent ? this.parent.trace() : "x") + " > " + this;
}
 
function trace(msg) {
  var ele = document.getElementById("trace");
  ele.value = msg + "\n" + ele.value;
}
 
var menubar1;
 
/*
 * for an example an all possible settings see:
 * dropdownmenu.js -> Menu.prototype.config
 */
function configMenu() {
  this.closeDelayTime = 300
  this.collapseBorders = true;
}
 
function initMenu() {
  // don't use var here!! we will need menu as a global variable to store the closingDelay
  // the first parameter is the id of
  menubar1 = new Menu('menubar1-root', 'menubar1', configMenu);
}
 
onload = initMenu;
 
 </script><div id="menubar1">
  <ul class="level1 horizontal" id="menubar1-root">
    <li id="level1-2" class="level1"><a href="#">Sample
      Item I</a>
      <ul id="level1-2" class="level2 dropdown">
        <li id="level2-1" class="level2"><a href="#">Sample
          Item I-I</a></li>
        <li id="level2-6" class="level2"><a href="#">Sample
          Item I-II</a>
          <ul id="level2-6" class="level3 flyout">
            <li id="level3-1" class="level3"><a href="#">Sample
              Item I-II-I</a></li>
          </ul>
        </li>
      </ul>
    </li>
    <li id="level1-3" class="level1"><a href="#">Sample
      Item II</a>
      <ul id="level1-5" class="level2 dropdown">
        <li id="level2-1" class="level2"><a href="#">Sample
          Item II-I</a>
          <ul id="level2-1" class="level3 flyout">
            <li id="level3-1" class="level3"><a href="#">Sample
              Item II-I-I</a></li>
          </ul>
        </li>
        <li id="level2-2" class="level2"><a href="#">Sample
          Item II-II</a>
          <ul id="level2-2" class="level3 flyout">
            <li id="level3-1" class="level3"><a href="#">Sample
              Item II-II-I</a></li>
            <li id="level3-2" class="level3"><a href="#">Sample
              Item II-II-II</a></li>
            <li id="level3-3" class="level3"><a href="#">Sample
              Item II-II-III</a></li>
          </ul>
        </li>
        <li id="level2-3" class="level2"><a href="#">Sample
          Item II-III</a></li>
        <li id="level2-4" class="level2"><a href="#">Sample
          Item II-IV</a>
          <ul id="level2-4" class="level3 flyout">
            <li id="level3-1" class="level3"><a href="#">Sample
              Item II-IV-I</a></li>
          </ul>
        </li>
      </ul>
    </li>
    <li id="level1-6" class="level1"><a href="#">Sample
      Item III</a>
      <ul id="level1-6" class="level2 dropdown">
        <li id="level2-5" class="level2"><a href="#">Sample
          Item III-I</a>
          <ul id="level2-5" class="level3 flyout">
            <li id="level3-1" class="level3"><a href="#">Sample
              Item III-I-I</a></li>
            <li id="level3-2" class="level3"><a href="#">Sample
              Item III-I-II</a></li>
            <li id="level3-3" class="level3"><a href="#">Sample
              Item III-I-III</a></li>
          </ul>
        </li>
        <li id="level2-6" class="level2"><a href="#">Sample
          Item III-I</a></li>
      </ul>
    </li>
  </ul>
 </div >
</clearsite:hidden>

    Copyright Clear Site Web Solutions Corporation  |  Victoria, B.C. Canada