Creating a SharePoint breadcrumb control with drop down menus

While there have plenty of things that people don't like about Windows Vista, one of the features I do like and I think is underappreciated is the new breadcrumb:

Unfortunately in SharePoint, the breadcrumbs don't work like this. There are no submenus. Lucky for us, it's pretty easy to create a breadcrumb that functions this way. Here's how:

Creating the web control

The first part we'll need is a web control that will render the markup. The web control I created is simple and straightforward. Only two public properties are available: SiteMapProvider (string) and NodeSeparator (string). The SiteMapProvider property is used to specify the named site map provider from the web.config file to use to build the breadcrumb. The NodeSeparator property is used to specify the character(s) to use to separate each item in the breadcrumb (like the sideway triangles in the screenshot above). By default, this is set to the '>' character. Also, this property will only be used if the breadcrumb item doesn't have any sub-items; if it does, then a clickable image like the one above will be displayed instead. Lastly, the breadcrumb's submenus will only go one level deep (like Vista's). So there won't be any "submenus of submenus".

The rendering logic takes place in the RenderContents(HtmlTextWriter) method. First, we get an instance of the SiteMapProvider object that has the same name as the one specified in the control's SiteMapProvider property. Then, using the provider, we traverse our way up the site map from the current page (specified by the SiteMapProvider.CurrentNode property) until we get to the top of the site map. While we traverse up, we will collect the nodes that we passed in a Stack<SiteMapNode> collection:

            SiteMapProvider provider = GetSiteMapProvider();

 

            Stack<SiteMapNode> nodes = new Stack<SiteMapNode>();

 

            SiteMapNode current = provider.CurrentNode;

            while (current != null)

            {

                nodes.Push(current);

                current = current.ParentNode;

            }

 

Once we've reached the top, we then pop the nodes out of the stack. Each node will be represented as a list item (li) in an unordered list (ul). If the node has any subitems, then we will render a clickable image and unordered list that is a child of the list item:

            while (nodes.Count > 0)

            {

                SiteMapNode node = nodes.Pop();

                sb.AppendFormat("<li class='dp-breadcrumbitem'><a href='{0}' title='{1}'>{1}</a>", node.Url, node.Title);

 

                //why not use SiteMapNode.HasChildNodes? see: http://social.msdn.microsoft.com/Forums/en-US/sharepointdevelopment/thread/37d10f92-140f-4ce8-b71c-388163721737/

                if (node.ChildNodes.Count > 0)

                {

                    sb.Append("<img src='/_layouts/images/marr.gif' class='dp-breadcrumbitemimage'/>");

 

                    sb.AppendFormat("<ul id='dp-submenu-{0}' class='ms-topNavFlyOuts dp-breadcrumbsubmenu'>", node.Key);

                    foreach (SiteMapNode subNode in node.ChildNodes)

                    {

                        sb.AppendFormat("<li class='dp-breadcrumbsubmenuitem'><a href='{0}' title='{1}' class='dp-submenulink'>{1}</a></li>", subNode.Url, subNode.Title);

                    }

                    sb.Append("</ul>");

                }

                else

                {

                    if (nodes.Count > 0) sb.AppendFormat("<span class='dp-breadcrumbseperator'>{0}</span>", nodeSeparator);

                }

                sb.Append("</li>");

            }

 

This is the typical html markup that is rendered by the breadcrumb control:

<ul class='dp-breadcrumb'>

    <li class='dp-breadcrumbitem'><a href='link' title-'Item 1'>Item 1</a> <img src='path_to_image' />

        <ul class='ms-topNavFlyOuts dp-breadcrumbsubmenu'>

            <li class='dp-breadcrumbsubmenuitem'><a href='link' title='Sub Item 1' class='dp-submenulink'>Sub Item 1</a></li>

            <li class='dp-breadcrumbsubmenuitem'><a href='link' title='Sub Item 2' class='dp-submenulink'>Sub Item 2</a></li>

        </ul>

    </li>

    <li class='dp-breadcrumbitem'><a href='link' title-'Item 2'>Item 2</a> <img src='path_to_image' />

        <ul class='ms-topNavFlyOuts dp-breadcrumbsubmenu'>

            <li class='dp-breadcrumbsubmenuitem'><a href='link' title='Sub Item 3' class='dp-submenulink'>Sub Item 3</a></li>

            <li class='dp-breadcrumbsubmenuitem'><a href='link' title='Sub Item 4' class='dp-submenulink'>Sub Item 4</a></li>

        </ul>

    </li>

</ul>

 

That is pretty much it for the web control. The next parts we need to build are the CSS and the JavaScript.

CSS and JavaScript

I won't bore you with the details of the CSS since it's available in the zip file below but the most important things with the CSS are: making sure the breadcrumb items are displayed inline and making sure the submenu uses absolute positioning and has a z-index that will place it on top of any other element that is going to be on the page.

Now for the UI magic to happen, we need a little client-side code. Here again, I use my new favorite client-side library, jQuery. The client script primarily consists of three event handlers: an event handler for the click event on the image, an event handler for a submenu item's hover event, and an event handler used to handle clicks anywhere else on the document.

