Menu Systems for Website Navigation

4.  Fixing a few problems

The IE simultaneous event problem

As we demonstrated in the previous example, we have a problem with IE when the mouse pointer happens to land on a link on a newly loaded page.  If the mouse is not moved, the onmouseover event does not fire until the mouse button is pressed.  The result is that when the mouse button is pressed, both the onmouseover and onmousedown events occur simultaneously, producing unpredictable behaviour - the link could end up in either state.

To prevent this from happening, we add a conditional statement in ButtonSel(), preventing the mouseover effect if this button has its className set.  If the onmousedown event occurs first, the className property will be set to "Dep" by the function ButtonDep(), so when the onmouseover event occurs, the function ButtonSel() will do nothing.  If the onmouseover event occurs first, it does not matter - ButtonDep() will override className property, setting it to "Dep".  Either way, the link will end up in the "depressed" state.


The IE "freezing" problem

We address the "freezing" problem by delaying the loading of the new page.  This allows time for the link appearance to be returned to "highlighted" before the new page begins loading and the page freezes.

First, we disable normal operation of the <a> tag by changing the onclick event handler for each link to return false instead of return true.

We add code to the function ButtonClick() to load the new page, after a preset delay.  In the function ButtonClick(), we create a variable to store the <a> tag's href attribute.  Note that this variable must be global (we do not use var), since it must exist after the function has executed.  We then set a timer which will load the new page after 250mS.

There is one drawback to this approach - when we disable normal HTML link operation in this manner, the browser may not recognise the link as being "followed".  This means that the link will never go into the visited state.  However, we would not normally want graphical menu buttons to do this anyway.

Loosing the Outline

The purpose of the dotted outline around links is to indicate focus.  For plain text links this is not a problem but for graphical buttons, the outline can ruin their appearance.  The CSS-2 specifications include rules to control this behaviour, but IE6 and below do not support them.

We can use the Button.blur() statement to remove focus from the link after it has been clicked by adding it to the link's onclick event handler.  Unfortunately the same technique does not work for the onmousedown event handler.  You may have noticed menu buttons on some sites where you see the dotted outline appear whilst the the mouse button is depressed, then disappear when the button is released.

The technique used here is to add a <span> element inside the <a> element.  By itself this does nothing but if we float both of these elements, we no longer see the dotted outline!  This is because we are now clicking on the floated inner <span>, not directly on the <a> element.  However, since it is enclosed by the <a> tag, it will still work as a link.

For this to work properly, the inner floated element must fully cover the <a> element.  This means:

For expandable buttons, the size of the inner element is determined by the size of its contents plus padding and/or borders.

For fixed size buttons, we run into a problem with IE if we try to specify a width as well as padding and/or borders for the inner element.  IE browsers incorrectly interpret box width by default, but in the case of IE5.5 and higher, they can by switched to "standards" mode by using the correct DOCTYPE declaration at the top of the HTML file.  However, this does not work for IE5.  We could use JavaScript to specifically detect IE5 and apply width compensation to the buttons, but this gets tacky.  Another alternative is to add a second nested and floated <span> element.  In the style sheet, we can safely specify the width for the internal <span>, because this one has no border or padding.  This is an old well-proven trick, often known as the "box within a box" solution.  The drawback of this solution is having the extra nested element.

Due to this structure, we also need to specify the mouse pointer style in the style sheet, since it will show the text insert pointer by default.  Some older browsers like IE5 do not recognise this CSS rule.

In this example, all items in the menu, including any separators, are individually floated and this results in them being "stacked" side by side in order.

The entire set of floated links and separators must be enclosed in an absolutely positioned or floated container or table cell.  If this container is floated, the following element should include a clear rule to ensure that anything following the menu will be below it, not next to it.

You might be thinking that this is a lot of trouble to go to, just to eliminate the outline.  However, this construct using floats also provides the basis for designing elaborate animated buttons.


Packaging the Menu System

In a practical website design, we would normally want to place both the CSS styles and the JavaScript code in external files.  These files would be shared between all pages that include a menu or individual buttons.  This arrangement is convenient from a maintenance point of view.

If the external CSS file is not found, the menu system would still be operational but the links would appear as raw HTML links like the example in Part 1.

