Now that you know the basic usage of the menu system I'll walk you through a few parts of the implementation.

Layout

The layout of the menus was designed to be as simple as possible to prevent browser incompatibilities as much as possible and it has been implemented using one DIV containing anchors.

<div id="ID" class="webfx-menu" style="width: 100px; left: 0px; top: 0px;">
<a id="ID" href="HREF" title="TITLE" onmouseover="webFXMenuHandler.overMenuItem(this)">
   TEXT
</a>
<a id="ID" href="HREF" title="TITLE" onmouseover="webFXMenuHandler.overMenuItem(this)">
   TEXT
</a>
<a id="ID" href="HREF" title="TITLE" onmouseover="webFXMenuHandler.overMenuItem(this)">
   <!-- when there is a sub menu an arrow image is added -->
   <img src="arrow.right.gif">
   TEXT
</a>
</div>

The usage of anchors as menu items allows us to use CSS to define the hover style in an easy way. The display property of the anchors has been changed to block to make them act and behave as rows.

Even if the code for this is not that cumbersome to write there is really no reason for anyone to do it manually. Therefore an object oriented js solution has been used.

The WebFXMenu Constructor

To create a menu all you have to do is call new WebFXMenu. The constructor for the menu contains all the info it needs to be able to generate the HTML needed for it as well as an id that allows the js object to find the HTMLElement later. The constructor code looks like this:

function WebFXMenu() {
   this._menuItems = [];
   this._subMenus  = [];
   this.id         = webFXMenuHandler.getId();
   this.top        = 0;
   this.left       = 0;
   this.shown      = false;
   this.parentMenu = null;
   webFXMenuHandler.all[this.id] = this;
}

webFXMenuHandler

The object webFXMenuHandler is a global object that handles a few different shared methods and properties. First it has a method that returns a unique id string. This id is used to map js objects to HTMLElement as well as allow you to find a js object by its id by looking it up in the webFXMenuHandler.all collection.

Adding Menu Items

The add method of a menu is pretty simple. It adds the menu item to its _menuItems collection and checks if the menu item has a sub menu and in that case it also adds that to its _subMenus collection. Last but not leat it also sets the parentMenu properties on the menu item and on the sub menu.

WebFXMenu.prototype.add = function (menuItem) {
   this._menuItems[this._menuItems.length] = menuItem;
   if (menuItem.subMenu) {
      this._subMenus[this._subMenus.length] = menuItem.subMenu;
      menuItem.subMenu.parentMenu = this;
   }

   menuItem.parentMenu = this;
};

Generating the Menu

This is the most important part of the menu and it is here that we generate the layout shown above.

The code is actually pretty simple. First we generate the code for the opening tag and then we loop through the menu items and draw them. Notice that since the menu items have their own toString method all we have to do is to concat the object and we will get the code needed for the menu item. After that we close the div tag.

Two more things are worth mentioning. Inside the loop that adds the code for the menu items we also set the top position of the sub menu if the menu item had a sub menu. After the code for the menu has been generated we also generate the code for all the sub menus. If we had not done this we would have had to manually do this. In this way we will only need to do one document.write for an entire menu hierarchy.

The code to generate the string for a menu looks like this:

WebFXMenu.prototype.toString = function () {
   var top = this.top + this.borderTop + this.paddingTop;
   var str = "<div id='" + this.id + "' class='webfx-menu' style='" + 
   "width:" +
   (!ieBox  ?
      this.width - this.borderLeft - this.paddingLeft - this.borderRight - this.paddingRight  : 
      this.width) +
   "px;" +
   (this.useAutoPosition ?
      "left:" + this.left + "px;" + "top:" + this.top + "px;" :
      "") +
   (ie50 ? "filter: none;" : "") +
   "'>";

   if (this._menuItems.length == 0) {
      str += "<span class='webfx-menu-empty'>" + this.emptyText + "</span>";
   }
   else {   
      // loop through all menuItems
      for (var i = 0; i < this._menuItems.length; i++) {
         var mi = this._menuItems[i];
         str += mi;
         if (!this.useAutoPosition) {
            if (mi.subMenu && !mi.subMenu.useAutoPosition)
               mi.subMenu.top = top - mi.subMenu.borderTop - mi.subMenu.paddingTop;
            top += mi.height;
         }
      }
   }
   
   str += "</div>";

   for (var i = 0; i < this._subMenus.length; i++) {
      this._subMenus[i].left = this.left + this.width - this._subMenus[i].borderWidth/2;
      str += this._subMenus[i];
   }
   
   return str;
};

