Building an ASP.NET Menu Server Control

 

Scott Mitchell
4GuysFromRolla.com

November 2003

Applies to:
    Microsoft® ASP.NET

Summary: Learn about skmMenu, an open-source ASP.NET menu control, and learn how to approach the challenges in designing a menu control. (23 printed pages)

Download MenuControlSample.msi.

Note   This article was based on version 1.0 of the skmMenu control. The current version is 1.2 which includes some additional properties, features and bug fixes. For additional information on the control, please see the skmMenu Information product page.

Contents

Introduction
Dynamically Displaying Submenus
Examining the Plumbing of the skmMenu Control
Using skmMenu in an ASP.NET Web Page
Customizing the Appearance of skmMenu
Responding to the MenuItemClick Event
Current Known Limitations
Conclusion

There are an abundant number of commercial Microsoft® ASP.NET server controls that will generate interactive menus in an ASP.NET Web page (see a list of such controls at the ASP.NET Control Gallery). Creating such a control offers a number of interesting challenges, which include determining the correct mix of CSS and client-side JavaScript to allow for interactive menus on a variety of browsers, deciding on how to represent a menu system in an object model, and figuring out how to provide maximum ease of use and flexibility for the ASP.NET page developer utilizing the control.

Introduction

A very common and well-understood computing meme by even the most novice of computer uses is that of the menu. Due to its ubiquity, many Web sites employ menu-like navigational constructs. The appearance and client-side functionality of Web menus varies from site to site. Web sites like Amazon.com and Yahoo tend to use simple, static menus composed of either just text or small, lightweight graphics. Other sites employ stylesheets and client-side JavaScript to provide a menu experience not unlike the Microsoft Windows® operating system's standard menus. In this article we will examine how to build an ASP.NET server control that displays a Windows-like menu. This server control, which I call skmMenu, can be downloaded and used in your ASP.NET Web applications. For the latest version and information on skmMenu, check out skmMENU Workspaces.

Before we dive into the specifics of skmMenu, it is important that we first gain a solid understanding of what constitutes a menu, the functionality of a Web-based menu interface, and the features skmMenu will provide. Let's start by examining the fundamentals of menus.

A menu is comprised of a set of menu items, where each menu item has some text associated with it, optional instructions on what to do when the menu item is clicked, and an optional submenu. For example, if you are using Microsoft Internet Explorer, then in your browser right now there is a menu with six menu items whose text values are: File, Edit, View, Favorites, Tools, and Help. All of these menu items have submenus and none of them have any special action take place when clicked; rather, when clicked, they simply display their submenu.

It is important to realize that submenus are full-blown menus themselves. For example, the Tools menu item's submenu has five menu items itself: Mail and News, Synchronize, Windows Update, Reset Web Settings, and Internet Options. These menu items have actions associated with them when clicked. For example, when Windows Update is selected, the browser is directed to https://windowsupdate.microsoft.com. When the Internet Options menu item is selected, a tabbed dialog box appears from which the Internet options can be tweaked. Furthermore, notice that these menu items can have submenus themselves: the Mail and News menu item has a submenu associated with it, with options Read Mail, New Message, and so on.

Armed now with our understanding of menus, let's turn our attention specifically to Web-based menus. With Web-based menu interfaces, there are only three possible actions that can be taken when a menu item is clicked:

  • Nothing happens (with skmMenu, if the menu item has an associated submenu the submenu is displayed when the user moves their mouse over the menu item),
  • The user is whisked to a specified URL, or
  • A postback occurs and the menu raises an event notifying that a specific menu item has been clicked.

As we build our menu control we'll see how to provide for all of these possibilities. Another challenge with Web-based menu interfaces is in having the submenus display (and disappear) next to the appropriate parent menu item. This feat requires a bit of cascading stylesheets (CSS) and client-side JavaScript, which we'll examine in detail shortly. Before we tackle the client-side script needed, let's first discuss what features skmMenu will provide.

The menu interface should allow for maximum customizability from the ASP.NET page developer using our control. That is, the developer should be able to specify border information, foreground and background colors, font properties, and other stylistic information for the menus as a whole as well as for the currently selected menu item (the selected menu item, with skmMenu, is the menu item that currently has the mouse pointer over it). Furthermore, the ASP.NET page developer should also be able to specify if the top-level menu items are displayed in a horizontal layout, as with the standard Windows menus, or in a vertical layout. The vertical layout is useful for Web pages that have their navigational menus along the left column as opposed to across the top.

