Pop-Up Menus
  The item layers are taken in order and as each is positioned it's resulting height is found and used to update a running total so that the next can be positioned just below it. When the last item has been built, the full height of the menu can be determined and the layers making up the base and bevel can be properly clipped.

Automatically Building Submenus

Once a menu has been built and added to a page, the last step is to check each item to see if a submenu was assigned to it. If so, the create() method is called for that submenu. This recursively builds all submenus so you only need to call create() for your top-level menus.

Each menu has a created property that is set to true once the create() method has been called for it. This prevents errors if you should inadvertently attempt to create a menu that has already been built along with it's parent or ancestor.

Setting the Item Text Appearance

As each item layer is built, HTML code is generated by wrapping the given item text with <SPAN></SPAN> tags to specify a CSS style for the text font based on the menu definition. This code is then wrapped by <TABLE></TABLE> tags to set up the proper spacing around the text.

If the menu has no submenus assigned to any of it's items, a single table cell is used for the text. But if one or more items does have a submenu, two table columns are used. The left contains the text and the right contains an image, either the menu's arrow gif or a transparent gif used as a spacer. This allows the text of each item to be properly aligned with the others.

Tracking Mouse Position

The script captures the onmouseover event for the page to keep track of the current mouse position. The coordinates are stored in the global variables mouseX and mouseY. These coordinates are used in positioning menus and to help in the opening and closing of menus and submenus (see details below).

Positioning Menus

When the open() method is called for a window it's position is set depending on several factors.

Submenus are always positioned relative to their parent menu item. If the resulting position would place part of the submenu past the viewable area of the window, it is adjusted so that the entire menu is visible.

Top-level menus are handled differently. If the isStatic flag is true the menu's current coordinates will be used. These default to (0,0) but can be changed by the moveMenuTo() and moveMenuBy() methods. If not static, the menu is positioned according to the current mouse position, just like a submenu.

The open() method optionally takes x and y arguments. If these are given for a top-level menu, they take precedence and will be used even it they place the menu outside of the viewable page area.

Once positioned, the menu is made visible and it's current position is saved within the menu object itself.

Event Capturing

In all, four event handlers are used to control menu behavior. For each item in a menu the onmouseover, onmouseout and onclick (IE) or onmouseup (NS) events are captured. For the menu's base layer the onmouseout event is captured. The event handlers assigned to these control the actions of the menu and submenus.

Event handlers are given an implied reference to the object that fired them. With the above events that will be the layer itself, not the menu or item object to which it belongs. So when the event capturing is defined for any layer a new targetMenu and/or targetItem property is added to it and assigned a pointer to the owning menu or item object. This allows the event handler functions to easily reference the menu or item itself.

Creating the Item Highlight Effect

As illustrated above, each item consists of three layers. The top, transparent layer is set up to capture mouse over, mouse out and mouse click events. The highlight layer is initially made invisible, allowing the normal layer to be seen. When a mouseover occurs, the highlight layer is made visible. On a mouseout it is hidden again, restoring the normal appearance.

Controlling Menus and Submenus

In addition to highlighting items there is also the need to control the opening and closing of menus and submenus. This can get complicated because the event models in Netscape and IE differ greatly and different actions are required depending on the circumstances.

Here's an example. In the case illustrated below, the mouse starts on an item with a submenu which has been opened. When the mouse moves off this item, it may have gone onto the submenu, in which case both menus need to be kept open.

But, if the mouse has moved to the second item, the first submenu must be closed and a new one opened.

Finally, if the mouse has moved completely off both menus, both should be closed (unless the main menu has been made static, then only the submenu should be closed).

To help keep track of each menu's state and their relationships to each other some additional properties are defined for menu.

  • left, top, right, bottom - current coordinates of a menu, set when opened.

  • isStatic - true if the menu should be kept open all the time. Valid for top-level menus only.

  • isOpen - true if the menu is currently open.

  • isSubmenu - true if the menu is a submenu.

  • parentMenu - points the menu's parent menu. Set to null for top-level menus.

  • openChild - points to the currently active submenu, or null if no submenu is open. Each item in a menu can have a submenu but only one can be active at a time. Note that the submenu can have a submenu active itself.

