This blog is part of the blog series SharePoint JavaScript Context Development
- Part 1 – SP.Init.js and SP.ScriptUtility
- Part 2 – SP.ScriptHelpers
- Part 3 – Minimal Download Strategy
- Part 4 – Async Delta Manager (This post)
- Part 5 – JavaScript Framework Governance
- Part 6 – jQuery vs SharePoint
Note: Parts 3 & 4 are linked. Please read in order.
Working with goldfish
If we are going to be feeding the goldfish with extra functionality in SharePoint (Injected JavaScript and Client-Side Rendering), we need to help them and ourselves by working with the garbage collector to provide a performant experience.
To do this our code needs to know when to:
- Tell ADM what to reinitialise
- Tell ADM what NOT to remove
This is an exceptionally useful approach when coupled with the Script Injection Pattern from Office PnP. This allows us as a developer to register a framework in our SharePoint sites, which includes all our common methods for any Display Templates and/or other in page Apps that we may have across our sites.
How to register with the Async Delta Manager/Minimal Download Strategy with an example framework bootstrap
The basic concept gives us a flow for our code. 1) Register the initialization method and 2) Use and Register a NameSpace.
There are 2 ways to register your initialization method. First is by using the RegisterModuleInit method directly, or letting SOD decide to call RegisterModuleInit depending on the state of MDS at that time. (Note: MDS will be off on a publishing site regardless)
The second method is my preferred method as it helps to also enforce the second point of requiring a namespace.
You have probably been told to use a Namespace a) your code can collide with other variables and b) you can risk losing information you don’t want to reset. The premise of this is simple. A correctly registered namespace will have a correctly named initialization method, SOD will see this registered and automatically register it with MDS for you.
This means that your code looks simpler and follows the same standard as Microsoft’s own SharePoint JavaScript code.
Example framework for SP.SOD and MDS usage patterns.
Below I have written an example framework bootstrap. This uses Scripts on Demand and when activated it will automatically register with the Minimal Download Strategy’s Async Delta Manager. It’s not a complete framework bootstrap in this example, but the patterns used are taken directly from Microsoft code in SharePoint 2013 SP1 which you can copy into your own code to provide the functionality you will need across SharePoint 2013/365. To allow you to follow each component in context I have heavily commented on the code used, and if you were at SharePoint Saturday UK you may recognize this example as I used it there, and it will be also a very small part of my presentation at SharePoint Saturday Stockholm.
// Create a global pattern for MDS module init registration (function $_global_rencore() { // Short object notation module registration var rencoreAB = function (my) { // Sealing pattern to allow modules to see frameworks // private variables when spread across multiple files var _private = my._private = my._private || {}, _seal = my._seal = my._seal || function () { delete my._private; delete my._seal; delete my._unseal; }, _unseal = my._unseal = my._unseal || function () { my._private = _private; my._seal = _seal; my._unseal = _unseal; }; // Short object notation private variables var _private = { // Either a fixed path/cdn/auto calculation of the script path installationFolder: "_layouts/rencore/js/", revision: "1.0a" }; var _public = { // Example SOD implementations for a SharePoint context framework RegisterScript: function(filename, key, dependencies, ondemand) { RegisterSod(key, _private.installationFolder + filename + "?v=" + _private.revision); j = dependencies.length; while (j--) { // Register dependencies for our script RegisterSodDep(key, dependencies[j]); } // Allow our scripts to be loaded in sync as they are registered if (typeof ondemand != "undefined" && ondemand) { rencore.GetScript(key, null, function() { }, true); } }, GetScript: function(key, namespace, callback, bSync) { // Apply sealing pattern to callback _unseal(); var callback = function() { callback.call(); // Async method so we have externalised unseal // This prevents memory leaks rencore._seal(); }; if (typeof key == "string") { // Ensure script is called from the server, and attach a loaded event SP.SOD.executeFunc(key, namespace, function() { // this only executes on loading of a namespace }, bSync); // This executes when NotifyScriptLoadedAndExecuteWaitingJobs is called SP.SOD.executeOrDelayUntilScriptLoaded(callback, key); } else if (typeof key.length != "undefined") { // Sometimes we want to load multiple scripts in parrellel // We can do this by accepting an array of keys and using loadMultiple SP.SOD.loadMultiple(key, callback, bSync); } else { return false; } return true; }, NotifySOD: function(key) { // Ensure that the bootstrap has loaded, if this is wrong then script load order is incorrect if (typeof (NotifyScriptLoadedAndExecuteWaitingJobs) == "function") { NotifyScriptLoadedAndExecuteWaitingJobs(key); } else { throw "SP Context not found."; } }, // Create a custom ready method compatible with MDS Ready: function(funcName) { // If our page is loaded then async execute the method if (typeof _spBodyOnLoadCalled == 'undefined' || _spBodyOnLoadCalled) { window[funcName](); } else { // Otherwise wait for the body/ADM deltas to load _spBodyOnLoadFunctionNames.push(funcName); } } }; // Externalise our public methods and members return _public; } // Make our module public now it is loaded window.rencore = rencoreAB({}); // Register rencore namespace and create the rencore object Type.registerNamespace("rencore"); // Register other scripts in the framework, with their dependencies rencore.RegisterScript("rencore.security.js", "rencore.security", ["rencore"], true); // This time we require rencore.js to be finished loaded, and we also want to fetch the Cross Domain Ajax Library rencore.RegisterScript("rencore.performance.js", "rencore.performance", ["rencore", "sp.requestexecutor.js"], true); // Automatically execute our init method incase MDS is turned of })(); // Notify that our script has loaded rencore.NotifySOD("rencore");
So what are the important features in the code?
The initialisation method naming is a strict pattern $_global_namespace_plus_parts(). You may have seen this mentioned in blogs by other developers, but its importance is quite substantial with MDS. This naming convention must match the filename of the file, and be the name of the namespace plus parts. For example
Rencore.js
Type.RegisterNamespace(“rencore”);
(function $_global_rencore() {…
And
Rencore.Performance.js
Type.RegisterNamespace(“rencore.Performance”);
(function $_global_rencore_performance() {
Naming your initialization method this way as I stated earlier will automatically call the initialization method, and prevent your namespace from being deleted by MDS. Then internally you can handle what variables are updated and what isn’t.
We also have error checking, and custom-ready methods to allow us to work with the ADM, we can use private variables as internals with our sealing pattern, and it also implements the module pattern making it flexible which allows our goldfish to remain happy and oblivious to our work.
It’s also important to note that this is a small script. It’s the base functionality for a framework in SharePoint 2013 and it allows you to load up the other components as you need them, when you need them. This allows you to stay ahead of the curve and heavily reduce page load times by spreading the modules loaded across several files. This wouldn’t be possible with our new friend the ADM.
Sharks still swim
I have presented my case for the use of MDS, SOD, CSR, and our new friend ADM. But many people out there are still unsure, or unaware to its benefits, and how to use it correctly. So please use this blog post as a start for their path to enlightenment. Remember people are turning into goldfish, without MDS you will alienate more people than you could imagine because it took more than 400ms to load.
Thank you for reading this longer than a normal blog post, and next time I assure you it won’t be so long. I will be doing a quick dive into Framework governance and then going back to the world of Script# written classes in SharePoint JS, and finding another bunch of helpful methods for you to further reduce the number of lines of code you have to write in SharePoint JS.