An ASP.NET page developer can specify the menu items for skmMenu in one of two ways: programmatically add the menu items or use databinding. With the databinding approach, the menu items are specified via an XML file and all the page developer has to do is set the menu control's DataSource property to either the physical path of an XML file or to an XmlDocument instance. I chose to use XML to express the menu options due to the recursive hierarchy present in menus (as discussed earlier, a menu is composed of menu items each of which can contain menus themselves). XML allows for such nested hierarchies to be specified in a human-readable, easily parse-able manner. We'll examine the XML file format and the databinding process later on in this article.

Now that we have touched upon the high-level details of skmMenu, we can start delving into the programming specifics. Before we do, however, let's take a moment to look at skmMenu in action. I find that when building an application it helps to have a visual as to what the end product will look like; for me, such images provide direction and motivation. Of course, when building an application from scratch you do not have the luxury of viewing a working application, but prototypes or even just simple sketches can help. Since skmMenu is already build and functional, though, you can benefit by viewing screenshots of the finished product. Figure 1 shows skmMenu in its horizontal layout. Figure 2 shows skmMenu in its horizontal layout with a top-level menu item's submenu displayed. Finally, Figure 3 shows skmMenu in its vertical layout with a submenu's submenu displayed. skmMenu allows for an arbitrary level of submenus.

Aa478963.aspnet-buildmenuservercontrol-01(en-us,MSDN.10).gif

Figure 1. skmMenu, in its horizontal layout

Aa478963.aspnet-buildmenuservercontrol-02(en-us,MSDN.10).gif

Figure 2. The Animal Facts submenu is displayed.

Aa478963.aspnet-buildmenuservercontrol-03(en-us,MSDN.10).gif

Figure 3. skmMenu, in its vertical layout, with the Animal Facts' Facts About Dogs' submenu displayed

Dynamically Displaying Submenus

Since the skmMenu control displays and hides submenus as needed, a bit of CSS and client-side JavaScript are needed to achieve the effect of the submenus appearing and disappearing when needed. The CSS specification outlines a number of properties that can be used to position an HTML element in the browser window. The ones we need to use are:

CSS Property Description
display Setting display to none hides the HTML element, while setting it to block displays the element.
top Specifies the coordinate for the top of the HTML element.
left Specifies the coordinate for the left of the HTML element.
offsetLeft Returns the left offset of the HTML element with respect to the element's parent.
offsetTop Returns the top offset of the HTML element with respect to the element's parent.
offsetWidth Returns the width of the HTML element.
offsetHeight Returns the height of the HTML element.
position Indicates how the top and left values are interpreted. A position value of absolute places the HTML element in the browser specified by the top, left, right, and bottom properties. For a position value of relative the top, left, right, and bottom properties specify the change from the HTML element's previous top, left, right, and bottom coordinates

An HTML element's CSS properties can be set through its style attribute. For example, to display an HTML paragraph at the coordinates (200,100), the following HTML markup could be used:

<p style="position:absolute; left: 100; top: 200;">
   This text is at position (200, 100)
</p>

For more information on positioning with CSS be sure to check out the following online articles:

Each menu is displayed using an HTML <table> element. Therefore, the skmMenu emits one <table> element for the top-level menu and a <table> element for each submenu. All of the submenus are initially hidden by having their display CSS property set to none. The top-level menu does not specify a value for the display property, meaning it defaults to block, thereby showing the <table> element. The top-level menu's top and left CSS properties are not set either. These CSS settings have the effect of hiding the submenus and displaying the top-level menu in the flow of the HTML content.

In order to better understand how these settings are specified in the HTML and their visual affect, consider the following menu:

What's New?
   New Articles
   New FAQs

Animal Facts
   Facts About Dogs
      Facts About Terriers
      Facts About Beagles
      Facts About Great Danes
   Facts About Goats

