Revenge of the Menu Bar
See the demo page for the finished version of the code.Fixing the Menu Display
If you recall, the first time a given menu needs to be displayed,
menuInit()
is called for it. The first step in this function is to
replace the arrow character for IE browsers.
function menuInit(menu) { var itemList, spanList; var textEl, arrowEl; var itemWidth; var w, dw; var i, j; // For IE, replace arrow characters. if (browser.isIE) { menu.style.lineHeight = "2.5ex"; spanList = menu.getElementsByTagName("SPAN"); for (i = 0; i < spanList.length; i++) if (hasClassName(spanList[i], "menuItemArrow")) { spanList[i].style.fontFamily = "Webdings"; spanList[i].firstChild.nodeValue = "4"; } }
It does this first setting an explicit line height for the menu. This is necessary because the replacement arrow character will increase the text height when used. Forcing a line height will keep all the menu items evenly spaced, regardless of whether they have the arrow or not.
It then checks every SPAN element in the menu DIV for the
menuItemArrow
class name. For these, it changes the
font-family
style to "Webdings" and the actual text is replaced
with the '4' character. In this font, that character represents a
right-pointing triangle very similar to the original Unicode character.
Next it works on making the arrows flush to the right. To do so, it must first find out how wide the items are.
// Find the width of a menu item. itemList = menu.getElementsByTagName("A"); if (itemList.length > 0) itemWidth = itemList[0].offsetWidth; else return;
Since the style class for the A tags within a menu DIV specify
display:block,
they should all be the same width. So the width of
the first item element can be used.
Now it goes through each item link, one at a time, looking for those with
both a menuItemText
-class SPAN and a
menuItemArrow
-class SPAN.
// For items with arrows, add padding to item text to make the // arrows flush right. for (i = 0; i < itemList.length; i++) { spanList = itemList[i].getElementsByTagName("SPAN"); textEl = null; arrowEl = null; for (j = 0; j < spanList.length; j++) { if (hasClassName(spanList[j], "menuItemText")) textEl = spanList[j]; if (hasClassName(spanList[j], "menuItemArrow")) arrowEl = spanList[j]; } if (textEl != null && arrowEl != null) { textEl.style.paddingRight = (itemWidth - (textEl.offsetWidth + arrowEl.offsetWidth)) + "px"; // For Opera, remove the negative right margin to fix a display bug. if (browser.isOP) arrowEl.style.marginRight = "0px"; } }
When it finds one, it measures the width of both SPAN elements and adds just enough padding to the item text SPAN to match the item width found earlier.
Additionally, for Opera browsers, the margin-right
style
of the SPAN containing the arrow character is set to zero. This will leave some
extra space to the right of the arrow when viewed with that browser, but it's
better than no arrow at all.
The next step is to address the hover problem in IE. For whatever reason,
setting an explicit style width
on any link tag within the menu
seems to correct it.
// Fix IE hover problem by setting an explicit width on first item of // the menu. if (browser.isIE) { w = itemList[0].offsetWidth; itemList[0].style.width = w + "px"; dw = itemList[0].offsetWidth - w; w -= dw; itemList[0].style.width = w + "px"; } }
The function does this with the first item link in the menu. Using the item
element's offsetWidth
property it calculates a corresponding pixel
value for its style.width
property.
width
style applies to the content of an
element. This does not include any padding or borders. The value of an
element's offsetWidth
property does include padding and borders,
however.
The code above accounts for this by setting an arbitrary value on the style
width
and comparing that to the resulting offsetWidth
of the element. The difference equals the width of any padding and borders.
It can then subtract that difference from the original offsetWidth
to get a value for the style width
that leaves the element the
size unchanged.
Finally, a user defined property is set on the menu element to mark it as having been initialized.
// Mark menu as initialized. menu.isInitialized = true;
Below, you can see the result, both before and after menuInit()
has been called. The background color of the item and arrow SPAN elements has
been altered so that you can better see the space occupied by each.
With the menus looking presentable, and the hover effect acting as expected in IE, we can go about setting up the event handling and code to control them.