Enhancing SharePoint breadcrumbs when navigating deeply nested folders

I had an interesting challenge to improve breadcrumbs in navigating a deeply folder nested library. The nice breadcrumb dropdown icon is actually gone in SP2013 by default; that can easily be re-enabled via CSS. an onMouseOver event action in JavaScript can eliminate the need to click on it. However to improve the breadcrumb navigation, I created an SPWeb feature to enhance breadcrumbs by overriding the OnPreRender event. The OnPreRender should only occur if the ‘Visible’ property of the web control is set to true. The PreRender event is raised just before the page is about to render its contents. This is the last chance we have to modify the page output before it is received by the browser.

I deployed this web Scoped solution that overrides the breadcrumbs with OnPreRender page processing, and provides the full folder path, clickable for each link. It recursively searches the page for the “TitleBreadcrumb” control, and builds a replacement, adding an a href, folder link name, and breadcrumb image iteratively. It only affects the page when you are navigating within a library.

namespace MyDocLibraryBreadcrumb
{
public class MyDocLibBreadcrumbDelegateControl : WebControl 
{
protected override void OnPreRender(EventArgs e)
{
try
{
base.OnPreRender(e);
// Get the path to the current folder
string path = Context.Request.QueryString["RootFolder"];
// if there's no path then there is nothing to do; it implies are are not in the context of the library 
if (String.IsNullOrEmpty(path))
{
return;
}
// Let's get the current folder
SPWeb web = SPContext.Current.Web;
SPFolder currentFolder = web.GetFolder(path);
// Let's find the breadcrumb control on the current page - it's a ListProperty control where the property is  "TitleBreadcrumb". 
Control c = Utils.FindRecursive(Page.Controls, ctrl => ctrl is ListProperty && ((ListProperty)ctrl).Property == "TitleBreadcrumb");
// If not found, nothing to do, and we are not likely in a library. 
if (c == null)
return;
// Let's subsitute the OOTB breadcrumb control with our replacement enhanced one
var parent = c.Parent;
var index = parent.Controls.IndexOf(c);
parent.Controls.RemoveAt(index);
parent.Controls.AddAt(index, new LiteralControl { Text = GetReplacementBreadCrumbOutput(currentFolder) });
}
catch (Exception ex)
{
// log errors quietly 
Utils.WriteMyLog(Utils.GetErrorInfo(ex));
}
}
/// SPFolder is the parameter to create navigation to
/// returns the HTML output
private string GetReplacementBreadCrumbOutput(SPFolder folder)
{
List<BreadcrumbNodeData> nodes = new List<BreadcrumbNodeData>();
// Collect a path from current folder to its root folder
SPFolder nodeFolder = folder;
while (nodeFolder != null)
{
// If we're in folder use the folder name as a title. If not use a library title enstead.
BreadcrumbNodeData node  = new BreadcrumbNodeData();
node.Url = nodeFolder.ServerRelativeUrl;
if (string.IsNullOrEmpty(nodeFolder.ParentFolder.Url))
{
if (nodeFolder.DocumentLibrary != null)
nodes.Add(new BreadcrumbNodeData
{Title = nodeFolder.DocumentLibrary.Title, Url = nodeFolder.ServerRelativeUrl});
}
else
{
nodes.Add(new BreadcrumbNodeData { Title = nodeFolder.Name, Url = nodeFolder.ServerRelativeUrl });
}
nodeFolder = string.IsNullOrEmpty(nodeFolder.ParentFolder.Url) ? null : nodeFolder.ParentFolder;
}
// Reverse the collected path because the root folder must be on the left in bredcrumb
nodes.Reverse();
// Create an HTML output similar to original. An arrow image we've created from the original
string htmlOutput = String.Empty;
foreach (var node in nodes)
{
if (node != nodes.Last())
htmlOutput +=
String.Format(
@"<A href=""{0}"">{1}</A> <IMG style=""vertical-align:middle"" alt=: src=""/_layouts/images/MyDocLibraryBreadcrumb/breadcrumb_arrow.png""/> ", node.Url, node.Title);
else
{
htmlOutput += node.Title;
}
}
return htmlOutput;
}
}
/// temporary class to holds navigation node data
public class BreadcrumbNodeData
{
/// Title for URL (it will be a folder name)
public string Title { get; set; }
/// Url to navigate on click (it will be a server relative URL of the folder)
public string Url { get; set; }
}
public class Utils
{
public static string GetErrorInfo(Exception ex)
{
string result = "ex.Message=" + ex.Message;
result += ex.InnerException == null ? "|ex.StackTrace=" + ex.StackTrace : String.Empty;
if (ex.InnerException != null)
result += "[INNER EXCEPTION: ]" + GetErrorInfo(ex.InnerException);
return result;
}
public static void WriteMyLog(string text)
{
SPDiagnosticsService.Local.WriteTrace(0,
new SPDiagnosticsCategory("MyDocLibBreadcrumbDelegateControl", TraceSeverity.High,
EventSeverity.Error),
TraceSeverity.High, text, null);
}
/// Finds a control based on provided criteria inside controls hierarchy
/// <param name="controls">A Controls collection to start search</param>
/// <param name="criteria">A criteria to return control</param>
/// <returns>The founded control </returns>
public static Control FindRecursive(ControlCollection controls, Func<Control, bool> criteria)
{
foreach (Control control in controls)
{
if (criteria(control))
return control;
var innerControl = FindRecursive(control.Controls, criteria);
if (innerControl != null)
return innerControl;
}
return null;
}
}
}
0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *