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
Let’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.