SharePoint Server 2007 uses this control to display the navigation path and whilst this appears to be acceptable, under the bonnet it renders non-semantic markup that doesn't fully represent a visitor's current position in the site and is also more difficult to brand. SharePoint 2010 ships with a new control called the ListSiteMapPath that uses semantic markup to display the navigation path and although this is a landmark development, if you need much greater control over the rendered content or you want to manipulate the behaviour of your navigation path (or you're simply using SharePoint 2007) then read on.
To begin with we're going to use the good old SiteMapPath control which works in both SharePoint 2007 and SharePoint 2010 but as previously described renders non-semantic markup:
<span><a href="...>Home</a></span><span> > </span><span><a href="...>Company</a></span><span> > </span><span><a href="...>Press</a></span><span> > </span><span><a href="...>A Lovely Day</a></span>
To a screen reader or a search engine this has no underlying context and will just appear as a chain of words so we need fix this up by transforming it into a nest of unordered lists:
<div class="AspNet-siteMap"> <ul> <li> <a href="/" class="AspNet-SiteMap-Link">Home</a> <ul> <li> <a href="… class="AspNet-SiteMap-Link">Company</a> <ul> <li> <a href="… class="AspNet-SiteMap-Link">Press</a> <ul> <li> <a href="… class="AspNet-SiteMap-Link">A Lovely Day</a> </li> </ul> </li> </ul> </li> </ul> </li> </ul> </div>
This can then be styled using CSS for our finished breadcrumb:
.AspNet-siteMap { font-family: Arial, Sans-Serif; font-size: 80%; font-weight: normal; color: #ffffff; margin: 0 0 20px 0; } .AspNet-siteMap ul {display: inline; margin: 0; padding: 0;} .AspNet-siteMap ul li {display: inline; margin: 0;} .AspNet-siteMap ul a { color: #9ACD34; margin-right: 3px; text-decoration: none; display: inline-block; /* IE needs this for proper background image position if lines break */ font-weight: bold; } .AspNet-siteMap ul ul a { color: #FFF; background: url(../images/BcBullet.gif) no-repeat 0 6px; padding: 0 0 0 12px; /* list indent */ font-weight: normal; } .AspNet-siteMap ul a:hover {text-decoration: underline;}
To get this up and running create a custom control that derives from the ASP.NET SiteMapPath control (signing the assembly and deploying it to the GAC):
Imports System Imports System.Collections.Generic Imports System.ComponentModel Imports System.Text Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Text.RegularExpressions Namespace Custom.Web.UI.WebControls <DefaultProperty("Text"), ToolboxData("<{0}:CustomSiteMapPath runat=server></{0}:CustomSiteMapPath>")> _ Public Class CustomSiteMapPath Inherits SiteMapPath <Bindable(True), Category("Appearance"), DefaultValue(""), Localizable(True)> Property Text() As String Get Dim s As String = CStr(ViewState("Text")) If s Is Nothing Then Return "[" + Me.ID + "]" Else Return s End If End Get Set(ByVal Value As String) ViewState("Text") = Value End Set End Property Protected Overrides Sub OnInit(ByVal e As System.EventArgs) If IsPublishingPage() Then MyBase.SiteMapProvider = "CurrentNavSiteMapProviderNoEncode" End If MyBase.OnInit(e) End Sub Public Overrides Sub RenderBeginTag(ByVal writer As System.Web.UI.HtmlTextWriter) writer.WriteLine() writer.WriteBeginTag("div") writer.WriteAttribute("class", "AspNet-siteMap") writer.Write(HtmlTextWriter.TagRightChar) End Sub Public Overrides Sub RenderEndTag(ByVal writer As System.Web.UI.HtmlTextWriter) writer.WriteEndTag("div") writer.WriteLine() End Sub Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter) writer.Indent += 1 Dim item As SiteMapPath = CType(Me, SiteMapPath) Dim Provider As SiteMapProvider = item.Provider Dim collection As New SiteMapNodeCollection() Dim node As SiteMapNode = Provider.CurrentNode While Not node Is Nothing collection.Add(node) node = node.ParentNode End While BuildItems(collection, True, writer) writer.Indent -= 1 writer.WriteLine() End Sub Private Sub BuildItems(ByVal items As SiteMapNodeCollection, ByVal isRoot As Boolean, ByVal writer As HtmlTextWriter) If items.Count > 0 Then For i As Integer = items.Count - 1 To -1 + 1 Step -1 BuildItem(items(i), writer, (i = 0)) '0 is current node Next 'close nested list For i As Integer = 0 To items.Count - 1 writer.Indent -= 1 writer.WriteLine() writer.WriteEndTag("li") writer.Indent -= 1 writer.WriteLine() writer.WriteEndTag("ul") Next End If End Sub Private Sub BuildItem(ByVal item As SiteMapNode, ByVal writer As HtmlTextWriter, ByVal isCurrentNode As Boolean) If (item IsNot Nothing) AndAlso (writer IsNot Nothing) Then If item.Url.Length > 0 Then writer.WriteLine() writer.WriteFullBeginTag("ul") writer.Indent += 1 writer.WriteLine() writer.WriteFullBeginTag("li") writer.Indent += 1 writer.WriteLine() writer.WriteBeginTag("a") writer.WriteAttribute("href", Page.ResolveUrl(item.Url)) writer.WriteAttribute("class", "AspNet-SiteMap-Link") writer.Write(HtmlTextWriter.TagRightChar) writer.Write(item.Title) writer.WriteEndTag("a") End If End If End Sub Private Function IsPublishingPage() As Boolean Dim url As String = HttpContext.Current.Request.RawUrl Dim pattern As String = ".*\/pages\/.+\.aspx" Dim urlMatch As Match = Regex.Match(url, pattern, RegexOptions.IgnoreCase) Return urlMatch.Success End Function End Class End Namespace
Mark the assembly as safe in the web.config file of the web application:
<SafeControl Assembly="CustomSiteMapPath, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3f64c3f72c5f0c95" Namespace="Custom.Web.UI.WebControls" TypeName="*" Safe="True" />
Register the control in the master page or page layout:
<%@ Register TagPrefix="uc" Namespace="Custom.Web.UI.WebControls" Assembly="CustomSiteMapPath, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3f64c3f72c5f0c95" %>
And finally add an instance of the control into the master page or page layout:
<uc:CustomSiteMapPath ID="CustomSiteMapPath1" runat="server" SiteMapProvider="SPContentMapProvider" SkipLinkText="" AdapterEnabled="False"></uc:CustomSiteMapPath>
Note that if you are using the ASP.NET CSS Friendly Control Adapters in your SharePoint site then you must set the AdapterEnabled property on your control instance to False otherwise the CSS Friendly Control Adapter will continue to take precedence.