Generating Menu Items

This one is even easier. I won't cover the WebFXMenuItem constructor but you are interested you can view the source for it. A menu item is actually an anchor tag with an onmouseover attribute. When the mouse is over the menu item, webFXMenuHandler.overMenuItem(this) is called. More about this later. Besides from this there is nothing strange here.

WebFXMenuItem.prototype.toString = function () {
   return "<a" +
          " id='" + this.id + "'" +
          " href='" + this.href + "'" +
          (this.toolTip ? " title='" + this.toolTip + "'" : "") +
          " onmouseover='webFXMenuHandler.overMenuItem(this)'" +
          (webfxMenuUseHover ? " onmouseout='webFXMenuHandler.outMenuItem(this)'" : "") +
          (this.subMenu ? " unselectable='on' tabindex='-1'" : "") +
          ">" +
          (this.subMenu ?
             "<img class='arrow' src='" + webfxMenuDefaultImagePath + "arrow.right.png'>" :
             "") +
          this.text + 
          "</a>";
};

Hiding and Showing

Hiding and showing is done by changing the visibility in the usual way. The hard part is just to know what and when to hide and show. When the mouse enters (onmouseover) a menu item we hide all sub menus of the current menu. This is done calling hideAllSubs() on the menu that the menu item is in. After that we check if the menu item has a sub menu and if so we show that.

This code is located in the global webfxMenuHandler so now is a good time to show the code for that.

var webFXMenuHandler = {
   idCounter     :   0,
   idPrefix      :   "webfx-menu-object-",
   getId         :   function () { return this.idPrefix + this.idCounter++; },
   overMenuItem  :   function (oItem) {
      var jsItem = this.all[oItem.id];
      jsItem.parentMenu.hideAllSubs();
      if (jsItem.subMenu)
         jsItem.subMenu.show();
   },
   blurMenu      :   function (oMenuItem) {
      window.setTimeout("webFXMenuHandler.all[\"" + oMenuItem.id + "\"].subMenu.hide();", 200);
   },
   all           :   {}
};

Notice that this is not the complete and mot current version of the webFXMenuHandler but the logic is still the same. You can see what has been changed either by check the hover part of this article or check the actual source code.

Menu Bar and Buttons

The menu bar is just another menu with a slight different toString method. The menu buttons also have a slight different behavior. Instead of showing the sub menu when the mouse hovers the element it is shown when the user clicks it (when the anchor recieves focus). The menus are hidden when the anchor looses focus. The toString method for the WebFXMenuButton looks like this:

WebFXMenuButton.prototype.toString = function () {
return	"<a" +
        " id='" + this.id + "'" +
        " href='" + this.href + "'" +
        (this.toolTip ? " title='" + this.toolTip + "'" : "") +
        (webfxMenuUseHover ?
           (" onmouseover='webFXMenuHandler.overMenuItem(this)'" +
           " onmouseout='webFXMenuHandler.outMenuItem(this)'") :
           (" onfocus='webFXMenuHandler.overMenuItem(this)'" +
           (this.subMenu ?
              " onblur='webFXMenuHandler.blurMenu(this)'" :
             "")
           )
        ) +
        ">" +
        this.text + 
        (this.subMenu ?
           " <img class='arrow' src='" + webfxMenuDefaultImagePath +
           "arrow.down.png' align='absmiddle'>" :
           "") +				
        "</a>";
};

Notice here that we use different events depending on whether we are using hover mode or not.

Introduction & Browser Issues
Usage
Implementation
API
Look & Feel
Hover Menu

Author: Erik Arvidsson