Assume each menu item without a submenu, when clicked, whisks the user to some predefined URL. This menu has a total of four menus:

  1. The top-level menu with menu items What's New? And Animal Facts,
  2. The submenu for the What's New menu item, with menu items New Articles and New FAQs,
  3. The submenu for the Animal Facts menu item, with menu items Facts About Dogs and Facts About Goats, and
  4. The submenu for the Facts About Dogs menu item, with menu items Facts About Terriers, Facts About Beagles, and Facts About Great Danes.

Since there are four menus, the skmMenu control needs to emit four HTML <table> elements, each with appropriate CSS properties. For such a menu configuration, skmMenu would emit the following HTML markup:

<!-- Table 2 -->
<table style="display: none;">
   <tr>
      <td>New Articles</td>
   </tr>
<tr>
      <td>New FAQs</td>
   </tr>
</table>

<!-- Table 4 -->
<table style="display: none;">
   <tr>
      <td>Facts about Terriers</td>
   </tr>
<tr>
      <td>Facts about Beagles</td>
   </tr>
<tr>
      <td>Facts about Great Danes</td>
   </tr>
</table>

<!-- Table 3 -->
<table style="display: none;">
   <tr>
      <td>Facts About Dogs</td>
   </tr>
<tr>
      <td>Facts About Goats</td>
   </tr>
</table>

<!-- Table 1 -->
<table>
   <tr>
      <td>What's New?</td>
   </tr>
<tr>
      <td>Animal Facts</td>
   </tr>
</table>

Realize that a large chunk of the HTML attributes and markup has been removed from the above markup listing. Much of this removed content is vital for understanding skmMenu and will be examined in detail later on.

The important thing to notice is that tables 2 through 4 have style="display: none;" specified, whereas table 1 does not. This alone displays the top-level menu while hiding the submenus. Also realize that this is the HTML generated when skmMenu is instructed to display a vertical layout for the top-level menu. Note that in this case table 1 has each menu item in a separate table row (<tr>) tag. Had the skmMenu been instructed to generate a horizontally oriented top-level menu, table 1's HTML markup would be:

<!-- Table 1 -->
<table>
   <tr>
      <td>What's New?</td>
      <td>Animal Facts</td>
   </tr>
</table>

Now, when the user moves their mouse over a menu item we need to display that menu's submenu, if it exists. The main challenge here is positioning the submenu correctly. The position of a menu item's submenu depends upon how the menu item and its siblings are laid out. If the menu item and its siblings are laid out horizontally, then the submenu's top coordinate needs to be the top coordinate of the menu item plus the menu item's height, and its left coordinate needs to be simply the left coordinate of the menu item. If, however, they are laid out vertically, then the submenu should appear at the same top coordinate and at the left coordinate that is the left coordinate of the menu item plus the width of the menu item (with skmMenus the only set of menu items that can be laid out horizontally are the top-level menu items). Figure 4 illustrates these layouts.

Aa478963.aspnet-buildmenuservercontrol-04(en-us,MSDN.10).gif

Figure 4. The coordinate location for the submenu Depends upon the menu item's coordinates and its layout

The black circles in Figure 4 illustrate the coordinates we are interested in. We can determine the left, top, width, and height of the selected menu item. Therefore, with a simple calculation, we can determine the coordinate position for the submenu. Using client-side JavaScript we can set the CSS properties of an HTML element on-the-fly.

While these positioning calculations seem straightforward, in practice they are a bit more complex than you might initially assume. The problem arises because the top-level menu is not explicitly positioned via the top and left CSS properties; rather, the top-level menu does not specify these settings at all. I chose this approach so that the top-level menu could flow within other HTML elements as opposed to being placed at an absolute position in the browser screen. This choice makes it more difficult to determine the actual values of the left and top of the selected menu item because the offsetLeft and offsetTop properties are relative to its position in the containing element. This isn't an issue if the menu is placed directly within the <body> element, but can cause havoc if the menu is placed within a <table> or other HTML element designed to position the menu.

