Team LiB
Previous Section Next Section

DHTML Menus

The goal of many JavaScript menu systems is to emulate the functionality of “real” GUIs, such as Windows, MacOS, or Linux’s KDE. Pull-down menus provide a convenient and familiar way to provide users with lists of choices. These choices are commonly links to pages with information about your products and company or links that trigger some sort of action in the page. By far the most common use for DHTML menus is for navigation enhancement.

Be forewarned, implementing complex menu systems in JavaScript requires a high level of skill and knowledge. The process necessitates that the HTML, CSS, event handling, and dynamic manipulation of document objects in your page work together harmoniously under a variety of browsers. With so many interacting technologies, a number of subtle details are often overlooked, particularly with regard to event handling. If not caught during your testing process, these oversights can frustrate your users to the point where they will not return to your site. As with any DHTML task, you should plan on spending a significant amount of time testing your code under a variety of browsers. A malfunctioning menu system is worse than none at all.

In this section we present some of the most popular varieties of JavaScript-based menus seen in sites, but this selection is by no means exhaustive. The DHTML Web sites mentioned in this chapter, particularly www.dynamicdrive.com, www.dhtmlcentral.com, and www.webreference.com, are all excellent sources of inspiration and code. We’ll present a few examples of what you can find at such sites to get the idea of how other forms of menus can be created.

Application-Like Menus

The first edition of this book included a lengthy demonstration of how to build application-style menus in JavaScript that mimic the look and feel of operating system menus. While the example was instructive, it was fairly complex and not really appropriate for use in the “real” world. In this edition we present a simple menu system purely as an instructional device.

One complexity with JavaScript-based menu systems is setup and appropriate application of CSS. For example, when creating a menu of any sort, generally we rely on the <<div>> tag to hold our various choices.

<<div id="menu3" class="menu">>
   <<div class="menuHead">>Book Releated Sites<</div>> 
   <<div id="menu3choices"  class="menuChoices">> 
      <<a href="http://www.javascript.com">>JavaScriptRef<</a>><<br />>
      <<a href="http://www.w3c.org">>W3C<</a>><<br />>
      <<a href="http://www.pint.com">>PINT<</a>><<br />>
    <</div>>
<</div>>

In this situation we distinguish between "menuHead", which will show as the trigger for the menu, and the various choices and then we enclose choices in another <<div>> tag for styling and scripting purposes. Now we can associate script to a mouseover event to hide and show the menu.

<<div id="menu3" class="menu" onmouseover="show('menu3');"
 onmouseout="hide('menu3');">>

The hide and show routines use the CSS visibility property to turn our menu off and on. Notice that we define a variable DOMCapable to keep us from triggering the menu features if the browser can’t handle them.

(document.getElementById ? DOMCapable = true : DOMCapable = false);

function hide(menuName)
{
 if (DOMCapable)
  {
    var theMenu = document.getElementById(menuName+"choices");
    theMenu.style.visibility = 'hidden';
  }
}

function show(menuName)
{
 if (DOMCapable)
  {
    var theMenu = document.getElementById(menuName+"choices");
    theMenu.style.visibility = 'visible';
  }
}

The complete example is shown here with a rendering in Figure 16-3.

