The correct way to execute JavaScript functions in SharePoint 2013 MDS enabled sites

Tags: SharePoint 2013, MDS

Introduction

JavaScript is the future of SharePoint development (and please don’t quote me on that :-). JavaScript is everywhere in SharePoint 2013 and upcoming incarnations, and you will see a couple of posts on this topic from me in the future. The JavaScript language is easy (well, sort of), but the many different implementations and API’s built using JavaScript might cause confusion. One of the things in SharePoint 2013 that makes JavaScript development quite problematic is the Minimal Download Strategy (MDS) in SharePoint 2013. In this post I will show you what to think of when building JavaScript features on top of SharePoint and make them aware of MDS and make them work with MDS.

Minimal Download Strategy

Almost a year and a half ago I wrote the Introduction to the Minimal Download Strategy (MDS) post, which since then surprisingly been one of the most visited ones on this little site! I won’t recap everything in that post, but basically MDS is a framework that allows SharePoint 2013 to download and render pages more efficient by focusing on only those parts of the page that actually needs an update. For instance, when navigating between folders in a document library, we do not need to re-render the top bar or footer etc. All this to make the perceived performance better and to reduce bandwidth.

The Problem

The big problem with MDS is that it only works well with the out-of-the-box stuff on a Team Site, or similar templates. As soon as you start to drop your own Web Parts, custom scripts or customizations that has not been adapted for the Minimal Download Strategy you will not see any performance benefits, au contrary you will see a performance degradation in many cases – so you turn the MDS feature off.

One of the biggest problems is that the more and more customizations in SharePoint involves JavaScript and so far I have not seen a single non-SharePoint native script that can handle MDS that well, and I’ve been bashing my head against the wall for some time to get it to work properly. Many, if not most, JavaScripts want to execute a function when the page loads either to initialize something or to change something (haven’t we all seen all those dreaded jQuery UI customizations that could have been done with a simple CSS fix!). Common approaches to this is to use the SharePoint _spBodyOnLoadFunctionNames.push() or the jQuery $(document).ready() {} method. These methods rely on the DOM events when the page loads, so it might work perfectly fine on your first page load when MDS is enabled, since we need to fetch the full page, but on subsequent page transitions the JavaScript will not fire. An MDS page transition does not fire any DOM document load events since it asynchronously partially updates the page.

A non MDS compatible custom field using JSLink

List with custom JSLink columnLet’s take a look at an example using a simple Field using Client Side Rendering (or JSLink) that just makes negative numbers red and positive numbers black.

This is how my field is defined using CAML. In this sample I create everything as a Sandboxed, purely declarative solution, but it’s easy to create the exact same solution using a SPApp or a Full Trust Code solution.

<Field
      ID="{ce3d02df-d05f-4476-b457-6b28f1531f7c}"
      Name="WictorNumber"
      DisplayName="Number with color"
      Type="Number"
      Required="FALSE"
      JSLink="~sitecollection/Wictor/NumberWithColor.js"
      Group="Wictor">
</Field>

As you can see it is a standard Number field with a JSLink attribute. Next I deploy the JavaScript file, using a Module element, that looks like this:

var Wictor = window.Wictor || {}
Wictor.Demos = Wictor.Demos || {};
Wictor.Demos.Templates = Wictor.Demos.Templates || {}
Wictor.Demos.Functions = Wictor.Demos.Functions || {}

Wictor.Demos.Functions.Display = function (context) {
	var currentValue = context.CurrentItem.WictorNumber
	if (currentValue > 0) {
		return currentValue;
	}
	else {
		return '<span style="color:red">' + currentValue + '</span>'
	}
}

Wictor.Demos.Templates.Fields = {
	'WictorNumber': {
		'DisplayForm': Wictor.Demos.Functions.Display,
		'View': Wictor.Demos.Functions.Display,
		'NewForm': null,
		'EditForm':  null
	}
}

SPClientTemplates.TemplateManager.RegisterTemplateOverrides(Wictor.Demos)

