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 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 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
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
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:
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;
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:
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
{
...
}
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:
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.
// 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.
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.CurrentNodeSiteMapNode 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.
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.
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.