With these set the event handlers can properly control how and when the submenus should appear. Here is a brief overview of the logic in each.

Item onmouseover

  1. If openChild for the corresponding menu is not null, close the active submenu.
  2. Display the highlight layer for this item.
  3. If the item has a submenu assigned to it, open it.

Item onmouseout

  1. If the item has a submenu, close it.
  2. Hide the highlight layer.
Item onclick/onmouseup

  1. If the item's link is a null string, exit.
  2. Otherwise, close all associated menus. This is done by working back up the menu hierarchy to the topmost parent (or the submenu before it if the topmost menu is static) and issuing a close for it. The close() method is recursive, so all submenus back down the line will be closed.
  3. If the link string begins with "javascript:", execute the code with the built-in eval() function.
  4. Otherwise, load the link URL in the current window or frame.

Menu onmouseout

  1. If isStatic is true, exit.
  2. If the browser is IE, check to see if the mouse has truly moved off the menu.*
  3. Check to see if a child menu is open. If not then close this menu.
  4. Otherwise, check to see if the mouse has moved onto the child menu, using the child menu's position and the current mouse coordinates. If not, close this menu.
  5. At this point, if the current menu has not been closed the function exits.

    If it has been closed, we need start going back up the menu hierarchy. So the function starts looping, taking the parent menu and checking to see if the mouse is currently on that menu. If so, the function exits. If not, the menu is closed and its parent is checked.

    The loop will eventually stop when it finds a menu that is under the mouse (and should therefore stay active) or reaches the top of the hierarchy (when the parentMenu property is null). Note that if it does reach the topmost menu and that menu is static, it will be kept open even if the mouse is not over it.

*This is necessary because in IE events bubble up from child elements, including the bevel and item layers, the HTML tags surrounding the item text and the item text itself. When the mouse moves onto these, an onmouseout is first fired for the menu.

Also note that in IE, the current mouse coordinates are updated using the values in the global event object. This is necessary because the onmousemove handler for the page may not fire before the onmouseout for the menu. So this function will update the global mouseX and mouseY values to use the most current values.

Window Resizing

Netscape browsers will lose track of DHTML elements when the browser window is resized. In order to ensure that the menu appears correctly, the window resize event is captured for both Netscape and IE. When a resize occurs, the entire page is reloaded by the popUpMenuReload() function, causing the menus to be rebuilt.

Note that some early releases of Netscape 4 have a bug that can cause a resize event to fire even when the window has not be resized. To correct this, the width and height of the window is saved at load time. On a resize, these are checked against the current width and height and a reload occurs only if the size has indeed changed.

To keep the behavior consistent with IE, resizes are handled the same way. However, when resizing an IE browser by dragging the window border, the 'Show window contents while dragging' display option on Windows platforms will cause the event to fire before the window can be resized, canceling the drag. This means the user would be unable to resize the browser via dragging.

To prevent this, a setTimeout() is used to reload the page. This allows the user a couple of seconds to resize the browser before the page gets refreshed and cancels the user drag action.

Source

  • index.html - includes an example of a menu with default settings.
  • index2.html - includes an example of a static menu with custom settings and arrow graphics.
  • index3.html
  • dhtmllib.js - the cross-browser DHTML library.
  • popupmenu.js - the PopUpMenu code.
  • popcond.js - combined version of the two .js files above, condensed for faster download.
The default arrow images are shown below, along with the images used in the other examples:

transparent.gif (1x1) default_norm.gif (8x12) default_high.gif (8x12)
menu2_norm.gif (10x12) menu2_high.gif (10x12)  

The script is fairly large, so if you are concerned about download time use the condensed popcond.js version which can be used in place of the dhtmllib.js and popupmenu.js files (21K vs. 37K). It contains all the code in the other two files but has been stripped of most comments and formatting.

You can also download popupmenu.zip (52K) which contains all the JavaScript code files, documentation, examples and graphics.

Page 1 - Page 2 - Page 3


Home