Now that you know the basic usage of the menu system I'll walk you through a few parts of the implementation.
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.
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; }
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.
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; };
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; };
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 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.
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