On the 6th of May, 2013, I had asked a question on
Stack Overflow on how one could create a term-driven hierarchical breadcrumb trail in SharePoint 2013. Alas, never really got an answer that suited my needs.
Yesterday, I suddenly had the idea to just try and find a solution with JavaScript. The idea was to loop through the subsite navigation and see which list item resembled the current page, then find all the parent elements of that list item and fetch their values. And in case I was on a page that wasn't part of a subsite (but rather just a page at the top site collection), I would just fetch the title of the page and add that to the breadcrumb trail.
Here's a screenshot of how my breadcrumb looked like earlier:
And here's a screenshot of how it looks like now:
Pretty nice, huh?
Let's just get started, I'll give you the code.
The code
SP.SOD.executeOrDelayUntilScriptLoaded("SP.UserProfiles.js",
"~sitecollection/Style Library/Scripts/jquery.SPServices-2013.01.js");
var siteCollection = "Your site collection name";
var siteCollectionUrl = "/";
var pageName;
var subsite;
$().SPServices({
operation: "SiteDataGetWeb",
async: false,
completefunc: function (xData, Status) {
subsite = $(xData.responseXML).SPFilterNode("Title").text();
}
});
var subsiteUrl = $().SPServices.SPGetCurrentSite();
var pageTitle = document.getElementsByTagName("title")[0].innerHTML
.replace("Pages - ", "").replace(/^\s\s*/, "").replace(/\s\s*$/, "");
if (subsite == siteCollection) {
var text = "<a href='" + siteCollectionUrl + "'>" + siteCollection
+ "</a> > <a href='" + document.URL + "'>" + pageTitle + "</a>";
}
else {
var text = "<a href='" + siteCollectionUrl + "'>" + siteCollection
+ "</a> > <a href='" + subsiteUrl + "'>" + subsite + "</a>";
}
$(function runMe() {
var $this = $("#NavRootAspMenu");
if($this != null) {
$(".ms-listMenu-editLink").removeClass("static");
$("ul[id*='RootAspMenu'] li.ms-navedit-editArea:last-child").remove();
$this.find("li").each(function(i){
var elem = $($this).find("li.static")[i];
if ($(elem).find("a").hasClass("ms-core-listMenu-selected")) {
$(elem).addClass("parentSelected");
}
});
$this.find(".parentSelected > a span span.menu-item-text")
.each(function(j) {
$(this).addClass("bcn");
var crumbLink = $($this).find(".parentSelected > a")[j].href;
var crumbName = $("span.bcn")[j].innerHTML;
if (crumbLink == "https://your-site-collection.com/") {
text = text + " > " + crumbName;
}
else {
text = text + " > <a href='" + crumbLink + "'>" + crumbName + "</a>";
}
});
}
document.getElementById("customBreadcrumb").innerHTML = text;
});
You'll also need to have the jQuery library for SharePoint Web Services, you can find it
here.
In order for our code to work properly, we will need to replace some code from the master page and add a reference to the breadcrumb script on the master page. Be sure to read the JavaScript code first, you will need to fill in a name for your site collection (var siteCollection) and give the url of your site collection followed by a slash (var crumbLink).
Adding a reference to the master page
If we want to apply this code on the site collection and all subsites, then we should add a reference to our script in the master page. Please do note that I'm using a HTML master page. This is the code you should add:
<!--SPM:<SharePoint:ScriptLink language="javascript" ID="scriptLink1"
runat="server" name="~sitecollection/Style Library/Scripts/breadcrumb.js"
OnDemand="false" Localizable="false"/>-->
Do note that the ID might be different. You must make sure that you do not already have a scriptlink with the same ID, so change the number of the ID and make it unique.
Also, the name (path to your script) might be different. Make sure that it matches the path of where your script is located.
Replacing code in the master page
Next, we need to replace some code in the master page.
In my HTML master page, I replaced any piece of code that had to do with the breadcrumb. Just to give you an idea, the following pieces of code are the ones that I REMOVED from my master page:
<!--SPM:<asp:sitemappath runat="server"
sitemapproviders="SPSiteMapProvider,SPXmlContentMapProvider"
rendercurrentnodeaslink="true"
nodestyle-cssclass="breadcrumbNode"
currentnodestyle-cssclass="breadcrumbCurrentNode"
rootnodestyle-cssclass="breadcrumbRootNode"
hideinteriorrootnodes="false"
SkipLinkText=""/>-->
<!--SPM:<SharePoint:AjaxDelta id="DeltaPlaceHolderPageTitleInTitleArea"
runat="server">-->
<!--SPM:</SharePoint:AjaxDelta>-->
<!--SPM:<SharePoint:AjaxDelta BlockElement="true"
id="DeltaPlaceHolderPageDescription" CssClass="ms-displayInlineBlock
ms-normalWrap" runat="server">-->
<a href="javascript:;" id="ms-pageDescriptionDiv"
style="display: none;">
<span id="ms-pageDescriptionImage"></span>
</a>
<span class="ms-accessible" id="ms-pageDescription">
<!--SPM:<asp:ContentPlaceHolder id="PlaceHolderPageDescription"
runat="server"/>-->
</span>
<!--SPM:<SharePoint:ScriptBlock runat="server">-->
<!--SPM:_spBodyOnLoadFunctionNames.push("setupPageDescriptionCallout");-->
<!--SPM:</SharePoint:ScriptBlock>-->
<!--SPM:</SharePoint:AjaxDelta>-->
It is very important that you keep the following code in your master page, but wrap something around it with a class. Like so:
<span class="hideDefaultBreadcrumb">
<!--SPM:<asp:ContentPlaceHolder id="PlaceHolderPageTitleInTitleArea"
runat="server">-->
<!--SPM:<SharePoint:SPTitleBreadcrumb runat="server"
RenderCurrentNodeAsLink="true"
SiteMapProvider="SPContentMapProvider"
CentralAdminSiteMapProvider="SPXmlAdminContentMapProvider">-->
<!--SPM:<pathseparatortemplate>-->
<!--SPM:<SharePoint:ClusteredDirectionalSeparatorArrow
runat="server"/>-->
<!--SPM:</PATHSEPARATORTEMPLATE>-->
<!--SPM:</SharePoint:SPTitleBreadcrumb>-->
<!--SPM:</asp:ContentPlaceHolder>-->
</span>
Then add the class to your style sheet:
.hideDefaultBreadcrumb {
display: none;
}
The reason that we must keep this content placeholder, is that otherwise you'll suddenly miss a great portion of the "Apps you can add". I forgot about this at first and then suddenly had only three apps left, but after reading about it
here I learned that I should have left in the content placeholder with ID PlaceHolderPageTitleInTitleArea. So that's fixed now.
I also removed a span with the ID "ctl00_DeltaPlaceHolderPageTitleInTitleArea" in the master page.
Bear in mind that your code might be different from mine, so always check your site with source view in your browser, to see the ID's and classes of the elements in the breadcrumb.
Now, ADD the following code to the location of where you just removed the piece of code that was previously your breadcrumb:
<span id="customBreadcrumb"></span>
In that tiny span, your breadcrumb trail will appear. This will be dynamically filled with text and links once your script is running.
Make sure your master page and your script are checked in. Now you can finally see the full breadcrumb trail, with the correct hierarchy!
No need to deploy solutions, no need to find a workaround or use webparts to do the trick, just some simple JavaScript is all you need. ;)
Enjoy!