Building breadcrumbs the way you want it in SharePoint 2010

, , , , ,

Code of the Mavention Templated SiteMap Path
A while ago I wrote an article about creating breadcrumbs using the Mavention Simple SiteMapPath control. And while I thought that the control would be sufficient to cover the most common scenarios just recently it turned out that the control has one major flaw. Find out how to build breadcrumbs in SharePoint really the way you want it.

Create SharePoint breadcrumbs with Mavention Simple SiteMapPath

One of the challenges while building Internet-facing websites is rendering breadcrumbs the way they are defined in the user experience. Out-of-the-box you can use either the ListSiteMapPath control provided with SharePoint 2010 or the SiteMapPath control which is a part of the .NET framework.

The ListSiteMapPath control uses nested unordered lists to describe the current position within the site. While it’s a good idea from the semantic point of view the rendered markup is far from ideal and unfortunately fixed, which makes using the control with custom user experiences challenging.

A good alternative to the SharePoint’s ListSiteMapPath control is the standard ASP.NET SiteMapPath control. Although standard Master Pages provided with SharePoint 2010 contain some examples of how you can use the SiteMapPath control to render breadcrumbs the real benefit can be achieved by using the templates to render breadcrumb nodes. And while, in terms of rendering, the SiteMapPath is more flexible than the ListSiteMapPath, if you take a closer look at its output, you will find some additional spans rendered which, depending on your HTML, might be problematic.

Given the limitations of the ListSiteMapPath and SiteMapPath controls a while ago I decided to create the Mavention Simple SiteMapPath control. The main idea behind the control was to have it render as clean as possible HTML – something you would expect from a webdesigner coding the user experience. Although Mavention Simple SiteMapPath didn’t offer as rich configuration capabilities as the SiteMapPath control, it did exactly what it was supposed to do and it did it good.

The flaw of Mavention Simple SiteMapPath

Taking the most common approach to rendering breadcrumbs on the web, the Mavention Simple SiteMapPath control used an unordered list as the markup base. And this is exactly why it’s flawed: although the control is simple, it does one and one thing only and it does it certain way which cannot be configured without rebuilding the code.

Building breadcrumbs the way you want it

To avoid the limitations of the Mavention Simple SiteMapPath control and to be able to support any kind of breadcrumbs possible, I have created the Mavention Templated SiteMapPath.

Mavention Templated SiteMapPath

The Mavention Templated SiteMapPath consists of the following three files:

using System;
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;

namespace Mavention.SharePoint.Controls {
    [ParseChildren(true), PersistChildren(false)]
    public class TemplatedSiteMapPath : Control  {
        public bool SkipRootNode { get; set; }
        public string SiteMapProvider { get; set; }

        public ITemplate HeaderTemplate { get; set; }
        public ITemplate FooterTemplate { get; set; }
        public ITemplate RootNodeTemplate { get; set; }
        public ITemplate NodeTemplate { get; set; }
        public ITemplate CurrentNodeTemplate { get; set; }
        public ITemplate SeparatorTemplate { get; set; }

        private SiteMapProvider provider;

        protected override void OnInit(EventArgs e) {
            SetSiteMapProvider();
        }

        private void SetSiteMapProvider() {
            if (String.IsNullOrEmpty(SiteMapProvider)) {
                provider = SiteMap.Provider;
            }
            else {
                provider = SiteMap.Providers[SiteMapProvider];
            }

            if (provider == null) {
                throw new ArgumentOutOfRangeException(String.Format("SiteMap Provider '{0}' not found", SiteMapProvider ?? "default"));
            }
        }

        protected override void CreateChildControls() {
            List<SiteMapPathNodeItem> breadcrumbs = new List<SiteMapPathNodeItem>();
            breadcrumbs.Add(new SiteMapPathNodeItem(Int32.MaxValue, SiteMapPathNodeItemType.Footer));

            SiteMapNode currentNode = provider.CurrentNode;
            SiteMapNode node = provider.CurrentNode;
            SiteMapNode rootNode = currentNode.RootNode;
            int i = 0;
            while (node != null) {
                if (node.Key != rootNode.Key) {
                    if (breadcrumbs.Count > 1) {
                        breadcrumbs.Add(new SiteMapPathNodeItem(i--, SiteMapPathNodeItemType.Separator));
                    }

                    breadcrumbs.Add(new SiteMapPathNodeItem(i--, node.Key == currentNode.Key ? SiteMapPathNodeItemType.CurrentNode : SiteMapPathNodeItemType.Node) { SiteMapNode = node });
                }
                else {
                    if (!SkipRootNode) {
                        if (breadcrumbs.Count > 1) {
                            breadcrumbs.Add(new SiteMapPathNodeItem(i--, SiteMapPathNodeItemType.Separator));
                        }

                        breadcrumbs.Add(new SiteMapPathNodeItem(i, SiteMapPathNodeItemType.RootNode) { SiteMapNode = node });
                    }
                }

                node = node.ParentNode;
            }
            
            breadcrumbs.Add(new SiteMapPathNodeItem(Int32.MinValue, SiteMapPathNodeItemType.Header));
            breadcrumbs.Reverse();

            if (breadcrumbs.Count > 2) {
                breadcrumbs[1].ItemType = SiteMapPathNodeItemType.RootNode;
            }

            foreach (SiteMapPathNodeItem breadcrumb in breadcrumbs) {
                InitializeItem(breadcrumb);
            }
        }