If the external JavaScript file is not found or has failed to load properly, an error message would be generated whenever a link event handler tries to access one of the menu logic functions.  We therefore configure the HTML link event handlers to test for the existence of the menu logic functions using inline if statements, before attempting to use them.

Note that when testing for the existence of pre-defined functions in this manner, we leave out the ().  For example, the expression window.ButtonOut will be true if the function ButtonOut() exists.

HTML menu definition

Building on the links in the previous example, we reconfigure the event handlers to test for the existence of the functions.

The onclick event handler returns the result from running the function ButtonClick() (which is false if the function executes successfully) to disable normal link operation.  If the function ButtonClick() is not found or JavaScript is unavailable, the link reverts to normal operation.

We add the <span>'s to enable us to style the links and separators.

Finally, we add the clear rule to the element immediately following the menu.


<div class="Menu">

  <a href="Target1.html" title="Test button #1"
    onmouseout ="window.ButtonOut ? ButtonOut(this) : null"
    onmouseover="window.ButtonSel ? ButtonSel(this) : null"
    onmousedown="window.ButtonDep ? ButtonDep(this) : null"
    onclick="return window.ButtonClick ? ButtonClick(this) : true"
    ><span>Link 1</span></a>

  <span class="MenuSep">&nbsp;|&nbsp;</span>

  <a href="Target2.html" title="Test button #2"
    onmouseout ="window.ButtonOut ? ButtonOut(this) : null"
    onmouseover="window.ButtonSel ? ButtonSel(this) : null"
    onmousedown="window.ButtonDep ? ButtonDep(this) : null"
    onclick="return window.ButtonClick ? ButtonClick(this) : true"
    ><span>Link 2</span></a>

  <span class="MenuSep">&nbsp;|&nbsp;</span>

  <a href="Target3.html" title="Test button #3"
    onmouseout ="window.ButtonOut ? ButtonOut(this) : null"
    onmouseover="window.ButtonSel ? ButtonSel(this) : null"
    onmousedown="window.ButtonDep ? ButtonDep(this) : null"
    onclick="return window.ButtonClick ? ButtonClick(this) : true"
    ><span>Link 3333</span></a>

</div>

<div style="clear: both">&nbsp;</div>


Menu logic functions


function ButtonSel(Button) {                      // called by onmouseover event

  // Highlight button
  if (!Button.className) {
    Button.className = "Sel";
  }
}

function ButtonOut(Button) {                       // called by onmouseout event

  // Return button to normal
  Button.className = "";
}

function ButtonDep(Button) {                      // called by onmousedown event

  // Depress button
  Button.className = "Dep";
}

function ButtonClick(Button) {                        // called by onclick event

  // Return button to highlighted
  Button.className = "Sel";

  // Set page address
  PageAddr = Button.href;         // This variable must survive outside function

  // Delay page loading to allow button to return to correct state
  setTimeout("window.location = PageAddr", 250);

  // Disable normal link operation
  return false;
}


Style Sheet

We must be careful to shield older browsers from things like floats, which can screw up badly in some older browsers.  One way to do this is to enclose the rules in an @media rule.  Most browsers prior to "level 5" (for example Netscape 4 and IE4) do not support this rule and simply ignore the contents.

Optionally, you could give older browsers some basic styles by moving things like the normal link color and font rules outside the @media rule, resulting in two versions of CSS - one for older browsers and one for newer ones.

@media all {

  .Menu {
    float: left;
    color: #904900;
    font-family: arial, helvetica, sans-serif;
  }

  .Menu a {
    float: left;
    text-decoration: none;
  }

  .Menu a span {
    float: left;
    cursor: pointer;
    font-weight: bold;
    color: #009900;
  }

  .Menu a.Sel span {
    text-decoration: underline;
    color: #FF6600;
  }

  .Menu a.Dep span {
    color: #CC00CC;
  }

  .MenuSep {
    float: left;
  }
}
 
 

You might like to confirm that the buttons now behave correctly and return to the "highlighted" state before the new page starts to load.

This 3-state framework is ideal for creating individual buttons on your pages, such as download or navigational buttons and buttons which open a new window.  It could also be used as the basis for a simple menu system.  All we need to do now is add CSS rules to turn these links into buttons.