Everything is pretty straight forward. I define a couple of namespaces (which imo is a good coding practice), create a display function for my custom field, create the JSLink templates and finally registers the templates with the TemplateManager. I will not dive deep into the JSLink stuff, but if you need a really good guide I urge you to read Martin Hatch’s JSLink series.

Once this little solution is deployed and the features activated we can add the field to a list and it should work pretty fine, unless we have MDS enabled on our site and start navigating back and forth between pages within the site. Once you navigate away from the list, containing this custom field, and navigate back using MDS page transitions it will stop using our custom template. Not funny..

The Solution to these problems

Fortunately there is a solution to this problem and there are two things that are really important.

The RegisterModuleInit method

The first reason that our field does not use the template that we defined in JavaScript is due to the fact that we register the templates with the field only when the JavaScript is loaded and executed. When navigating, using MDS, to another page this registration is reset and the JavaScript file is not evaluated again. So we need to find a way to register the module each and every time that page is used. Traditional web browsing, with full page reloads, allows us to use jQuery $(document).ready or the SharePoint function _spBodyOnLoadFunctionNames.push() to do these kind of things. But, they only do stuff when the page load event is fired – and an MDS page transition does not trigger that event.

The SharePoint team has of course not forgotten about this scenario and has given us a JavaScript function called RegisterModuleInit(). This method is specifically designed for these kind of scenarios, we can use it to add methods that are to be executed whenever a page transition in MDS is done. The RegisterModuleInit() function takes two parameters; the first one is the path the the JavaScript file that is associated with the function to execute and the second parameter is the function to execute. One really important thing is to note that the the path to the JavaScript file must be exactly the same as used when registering it, so depending on if it’s loaded from the Layouts folder, a folder within the site etc you have to make sure to use the exact same path in the RegisterModuleInit().

Let’s rewrite the last part of our JavaScript file and replace the line that registers the templates with these lines:

Wictor.Demos.Functions.RegisterField = function () {
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(Wictor.Demos)
}

RegisterModuleInit(
  _spPageContextInfo.siteServerRelativeUrl + 'Wictor/NumberWithColor.js', 
  Wictor.Demos.Functions.RegisterField)

Wictor.Demos.Functions.RegisterField()

I’ve encapsulated the template registration into a new function, called RegisterField(). We then use the RegisterModuleInit() function to register this function to be executed whenever our JavaScript file is used on the page. The _spPageContextInfo object is used to get the site relative URL to which we append the relative path to where the JavaScript file is deployed. Finally we execute the RegisterField() function directly, since the RegisterModuleInit() only handles upcoming page transitions.

If we now try this on an MDS enabled site you will quickly notice that you get JavaScript errors the second time we visit the list with this custom field, it should say something like below if you have a debugger attached or configured. In worst case MDS will notice that there is a JavaScript error and silently reload the page causing a second page load and reducing performance (you will then likely also see another JavaScript error, that we’ll talk about in a bit).

Error: Unable to get property 'Demos' of undefined or null reference

Looks like there’s something wrong with our namespaces!

The Garbage Collecting issue

The second issue requires some understanding of the Minimal Download Strategy and knowing that MDS actually has a built-in garbage collector (you didn’t see that coming right!). MDS will when doing a page transition clear up window scoped variables and delete them. This is a good thing, just imagine the number of potential JavaScript objects and structures that might have been created and stored in memory if you’re working within a site and jumping back and forth between pages. The good thing is that it will not delete objects that are properly registered as namespaces, and with that I mean Microsoft Ajax namespaces. Let’s go back to our very first sample, the one with a JSLink field. Remember I created a number of namespaces in the JavaScript file to hold my templates and functions. If I change the very first namespace definition in that file from:

var Wictor = window.Wictor || {}

To instead utilize the Microsoft Ajax Type.registerNamespace() function like this we will be golden:

Type.registerNamespace('Wictor')

Try that, redeploy your JavaScript with both the RegisterModuleInit() function and the Type.registerNamespace() declaration and you will see that (almost) everything executes just as expected. The field will render just as we want even though we navigate back and forth from the list containing the custom field.

Getting it to work without MDS as well

When disabling MDS on the site, or when using the “normal” URL to the list with the custom field, when a JavaScript occurs like above and on some other occasions your page will do a full page load, that is not an MDS page transition, you will get a JavaScript error that states:

