To read the article online, visit http://www.4GuysFromRolla.com/articles/060309-1.aspx

Implementing Incremental Navigation with ASP.NET

By Andrew Wrigley


Introduction


Traditionally, website navigation has been focused on minimizing the number of clicks required to open a given page. However, this goal has nothing to do with the real purpose of navigation, which is to make finding information easy, consistent, and transparent to the user. Also, as websites get bigger, traditional navigation controls such as drop-down menus or tree views become impractical. Faster Internet connections and larger screen sizes now allow developers to experiment with new styles of navigation.

This article shows how to implement incremental navigation, which is a style of navigation where users find information by clicking through a series of lightweight pages, with each click resulting in a small, but highly visible change to the navigation user interface. It differs from traditional drop-down menu navigation in that incremental navigation limits the amount of new choices available to just the next level in the sitemap hierarchy.

I've created a customizable framework for implementing this sort of navigation scheme named Theseus, which you can download from the end of this article. Underneath the covers, Theseus uses ASP.NET's SiteMap class and the configured sitemap provider to implement the incremental navigation. This article starts with an overview of incremental navigation and then goes on to examine how to use Theseus to implement such a navigation scheme in your website. Read on to learn more!

The Benefits of Incremental Navigation


Modern websites increasingly use an incremental navigation paradigm: users find information by clicking through a series of lightweight pages, with each click resulting in minimal but highly visible changes to the navigation controls. Incremental navigation differs from traditional drop-down menu navigation in that it limits the amount of new choices available to just the next level in the hierarchy of information. It also highlights the "current branch" (or "breadcrumb") in the hierarchy, where the current branch is the path that the user has followed to reach the current node. Highlighting the current branch shows users how they got to where they are and how they can retrace their steps. This is how our brains work, so users find this kind of navigation intuitive. It also minimizes the markup that gets rendered with each request.

On the contrary, drop-down menus force users to think in terms of hierarchies of which they may have no previous knowledge. They also offer all available choices at all times thereby maximizing the rendered markup, but offer little or no visual indication of the current branch. As a result, a user's sense of frustration and confusion increases exponentially with the number of levels in the hierarchy of information. Now why would you do that to your users?

A Working Example of Incremental Navigation


To see incremental navigation in action, take a look at the The Encephalitis Society website. This site consists of hundreds of pages nested in a hierarchy that is up to five levels deep. Visitors to this site vary from people affected by Encephalitis - which can cause severe brain injury - to medical professionals. As a result, a consistent navigation is central to the usability of this website.

The navigation on the Encephalitis Society website has three main elements:

  • The site tabs,
  • The section menu, and
  • The side menu
These three sections are annotated in the screen shot below.

The Structure of the Navigation System


The three sections in an incremental navigation user interface.

The site tabs are the series of tabs along the top of the page - Home, Information, The Society, and so forth. Selecting a site tab takes the user to the landing page for that tab. It also highlights the site tab and displays the subelements in the section menu. The screen shot below shows the navigation UI after the user has selected the Information tab. The background and foreground colors of the site tabs makes it clear that the Information tab has been selected.

A Tab Selected From the Site Tabs


The Information site tab has been selected.

The user can drill down further into the site by clicking on one of the links in the section menu - The Illness, Recovery and Rehabilitation, and so on. Clicking on one of these section menu options takes the user to a new page and highlights the selected section menu option by bolding its text (see the screen shot below). It also loads any subelements of the section menu into a side menu.

An Item Selected from the Section Menu


Selecting 'The Illness' loads options in the side menu.

The side menu displays all remaining sitemap nodes for the selected section menu option. In the following screen shot the user has expanded the What is Encephalitis option from the side menu and clicked on the Causes of Encephalitis entry. This displays a page that details the causes of the encephalitis. Note that the navigational user interface clearly indicates the current branch to the user. Through the use of colors and bolded text it is apparent that the user is viewing the Causes of Encephalitis page, which is a subelement of What is Encephalitis, which is a subelement of The Illness, which is under the Information section.

A Page Selected from the Side Menu


The user has selected an option from the side menu.

An incremental navigation system obviates the need for a breadcrumb control (the ASP.NET SiteMapPath control) because it is immediately clear how the user arrived at any page: the current branch is highlighted in an obvious way. The user is not forced to think hierarchically and so navigating the site is intuitive and easy.