The click event for the image is used to show/hide the corresponding submenu. It will also make sure that no other menu is visible besides the one that corresponds to the image that was clicked. Finally, it will toggle an appropriate image to use, depending on whether or not the submenu is visible or hidden:

    //add the event handler for the click on the image

    $("img.dp-breadcrumbitemimage").click(function(e){   

        e.stopPropagation();

 

        var theImage = $(this);

 

        var left = this.offsetLeft + 12;

        var top = this.offsetTop + 12;

 

        //get the submenu corresponding to the image.           

        var submenu = theImage.next("ul.dp-breadcrumbsubmenu");

 

        //iterate over all the submenus in this breadcrumb and hide any that isn't the target submenu.

        $("ul.dp-breadcrumbsubmenu").each(function(idx){           

            if (this.id != submenu.attr("id")){

                $(this).hide();

                $(this).prev("img.dp-breadcrumbitemimage").attr({src:"/_layouts/images/marr.gif"});

            }

        });

 

        //if the target submenu is visible, hide it. if it's invisible, show it.

        //also change the image that is being displayed.

        if(submenu.is(":visible"))

        {

            theImage.attr({src : "/_layouts/images/marr.gif"});

            submenu.slideUp();

        }

        else

        {

            theImage.attr({src : "/_layouts/images/menu2.gif"});

            submenu.css("position", "absolute").css("top", top).css("left", left).slideDown();

        }       

    });

The hover event handler for each submenu item will take care of making sure the item is highlighted properly. The highlighting we will use is the same highlighting used by the top navigation menu:

    //add a hover event for the items in the submenu so that they are highlighted.

    $("li.dp-breadcrumbsubmenuitem").hover(function(){

        $(this).addClass("ms-topNavFlyOutsHover");

    },

    function(){

        $(this).removeClass("ms-topNavFlyOutsHover");   

    });

Lastly, the click event handler for the document will make sure that if the user clicks outside of the breadcrumb, any visible submenu will be hidden:

    //add a click handler for the entire page so that when the user clicks outside of the breadcrumb, any visible menu will be hidden.

    $(this).click(function(){

        $("ul.dp-breadcrumbsubmenu").slideUp();

        $("img.dp-breadcrumbitemimage").attr({src:"/_layouts/images/marr.gif"});

    });

 

Putting it all together

So now that we've built the components, it's time to put it all together. First, add the breadcrumb.css file and breadcrumb.js file in a document library or folder in the SharePoint site collection. You also need to place the jQuery javascript library in a document library/folder (note: I used jQuery version 1.2.6 for this).

Second, you need to deploy the assembly 'DeviantPoint.SharePoint.Web.UI.dll' to the Global Assembly Cache (GAC) and then reset IIS.

Third, in order to be able to use the web control we developed, you need to add an entry into the SafeControls section of your web.config file:

      <SafeControlAssembly="DeviantPoint.SharePoint.Web.UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d767c3b2d2145e42"Namespace="DeviantPoint.SharePoint.Web.UI.WebControls.Navigation"TypeName="*"Safe="True" />

Lastly, you need to modify your site collection's master file by first registering our custom assembly:

<%@ Register Tagprefix="DeviantPoint" Namespace="DeviantPoint.SharePoint.Web.UI.WebControls.Navigation" Assembly="DeviantPoint.SharePoint.Web.UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d767c3b2d2145e42" %>

Then you need to add a reference to the css and javascript files we are using:

    <!-- reference to the breadcrumb css -->

    <link href="/Style Library/Breadcrumb.css" type="text/css" rel="stylesheet"/>

 

    <!-- add the reference to the two jquery libraries -->

    <script src="/scripts/jquery-1.2.6.min.js" type="text/javascript" language="javascript"></script>

    <script src="/scripts/Breadcrumb.js" type="text/javascript" language="javascript"></script>   

Note that in this case, I put 'Breadcrumb.css' in the default Style Library document library. I put the javascript files in a folder I created with SharePoint designer called scripts. Also, make sure to place these lines right before the closing 'head' tag in the master file.

Lastly, we need to place the breadcrumb control in an appropriate spot on the master page where we'd like the breadcrumb to appear:

              <asp:ContentPlaceHolder id="PlaceHolderGlobalNavigationSiteMap" runat="server">

                <!-- DEVIANTPOINT breadcrumb -->

                <DeviantPoint:Breadcrumb ID="bcGlobal" runat="server" SiteMapProvider="GlobalNavSiteMapProvider"></DeviantPoint:Breadcrumb>

              </asp:ContentPlaceHolder>

In this case, I actually replaced the default global breadcrumb that comes out of the box with my own breadcrumb. I also used the GlobalNavSiteMapProvider because that site map provider will have all of the nodes for the site collection.

Results

Here are some screenshots of the new breadcrumb in action:

 

 

See? Not too difficult!

Files

In the zip file below, you'll find the assembly that needs to be deployed to the GAC, Breadcrumb.cs, breadcrumb.css, breadcrumb.js, and deviantpoint.master. Deviantpoint.master is just the out of the box master with all the changes to the master file I specified above. You will need to download jQuery v. 1.2.6 yourself.

DeviantPoint_Breadcrumb.zip (11.12 kb)

Comments (2) -

daoming
daoming
5/8/2009 2:22:38 AM #

thank you very much.

Giancarlo Leonio
Giancarlo Leonio
3/13/2013 5:44:06 AM #

Hi Bart, thanks for this tutorial on creating a SharePoint breadcrumb control with drop down menus. I found it really helpful! I compiled a list of some top resources on building a breadcrumb menu. I included your post. Check it out/feel free to share. www.verious.com/.../ Hope other web developers find this useful too. Smile

Pingbacks and trackbacks (2)+

Add comment

biuquote
  • Comment
  • Preview
Loading

About the author

Bart X. Tubalinal is a Solutions Architect with over 10+ years experience in building enterprise applications. He also considers himself to be, pound for pound, one of the best developers there is.

Archives

Comments

Comment RSS