Sunday, May 1, 2016

A Better Pattern for Client Side Rendering Scripts

Recently I did some work for a client that required combining multiple List View Web Parts on one page, and using Client Side Rendering (CSR) to apply custom styling to each of the the various views. The site was a Team Site that had Minimal Download Strategy (MDS) enabled, and this led to some interesting challenges. The MDS feature is supposed to speed up browsing by having the client only process the differences between the current page and the new page. However, this can lead to situations where custom CSR scripts do not get executed correctly.

In a nutshell, MDS registers JavaScript files as it downloads/executes them, and on a subsequent page load, if the request calls for the same file, the MDS engine will check the list, see that it already has the script, and not download/execute it a second time. Thus, the CSR code is not run and the custom rendering is not applied.

There is a way around this. SharePoint provides a function called RegisterModuleInit() which you can use to tell MDS that a particular function from a particular file should always be executed. RegisterModuleInit() takes two arguments – the path of the file that has the function to execute, and the name of the function. Now, let’s take a look at one of the popular patterns for constructing CSR template overrides, which happens to be the one I used up until this project:

  1. (function () {
  2.     var overrideCtx = {};
  3.     overrideCtx.Templates = {};
  4.     overrideCtx.Templates.Item = customItem;
  5.  
  6.     SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
  7. })();
  8.  
  9. function customItem(ctx) {
  10.     var itemHtml = "";
  11.     // do whatever processing is necessary
  12.     // here to generate the custom html
  13.     return itemHtml;
  14. }

We can see two things here:

1. The template overrides are defined and registered with the template manager in an anonymous self-executing function, thus there is no named entry point to register with RegisterModuleInit(). In order to use RegisterModuleInit() to get the code functioning properly with MDS, the template override code needs to be changed to have a named entry point.

2. The customItem() function is defined in the global namespace. Keep in mind, for this project I was working with several template override files being loaded on the same page, many with multiple rendering override functions defined. If not handled correctly, the global namespace was going to get polluted.

So, in order to fix both of these problems, I came up with the following pattern, which I now use for all of my CSR template override scripts:

  1. var DEC = DEC || {};
  2.  
  3. DEC.thisOverride = (function () {
  4.     function customItem (ctx) {
  5.         var itemHtml = "";
  6.         // do whatever processing is necessary
  7.         // here to generate the custom html
  8.         return itemHtml;
  9.     }
  10.  
  11.     return {
  12.         render: function () {
  13.             SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
  14.                 Templates: {
  15.                     Item: customItem
  16.                 }
  17.             });
  18.         }
  19.     }
  20. })();
  21.  
  22. // register for MDS enabled sites
  23. RegisterModuleInit(SPClientTemplates.Utility.ReplaceUrlTokens("~site/SiteAssets/Scripts/myOverrideScript.js"), DEC.thisOverride.render);
  24.  
  25. // fallback for non-MDS enabled sites
  26. DEC.thisOverride.render();

Let’s look at what’s going on in the code.

First, I register (or retrieve) my own namespace, so I can stay organized (line 1).

Second, I add a property thisOverride (line 3). I named it generically for this example, but the name of this property should reflect the specific SharePoint asset you are applying the override to. That way many overrides can co-exist on the same page within the custom namespace (e.g. DEC.calendarOverride, DEC.contactsOverride, DEC.customFieldXOverride, DEC.customListYOverride).

I then use the revealing module pattern to create a closure in which I contain all the functions I might need for the custom rendering. By keeping those functions private, I can still use the same internal naming convention for the actual override functions (e.g. customItem, render, etc.).  The only publicly exposed member is the render function which registers the overrides with the template manager, and becomes the named entry point.

Lastly, I register the entry point function with the MDS system (line 23), and provide a fallback for non-MDS enabled sites (line 26), ensuring that the template overrides will get applied no matter what.

Notice also that the SPClientTemplates.Utility namespace offers a function to replace URL tokens, so you can use tokens when defining the path to your override file. This allows for much greater flexibility when including the CSR script as part of a feature that may be activated on a number of sites.

So there you have it – a CSR template override pattern that is flexible enough to work on MDS enabled or disabled sites, while maintaining responsible use of the global namespace.

No comments:

Post a Comment