In order to determine the absolute coordinates of a non-positioned element, we have to sum up the offsetTop and offsetLeft coordinate values of the element and all of its ancestors (an element's containing element can be obtained via the offsetParent property). We'll examine how to determine a non-positioned element's absolute position shortly. For more information on this be sure to read Determining the Location of a Non-Positioned Element.

Before we examine the client-side JavaScript needed to place the submenu in the correct coordinates, let's first take a moment to discuss how to use client-side JavaScript to interact with an HTML element's CSS properties. HTML elements have an id attribute that can be used to uniquely identify each element. Through client-side script, you can access an object representing a specified HTML element via the document.getElementById(id) function. Once you have a reference to the HTML element, you can set its CSS properties. For example, the following simple HTML page displays the text "Hello, World!" with a red background. Note that the background-color CSS property for the paragraph tag is specified programmatically through client-side script, and that the reference to the paragraph tag was made via the document.getElementById(id) function.

<p id="myParagraph" style="position:absolute; left: 100; top: 200;">
   Hello, World!
</p>

<script language="JavaScript">
   var welcomeMessage = document.getElementById("myParagraph");
   welcomeMessage.style.backgroundColor = 'red';
</script>

A thorough discussion of accessing CSS properties through client-side script is beyond the scope of this article. For more information on this topic check out the following online resources:

Every time a user moves his mouse over a menu item, we need to close all open submenus and check to see if that menu item has any submenus. If it does, we want to display its submenu, which involves computing the correct coordinates for the submenu. The challenge that faces us is being able to determine if a menu has a submenu or not, as well as how to close all submenus.

The first obstacle is handled by intelligently assigning the id attributes to the various <table> and <td> HTML elements. The technique I chose to use is to start by assigning the control's ClientID property to the top-level menu's id. For example, if the skmMenu's ID is set to Menu1, then the top-level menu's associated <table> HTML elements will have an id of Menu1. Next, each menu item is labeled as Menu1-menuItemCOUNT, where COUNT is a three-digit number incremented for each menu item. This three-digit number does impose a restriction of at most 1,000 menu items per (sub)menu. These are the ids for the individual <td> tags of the <table>, since each menu item is represented as a table column.

This process is repeated recursively. A submenu <table> element's id is the id of the menu item the submenu belongs to concatenated with the string –subMenu. Menu items in the submenu are the submenu's id concatenated with -menuItemCOUNT. To help hammer this concept home, let's reexamine the <table> elements generated by skmMenu, this time showing the id attributes of the <td> and <table> elements. To best understand the concatenated id attributes, start by examining table 1, then table 2, and so on.

<!-- Table 2 -->
<table style="display: none;" id="Menu1-menuItem000-subMenu">
   <tr>
      <td id="Menu1-menuItem000-subMenu-menuItem000">New Articles</td>
   </tr>
<tr>
      <td id="Menu1-menuItem000-subMenu-menuItem001">New FAQs</td>
   </tr>
</table>

<!-- Table 4 -->
<table style="display: none;" id="Menu1-menuItem001-subMenu-
  menuItem000-subMenu">
   <tr>
      <td id="Menu1-menuItem001-subMenu-menuItem000-subMenu-
        menuItem000">Facts about Terriers</td>
   </tr>
<tr>
      <td id="Menu1-menuItem001-subMenu-menuItem000-subMenu-
        menuItem001">Facts about Beagles</td>
   </tr>
<tr>
      <td id="Menu1-menuItem001-subMenu-menuItem000-subMenu-
        menuItem002">Facts about Great Danes</td>
  </tr>
</table>

<!-- Table 3 -->
<table style="display: none;" id="Menu1-menuItem001-subMenu">
   <tr>
      <td id="Menu1-menuItem001-subMenu-menuItem000">Facts About 
        Dogs</td>
   </tr>
<tr>
      <td id="Menu1-menuItem001-subMenu-menuItem001">Facts About 
        Goats</td>
   </tr>
</table>

<!-- Table 1 -->
<table id="menu">
   <tr>
      <td id="Menu1-menuItem000">What's New?</td>
   </tr>
<tr>
      <td id="Menu1-menuItem001">Animal Facts</td>
   </tr>
</table>

The two essential things to grasp here are:

  • One can determine if a menu item has a submenu merely by checking to see if there exists an element with an id attribute of the menu item's id concatenated with the string -subMenu.
  • All menus at a given level in the menu hierarchy have an id attribute value with the same number of characters, while all menus closer to the top-level menu have fewer characters in their id attribute value, and while all menus at a deeper level have more characters in their id attribute value.

These facts allow for determining if a submenu needs to be display, and for correctly closing submenus when a menu item has the mouse moved over it.

In order to display or hide submenus when the mouse cursor arrives at or leaves a menu item, the menu items' <td> elements call client-side JavaScript functions in their onmouseover and onmouseout events. The onmouseover event calls the mousedOverMenu() JavaScript function, which expects three input parameters:

  • The reference to the <td> element that was moused over,
  • The reference to the menu item's menu (the <table> the <td> element is contained within), and
  • A Boolean value indicating if the menu items are laid out vertically (true) or horizontally (false).

An abbreviated form of the mousedOverMenu() function is shown below, with the germane parts in bold:

function mousedOverMenu(elem, parent, displayedVertically)
{
   closeSubMenus(elem);

   // Display child menu if needed
   var childID = elem.id + "-subMenu";
   if (document.getElementById(childID) != null)
   {
      // make the child menu visible and specify that 
// its position is specified in absolute coordinates
      document.getElementById(childID).style.display = 'block';
      document.getElementById(childID).style.position = 'absolute';

      if (displayedVertically) {
         // Set the child menu's left and top attributes 
// according to the menu's offsets
         document.getElementById(childID).style.left = 
           getAscendingLefts(parent) + parent.offsetWidth;
         document.getElementById(childID).style.top = 
           getAscendingTops(elem);
      }
      else
      {
         // Set the child menu's left and top attributes 
// according to the menu's offsets
         document.getElementById(childID).style.left = 
           getAscendingLefts(elem);
         document.getElementById(childID).style.top =
           getAscendingTops(parent) + parent.offsetHeight;
      }
   }
}

This function starts by closing any open submenus that exist at a deeper level in the menu hierarchy (we'll examine the code to closeSubMenus() shortly). Next, a check is done to see if the menu item has a submenu. If it doesn't then we're done with this function, otherwise we need to display the submenu. This process starts by setting the display and position CSS properties of the submenu to block and absolute, respectively. Next, the coordinates are calculated depending on if the menu items are arranged vertically or not. And that's all there is to it.

When the user moves the mouse out of a menu item, eventually the closeSubMenus() function is called. A JavaScript timer is used to leave a submenu displayed for two seconds after the mouse leaves the submenu. For more details on this consult the code directly—we won't be discussing it in this article. As we saw, the closeSubMenus() function is also called upon immediately entering the mousedOverMenu() function. The closeSubMenus() function closes all submenus that are at a deeper level in the menu hierarchy than the menu item passed into the function.

To see why we only want to close the menus that are in a deeper level in the menu hierarchy assume, for a moment, that the closeSubMenus() function closed all submenus. This would be disastrous when moving the mouse onto a submenu, since doing so would cause that submenu to disappear! The code for the closeSubMenus() function can be seen below. Note that it takes advantage of the fact that all menu items with more characters in their id attribute must be at a deeper level in the hierarchy.

function closeSubMenus(parent)
{
   // Hide **all** lower-ordered submenus
   for (var i = 0; i < subMenuIDs.length; i++)
      if (subMenuIDs[i].length > parent.id.length)
         document.getElementById(subMenuIDs[i]).style.display = 'none';
}

subMenuIDs is an array of strings that contains a list of all of the ids of the submenus. This array, as we'll see in the second part of this article series, is generated by skmMenu. For now, just realize that subMenuIDs contains a list of all submenus.

This wraps up our examination of the HTML, CSS, and client-side script needed to display a menu interface in a Web page. In part two of this article series we'll see how this script is generated by the skmMenu control, but next let's turn our attention to the basic components of the skmMenu control.

Examining the Plumbing of the skmMenu Control

There are three main classes used to generate the menus. First, there is the MenuItem class. Each instance of a MenuItem class represents a menu item in some menu. The MenuItem class' germane properties are:

Property Description
ID The ID of the MenuItem. This string property is automatically set by skmMenu during the databinding process. An ASP.NET page developer will likely never need to read or manipulate this property.
Text This string property indicates the text that is displayed in the menu item.
Url If provided, indicates the URL that the end user is sent to upon clicking the menu item.
CommandName If provided, the menu item, when clicked, causes the Web Form to be posted back. The Menu will then raise a MenuItemClick event, passing in this CommandName value through the MenuItemClickEventArgs instance.
SubItems An optional MenuItemCollection instance of the menu item's submenu.

The MenuItemCollection class maintains a set of MenuItems. It has intuitive methods and properties, such as Add(menuItem), Insert(int, menuItem), Remove(menuItem), RemoveAt(menuItem), and so on.

Finally, the Menu class is responsible for generating the HTML markup for the menu. Note that Menu is derived from the Control class as opposed to the WebControl class because controls derived from WebControl wrap the entire content within some existing HTML element. For skmMenu, we want to emit an HTML <table> element for each menu (the top-level menu and all submenus).

Figure 5 contains a diagram illustrating the relationships among the Menu, MenuItemCollection, and MenuItem classes.

Aa478963.aspnet-buildmenuservercontrol-05(en-us,MSDN.10).gif

Figure 5. The relationships among the classes

As Figure 5 shows, the Menu class has precisely one MenuItemCollection instance, which corresponds to the top-level menu. The MenuItemCollection instance has a set of MenuItem instances. Each MenuItem instance can contain an optional MenuItemCollection, which, in turn, contains a set of MenuItem instances, which, in turn, can contain a MenuItemCollection instance, and so on. This object model accurately describes the recursive nature of menu systems that we talked about earlier in this article.

Fortunately, the ASP.NET page developer will only need to concern himself with this underlying object model if he decided to explicitly add or access MenuItems from a Menu. These objects can be automatically built up for the ASP.NET page developer through databinding.

The Menu class contains a DataSource property that can accept either a string path to an XML file or an XmlDocument instance that has already loaded the proper XML data. Specifically, the XML data must have a root element of <menu> with an arbitrary number of <menuItem> elements. Each <menuItem> element must have a <text> element that specifies the text to display for the menu item, as well as optional <url> and <commandName> elements. If the menu item has a submenu, the <menuItem> element should contain a <subMenu> element, which, like the <menu> element, can have an arbitrary number of <menuItem> elements.

An example valid XML file can be seen below. This XML file creates a menu with the following structure:

What's New?
   New Articles
   New FAQs

Animal Facts
   Facts About Dogs
      Facts About Terriers
      Facts About Beagles
      Facts About Great Danes
   Facts About Goats
   Facts About Snakes

Contact

<?xml version="1.0" encoding="utf-8" ?> 
<menu>
   <menuItem>
      <text>&lt;img align="middle" src="images/new.gif" width="32" 
        height="16" /&gt; What's New? &lt;img align="middle"
        src="images/right.gif" width="16" height="16" /&gt;</text>
      <subMenu>
         <menuItem>
            <text>&amp;nbsp;&amp;nbsp;&amp;nbsp;New Articles</text>
            <url>/suboption1.1.html</url>
         </menuItem>
         <menuItem>
            <text>&amp;nbsp;&amp;nbsp;&amp;nbsp;New FAQs</text>
            <commandName>NewFAQ</commandName>
         </menuItem>
      </subMenu>
   </menuItem>
   <menuItem>
      <text>&lt;img align="middle" src="images/paw.gif" width="20" 
        height="16" /&gt; Animal Facts &lt;img align="middle" 
        src="images/right.gif" width="16" height="16" /&gt;</text>
      <url>/option2.html</url>
      <subMenu>
         <menuItem>
            <text><![CDATA[<img align="middle" src="images/paw.gif" 
              width="20" height="16" /> Animal Facts <img 
              align="middle" src="images/right.gif" width="16" 
              height="16" />]]></text>
            <url>/suboption2.1.html</url>
            <subMenu>
               <menuItem>
                  <text>Facts about Terriers</text>
                  <url>/suboption2.1.1</url>
               </menuItem>
               <menuItem>
                  <text>Facts about Beagles</text>
                  <url>/suboption2.1.2</url>
               </menuItem>
               <menuItem>
                  <text>Facts about Great Danes</text>
                  <url>/suboption2.1.3</url>
               </menuItem>
               <menuItem>
                  <text>Facts about Poodles</text>
                  <url>/suboption2.1.4</url>
               </menuItem>
            </subMenu>
         </menuItem>
         <menuItem>
            <text>&amp;nbsp;&amp;nbsp;&amp;nbsp;Facts About 
              Goats</text>
            <url>/suboption2.2.html</url>
         </menuItem>
         <menuItem>
            <text>&amp;nbsp;&amp;nbsp;&amp;nbsp;Facts About 
              Snakes</text>
            <url>/suboption3.2.html</url>
         </menuItem>
      </subMenu>
   </menuItem>
   <menuItem>
      <text>&lt;img align="middle" src="images/email.gif" width="18" 
        height="18" /&gt; Contact</text>
      <url>mailto:mitchell@4guysfromrolla.com</url>
   </menuItem>
</menu>

The important things to notice in the above XML document:

  • The menu items' text properties can contain HTML markup, such as <img> tags. However, since XML contains certain reserved characters - <, >, &, ", and ' – be sure to either escape them or wrap the <text> element in a CDATA section (the above XML document shows escaping in the What's New? and Contact <text> elements and using CDATA in the Animal Facts <text> element).
  • Examine the submenu items for What's New? Notice that New Articles has a <url> element while New FAQs has a <commandName> element instead. The effect of this is that when New Articles is clicked the user will be whisked to the specified <url> value, but when New FAQs is clicked, the Web Form will postback and the MenuItemClick event will fire passing along the specified <commandName> value.
  • The XML structure allows for an arbitrary submenu depth.

This concludes our introductory examination of skmMenu. In the second upcoming part of this article series, the skmMenu code will be examined in depth. Let's now turn our attention to creating an ASP.NET Web page that utilizes skmMenu.

Using skmMenu in an ASP.NET Web Page

To use skmMenu in an ASP.NET Web page you must first download the code from the end of this article. The code contains a pre-built assembly, skmMenu.dll, which you need to add to your ASP.NET Web project. You can optionally compile the source code to generate the assembly yourself. If you are using Visual Studio .NET, you can add the assembly to your Web application by simply right-clicking on the References folder in the Solution Explorer and then browsing to the skmMenu.dll file. If you are not using Visual Studio .NET, simply copy the assembly into your Web application's /bin directory.

For those using Visual Studio .NET, you will also want to add skmMenu to the Toolbox. To do this, right-click on the Toolbox and choose Add/Remove Items, and browse to the skmMenu.dll assembly; at this point, you can add skmMenu to an ASP.NET Web page by simply dragging and dropping it from the Toolbox onto the Designer. If you are not using Visual Studio .NET, add the following line of code to your ASP.NET Web page:

<%@ Register TagPrefix="skm" Namespace="skmMenu" Assembly="skmMenu" %>

Then, to add it in your ASP.NET Web page, simply use:

<skm:Menu runat="server" id="ID"></skm:Menu>

skmMenu provides a rich design-time experience for those developers using Visual Studio .NET. As Figures 6 and 7 show, after adding skmMenu to the Toolbox you can drag skmMenu onto the Designer to provide a WYSIWYG editing interface. Figure 6 shows skmMenu immediately after being added to the Designer, while Figure 7 shows skmMenu after the Layout property has been changed from Vertical to Horizontal and some stylistic properties have been set.

Aa478963.aspnet-buildmenuservercontrol-06(en-us,MSDN.10).gif

Figure 6. The Visual Studio .NET designer after skmMenu has been added

Aa478963.aspnet-buildmenuservercontrol-07(en-us,MSDN.10).gif

Figure 7. The designer after some skmMenu properties have been set

To bind skmMenu to an XML file containing information for the menu hierarchy, first create the XML file and place it within your Web application. Then, in the Page_Load event handler, add the following code:

private void Page_Load(object sender, System.EventArgs e)
{
   // Put user code to initialize the page here
   if (!Page.IsPostBack)
   {
      menuID.DataSource = Server.MapPath(xmlFileName);
      menuID.DataBind();
   }
}

That's all there is to it.

Customizing the Appearance of skmMenu

skmMenu contains four properties that can be used to customize appearance:

Property Description
MenuStyle Specifies the style settings for each of the menus (the <table> elements).
UnselectedMenuItemStyle Specifies the style settings for the unselected menu items (recall that a menu item is selected if and only if the mouse pointer is currently above the menu item). Since this style is applied to menu items, it is applied to the <td> elements.
SelectedMenuItemStyle Specifies the style settings for the unselected menu items. Since this style is applied to menu items, it is applied to the <td> elements.
Layout Specifies if the top-level menu is laid out in a horizontal or vertical fashion.

These stylistic settings can be set through the Properties pane in the Visual Studio .NET Designer, programmatically in the ASP.NET Web page's source code portion, or declaratively in the server control tag. As demonstrated in Figure 7, the Visual Studio .NET Designer provides a WYSIWYG view of the menu based upon these stylistic property settings.

Responding to the MenuItemClick Event

Recall from our earlier discussions that menu items, when clicked, can either: do nothing; whisk the user to some specified URL; or cause the Web Form to postback, raising the MenuItemClick event. To specify that the user should be taken to a specified URL, provide a <url> element for the menu item; to indicate that a postback should occur, specify a <commandName> element. If you provide neither a <url> or <commandName> element, nothing happens when the menu item is clicked.

When the postback occurs, the Menu class raises the MenuItemClick event, passing along the CommandName property of the MenuItem that was clicked in a MenuItemClickEventArgs class instance. You can create an event handler for this event in your ASP.NET Web page by simply double-clicking the skmMenu in the Visual Studio .NET Designer. Alternatively, you can add OnMenuItemClick="EventHandlerName" to the skmMenu declaration like so:

<skm:menu id="Menu1" runat="server" Layout="Horizontal" 
   OnMenuItemClick="EventHandlerName"></skm:menu>

And then create a public event handler like so:

public void EventHandlerName(object sender, 
                             skmMenu.MenuItemClickEventArgs e)
{
   ...
}

The second parameter to the event handler, of type MenuItemClickEventArgs, contains a property CommandName, which has the value specified in the clicked menu item's CommandName property. To respond to various menu item clicks in this event handler, you would just need to use a series of conditional statements to determine what menu item had been clicked, and then take appropriate action.

In the download available with this article you will find an ASP.NET Web application called MenuTester, which has a Default.aspx page. This page includes an event handler for the Menu control Menu1 with the following code:

public void Menu1_MenuItemClick(object sender, 
  skmMenu.MenuItemClickEventArgs e)
{
   this.EventResults.Text = "The following MenuItem was clicked: " + 
     e.CommandName;
}

Here, EventResults is a Label Web control declared in the HTML portion of the Web page.

Figure 8 shows this page in action after the New FAQs menu item has been clicked. Realize that in the XML file the New FAQs menu item has its <commandName> element specified as NewFAQ.

Aa478963.aspnet-buildmenuservercontrol-08(en-us,MSDN.10).gif

Figure 8. The New FAQs menu item was clicked, causing a postback.

Current Known Limitations

skmMenu is a work in progress. While I have spent many hours testing skmMenu, there may be bugs or other problems yet unearthed. Of course, these problems may be fixed by the time you read this article, so be sure to visit the skmMenu GotDotNet workspace for the latest version of skmMenu

Conclusion

This article, the first in a two-part series, introduced skmMenu, an ASP.NET server control that displays an interactive menu. Specifically, we examined in-depth the mix of CSS and JavaScript needed to interactively display and hide submenus. In this article we also examined the classes that skmMenu utilizes and their relationships. We examined the XML structure for describing a skmMenu, and looked at adding skmMenu to an ASP.NET Web page. In the upcoming second part of this article series we'll turn our attention to the code in the Menu, MenuItem, and MenuItemCollection classes.

skmMenu is being developed as an open-source control, meaning anyone can help to make skmMenu a better product. To download the latest version of skmMenu, or to find out more about working on this open-source project, visit skmMENU Workspaces.

Happy Programming!

About the Author

Scott Mitchell, author of five ASP/ASP.NET books and founder of 4GuysFromRolla.com, has been working with Microsoft Web technologies for the past five years. An active member in the ASP and ASP.NET community, Scott is passionate about ASP and ASP.NET and enjoys helping others learn more about these exciting technologies. For more on the DataGrid, DataList, and Repeater controls check out Scott's book ASP.NET Data Web Controls Kick Start (ISBN: 0672325012).

© Microsoft Corporation. All rights reserved.