Image 1: List view webpart with folder-navigation breadcrumb
I’ll explain in the post how you can achieve this kind of folder navigation with minimum changes/customisation.
Deployment
Download the FolderNavigation.js file from MSDN Code Gallery. You can deploy the script either in Layouts folder (in case of full trust solutions) or in Master Page gallery (in case of SharePoint Online or full trust). I would recommend to deploy in Master Page Gallery so that even if you move to cloud, it works without modification. If you deploy in Master page gallery, you don’t need to make any changes, but if you deploy in layouts folder, you need to make small changes in the script which is described in section ‘Deploy JS Link file in Layouts folder’.Option 1: Deploy in Master Page Gallery (Suggested)
If you are dealing with SharePoint Online, you don’t have the option to deploy in Layouts folder. In that case you need to deploy it in Master page gallery. Note, deploying the script in other libraries (like site assets, site library) will not work, you need to deploy in master page gallery. To deploy in master page gallery manually, please follow the steps:- Download the js file from MSDN Code Gallery.
- Navigate to Root web => site settings => Master Pages (under group ‘Web Designer Galleries’).
- From the ‘New Document’ ribbon try adding ’JavaScript Display Template’ and then upload the FolderNavigation.js file and set properties as shown below:
Image 2: Upload JS file to Master Page Gallery
Option 2: Deploy JS Link file in Layouts Folder
If you are deploying the FolderNavigation.js file in Layouts folder, you need to make small changes in the downloaded script’s RegisterModuleInti method as shown below:RegisterModuleInit('FolderNavigation.js', folderNavigation);
In this case the ‘RegisterModuleInit’ first parameter will be the path relative to Layouts folder. If you deploy your file in path ‘/_Layouts/folder1’, the then you need to modify code as shown below:
RegisterModuleInit('Folder1/FolderNavigation.js', folderNavigation);
If you are deploying in other subfolders in Layouts folder, you need to update the path accordingly. What I’ve found till now, you can only deploy in Layouts and Master page gallery. But if you find deploying in other folders works, please share.
Use The JS File in List View WebPart
Once you deploy the FolderNavigation.js file, you can start using it in list view webpart. Edit the list view web part properties and then under ‘Miscellaneous’ section put the file url for JS Link as shown below:Image 3: JS Link for list view webpart
Few points to note for this JS Link:
- if you have deployed the js file in Master Page Gallery, You can use ~site or ~SiteCollection token, which means current site or current site collection respectively.
- If you have deployed in Layouts folder, you need to use corresponding path in the JS Link properties. For example if you are deploying the file in Layouts folder, then use ‘/_layouts/15/FolderNavigation.js’, if you are deploying in ‘Layouts/Folder1’ then, use ‘/_layouts/15/Folder1/FolderNavigation.js’.
How it works?
If you are interested how it works, you can carry on reading, either move to deployment section. Basically I’ve utilized the SharePoint 2013 Client Side Rendering (CSR) concept. If you are not familiar with this CSR, you can get the basic idea by Googling. Simply, CSR allows us to customize the rendering of SharePoint fields using JavaScript. I’ve developed a client side rendering JavaScript file which is shown below:
///Author: Sohel Rana //Version 1.2 //Last Modified on 27-Oct-2013 //Version History: // 1. Added // 2. Fixed the bug 'Filtering by clicking on field title would result duplicate navigation link' // 3. Fixed the bug 'breadcrumb title always lowercase'. Now breadcrumb title is as like the folder name in the library even with case (lower/upper) //replace query string key with value function replaceQueryStringAndGet(url, key, value) { var re = new RegExp("([?|&])" + key + "=.*?(&|$)", "i"); separator = url.indexOf('?') !== -1 ? "&" : "?"; if (url.match(re)) { return url.replace(re, '$1' + key + "=" + value + '$2'); } else { return url + separator + key + "=" + value; } } function folderNavigation() { function onPostRender(renderCtx) { if (renderCtx.rootFolder) { var listUrl = decodeURIComponent(renderCtx.listUrlDir); var rootFolder = decodeURIComponent(renderCtx.rootFolder); if (renderCtx.rootFolder == '' || rootFolder.toLowerCase() == listUrl.toLowerCase()) return; //get the folder path excluding list url. removing list url will give us path relative to current list url var folderPath = rootFolder.toLowerCase().indexOf(listUrl.toLowerCase()) == 0 ? rootFolder.substr(listUrl.length) : rootFolder; var pathArray = folderPath.split('/'); var navigationItems = new Array(); var currentFolderUrl = listUrl; var rootNavItem = { title: 'Root', url: replaceQueryStringAndGet(document.location.href, 'RootFolder', listUrl) }; navigationItems.push(rootNavItem); for (var index = 0; index < pathArray.length; index++) { if (pathArray[index] == '') continue; var lastItem = index == pathArray.length - 1; currentFolderUrl += '/' + pathArray[index]; var item = { title: pathArray[index], url: lastItem ? '' : replaceQueryStringAndGet(document.location.href, 'RootFolder', encodeURIComponent(currentFolderUrl)) }; navigationItems.push(item); } RenderItems(renderCtx, navigationItems); } } //Add a div and then render navigation items inside span function RenderItems(renderCtx, navigationItems) { if (navigationItems.length == 0) return; var folderNavDivId = 'foldernav_' + renderCtx.wpq; var webpartDivId = 'WebPart' + renderCtx.wpq; //a div is added beneth the header to show folder navigation var folderNavDiv = document.getElementById(folderNavDivId); var webpartDiv = document.getElementById(webpartDivId); if(folderNavDiv!=null){ folderNavDiv.parentNode.removeChild(folderNavDiv); folderNavDiv =null; } if (folderNavDiv == null) { var folderNavDiv = document.createElement('div'); folderNavDiv.setAttribute('id', folderNavDivId) webpartDiv.parentNode.insertBefore(folderNavDiv, webpartDiv); folderNavDiv = document.getElementById(folderNavDivId); } for (var index = 0; index < navigationItems.length; index++) { if (navigationItems[index].url == '') { var span = document.createElement('span'); span.innerHTML = navigationItems[index].title; folderNavDiv.appendChild(span); } else { var span = document.createElement('span'); var anchor = document.createElement('a'); anchor.setAttribute('href', navigationItems[index].url); anchor.innerHTML = navigationItems[index].title; span.appendChild(anchor); folderNavDiv.appendChild(span); } //add arrow (>) to separate navigation items, except the last one if (index != navigationItems.length - 1) { var span = document.createElement('span'); span.innerHTML = ' > '; folderNavDiv.appendChild(span); } } } function _registerTemplate() { var viewContext = {}; viewContext.Templates = {}; viewContext.OnPostRender = onPostRender; SPClientTemplates.TemplateManager.RegisterTemplateOverrides(viewContext); } //delay the execution of the script until clienttempltes.js gets loaded ExecuteOrDelayUntilScriptLoaded(_registerTemplate, 'clienttemplates.js'); }; //RegisterModuleInit ensure folderNavigation() function get executed when Minimum Download Strategy is enabled. //if you deploy the FolderNavigation.js file in '_layouts' folder use 'FolderNavigation.js' as first paramter. //if you deploy the FolderNavigation.js file in '_layouts/folder/subfolder' folder, use 'folder/subfolder/FolderNavigation.js as first parameter' //if you are deploying in master page gallery, use '/_catalogs/masterpage/FolderNavigation.js' as first parameter RegisterModuleInit('/_catalogs/masterpage/FolderNavigation.js', folderNavigation); //this function get executed in case when Minimum Download Strategy not enabled. folderNavigation();
Code Snippet 1: Folder Navigation JavaScript
Let me explain the code briefly.
- The method ‘replaceQueryStringAndGet’ is used to replace query string parameter with new value. For example if you have url ‘http://abc.com?key=value&name=sohel’ and you would like to replace the query string ‘key’ with value ‘New Value’, you can use the method like
replaceQueryStringAndGet("http://abc.com?key=value&name=sohel","key","New Value")
- The function folderNavigation has three methods. Function ‘onPostRender’ is bound to rendering context’s OnPostRender event. The method first checks if the list view’s root folder is not null and root folder url is not list url (which means user is browsing list’s/library’s root). Then the method split the render context’s folder path and creates navigation items as shown below:
var item = { title: title, url: lastItem ? '' : replaceQueryStringAndGet(document.location.href, 'RootFolder', encodeURIComponent(rootFolderUrl)) };
As shown above, in case of last item (which means current folder user browsing), the url is empty as we’ll show a text instead of link for current folder.
- Function ‘RenderItems’ renders the items in the page. I think this is the place of customisation you might be interested. Having all navigation items passed to this function, you can render your navigation items in your own way. renderContext.wpq is unique webpart id in the page. As shown below with the wpq value of ‘WPQ2’ the webpart is rendered in a div with id ‘WebPartWPQ2’.
Image 4: How list view webpart is rendered
In ‘RenderItems’ function I’ve added a div just before the webpart div ‘WebPartWPQ2’ to put the folder navigation as shown in the image 1.
- In the method ‘_registerTemplate’, I’ve registered the template and bound the OnPostRender event.
- The final piece is RegisterModuleInit. In some example you will find the function folderNavigation is executed immediately along with the declaration. However, there’s a problem with Client Side Rendering and Minimal Download Strategy (MDS) working together as described in here. To avoid this problem, we need to Register foldernavigation function with RegisterModuleInit to ensure the script get executed in case of MDS-enabled site. The last line ‘folderNavigation()’ will execute normally in case of MDS-disabled site.