Before continuing on, take a moment to visit The Encephalitis Society website to get a good feel of how the navigation system works. While it may not be apparent, the navigation system on The Encephalitis Society website uses ASP.NET's sitemap system and navigational Web controls. The navigational sections of the site are defined in the Web.sitemap file. The site tabs and section menu are implemented using the ASP.NET Menu control; the side menu is implemented via the ASP.NET TreeView control.

Building an Incremental Navigation Framework


When designing the incremental navigation for The Encephalitis Society website I wanted to do so using a componentized, reusable model so that I (or others) could plug such a navigation system into other ASP.NET websites. I ended up creating a framework (written in C#) that I named Theseus after the mythological slayer of the Minotaur in the Labyrinth. The Theseus framework and a demo web application are available for download at the end of this article.

Theseus is packaged into five files. The names are pretty self explanatory:

  • Two User Controls (.ascx files) provide the UI and plug into your master page(s):
    • /Controls/Menus/TopMenu.ascx
    • /Controls/Menus/SideMenu.ascx
  • Two classes contain the code that powers Theseus:
    • /App_Code/Library/Theseus/TheseusClass.cs
    • /App_Code/Library/Extensions/SiteMapExtender.cs
  • Finally, there is a class that provides the structure to ensure that the Theseus boilerplate code is available to all pages in your website:
    • /App_Code/Library/BasePages/TheseusPage.cs
The data used by the framework is drawn from ASP.NET's sitemap system. The demo website is setup to use the default sitemap provider and therefore the sitemap is defined in the Web.sitemap file; however, you could plug in an alternate sitemap provider and use Theseus the same. What's more, the demo application uses the CSS Friendly Menu and TreeView control adapters and uses CSS for styling. The CSS Friendly adapters alter the markup emitted by ASP.NET's TreeView and Menu controls to use <ul>'s rather than <table>s, and focuses on defining style information through CSS classes rather than through the HTML elements' style attributes. For more on the CSS Friendly Control Adapters, see http://www.asp.net/CssAdapters.

Nuts and Bolts


The site tabs and the section menu are both implemented using ASP.NET Menu controls and are packaged into the TopMenus.ascx file. The side menu is implemented using an ASP.NET TreeView control and is packaged into the SideMenu.ascx file. The Menu controls and the TreeView control are declaratively bound to their own SiteMapDataSource controls.

Take a look at the pertinent markup for SideMenu.ascx:

<asp:Panel ID="pnlSideMenu" runat="server" CssClass="SideMenuWrapper">
   <h3>
      <asp:Label ID="lblSideMenuHeader" runat="server" Text="..." />
   </h3>            

   <asp:TreeView ID="tvMenu3" runat="server" DataSourceID="dsMenu3"
      CssSelectorClass="SideMenu" EnableViewState="false"
      OnDataBound="SideMenu_DataBound"
      CollapseImageUrl="~/App_Themes/WS/Images/collapse.gif"
      ExpandImageUrl="~/App_Themes/WS/Images/expand.gif"
      NoExpandImageUrl="~/App_Themes/WS/Images/blank.gif">
         <RootNodeStyle ImageUrl="~/App_Themes/WS/Images/blank.gif" />
   </asp:TreeView>      
</asp:Panel>

<asp:SiteMapDataSource runat="server" ID="dsMenu3" ShowStartingNode="false" SiteMapProvider="secureSiteMap" />

The markup for TopMenus.ascx is even simpler. You can view it by downloading the demo available at the end of this article.

The SideMenu.ascx code behind file, shown below, is fairly straightforward, too. Note that the code to populate the side menu is contained within the Theseus framework. The SideMenu.ascx User Control merely hands over the TreeView control to the Theseus framework via a call to the LoadSideMenu method. The LoadSideMenu method then adds the nodes to show in the side menu. The SetNodeSideMenu method, called from the DataBound event handler, expands the necessary nodes in the TreeView. We'll examine the inner workings of both these method later on in this article.

public partial class SideMenu : System.Web.UI.UserControl
{
   private TheseusClass Theseus;

   protected void Page_Load(object sender, EventArgs e)
   {
      Theseus = ((TheseusPage)Page).Theseus;
      Theseus.LoadSideMenu(dsMenu3, tvMenu3);

      bool b = tvMenu3.Visible;
      if (b)
         lblSideMenuHeader.Text = Theseus.SectionMenuNode.Title;
      pnlSideMenu.Visible = b;
   }

   protected void SideMenu_DataBound(object sender, EventArgs e)
   {
      Theseus.SetNodeSideMenu((TreeView)sender);
   }
}

In addition to populating and expanding the side menu as needed, the Page_Load event handler also displays the title of the section menu node in the lblSideMenuHeader Label control. This Label control is not part of the Theseus framework, but is rather an example of how to add additional, customized content to the TopMenus.ascx and SideMenu.ascx User Controls to conform to any specific requirements for the site.

A Custom Base Class for All Pages: TheseusPage


The Theseus framework is powered by the class TheseusClass. This class contains methods like LoadSectionMenu and LoadSideMenu, and is used by the TopMenus.ascx and SideMenu.ascx User Controls to populate the appropriate content into their navigation user interface elements. Since this class is used by every page in the website, I created a custom base page class named TheseusPage that has a property named Theseus that returns an instance of the TheseusClass class. Consequently, all ASP.NET web pages that use the Theseus framework need to inherit from this custom base class.

If you create your ASP.NET pages where the markup and code is in the same file then you can make TheseusPage the base class to all your pages by using the pageBaseType attribute of the <pages> section in Web.config:

<pages pageBaseType="Wingspan.Web.Core.TheseusPage">

If you have your ASP.NET pages' markup and code sections separated into two files then you will need to go through each code behind file and changing the base class that your pages inherit from System.Web.UI.Page to Wingspan.Web.Core.TheseusPage (or whatever you call your custom base class):

public partial class MyPage : Wingspan.Web.Core.TheseusPage
{
   ...
}

For more information on using a custom base page class and its benefits, refer to Using a Custom Base Class for your ASP.NET Pages' Code-Behind Classes.

The Engine That Powers Theseus: TheseusClass


The Theseus framework is powered by TheseusClass, which has two core tasks:
  • Determining what items in the sitemap need to be displayed in the top menus and side menu given the page being visited, and
  • Determining the current branch so that we know what items in the navigation user interface need to be highlighted.
These tasks can be accomplished with the help of the ASP.NET SiteMap class. The SiteMap class is part of the ASP.NET framework and provides an "in memory representation of the navigation structure for a site, which is provided by one or more site map providers."

The most important property of the SiteMap class is CurrentNode, which represents the SiteMapNode object in the sitemap structure that corresponds to the page being currently requested.

SiteMapNode cn = SiteMap.CurrentNode;

If the page being requested is not represented by a node in Web.sitemap then CurrentNode returns null. In this case, Theseus goes into its "inactive" state and shows only the Site Menu with no tabs highlighted.

The other important SiteMap property is RootNode, which returns the SiteMapNode that is the root of the sitemap hierarchy. The CurrentNode and RootNode properties are used to determine the current branch, as the current branch is, by definition, the path between the current node and the root.

The SiteMapExtensions Class


Theseus needs to calculate the current branch as a List of SiteMapNode objects. It would have been nice if Microsoft had implemented a property such as SiteMap.CurrentBranch, but they didn't. Fortunately, this List can be computed with only a few lines of code. In addition to computing the current branch List, there are a number of other sitemap-related tasks that Theseus needs to perform that are not built into the SiteMap class. These methods and properties are grouped into a class named SiteMapExtensions; this class is used from the TheseusClass class, which includes a member variable named SiteMapExtender of type SiteMapExtensions.

namespace Wingspan.Web.Core
{
   public class TheseusClass
   {
      SiteMapExtensions SiteMapExtender;

      public TheseusClass()
      {
         SiteMapExtender = new SiteMapExtensions();
      }
      
      ...
   }
}

The code in SiteMapExtensions class is pretty straight forward and well documented. There is the CurrentBranch property, which returns the List of SiteMapNodes that makes up the current branch; a CurrentNodeLevel property, which returns the level of the current node in the sitemap hierarchy; and a handful of methods.

Examining TheseusClass In Detail


To get a better understanding of how Theseus works, I'd like to explore the code in TheseusClass that relates to the TreeView control (the Side Menu) in detail. Recall that the SideMenu.ascx User Control is responsible for displaying the side menu using a TreeView control. Its Page_Load event handler populates the TreeView with the appropriate nodes based on the requested page by calling the TheseusClass's LoadSideMenu method, passing in both the SiteMapDataSource (dsMenu3) and the TreeView (tvMenu3) controls defined in the User Control's markup portion:

private TheseusClass Theseus;

protected void Page_Load(object sender, EventArgs e)
{
   Theseus = ((TheseusPage)Page).Theseus;
   Theseus.LoadSideMenu(dsMenu3, tvMenu3);
   // do dah
}

The LoadSideMenu method starts by getting a reference to the node in the Section Menu that has been selected and storing it in a variable named node2. (Recall that the Side Menu displays the descendant nodes of the SiteMapNode selected from the Section Menu.) This selected Section Menu SiteMapNode is retrieved by calling the SiteMapExtension class's CurrentBranchNode method, passing in the SectionMenuLevel variable. This SectionMenuLevel variable is an integer value that indicates what level of the sitemap hierarchy was rendered in the Section Menu. In our sample, this value is set to 2, but can be customized via the TheseusClass's constructor.

public void LoadSideMenu(SiteMapDataSource dsMenu3, TreeView tv)
{
   // I get a reference to the Section Menu level node on the Current Branch
   // as this provides all the parameters I need to setup the Side Menu
   SiteMapNode node2 = SiteMapExtender.CurrentBranchNode(SectionMenuLevel);

   ...
}

Now that we have the selected Section Menu node we check to see if it is null. It would be null if the selected Site Tab is a leaf node. For example, if the page being requested was the Home Page then node2 would be null. If node2 is not null, then we need to ensure that the Side Menu TreeView control (the tv parameter) is made visible, that it is expanded to the correct depth, and that it shows the descendant nodes of node2.

public void LoadSideMenu(SiteMapDataSource dsMenu3, TreeView tv)
{
   ...
   bool bVisible = false;

   // Always check the value returned by CurrentBranchNode for null!
   if (node2 != null)
   {
      if (node2.HasChildNodes)
      {
         dsMenu3.StartingNodeUrl = node2.Url;
         tv.ExpandDepth = SiteMapExtender.CurrentNodeLevel;
         bVisible = true;
      }
   }
   
   // unless the Side Menu has nodes, hide it!
   tv.Visible = bVisible;
}

Note that we set the dsMenu3 SiteMapDataSource control's StartingNodeUrl property to the URL of the selected Section Menu node (node2.Url). Because the SiteMapDataSource has had its ShowStartingNode property set to false in the User Control markup only the children nodes of node2 will be returned.

The TreeView's ExpandDepth property indicates how many levels of TreeView nodes are expanded, by default. This value is determined by the level of the current node in the sitemap hierarchy, which is available via the CurrentNodeLevel property in the SiteMapExtensions class. That's all we need to do to load the Side Menu. However, there still remains one important part in rendering the Side Menu: we need to highlight the appropriate nodes in the TreeView based on the current branch.

Highlighting the appropriate nodes in the TreeView can only happen once the data has been bound to the control. The TreeView's DataBound event is raised after the data has been bound to the TreeView. Therefore, it makes sense to create an event handler for the DataBound event and to put the logic there for selecting the appropriate TreeView nodes. This event handler is defined in the SideMenu.ascx User Control and simply calls the TheseusClass's SetNodeSideMenu method, passing in the TreeView control.

protected void SideMenu_DataBound(object sender, EventArgs e)
{
   Theseus.SetNodeSideMenu((TreeView)sender);
}

The SetNodeSideMenu method starts by collapsing all of the TreeView nodes. This guarantees that only the nodes on the current branch will be expanded, which is what we want. Next, the method checks to see if there is a selected node in the TreeView; if so, that node is expanded. If there is no selected node then the TreeNode that represents the SiteMap.CurrentNode is selected. Finally, the selected node's parent is expanded to guarantee that the selected node will be visible on the rendered page.

The SetNodeSideMenu method follows:

public void SetNodeSideMenu(TreeView tv)
{
   tv.CollapseAll();

   // If a node is already selected, expand it!
   if (tv.SelectedNode != null)
      tv.SelectedNode.Expand();
   else
      SetSelectedTreeNode(tv);

   // Now that I have my selected mode,
   // I expand the nodes along the Current Branch.
   // TreeView's Expanded property is set to true!
   ExpandSideMenu(tv);
}

The most common scenario is that there was no selected node. In this case the SetSelectedTreeNode method gets called. This SetSelectedTreeNode method is in charge of selecting the TreeNode that represents the SiteMap.CurrentNode SiteMapNode object. The only complication with this code is to map SiteMap.CurrentNode to one of the TreeNodes. To do that, I use the TreeView's FindNode method, and the whole problem is reduced to building the ValuePath to the current node:

private void SetSelectedTreeNode(TreeView tv)
{
   if (SiteMap.CurrentNode != null)
   {
      // As there is more than one potential level to search for the node I want,
      // I use the property CurrentNodeValuePath to calculate the ValuePath
      TreeNode curr = tv.FindNode(SiteMapExtender.CurrentNodeValuePath(SideMenuLevel));

      if (curr != null)
         curr.Select();
   }
}

The eagle eyed reader may have noticed that this code only selects the current node. How do I make the other nodes along the current branch aware that they should display in bold? The answer is that I don't need to do anything. Using the CSS Friendly Adapters causes the nodes along the current branch to all have a CSS class of AspNet-TreeView-ChildSelected. Therefore, I can set these display-related settings in the CSS definition without having to write any C# code. For more on this look at the CSS in the ~/App_Themes/ws/SideMenu.css file.

Building the CurrentNode's ValuePath is really simple because we can build it from the CurrentBranch property of the SiteMapExtender object. I have packaged the code that does this into the SiteMapExtensions class's CurrentNodeValuePath method:

public string CurrentNodeValuePath(int startFromLevel)
{
   string s = string.Empty;
   List<SiteMapNode> ln = CurrentBranch;

   if (ln.Count > 0)
   {
      for (int i = startFromLevel; i == CurrentNodeLevel; i++)
         s = s + ln[i].Title + "/";
   }

   return s;
}

The code for CurrentNodeValuePath is pretty simple, you just need to be careful with the startFromLevel parameter to make sure you calculate the ValuePath from the right place.

All we need now is the code for the ExpandSideMenu method. This method starts from the CurrentNode and works back up the tree using the TreeNode object's Parent property, expanding the parent at each step.

private void ExpandSideMenu(TreeView tv)
{
   TreeNode tn = tv.SelectedNode;
   if (tn != null)
   {
      while(tn.Parent != null)
      {
         tn.Parent.Expanded = true;
         tn = tn.Parent;
      }
   }
}

Conclusion


Many users (and clients) hate drop down menus. Users hate thinking hierarchically, which is what drop down menus force them to do.

From a technological perspective, drop down menus are a hangover from the days of small screens and dial up Internet connections and if you, as the developer, are not yet convinced of the benefits of incremental navigation, just think of a website where the information is nested in a hierarchy of pages that is five levels deep. Drop-down menus, anyone? No, I didn't think so.

The Theseus framework is just as easy to use in your websites as a Menu control. Its User Interface is highly customizable and it renders standards compliant markup that is much more optimized than traditional drop down menus. Your users no longer need a ball of string to kill the Minotaur.

Happy Programming!

  • By Andrew Wrigley


    Attachments


  • Download the Theseus framework and demo application
  • Further Reading


  • Incremental Navigation: Providing Simple and Generic Access to Heterogeneous Structures
  • SiteMap class technical docs
  • Using a Custom Base Class for your ASP.NET Pages' Code-Behind Classes
  • Four Helpful Features to Add to Your Base Page Class
  • ASP.NET 2.0 CSS Friendly Control Adapters 1.0
  • A Word About The Encephalitis Society


    I decided to write this article in March 2009 after watching Red Nose Day coverage on the BBC. Red Nose Day is a UK thing, where you are encouraged to do something fun for charity.

    My first ever client was Elaine Dowell, chairperson of the Encephalitis Society. Encephalitis is an inflammation of the brain, caused by an infection. It can cause severe acquired brain injury, even death. Many people recover pretty much completely, but it can also lead to terrible long term consequences and suffering for the people affected, as well as for their family and caretakers. When Elaine's son got Encephalitis, a rare but all too often devastating syndrome, she decided to do something. Ten years later, she is still doing just that.

    The story that best sums Elaine up is this one: I was in the Society offices one day discussing changes to the website. A group of new regional reps turned up for a meeting with the regional coordinator whilst Elaine and I went to war over a pixel here, a hue there. As the reps were leaving one of them, a middle aged lady, hung back. When the others had left, she asked: "Is Elaine Dowell here?" The receptionist brought her over to meet Elaine. "You are my family's hero," said the new rep. "We would never have survived without you."

    Hopefully, you won't ever need a hero to save your family. But if this article helped you in your daily work then go do the right thing - visit The Encephalitis Society website and click the Make a Donation button. You will feel really good and your nose will turn red. The more you donate, the redder it will go. If you don't believe me, just click the Make a Donation button and see for yourself. Make sure you have a mirror handy.

    Article Information
    Article Title: ASP.NET.Implementing Incremental Navigation with ASP.NET
    Article Author: Andrew Wrigley
    Published Date: June 3, 2009
    Article URL: http://www.4GuysFromRolla.com/articles/060309-1.aspx


    Copyright 2017 QuinStreet Inc. All Rights Reserved.
    Legal Notices, Licensing, Permissions, Privacy Policy.
    Advertise | Newsletters | E-mail Offers