Error: '_spPageContextInfo' is undefined

In this case the JavaScript object that we use to get the site relative URL is not created and does not exist. You will not get this error while doing MDS page transitions, since that object is created on the first page load. So how do we handle this situation?

Since we don’t have the _spPageContextInfo object on the page, then we cannot do the RegisterModuleInit() move. But on the other hand if we get into this situation, we’re not in “MDS mode” and does not need it…clever huh! Also note, that we can get around this error by not using a site relative path and deploying stuff into the Layouts folder – but try to do that in the cloud. Let’s rewrite the last part again:

Wictor.Demos.Functions.RegisterField = function () {
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(Wictor.Demos)
}

Wictor.Demos.Functions.MdsRegisterField = function () {
    var thisUrl = _spPageContextInfo.siteServerRelativeUrl
		+ "Wictor/NumberWithColor.js";
    Wictor.Demos.Functions.RegisterField();
    RegisterModuleInit(thisUrl, Wictor.Demos.Functions.RegisterField)
}

if (typeof _spPageContextInfo != "undefined" && _spPageContextInfo != null) {
    Wictor.Demos.Functions.MdsRegisterField()
} else {
    Wictor.Demos.Functions.RegisterField()
}

We still have a function for registering the field with the template manager, exactly the same as previously, then we introduce another method that is only used when MDS is enabled and we’re in MDS mode, that method uses the _spPageContextInfo to register the script to run for each MDS page transition. Finally we do a check in our JavaScript that if the _spPageContextInfo exists, then use our MdsRegisterField method otherwise just call the function that registers the template.

Our full JavaScript should now look something like this:

Type.registerNamespace('Wictor')
Wictor.Demos = Wictor.Demos || {};
Wictor.Demos.Templates = Wictor.Demos.Templates || {}
Wictor.Demos.Functions = Wictor.Demos.Functions || {}

Wictor.Demos.Functions.Display = function (context) {
	var currentValue = context.CurrentItem.WictorNumber
	if (currentValue > 0) {
		return currentValue;
	}
	else {
		return '<span style="color:red">' + currentValue + '</span>'
	}
}

Wictor.Demos.Templates.Fields = {
	'WictorNumber': {
		'DisplayForm': Wictor.Demos.Functions.Display,
		'View': Wictor.Demos.Functions.Display,
		'NewForm': null,
		'EditForm':  null
	}
}

Wictor.Demos.Functions.RegisterField = function () {
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(Wictor.Demos)
}

Wictor.Demos.Functions.MdsRegisterField = function () {
    var thisUrl = _spPageContextInfo.siteServerRelativeUrl
		+ "Wictor/NumberWithColor.js";
    Wictor.Demos.Functions.RegisterField();
    RegisterModuleInit(thisUrl, Wictor.Demos.Functions.RegisterField)
}

if (typeof _spPageContextInfo != "undefined" && _spPageContextInfo != null) {
    Wictor.Demos.Functions.MdsRegisterField()
} else {
    Wictor.Demos.Functions.RegisterField()
}

Now, when we test this solution it should work with and without MDS enabled on the site, on all MDS Page transitions back and forth and we spare at least a handful of kittens using this code.

Summary

I’ve just shown you how you create a custom field rendering using JSLink that works with and without MDS. It requires you to pop in a set of additional JavaScript lines into each JavaScript file, but it is basically exactly the same JavaScript snippet each and every time. This solution does not only work for JSLink fields, it is valuable for delegate controls, web parts, ribbon customizations etc. How my life had been easier if this had been documented on MSDN twelve months ago…

PS: If you find any situation where this does not work, please contact me and I’ll try to extend the scenario to cover that as well.