        public virtual void InitializeItem(SiteMapPathNodeItem item) {
            ITemplate nodeTemplate = null;
            SiteMapPathNodeItemType itemType = item.ItemType;

            switch (itemType) {
                case SiteMapPathNodeItemType.Header:
                    nodeTemplate = HeaderTemplate;
                    break;
                case SiteMapPathNodeItemType.Footer:
                    nodeTemplate = FooterTemplate;
                    break;
                case SiteMapPathNodeItemType.RootNode:
                    nodeTemplate = RootNodeTemplate ?? NodeTemplate;
                    break;
                case SiteMapPathNodeItemType.Node:
                    nodeTemplate = NodeTemplate;
                    break;
                case SiteMapPathNodeItemType.CurrentNode:
                    nodeTemplate = CurrentNodeTemplate ?? NodeTemplate;
                    break;
                case SiteMapPathNodeItemType.Separator:
                    nodeTemplate = SeparatorTemplate;
                    break;
                default:
                    break;
            }

            if (nodeTemplate != null) {
                nodeTemplate.InstantiateIn(item);
                Controls.Add(item);
                item.DataBind();
            }
        }
    }
}
namespace Mavention.SharePoint.Controls {
    public enum SiteMapPathNodeItemType {
        Header,
        Footer,
        RootNode,
        Node,
        CurrentNode,
        Separator
    }
}
using System.Web;
using System.Web.UI;

namespace Mavention.SharePoint.Controls {
    public class SiteMapPathNodeItem : Control, IDataItemContainer, INamingContainer {
        public SiteMapPathNodeItemType ItemType { get; set; }
        public SiteMapNode SiteMapNode { get; set; }
        public int ItemIndex { get; set; }

        public SiteMapPathNodeItem(int itemIndex, SiteMapPathNodeItemType itemType) {
            ItemIndex = itemIndex;
            ItemType = itemType;
        }

        public object DataItem {
            get { return SiteMapNode; }
        }

        public int DataItemIndex {
            get { return ItemIndex; }
        }

        public int DisplayIndex {
            get { return ItemIndex; }
        }
    }
}

The first file – TemplatedSiteMapPath.cs, is the Mavention Templated SiteMapPath control itself. If you compare its code to the Mavention Simple SiteMapPath you can see that the code is much simpler and the biggest part of it supports rendering using Templates.

The second file – SiteMapPathNodeItemType.cs contains the definition of the enumeration that describes the type of the breadcrumb node. The node type is being used to apply the right rendering template while rendering breadcrumbs.

Finally there is the SiteMapPathNodeItem.cs file which contains the definition of a control in which a breadcrumb node is wrapped. The important part here is that this class inherits from the Control class, and not WebControl like the standard ASP.NET SiteMapNode class, so no additional spans or other markup for that matter is rendered.

Using the Mavention Templated SiteMapPath

The following code snippet illustrates how you can use the Mavention Templated SiteMapPath control:

<Mavention:TemplatedSiteMapPath runat="server">
    <HeaderTemplate><div id="breadcrumbs"></HeaderTemplate>
    <RootNodeTemplate><asp:Hyperlink Text='home' NavigateUrl='<%# Eval("Url") %>' runat="server"/></RootNodeTemplate>
    <NodeTemplate><asp:Hyperlink Text='<%# Eval("Title") %>' NavigateUrl='<%# Eval("Url") %>' runat="server"/></NodeTemplate>
    <CurrentNodeTemplate><asp:Literal Text='<%# Eval("Title") %>' runat="server"/></CurrentNodeTemplate>
    <SeparatorTemplate> / </SeparatorTemplate>
    <FooterTemplate></div></FooterTemplate>
</Mavention:TemplatedSiteMapPath>

Using this snippet will render breadcrumbs as hyperlinks, separated with a / (slash) where the current node is rendered as plain text. As you can imagine with some slight modifications you could change the breadcrumbs to be rendered as an unordered list should you need it, without touching the code!

Dealing with Variations

One of the common challenges when building breadcrumbs for a public-facing websites is dealing with Variations. Whenever a site is using the Variations capability to deliver multilingual content the desired way of rendering breadcrumbs is to have the first node point to the home page of the current Variation. Unfortunately the standard Navigation Providers available with SharePoint 2010 render the root site as the first node, so instead of:

home > subsite > page

you get:

root > home > subsite > page

This happens no matter how your navigation settings are configured and even if you choose to break navigation inheritance for the Variation root sites.

