Patterns for navigation controls, part 2

Last week I started looking at some simple ideas for the top level navigation for Sitecore websites. This week, I’ll continue that theme with some simple examples of the more localised navigation that you might use on content pages.

So where am I on this site?

A common, but simple type of localised navigation is to have a “breadcrumb trail” showing the ancestor pages of the current page back up to the root of your site. This list of links allows users to easily navigate back up your content tree.

It is fairly simple to achieve. Simple mark-up might look like:

<div>
    <asp:Repeater runat="server" ID="breadcrumbRepeater">
        <ItemTemplate>
            <span runat="server" id="divider"> &gt; </span>
            <asp:HyperLink runat="server" ID="breadcrumbLink" />
        </ItemTemplate>
    </asp:Repeater>
</div>

and the code-behind would be:

protected void Page_Load(object sender, EventArgs e)
{
    var rootItem = Sitecore.Context.Database.GetItem(Sitecore.Context.Site.ContentStartPath);

    var ancestors = Sitecore.Context.Item
        .Axes.GetAncestors()
        .Where(i => !i.Axes.IsAncestorOf(rootItem));

    breadcrumbRepeater.DataSource = ancestors;
    breadcrumbRepeater.ItemDataBound += breadcrumbRepeater_ItemDataBound;
    breadcrumbRepeater.DataBind();
}

private void breadcrumbRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    if(e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item)
    {
        var item = e.Item.DataItem as Sitecore.Data.Items.Item;

        var breadcrumbLink = e.Item.FindControl("breadcrumbLink") as HyperLink;
        var divider = e.Item.FindControl("divider") as HtmlGenericControl;

        if(e.Item.ItemIndex == 0)
        {
            divider.Visible = false;
        }

        breadcrumbLink.Text = item.DisplayName;
        breadcrumbLink.NavigateUrl = Sitecore.Links.LinkManager.GetItemUrl(item);
    }
}

As with last week’s examples, we use the Sitecore.Context.Site.ContentStartPath property to find the item in your content tree which represents the root of the current site. The ancestor items for the current page can then be computed by asking Sitecore for the page’s set of ancestors, and filtering out any of those pages which is not an ancestor of the root item. That leaves you with a collection of items to display.

The display can be achieved with a simple data binding to a repeater. The one important thing to remember here is that you usually have some sort of “divider” character between each item in your breadcrumb list. It’s possible to do this via CSS or via code, but since I’m generally skipping over the CSS aspects of the UI in these examples, I’ve done it via code above. The mark-up includes a <span> element to contain the divider character. This is marked as runat="server" so that it can be enabled and disabled in the code-behind. When the data binding is processed for each item, the code checks if the index for the item being processed is zero (ie “is this the first item in your data list”). If so, it hides the divider.

Local navigation for sibling pages

In parallel with the breadcrumb style navigation it’s also common to show a list of the sibling pages for your current location – sometimes referred to as “secondary navigation”. This allows users to move about in the current “folder” of your site. When implementing this approach to navigation it is also common to mark the current page in some way (usually via the CSS style) so that it is obvious to the user which page they are currently on.

A basic secondary navigation could start from the following mark-up:

<asp:Repeater runat="server" ID="navRepeater">
    <HeaderTemplate><ul></HeaderTemplate>
    <ItemTemplate>
        <li>
            <asp:HyperLink runat="server" id="navLink"/>
        </li>
    </ItemTemplate>
    <FooterTemplate></ul></FooterTemplate>
</asp:Repeater>

and code behind:

protected void Page_Load(object sender, EventArgs e)
{
    var parent = Sitecore.Context.Item.Parent;

    navRepeater.DataSource = parent.Children;
    navRepeater.ItemDataBound += navRepeater_ItemDataBound;
    navRepeater.DataBind();
}

private void navRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item)
    {
        var item = e.Item.DataItem as Sitecore.Data.Items.Item;
        var navLink = e.Item.FindControl("navLink") as HyperLink;

        if(item.ID == Sitecore.Context.Item.ID)
        {
            if(navLink.CssClass.Length > 0)
            {
                navLink.CssClass += " ";
            }
            navLink.CssClass += "currentNavItem";
        }

        navLink.Text = item.DisplayName;
        navLink.NavigateUrl = Sitecore.Links.LinkManager.GetItemUrl(item);
    }
}

The code finds the parent item of the current page, and uses its children as the data source for the repeater. For each item that it renders in the repeater, it checks if its ID matches the ID of the current page. If it does, then it adds an extra CSS class to allow the item to be displayed with a “this is the current page” style. Note that the code won’t work if you try to compare the these two Item instances directly – as that would be a reference comparison. Comparing the IDs is a value comparison, and hence will be a valid test if the two classes represent the same content item.

A bit more nav related stuff next week, I think…

Advertisements

One thought on “Patterns for navigation controls, part 2

  1. Pingback: Patterns for navigation controls, part 4 | Jeremy Davis

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s