13 Comments

  • Anatoly Mironov said

    Hi Wictor! Very good post! I’ve been also "bashing my head against the wall" about the MDS and non-SharePoint javascript code. For a while ago I wrote down my findings on my blog <a href="http://chuvash.eu/2013/06/18/make-javascript-code-work-with-minimal-download-strategy-part-1/">MDS part 1</a> and <a href="http://chuvash.eu/2013/06/26/make-javascript-code-work-with-minimal-download-strategy-part-2/">MDS part 2</a>. Those blog posts are from Juny 2013.

    The Minimal Download Strategy Feature is still a big issue in many projects. I appreciate your investigation. For me Garbage Collection functionality within the MDS was new. It explains a lot.

    Another point. JSLink and other scripts. While using JSLink we often write only code that is not dependant from other frameworks like jQuery. It is simpler to isolate our code. I managed to get jslink working with only _spBodyOnloadFunctionNames.push in my lab (in my blog post).

    Other scripts that are used for custom webparts and pages are more complex. I found that SharePoint internally uses $_global_ prefix for their functions that work with MDS. In an example I could get even knockout.js working with SharePoint MDS by just adding $_global_knockout wrapping function for knockout code. I've written more about it on my blog.

    I would appreciate to have a dialog about it and that we can spread knowledge about how to implement custom javascript code and still benefit of the SharePoint MDS.

  • Hugh Wood said

    I applaud your correct use of JavaScript Objects and functions. I won't mention the amount of times I have seen this implementation done incorrectly.

    Anatoly Mironov (Above) has made some great blog posts on this subject as well, however I do feel implementation isn't quite as solid as we could make it but at least now it is becoming more common knowledge on how to get JS to work with it.

    I am writing a blog post on SharePoint community at the moment that covers all the above for best practices of implementation of JavaScript into a SP2013 site and also best practices of JavaScript Performance in a SharePoint site. There are many practices in JavaScript which cause issues over time as they can build up and being a JavaScript heavy application I feel it's vital to be aware of them.

  • Anatoly Mironov said

    @Hugh Wood, I am looking forward to seeing your blog post. MDS and custom javascript code is still a big question. I wrote my posts for 4 months ago. The intention was to discover more. Maybe some statements can seem naïve today. As I see it, we have to get better understanding in SharePoint Community for how MDS works. Wictor has done a good work. But it is just a beginning. We also have to broaden the knowledge for different javascript solutions, including the 3rd party frameworks like jQuery, knockout, angular, jQuery UI and more.

  • Jomit said

    Excellent post Wictor. I always like how you explain things in your posts. I am a web developer turned SharePoint developer and I have always tried hard to stay away from typical 'SharePointisms' like this but it is interesting to see the abstractions & solutions that exists in the SharePoint world.

  • Christophe said

    Thanks for this post.

    Based on my tests, _spBodyOnLoadFunctionNames works fine with the minimal download strategy. After rendering the content, the MDS page runs a function called ProcessOnLoadFunctionNames to call these functions.

    If you have an example where _spBodyOnLoadFunctionNames doesn't work, I would definitely be interested to try it out.

  • David Mann said

    Christophe,

    In my testing, _spBodyOnLoadFunctionNames and ProcessOnLoadFunctionNames are only called because my browser is redirected out of the MDS page (i.e. its no longer using start.aspx) and instead loading my test page directly, so those functions are called by the direct (non-MDS) loading of the page.
    Are you seeing something different?

    Dave

  • Nachiket said

    I would like to know your feedback on converting the JSOM code in the foll. msdn article:
    http://msdn.microsoft.com/en-us/library/office/jj920104(v=office.15).aspx#bk_examplePersonPropsObj

  • harrison said

    Thanks for the blogpost!
    It should also be noted that _spPageContextInfo.siteServerRelativeUrl might not always end with a slash. I had to add a check and combine both urls with a slash

  • Ronald van Herk said

    Great post, but I cannot get it to work with SharePoint Online (Office 365 E3 Plan). Does anybody know if that should work, or needs another approach?

Comments have been disabled for this content.

About Wictor...

Wictor Wilén is the Nordic Digital Workplace Lead working at Avanade. Wictor has achieved the Microsoft Certified Architect (MCA) - SharePoint 2010, Microsoft Certified Solutions Master (MCSM) - SharePoint  and Microsoft Certified Master (MCM) - SharePoint 2010 certifications. He has also been awarded Microsoft Most Valuable Professional (MVP) for seven consecutive years.

And a word from our sponsors...