Global navigation in SharePoint 2013 revisited

Recently I wrote about how you could create consistent global navigation across multiple Site Collections with SharePoint 2013. This article presents some improvements to the concept presented previously.

Recap

One of the new capabilities of SharePoint 2013 is Managed Navigation that allows you to define site’s navigation using Managed Metadata. Unfortunately Managed Navigation is bound to a specific Site Collection and cannot be reused across different Site Collections. Using the standard capabilities of SharePoint 2013 you can leverage Managed Metadata and build global navigation within your organization.

In my previous post I showed you how you can leverage the capabilities of SharePoint 2013 and build consistent global navigation across multiple Site Collections using SharePoint 2013 ECMA Script API, Managed Metadata and Knockout.js.

Limitations of the previous concept

Although very useful the previous concept had two limitations.

Where am I?

If you look at the standard global navigation used by SharePoint 2013 you will see that the site that you are currently on is highlighted using a pointer icon.

The global navigation concept I showed in my previous post lacked this capability.

Inconvenient SP.Taxonomy.js

After testing the global navigation script that I have built on different sites I noticed some strange behavior where in some cases the global navigation wouldn’t load. The script would run correctly, but no links would be added to the menu.

For this navigation to be truly global and work across all the different Site Collections this issue had to be solved.

Global navigation in SharePoint 2013 revisited

This concept builds upon the steps described in the previous article. The same taxonomy is used for the global navigation as previously. What has changed is the Mavention.GlobalNavigation.js file and the Knockout.js template in the Master Page.

Where am I?

In order to highlight the current site we need to modify the Knockout.js template included in the Master Page. Following is the new template with the changes highlighted.

<script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js"></script>
<script src="/Style Library/Mavention/Mavention.GlobalNavigation.js"></script>
<div>
    <ul class="ms-core-suiteLinkList" data-bind="foreach: globalMenuItems">
        <li class="ms-core-suiteLink"><a class="ms-core-suiteLink-a" data-bind="attr: { href: url }">
                <span>
                    <span data-bind="text: title"></span>
                    <!-- ko if: location.href.slice(0, url.length) == url || (location.href.slice(0, 8) === 'https://' && location.href.slice(0, url.replace(':443/', '/').length) == url.replace(':443/', '/')) -->
                    <span class="ms-suitenav-caratBox" id="Suite_ActiveLinkIndicator_Clip"><img class="ms-suitenav-caratIcon" id="Suite_ActiveLinkIndicator" src="/_layouts/15/images/spcommon.png?rev=23" /></span>
                    <!-- /ko -->
                </span>
        </a></li>
    </ul>
</div>
<script>
Mavention.GlobalNavigation.init('Managed Metadata Service', '779a5609-7190-4b43-9071-6979841a8d71');
</script>

Similarly to the previous snippet the above code snippet should be pasted on line 81 right after the div with it suiteLinksBox in the copy of the seattle.master Master Page.

If you compare the snippet above with its previous version you should notice how the simple notation of a link has changed to accommodate highlighting current site. Using the ko if notation (Knockout.js if clause for use in templates) the URL of the current page (location.href) is compared with the URL of the particular link (url). If the URL of the current page begins with the URL of the particular link that link is highlighted. The inconvenient thing here is that SharePoint automatically appends the :443 port number to the URL defined in the taxonomy for all HTTPS URLs. Unfortunately :443 isn’t present in the browser’s URL bar so prior to comparing the URLs, :443 has to be removed from the URL defined on the links.

With the new template you should be able to see the current site highlighted in the global navigation.

Inconvenient SP.Taxonomy.js

While testing the global navigation script with different sites I noticed that in some scenarios the navigation wasn’t loading. No JavaScript errors were thrown yet no links were being added to the navigation.

This odd behavior turned out to be caused by the SP.Taxonomy.js file being already loaded. In such cases the SP.SOD.executeFunc function would run but since the file was loaded it wouldn’t call the callback function. To work around this issue I have modified the Mavention.GlobalNavigation.js script:

Function.registerNamespace('Mavention.GlobalNavigation');

Mavention.GlobalNavigation.MenuItem = function(title, url) {
    this.title = title;
    this.url = url;
};

Mavention.GlobalNavigation.viewModel = {
    globalMenuItems: new Array()
};

Mavention.GlobalNavigation.init = function(termStoreName, termSetId) {
    this.termStoreName = termStoreName;
    this.termSetId = termSetId;

    SP.SOD.executeOrDelayUntilScriptLoaded(Function.createDelegate(this, function() {
        'use strict';
        this.nid = SP.UI.Notify.addNotification("<img src='/_layouts/15/images/loadingcirclests16.gif?rev=23' style='vertical-align:bottom; display:inline-block; margin-" + (document.documentElement.dir == "rtl" ? "left" : "right") + ":2px;' />&nbsp;<span style='vertical-align:top;'>Loading navigation...</span>", false);
        
        var taxonomySodLoaded = false;

        if (typeof(_v_dictSod) !== 'undefined' &&
            _v_dictSod['sp.taxonomy.js'] == null) {
            SP.SOD.registerSod('sp.taxonomy.js', SP.Utilities.Utility.getLayoutsPageUrl('sp.taxonomy.js'));
        }
        else {
            taxonomySodLoaded = _v_dictSod['sp.taxonomy.js'].state === Sods.loaded;
        }
        
        if (taxonomySodLoaded) {
            Function.createDelegate(this, Mavention.GlobalNavigation.loadNavigationInternal)();
        }
        else {
            SP.SOD.executeFunc('sp.taxonomy.js', false, Function.createDelegate(this, Mavention.GlobalNavigation.loadNavigationInternal));
        }
    }), 'core.js');
};

Mavention.GlobalNavigation.loadNavigationInternal = function() {
    var context = SP.ClientContext.get_current();
    var taxonomySession = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);
    var termStore = taxonomySession.get_termStores().getByName(this.termStoreName);
    var termSet = termStore.getTermSet(this.termSetId);
    var terms = termSet.getAllTerms();
    context.load(terms);
    context.executeQueryAsync(Function.createDelegate(this, function(sender, args) {
        var termsEnumerator = terms.getEnumerator();
        var menuItems = new Array();
        
        while (termsEnumerator.moveNext()) {
            var currentTerm = termsEnumerator.get_current();
            Mavention.GlobalNavigation.viewModel.globalMenuItems.push(new Mavention.GlobalNavigation.MenuItem(currentTerm.get_name(), currentTerm.get_localCustomProperties()['_Sys_Nav_SimpleLinkUrl']));
        }
        
        ko.applyBindings(Mavention.GlobalNavigation.viewModel);
        SP.UI.Notify.removeNotification(this.nid);
    }), Function.createDelegate(this, function(sender, args) {
        alert('The following error has occured while loading global navigation: ' + args.get_message());
    }));
};

First of all I check if the SP.Taxonomy.js SOD has already been defined (lines 22-23) and loaded (line 27). If it has been loaded I simply call the function responsible for loading global navigation terms (line 31). If the SOD hasn’t been loaded yet, I load it using the SP.SOD.executeFunc function and pass the function responsible for loading navigation terms as a delegate (line 34). With these modifications the global navigation script should work with all sites.

Summary

One of the new capabilities of SharePoint 2013 is Managed Navigation that allows you to define site’s navigation using Managed Metadata. Unfortunately Managed Navigation is bound to a specific Site Collection and cannot be reused across different Site Collections. Using the standard capabilities of SharePoint 2013 you can leverage Managed Metadata and build global navigation within your organization.