Click to return to the DHTML, HTML     
Mouse Capture Overview     How To Create a Mouse Cap...     Dynamic HTML    
Web Workshop  |  DHTML, HTML & CSS

How To Create a Mouse Capture Drop-down Menu


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 Essential Mouse Capture Menu

The following steps outline using mouse capture to enhance a drop-down menu.

  1. Create a menu bar.

    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>
    

  2. Create a drop-down menu and a menu item.

    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>
    

  3. Give the menu bar mouse capture when it is clicked.

    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;
          }
       }
    }
    

  4. Set the menu positions.

    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>
    

  5. Initialize the menu when the page loads.

    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(){
    
    }
    

  6. Add the initialization function to the BODY. Then, add an onload event to the BODY to call the initialization function, fnInit.
    <BODY onload="fnInit()">
    

  7. Test mouse capture on the menu.

    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>
    

    This feature requires Microsoft Internet Explorer 5 or later. Click the icon below to install the latest version. Then reload this page to view the sample.
    Microsoft

The Dynamic Mouse Capture Menu

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.

  1. Update global variables.

    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;
    

  2. Update the initialization function.

    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;
       }
    }
    

  3. Add a function to define new menu groups.

    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);
    }
    

  4. Add a function to align new menu labels.

    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";
    }
    

  5. Add a function to define new menu items.

    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:

    1. an integer specifying the drop-down menu it is added under
    2. the name of the menu item
    3. an action to perform when it is clicked

    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";
    }
    

  6. Add code for the sample menu item.

    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";
    }
    

    This feature requires Internet Explorer 5 or later. Click the icon below to install the latest version. Then reload this page to view the sample.
    Microsoft

Adding Styles and Options

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.

  1. Create the styles for the menu appearance.

    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>
    
  2. Add mouseover and mouseout functionality.

    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.

    1. It checks the value of the MENUTYPE expando to see if a menu label fired the event. The function only performs an action if a menu label or menu item fired the event.
    2. If a menu label fired the event, a combination of the menu label's className, the status of the menu, and an attribute referencing an open menu are used to determine what action, if any, should be performed.
    3. The oDistinctMenu variable refers to the last open menu and is used to determine if the menu should be opened and follow the mouse.

    The menu is displayed until one of three things happens:

    1. anything outside the menu is clicked
    2. another menu is opened
    3. there are no open menus

    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";
          }
       }
    }
    

  3. Add menu history.

    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");
    }
    

    This feature requires Internet Explorer 5 or later. Click the icon below to install the latest version. Then reload this page to view the sample.
    Microsoft

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.

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



Back to topBack to top

Did you find this topic useful? Suggestions for other topics? Write us!

© 1999 Microsoft Corporation. All rights reserved. Terms of use.