The Mavention Templated SiteMapPath control allows you to easily work around this issue. By setting the SkipRootNode property to true, the root node is omitted and the second node in hierarchy is promoted as the root node so that it can be properly rendered as a root node (TemplatedSiteMapPath.cs lines 55-61 and 70-72). As a result breadcrumbs will be rendered as you would expect them to.

It’s all about permissions

Using the code samples above you can implement the Mavention Templated SiteMapPath in your solution. One important thing that you should keep in mind is, that the code should be included in an assembly deployed to GAC. Without it, in some scenarios depending on how your website is built, you might experience exceptions being thrown while instantiating the ExtendedSearchXmlContentMapProvider navigation provider which is one of the standard navigation providers delivered with SharePoint 2010.

Summary

Implementing custom user experiences in public-facing websites built on the SharePoint 2010 platform knows many challenges. One of those challenges is building breadcrumbs. Using the Mavention Templated SiteMapPath control you can build virtually any kind of breadcrumbs without the need to modify the control’s code.

18 Responses to “Building breadcrumbs the way you want it in SharePoint 2010”

  1. Matt Menezes Says:

    SimpleSiteMapPath meets TemplatedMenu? :) Nice addition, love both controls and use regularly.

    Got around the 'variation' issue before by including some CSS to hide the :first-child and some jQuery to remove the padding for the 2nd, but this change makes it cleaner.

    Love your work!

  2. Waldek Mastykarz Says:

    @Matt: Thank you! Great to hear I could help :)

  3. kevin dube Says:

    Will this solve the problem of showing the link to the portal site home in the breadcrumb trail? I have tried using the SiteMapPath OOTB provider but it will not show the portal site connection like the ListSiteMapPath control will

  4. Waldek Mastykarz Says:

    @Kevin Dube: Since breadcrumbs are based on Navigation Providers I would expect them not to show the link to the Portal Site Connection, although it might depend on the Navigation Provider that you will link it to. The control described in this article does nothing more than to display navigation nodes provided by the Navigation Provider.

  5. Chris Says:

    Excuse my ignorance, but unlike your other tools, this doesn't already appear to be a packaged solution. What is required to put this into play?

    Thanks, Waldek!

  6. Waldek Mastykarz Says:

    No problem at all. You would need to pick up this code and build your own solution of it.

  7. Ronakq Says:

    Thanks Waldek for useful code and its working fine but in breadcrumb it display page name instead of Title for example in my case i have webpart page in sitePages library with Title is Study Participants and name of file is participants.aspx so when i go to this page it display Home–>Subsite–>participants instead of Home->subsite->Study Participants.Please advise
    Thanks
    ROnak

  8. Waldek Mastykarz Says:

    This control has been designed for use on Publishing Pages. If I understand it correctly you're using it with Wiki Pages which might be the reason for what you are seeing.

  9. Ronak Says:

    Thanks Waldek,I am using with Document center site template and i am using SPContentMapProvider siteMap provider.is there any workaround for this ?

    Ronak

  10. Waldek Mastykarz Says:

    Not of the top of my head, sorry.

  11. Mathieu Blondin Says:

    Hi Waldek. Thank you for publishing this code. This reduced my workload alot. One thing, I'm using Variations and on my home pages (per variations) My output is the following:
    Home > Variation Site Name instead of :
    Home

    Any thoughts and help will be greatly appreciated.

  12. Waldek Mastykarz Says:

    Have you read the 'Dealing with Variations' section in the article?

  13. Gavin Barron Says:

    Thanks again Waldek!
    This code works perfectly in 2013 too ;)

  14. Phyrom Says:

    Hi Waldek,

    Finally I get the information I need. While reading this and I strongly believe that this particular solution will solve my problem.

    1) However, I need a little bit more guidance from you how to assemble your tree files in Visual Studio. What type of SharePoint project should I create? Would that be possible that you can send the VS source?

    2) Is this solution depending on this http://blog.mastykarz.nl/create-sharepoint-breadcrumbs-mavention-simple-sitemappath?

    Regards,
    Phyrom

  15. Waldek Mastykarz Says:

    1) Unfortunately I don't have a complete project so you would have to assemble all of the code yourself
    2) All of the code is included in the article and there are no dependencies on code from other articles

  16. Olivier Says:

    Hello Waldek,
    Your control is exactly what i need but i'm really beginner in developpment, where could i find instruction to use your source code and create a wp package ?
    Thanks in advance
    Olivier

  17. Waldek Mastykarz Says:

    I don't know of any step-by-step guide of the top of my head. I guess you would have to search for it on the Internet.

  18. Phyrom Says:

    Hi Waldek,

    Thanks for your comments. In facts, I have developed quite a few wsp solutions and most of them are visual web part and timer job projects.

    However, for your three code files I really couldn't figure out which VS project template it should fall in. Can you just give me a shot for what VS SharePoint project template you are choosing? And If I'm not wrong this is a C# project, right?

    Really appreciate your contribution.

    Regards,
    Phyrom

Leave a Reply

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS
Copyright © 2007 - 2013 Waldek Mastykarz

Creative Commons License