Click To expand
Figure 16-3: A simple DHTML pull-down menu
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html xmlns="http://www.w3.org/1999/xhtml">>
<<head>>
<<title>>Simple CSS Based Pulldowns<</title>>
<<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />>
<<style type="text/css">>
<<!--
  /* set the menu style */
  .menuHead { font-weight: bold; font-size: larger;  background-color: #A9A9A9;}
  .menuChoices { background-color: #DCDCDC; width: 200px;}
  .menu a {color: #000000; text-decoration: none;}
  .menu a:hover {text-decoration: underline;}  
  /* position your menus */
   #menu1 {position: absolute; top: 10px; left: 10px; width: 200px;}
   #menu2 {position: absolute; top: 10px; left: 210px; width: 200px;}
   #menu3 {position: absolute; top: 10px; left: 410px; width: 200px;}
-->>
<</style>>
<<script type="text/javascript">>
<<!--
/* we'll only allow DOM browsers to simplify things*/
(document.getElementById ? DOMCapable = true : DOMCapable = false);
function hide(menuName)
{
 if (DOMCapable)
  {
    var theMenu = document.getElementById(menuName+"choices");
    theMenu.style.visibility = 'hidden';
  }
}

function show(menuName)
{
 if (DOMCapable)
  {
    var theMenu = document.getElementById(menuName+"choices");
    theMenu.style.visibility = 'visible';
  }
}
//-->>
<</script>>
<</head>>
<<body>>
<<div id="menu1" class="menu" onmouseover="show('menu1');"
 onmouseout="hide('menu1');">>
   <<div class="menuHead">>Search Sites<</div>>
      <<div id="menu1choices" class="menuChoices">> 
        <<a href="http://www.google.com">>Google<</a>><<br />>
        <<a href="http://www.yahoo.com">>Yahoo<</a>><<br />>
        <<a href="http://www.teoma.com">>Teoma<</a>><<br />>
        <<a href="http://www.msn.com">>MSN<</a>><<br />>
        <<a href="http://www.dmoz.org">>DMOZ<</a>><<br />>
      <</div>>
<</div>>

<<div id="menu2" class="menu" onmouseover="show('menu2');"
 onmouseout="hide('menu2');">>
   <<div class="menuHead">>E-commerce Sites<</div>>
      <<div id="menu2choices"  class="menuChoices">> 
       <<a href="http://www.google.com">>Amazon<</a>><<br />>
       <<a href="http://www.ebay.com">>Ebay<</a>><<br />>
       <<a href="http://www.buy.com">>Buy.com<</a>><<br />>
      <</div>>
<</div>>

<<div id="menu3" class="menu" onmouseover="show('menu3');"
 onmouseout="hide('menu3');">>
   <<div class="menuHead">>Book Releated Sites<</div>> 
      <<div id="menu3choices"  class="menuChoices">> 
       <<a href="http://www.javascript.com">>JavaScriptRef<</a>><<br />>
       <<a href="http://www.w3c.org">>W3C<</a>><<br />>
       <<a href="http://www.pint.com">>PINT<</a>><<br />>
      <</div>>
<</div>>
<<script type="text/javascript">>
<<!--
/* Don't hide menus for JS off and older browsers */

if (DOMCapable)
 {
  hide("menu1"); 
  hide("menu2");
  hide("menu3");
 }
//-->>
<</script>>
<</body>>
<</html>>

The example presented does degrade in the sense that older browsers will still see all the choices. We could extend the script to work under many DHTML-generation browsers using the layerlib.js discussed in the previous chapter. Then we could start to address all sorts of quirks browsers have with CSS and JavaScript. However, implementing a bullet-proof menu would take up dozens and dozens of pages and focus more on minor browser annoyances and work-arounds than actual valuable JavaScript coding practices. It’s our suggestion to study the menu we present, and after you understand its concepts, look into some of the widely available JavaScript libraries on the Web such as HierMenus (http://www.webreference.com/dhtml/hiermenus/) before you go about rolling your own menu script.

Remote Control Menus

Remote control menus are pop-up windows that control the behavior of the main browser window. Chapter 12 covered the essentials of manipulation of one window by another, and the same techniques apply here. These types of menus are often useful if you need to present the user with a large number of complex capabilities. Often, screen real estate is at a premium, and a large menu of actions appearing in the main content window might clutter the interface unnecessarily.

The basic idea is to window.open() a new control window from the main content window. The remote control window would have buttons, links, or menus of actions, and would carry out the necessary functionality in the main browser window by using its window.opener reference. You may find it useful to invoke the focus() method of the window being controlled after the user performs an action in the control panel. Doing so improves usability by freeing the user from having to focus the content window manually. Additionally, windows containing remote control menus are often brought up “naked,” that is, without scrollbars, browser buttons, or a location bar. To bring up a window this way, pass the empty string as the third argument to window.open(), or use the configuration string options covered in Chapter 12 to turn off the features you don’t want.

As a concrete example, suppose you wished to show one of several very large images in the content window, but didn’t want the HTML controls to get in the way. You could use the following main content window (let’s call it “imageviewer.html”), which users would initially load:

File: imageviewer.html

<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html xmlns="http://www.w3.org/1999/xhtml">>
<<head>>
<<title>>Image Display Window<</title>>
<<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />>
<</head>>
<<body>>
<<img name="displayedImage" id="displayedImage" alt="" src="image1.jpg" />>

<<script type="text/javascript">>
var remoteControlURL = "remotecontrol.html";
var remoteFeatures = "height=150,width=400,location=no,";
remoteFeatures += "menubar=no,scrollbars=no,status=no,toolbar=no";
var remoteControl =
    window.open(remoteControlURL, "controlWindow", remoteFeatures);
<</script>>
<</body>>
<</html>>

Now you need the remote control window (remotecontrol.html) that this window will load. It will contain the JavaScript setting displayedImage.src in the previous document.

File: remotecontrol.html

<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html xmlns="http://www.w3.org/1999/xhtml">>
<<head>>
<<title>>Remote Control<</title>>
<<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />>
<<script type="text/javascript">>
<<!--
function loadImage(which) 
{
  var contentWindowURL = "imageviewer.html";
  if (!window.opener || window.opener.closed) 
   {
    alert("Main window went away. Will respawn it.");
    window.open(contentWindowURL, "_blank");
    window.close();
    return;
  }
  window.opener.document.images["displayedImage"].src = which;
  window.opener.focus();
}
//-->>
<</script>>
<</head>>
<<body>>
<<h3>>Select an Image to Display<</h3>>
<<form action="#" onsubmit="return false;" method="get">>
<<input type="button" value="Image 1" onclick="loadImage('image1.jpg');" />>
&nbsp;
<<input type="button" value="Image 2" onclick="loadImage('image2.jpg');" />>
&nbsp;
<<input type="button" value="Image 3" onclick="loadImage('image3.jpg');" />>
&nbsp;
<<input type="button" value="Image 4" onclick="loadImage('image4.jpg');" />>
<</form>>
<</body>>
<</html>>

The most important thing to note about this remote control script is that it takes careful measures to ensure that the main content window actually exists before using it. The user might have closed the window accidentally, in which case this script reloads imageviewer.html and closes itself. You can see this script in action in Figure 16-4.

Click To expand
Figure 16-4 : Remote control windows give you a way to move controls outside of the main browser window.

Using a separate window as a menu is not the only way to move menu functionality outside of the main browser window. Slide-in menus are also often appropriate for this task.

Slide-In Menus

A slide-in menu is a layer containing menu items that is partially hidden off-screen, usually to the left. Only a tab or thin vertical slice of the layer remains visible to the user. When the user activates the menu by mousing over or clicking on the exposed portion, the menu slides smoothly onto the page. When the user moves the mouse away from the menu, the layer slides back to its original position off-screen.

The following code illustrates the basic technique with which slide-in menus are usually implemented. The idea is to initially place the layer off the left side of the screen and then incrementally move the menu onto the screen while the mouse is over it. A timer wakes the scrolling function up at regular intervals, at which times the menu is moved slightly farther to the right. Once a predefined menu position is reached, the timer is cleared in order to stop the scrolling. When the user moves the mouse away from the menu, the scrolling function is invoked at regular intervals to move the layer back to its original position. Note that your users may find it more convenient if the menu is placed directly on the screen when activated (rather than having it slide in).

Although the following code is written for modern DOM-capable browsers, you can write cross-browser sliders using standard cross-browser DHTML found in the previous chapter and on the Web.

<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">>
<<html xmlns="http://www.w3.org/1999/xhtml">>
<<head>>
<<title>>Slide-in Menu Example<</title>>
<<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />>
<<style type="text/css">>
<<!--
.menu { background: blue;  padding: 0px; margin: 0px;
        border-style: solid; border-width: 2px;
        border-color: lightblue; position: absolute;
        text-align: left; width: 150px; top: 80px;
        z-index: 100;  }
.menuitem { color: white; position: relative;
            display: block; font-style: normal; margin: 0px;
            padding: 2px 15px 2px 10px; font-size: smaller;
            font-family: sans-serif; font-weight: bold;
            text-decoration: none;  }
a.menuitem:hover { background-color: darkblue; }
-->>
<</style>>
<<script type="text/javascript">>
<<!--
var leftmost = -120;
var rightmost = 5;
var interval = null;
var DOMCapable;
document.getElementById ? DOMCapable = true : DOMCapable = false;
function scrollRight(menuName)
{
  var leftPosition;
  if (DOMCapable)
    {
        leftPosition = parseInt(document.getElementById(menuName).style.left);
     if (leftPosition  >>= rightmost)
      {
       // if the menu is already fully shown, stop scrolling
       clearInterval(interval);
       return;
      }
        else
         {
       // else move it 5 more pixels in
       leftPosition += 5;
          document.getElementById(menuName).style.left = leftPosition+"px";
       }
     }
}

function scrollLeft(menuName)
{
  if (DOMCapable)
    {
        leftPosition = parseInt(document.getElementById(menuName).style.left);
     if (leftPosition  << leftmost)
      {
       // if menu is fully retracted, stop scrolling
       clearInterval(interval);
       return;
      }
        else
         {
          // else move it 5 more pixels out
          leftPosition -= 5;
          document.getElementById(menuName).style.left = leftPosition+"px";
         }
      }
}

function startRightScroll(menuName)
{
   clearInterval(interval);
   interval = setInterval('scrollRight("' + menuName + '")', 30);
}
function startLeftScroll(menuName)
{
  clearInterval(interval);
  interval = setInterval('scrollLeft("' + menuName + '")', 30);
}
//-->>
<</script>>
<</head>>
<<body onload="document.getElementById('slider').style.left=leftmost+'px';">>
<<!-- the hidden menu -->>
<<div class="menu" id="slider"
 onmouseover="startRightScroll('slider');"
 onmouseout="startLeftScroll('slider');">>
 <<h3 class="menuitem">><<u>>Our Products<</u>><</h3>>
  <<a class="menuitem" href="widgets.html">>Widgets<</a>>
  <<a class="menuitem" href="swidgets.html">>Super Widgets<</a>>
  <<a class="menuitem" href="sprockets.html">>Sprockets<</a>>
  <<a class="menuitem" href="vulcans.html">>Vulcans<</a>>
<</div>>
<<h1>>Welcome to our company<</h1>>
<</body>>
<</html>> 

The menu is shown in action in Figure 16-5.

Click To expand
Click To expandáClick To expand
Figure 16-5: The slide-in menu in action

Static Menus

If you include menus in a page that has more than one screenful of content, you might consider using static menus. A static menu is one that appears in one place in a browser window at all times, regardless of any scrolling the user might undertake. As you might imagine, implementing a static menu is similar to implementing a “normal” menu, except that the menu stays put on the screen despite user scrolling. While it is possible to trap scrolling events in some modern browsers, an easy cross-browser implementation of static menus can be achieved with a simple application of setInterval(). The idea is to “wake up” repositioning code at regular (short) intervals.

Despite the ease of implementation, the menu will appear to jump with such a technique in place. Instead, we may want to rely on the possibility of using the CSS2 position: fixed property to peg our navigation to a certain region on the screen. Unfortunately, IE6 does not support this property, but with a bit of CSS hacking one can imitate it.

The repositioning code adjusts the position of the menu to some predefined location. The implementation is straightforward. An onload handler for the document starts a timer that invokes makeStatic() on the menu every 30 milliseconds, and makeStatic() accepts the id (or layer name in Netscape 4) of the element that is to be repositioned. In this example, the “menu” is placed five pixels from the top-left of the screen, but this position can be easily changed.

To remind you of the trouble with DHTML-based solutions, we implement this particular example not only in DOM style, but in the IE document.all and Netscape 4 layers style as one final reminder of the challenges with JavaScript-based navigation.

<<html>>
<<head>>
<<title>>Static Menu Example<</title>>
<<!--     do not make XHTML due to IE box model issues -->>
<<style type="text/css">>
<<!--
.menu { background: blue;  padding: 0px; margin: 0px; border-style: solid;
        border-width: 0px; border-color: lightblue; color: white;
        position: absolute; text-align: left; width: 150px;  }
-->>
<</style>>
<<script language="JavaScript" type="text/javascript">>
<<!--
var xoff = 5;
var yoff = 5;
function makeStatic(elementName)
{
  if (document.layers)  // if ns4
  {
    document.layers[elementName].x = window.pageXOffset + xoff;
    document.layers[elementName].y = window.pageYOffset + yoff;
  }
  else if (document.all)  // else if ie4+
  {
    document.all(elementName).style.left = document.body.scrollLeft + xoff;
    document.all(elementName).style.top = document.body.scrollTop + yoff;
  }
  else if (document.getElementById)  // else if DOM-supporting
  {
    document.getElementById(elementName).style.left =
                                             window.pageXOffset + xoff;
    document.getElementById(elementName).style.top =
                                              window.pageYOffset + yoff;
  }
}
//-->>
<</script>>
<</head>>
<<body onload="setInterval('makeStatic(\'staticmenu\')',30)">>
<<layer class="menu" name="staticmenu">>

<<div class="menu" id="staticmenu">>
This is the menu content.
<</div>>

<</layer>>
<<h1>>Welcome to our Company<</h1>>
<<!-- Include more than one screenful of content here -->>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>><<br>>
<<h1>>Bottom of the page<</h1>>
<</body>>
<</html>>

As with all things DHTML, there are many ways to implement features like this. Other techniques for static menus include pure-CSS menus that use the CSS2 property position: fixed as well as many DHTML variations that track scrolling in a browser-specific manner. The CSS approach is by far the best way since it avoids the “jumping” effect of the menu, but since IE6 does not support it, we presented the JavaScript approach for those so inclined to implement it.

Context Menus

A context menu is a special context-specific menu that most programs display when the right mouse button is clicked. What makes this menu special is that its composition depends upon the situation in which it is activated. Right-clicking around a Web page (or on a Mac, holding the button down) is a good way to familiarize yourself with the concept. In most of the areas of a page, when you right-click you are presented with a menu with options such as viewing the source file, printing, or moving backward in your session history. Right-clicking on an image, however, typically results in a different menu, perhaps with the option to save the image to your local drive or set the image as wallpaper for your GUI.

Internet Explorer 5+ and Mozilla-based browsers enable you to define customized responses to contextual activations with the oncontextmenu event handler associated with the Document object. Associating a function with this handler allows you to customize contextual events— for example, to display a context menu of your own construction. Assuming you have defined functions showMyMenu() and hideMyMenu() to display and hide a custom DHMTL menu, you might use

document.oncontextmenu = showMyMenu;
document.onclick = hideMyMenu;

Hiding your menu when the user clicks normally is an important thing to remember to implement. Doing so mimics the behavior of the default context menus, making use of a process that your users are accustomed to seeing.

Like any other event handler, returning false from the context menu handler prevents the default action (the display of the default context menu) from occurring. If the handler returns no value or true, the “regular” context menu will be shown in addition to whatever action the custom handler takes.

It is interesting that context menus are often used to attempt to prevent images in the page from being saved to the user’s local drive. The typical way a user saves an image or other page content is by right-clicking the content and using the context menu option to save it to disk. Trapping context menu events can prevent na´ve users from doing so. For example, you could use a short script like this at the end of an HTML document:

<<script type="text/javascript">>
<<!--
function killContextMenu() 
{
  alert("Context menu disabled -- please do not copy our content.");
  return false;
}
document.oncontextmenu = killContextMenu;
-->>
<</script>>

While this technique seems valuable, the reality is that it is fraught with problems. First, the user can simply disable JavaScript, reload the page, and download the image as usual. Further, using this example might anger the user who expects to see a context menu. We could certainly try to sense if the right-click was on an image or not and improve the script—but the point is the same: disrupting the context menu may confuse or annoy many users. JavaScript should be used to improve a user’s visit, not disrupt it. Even if you think you’re justified in doing so from a legal perspective, think again: The image has already been downloaded to the user’s computer (or else they wouldn’t be viewing it), and, in any case, most fair-use laws permit users to save content for their own personal use.


Team LiB
Previous Section Next Section