Dynamic HTML menus can be tuned to better respond to mouse activity by coordinating the use of the mouse capture methods and event in Microsoft® Internet Explorer 5 and later. The mouse capture methods setCapture and releaseCapture, and the onlosecapture event allow Web authors to create dynamic features that do not interfere with other content on the page.
A drop-down menu is a dynamic feature that can use mouse capture to prevent objects on the page from firing events while the menu is open. This document will guide you in creating a drop-down menu that captures mouse events. If you are not familiar with mouse capture or Dynamic HTML, the Related Topics section provides links to additional information.
The following steps outline using mouse capture to enhance a drop-down menu.
A DIV object is used for the menu bar. The onmouseover, onmouseout, and onclick events are set on the DIV. These events are sufficient to handle navigating through the menu bar, the drop-down menu, and each menu item. Event bubbling in Internet Explorer sufficiently handles these events, leaving mouse capture to intervene when events are fired on objects outside the menu.
<DIV onmouseover = "fnHighlight()" onclick = "fnSwitchMenu()" onmouseout = "fnHighlight()" ID = oMenuContainer > <!--All menu labels, drop-down menus, and menu items go in here.--> </DIV>
A SPAN object is used for a menu label and menu item, and a DIV object is used for the drop-down menu. The menu labels and drop-down menus are enumerated by the customized expando attribute, GROUPID. The value of GROUPID is appended to the ID property. This allows the associated drop-down menu and menu items to be easily identified within script.
The customized expando attributes, MENUTYPE and MENUACTION, identify the role of the object and what action the object performs when clicked. When mouse capture is assigned, the menu needs to know what to do with the object. These expandos provide that information.
<!-- The menu label--> <SPAN GROUPID=1 MENUTYPE="menulabel" ID=oLabel1 CLASS="menulabel"> Menu Label 1 </SPAN> <!--The drop-down menu--> <DIV ID=oGroup1 CLASS="menugroup"> <!--The menu item--> <IMG ID = "oItemImg1" SRC = "/graphics/document.gif" WIDTH = 15 HEIGHT = 13 ALT = ""> <SPAN GROUPID = 1 MENUTYPE = "menuitem" ID = "oItem1" ITEMID = 1 MENUACTION = "fnJScript()"> Menu Item 1 </SPAN> <br>; </DIV>
Setting mouse capture on the menu will allow the menu to handle mouse events that originate anywhere on the document. This allows the menu to close when an element outside the menu is clicked, and to prevent the default event for an element from firing.
The fnSwitchMenu function is called when the menu bar or an object within the menu is clicked. The function looks at what was clicked and decides what to do about it. It is important to keep in mind that determining which object was clicked is necessary once mouse capture is set on the menu because the fnSwitchMenu function fires for all mouse clicks on the page.
If the object has a MENUTYPE expando value equal to menulabel, then the fnSwitchMenu function treats the object like a menu label and checks to see if the drop-down menu should be opened or closed. If the attribute is equal to menuitem, then the fnSwitchMenu function treats the object like a menu item and evaluates the script expression stored in the expando, MENUACTION.
The oDistinctMenu and oDistinctGroup variables are used to monitor the current menu label and menu group.
function fnSwitchMenu(){ oWorkItem=event.srcElement; // Determine if object is a menu label, menu item, or other. if(oWorkItem.MENUTYPE== "menulabel"){ // If object is menu label, switch its status.if((oDistinctMenu!=oWorkItem)&&(oDistinctMenu!="")){ oDistinctGroup=eval("oGroup" + oDistinctMenu.GROUPID); oDistinctGroup.style.display="none"; oDistinctMenu.menustatus="closed"; } oDistinctMenu=oWorkItem; oDistinctGroup=eval("oGroup" + oWorkItem.GROUPID); if(oDistinctMenu.menustatus=="closed"){ oDistinctMenu.menustatus="open"; oDistinctGroup.style.display="block"; if(bOpenMenu==false){ bOpenMenu=true; // Capture mouse events. oMenuContainer.setCapture(); /* Capturing mouse events will allow the menu to respond when the user clicks anywhere on the document. */ } } else{ bOpenMenu=false; oMenuContainer.releaseCapture(); oDistinctMenu.menustatus="closed"; oDistinctGroup.style.display="none"; } } // If object is a menu item, process its MENUACTION if(oWorkItem.MENUTYPE=="menuitem"){ if(oWorkItem.MENUACTION!="Invalid"){ if(oWorkItem.MENUACTION.indexOf(")")==-1){ eval(oWorkItem.MENUACTION + "()"); } else{ eval(oWorkItem.MENUACTION); } } oMenuContainer.releaseCapture(); oDistinctMenu.click(); oDistinctMenu.className="menulabel"; } // If object is not in the menu container, // close menu, stop event. if(!oWorkItem.MENUTYPE){ if(bOpenMenu==true){ oMenuContainer.releaseCapture(); oDistinctMenu.click(); event.returnValue=false; } } }
The menugroup, menulabel, and menulabel2styles need to be defined. The only cascading style sheets (CSS) attributes that are used right now are position and display. Setting the value of position to absolute allows us to place the objects anywhere we want on the page. The menu group is hidden so the page starts with it in a collapsed state. The purpose of the two menu label styles becomes clear during the advanced menu topics described later in the article. You can also add an absolute position to the STYLE attribute on the menu label for right now.
<STYLE> .menugroup {position: absolute; display: none;} .menulabel {position: absolute;} </STYLE>
To get the menu working, there are only a few finishing touches that need to be added.
An initialization function and a few variables need to be included that set the start positions for the menu label and menu group. Notice that the menu label can be positioned wherever you want. The menu group is aligned with the menu label so that the group opens beneath the label.
The variables are used to keep track of the open menu and the total number of menus.
Go ahead and include the fnHighlight function at this time, but don't worry about putting any code in it just yet.
var bOpenMenu=false; var oDistinctMenu=""; var oDistinctGroup=""; var iGroupCount=1; function fnInit(){ oLabel1.menustatus="closed"; oLabel1.style.top=2; oLabel1.style.left=75 * (iGroupCount-1) + 2; oLabel1.style.height=15; oGroup1.style.top=oLabel1.offsetHeight + oLabel1.offsetTop+2; oGroup1.style.left=oLabel1.offsetLeft; oGroup1.style.display="none"; } function fnHighlight(){ }
<BODY onload="fnInit()">
To see the effects of mouse capture on the menu, you add a link to the page. When you click the menu, the fnSwitchMenu function sets capture on the menu and the click event on the link is cancelled. If the click event is cancelled, the function closes and removes mouse capture from the menu. While the menu is not capturing mouse events, the click event on the link will successfully fire.
<P STYLE="position: absolute; top: 50;"> <A HREF="javascript:alert('test');">Test Link</A> </P>
One thing you probably noticed was that several menu groups and menu items can consume a lot of space. Furthermore, for every set of menu labels, groups, and items you add, you need to set the respective CSS values in order to align them with one another. To make the most of a mouse capture menu, the menu needs to be extensible and readily deployed for any Web application.
With the mouse capture and basic functionality of the menu in place, Dynamic HTML is used to automate the addition of menu items.
Several global variables are used to track the number of menu labels and to monitor the general status of the menu.
var iGroupCount=0; var iItemCount=0; var oWorkItem=""; var oDistinctMenu=""; var oDistinctGroup=""; var bOpenMenu=false; var iHistoryID=-1;
The initialization function fnInit aligned the menu groups and items. Instead, the new function is used to dynamically add new menu labels, groups, and items, and to provide height and width dimensions to the menu bar. The oScratch object is used for a sample menu item.
function fnInit(){ // Set the menu bar to the width of the client. oMenuContainer.style.width=document.body.clientWidth-2; // Add a menu label, "Search". fnAddMenuGroup("Search"); // Add a menu item, "Search Engines", to the first menu label, "Search". fnAddMenuItem(1,"Search Engines","fnSearch","folder"); // If there is something in the menu bar, set its height. if(oMenuContainer.innerHTML!=""){ oMenuContainer.style.height=oLabel1.offsetHeight + oLabel1.offsetTop + 4; } }
The fnAddMenuGroup function adds a menu label and the DIV object used for the drop-down menu.
The DIV object included in the document gives you a point of reference for inserting more objects. Each menu is comprised of three distinct components: the label, the drop-down menu, and items within the drop-down menu. The fnAddMenuGroup function is defined for adding the menu labels and drop-down menus and the fnAddMenuItem function inserts the menu items into the drop-down menu.
The fnAddMenuGroup function accepts a string argument for the menu label. This function adds the menu label and the drop-down menu. Once the menu label and drop-down menu have been added to the menu bar, the fnAlign function is called. The fnAlign function sets the position value for the respective menu label and drop-down menu.
Two integer variables, iGroupCount and iItemCount are used to track the incrementing menu labels, drop-down menus, and menu items. These integers are recorded in the ID on an object and within several expando. Doing so allows you to dynamically refer to a particular portion of the menu without knowing its ordinal position in the document or searching through the children of an object. If you look at the fnSwitchMenu function, you can see how the oDistinctMenu and oDistinctGroup variables are designated using these numbers.
function fnAddMenuGroup(sGroupName){ iGroupCount++; oMenuContainer.innerHTML += '<SPAN GROUPID=' + iGroupCount + ' MENUTYPE = "menulabel" ID=oLabel' + iGroupCount + ' CLASS = "menulabel">' + sGroupName + '</SPAN>' + '<DIV ID = oGroup' + iGroupCount + ' CLASS = "menugroup"></DIV>'; var oNewLabel = eval("oLabel" + iGroupCount); var oNewGroup = eval("oGroup" + iGroupCount); fnAlign(oNewLabel, oNewGroup); }
The fnAlign function performs the necessary menu alignments to keep the menu groups and labels together.
function fnAlign(oLabel,oGroup){ // Set the default status to "closed". oLabel.menustatus = "closed"; // Set the top position. oLabel.style.top = 2; // Set the left position to increment from the last menu label. oLabel.style.left = 75 * (iGroupCount - 1) + 2; // Set the width. oGroup.style.width = 150; // Set the top position to 5 pixels below the respective label. oGroup.style.top = oLabel.offsetHeight + oLabel.offsetTop + 5; // Set the left position to the same as the respective label. oGroup.style.left = oLabel.offsetLeft; // Hide the menu item. oGroup.style.display = "none"; }
The fnAddMenuItem function does several things. It adds a menu item to a specific group and specifies an action to perform when the item is clicked It even allows an icon to be included next to the menu item like in your favorite Microsoft Office applications.
Once you've added a menu label and drop-down menu, you can begin adding menu items. The fnAddMenuItem function requires at least three arguments:
The iGroupId argument refers to the iGroupCount integer value that was appended to the ID on the menu label and drop-down menu. You have to specify this number when setting up your menus, so keep a mental note of the number of drop-down menus you've added.
The sItemName argument is any text string that is used as the visible name of the menu item.
The sItemAction argument is a reference to any JScript® (compatible with ECMA 262 language specification) or Visual Basic® Scripting Edition (VBScript) function.
An additional accessory is included with the sItemImage argument. This argument is the name of an image that you would like to use next to the menu item. If a name is not specified, then a blank space is used.
function fnAddMenuItem(iGroupId, sItemName, sItemAction, sItemImage){ // Increment the number of menu items. iItemCount++; var sTmpSrc = ""; // If no image name is provided, use a default. if(sItemImage + "" == "undefined"){ sTmpSrc = "canvas.gif"; } else{ sTmpSrc = sItemImage + ".gif"; } var oNewGroup = eval("oGroup" + iGroupId); // Add the menu item. oNewGroup.innerHTML += '<IMG ID="oItemImg' + iItemCount + '" SRC="/graphics/' + sTmpSrc + '" WIDTH=15 HEIGHT=13 ALT=""> <SPAN GROUPID=' + iGroupId + ' MENUTYPE="menuitem" ID="oItem' + iItemCount + '" ITEMID=' + iItemCount + ' MENUACTION="' + sItemAction + '">' + sItemName + '</SPAN> <br>'; var oNewItem = eval("oItem" + iItemCount); oNewItem.style.textDecoration = "none"; }
Since the MENUSTATUS expando refers to a JScript function, the function needs to be defined in order for the menu item to do anything. For this sample, the code lists a few of the top search engines.
function fnSearch(){ var aSearch=new Array(); function aSD(sName,sHref){ this.sname=sName; this.shref=sHref; } aSearch[aSearch.length] = new aSD("AltaVista","http://www.altavista.digital.com"; aSearch[aSearch.length] = new aSD("Infoseek","http://www.infoseek.com"); aSearch[aSearch.length] = new aSD("Lycos","http://www.lycos.com"); aSearch[aSearch.length] = new aSD("Yahoo!","http://www.yahoo.com"); oScratch.innerHTML = sScratchInclude; oScratch.innerHTML += "<b>Search Engines (" + aSearch.length + ")</b><p>"; for(var i = 0;i < aSearch.length; i++){ oScratch.innerHTML += '<a href="' + aSearch[i].shref + '">' + aSearch[i].sname + '</a><br>'; } oScratch.style.display = "block"; }
There are a multitude of options and styles that can be applied to a menu to enhance its appearance and usability. To polish the mouse capture menu, the menu appearance is defined, the script for the fnHighlight function is added, and a menu history is created.
The menu bar style includes absolute positioning and uses the top and left properties to position the menu bar in the upper-left corner of the client window. A three-dimensional border property is created by using a solid border-style property and 1pixel border-width property. Adjusting the borderTopColor, borderLeftColor, borderRightColor, and borderBottomColor properties allows the author to change the shading for a 3D effect. A "clicked" style is created by switching the borderTopColor and borderLeftColor colors with the borderRightColor and borderBottomColor colors, which complement the mouseover highlights. You can choose your own combination of colors to adjust the border shading so that the menu is consistent with any other images and styles present on your Web page.
<STYLE> .menucontainer{ position: absolute; top: 0; left: 0; font-family: Arial; font-size: 8pt; background-color: #000000; border: "1 solid"; border-top-color: #CFCFCF; border-left-color: #CFCFCF; border-right-color: #505050; border-bottom-color: #505050; } .menulabel{ position: absolute; cursor: hand; color: #FFFFFF; width: 75; text-align: center; background-color: #000000; z-index: 2; } .menugroup{ display: none; padding-left: 10px; position: absolute; top: 0; left: 0; font-family: Arial; font-size: 8pt; background-color: #000000; border: "1 solid"; border-top-color: #CFCFCF; border-left-color: #CFCFCF; border-right-color: #505050; border-bottom-color: #505050; z-index: 2; } </STYLE>
So far, the script allows you to add menu labels, drop-down menus, and menu items to the menu bar. To make the menus follow the mouse, the onmouseover and onmouseout events refer to a function that checks the status of the expando set on the menu labels and drop-down menus. Since mouse capture is set on the menu, the menu labels and menu items must be identified. The identification of the different objects in the menu is especially important since all mouseover and mouseout events are directed through the fnHighlight function, just like all click events are directed through the fnSwitchMenu function. The fnHighlight function is directly tied to the fnSwitchMenu function, so when the mouse moves over and out of the menu bar, both functions get called.
The fnHighlight function uses three different values to determine state.
The menu is displayed until one of three things happens:
function fnHighlight(){ oWorkItem = event.srcElement; if(oWorkItem.MENUTYPE == "menulabel"){ if(oWorkItem.className == "menulabel"){ oWorkItem.className = "menulabel2"; if((oDistinctMenu != "") && (oDistinctMenu.menustatus == "open")){ oWorkItem.click(); } } else{ if(oWorkItem.menustatus == "closed"){ oWorkItem.className = "menulabel"; } } } if(oWorkItem.MENUTYPE == "menuitem"){ if(oWorkItem.style.textDecoration == "none"){ oWorkItem.style.textDecoration = "underline"; } else{ oWorkItem.style.textDecoration = "none"; } } }
One nice feature of many applications is a history of open documents. A similar feature can be implemented in the mouse capture menu by adding a few short lines to the code.
Add a new variable, iHistoryID, to the global variables for the page. Give the variable a value of -1. This value is used to check if a "History" group exists.
var iHistoryID=-1;
In the fnInit function, add a new menu group called "History".
fnAddMenuGroup("History");
In the fnAddMenuGroup function, include a conditional statement that assigns the GROUPID for the new "History" group to a variable, iHistoryID.
if(sGroupName=="History"){ iHistoryID=iGroupCount; }
Finally, in the fnSwitchMenu function, include a conditional statement that adds a new menu item to the "History" group if the variable iHistoryID is not equal to -1. The statement should be included adjacent to the line that evaluates the MENUACTION on a menu item.
if(oWorkItem.MENUACTION.indexOf(")")==-1){ eval(oWorkItem.MENUACTION + "()"); } else{ eval(oWorkItem.MENUACTION); } if(iHistoryID != -1){ fnAddMenuItem(iHistoryID,oWorkItem.innerText,oWorkItem.MENUACTION,"document"); }
A drop-down menu is only one implementation of mouse capture. Web sites with a lot of interactive elements, or Web-based games, can benefit from mouse capture. For more information, refer to the following related topics.
The following links provide additional information about mouse capture and DHTML.Mouse Capture
Dynamic HTML
How To Create a Mouse Capture Context Menu