Loading JavaScript via CustomAction and Initializing Functions with SharePoint 2013’s Minimal Download Strategy

9 Apr

For any of you who have worked with the new SharePoint 2013 Minimal Download Strategy (MDS) and custom scripts, you have probably run into some of the same headaches as myself.

While “official” documentation on MDS is greatly lacking (surprise, surprise), there are some great articles out there that really helped me build a foundational understanding. However most are focused around the use of custom scripts in conjunction with JSLink and tend to assume all the JavaScript references are embedded in the master page. So if you are in the same boat, check out the my reference section at the bottom as I have listed some of the better articles that are great reads and very helpful.

Scenario

Current project requires implementing multiple host names site collections, each with their own branded custom master page, and include about 20 different core scripts that are needed for various features and functionalities (such as jQuery, Knockout, Bootstrap, Amplify, etc.). Each master page will then load client-side initialization functions for a handful custom features, the most common being a world clock.

Solution v1.0

From a maintenance standpoint, loading my scripts via Custom Actions in a single Site feature is a much better and simpler solution than creating a ScriptLink and listing out each of the 20 core scripts in all the master pages and then having to add/remove them when the project requirements change or additional enhancement phases are started.

Loading JavaScript via CustomActions

Specifically, I need a way that once the page is loaded that I am able to initialize and world clock component that is being shown on the page, which utilizes jQuery, Knockout, and a custom Utility class. To do so I have initialization scripts that are directly tied to each master page via a ScriptLink reference so that I can have a different Custom and System master page for each site and can fire off additional, master page specific initializations that might be needed, which means I can’t use a CustomAction for those scripts.

ScriptLink LoadAfterUI

Wall Ramming

Attempt 1

This concept works great when MDS is disabled or when Publishing is enabled as Publishing negates MDS. However, when using MDS, any time a partial load fires, noted by “/_layouts/15/start.aspx#” displaying in browser address bar, my initialization script is called before the CustomActions have fired to load the core scripts and subsequently errors out.

MDS Undefined Error

After reviewing a lot of different articles, the common thread was to create a non-anonymous function (in my case within a Singleton object) and register it with SharePoint’s RegisterModuleInit() function.

MDS Attempt 1

Note: As you can see above, I do have a delay in place to wait until the body and SP.js is loaded, so I can rely upon _spPageContextInfo to always be populated.

I would like to say that worked, that doesn’t cover everything situation, or rather only works when the MDS partial page load is in play. See also:


Attempt 2

Now that I have my solution partially responding, I wanted to figure out what the trigger was to get it to fully function as expected. Upon diving deeper into Anatoly’s post I saw he ran into a similar quirk and so I attempted to use the $_global_ trick that worked for him. I had some mixed results with this solution. My best guess is it has to do with my utilizing CustomActions to load my core scripts or during testing I didn’t add $_global_ to all my core scripts. Either way, it would have been A LOT of files to modify, including 3rd party scripts as well as providing training to the client on the concept was not ideal. So I figured there had to be another way.

After walking through a lot of trial and error debug sessions, and thanks to insight from Sridhar’s CSR-override on MDS post, the simplest solution I found was to check to see if my core utility object was already loaded and if so call my initialization function directly, otherwise I assume MDS is in effect and I register my function to fire once MDS has completed.

MDS Attempt 2

Resolution

Once I implemented the combination of Attempt 1 and Attempt 2, everything now appears to be functioning as expected. I am not sure if this concept will work when using JSLink, but again, here are some really great resources to reference:

VERY IMPORTANT: One thing I want to make sure to point out that the ScriptLink for the initialization script in the master page must include LoadAfterUI=”True” or it will not initiate correctly. I was reminded of this the hard way when I redeployed one of my master pages without this and my script stopped fired after a partial MDS load.

Final Result is that each and every time my core scripts are being loaded before my initialize initialization script so my world clock which is now displaying on both full and partial loads.

MDS Resolution

Summary

In this post my goal was to get a custom script to initialize after scripts loaded via CustomActions in conjunction with a site using Minimal Download Strategy (MDS). Here are key factors that are required in order to get this working:

  • Initialization script must be referenced in a ScriptLink that has the property of LoadAfterUI set to “True”
  • Initialization script’s primary function must be a named function and not anonymous
  • When calling the named initialization function, you must register it to fire after MDS is completed using the RegisterModuleInit() function


References

Advertisements

9 Responses to “Loading JavaScript via CustomAction and Initializing Functions with SharePoint 2013’s Minimal Download Strategy”

  1. slalomedd April 9, 2014 at 7:24 pm #

    Nice post. Thanks for sharing.

  2. Paulo Fagundes May 2, 2014 at 2:44 pm #

    Hi Justin,

    I have been following the same aproach to deploy my JS files ( Custom Actions), and all was working great in Publishing and non MDS sites.

    I was trying to use the same aproach in Team Sites, witch by default have de MDS feature activated and i was able to put it to work.

    After some testing i realized that when using MDS if i add JS files using CustomAction the page leaves the MDS mode ( no more start.aspx ). And this occurs because after the first correct request to a start.aspx url, e does another one to the same page, but without the start.aspx.

    Have you experienced the same issue? Do you get 2 request per page?

    Thanks
    Paulo Fagundes

    • Justin Fentress May 2, 2014 at 7:03 pm #

      Hi Paulo,

      I have run into what you have seen where it does appear to double load, first with just core SharePoint script files and then with all expected script files.

      However I have mixed results on my site collections and sub-sites. Some of them will always remove the start.aspx reference even when opening just the site’s URL (no aspx reference in address bar). But on other sites the load will alternate the start.aspx page reference, so first load it shows, second doesn’t show, third shows, fourth doesn’t, etc.

      What I have noticed is if you debug session each site will always have the ootb g_MinimalDownload variable set to true, which indicates it is at least attempting to use MDS.

      I haven’t dove a lot into this, but if you look through your ULS logs for MDSFailover or MDSLog, you might be able to track down the cause for the start.aspx not being consistently utilized.

      • Paulo Fagundes May 6, 2014 at 3:16 pm #

        Justin,

        I solved my issue. In the end the problem was about adding jQuery.

        In my solution, i deploy javascript files to the style library and use them with custom actions (all of them).

        My problem was related to the jQuery file i was using to include in the page – it had dots in the filename. After renaming it to simply jquery.js , everything went ok.

        Thanks for your pointers

  3. Shahnawaz Khan June 25, 2014 at 1:42 pm #

    Hi Justin,

    I ran into the same problem that you described above, my code works well in non MDS mode but not in MDS enabled site.

    When running in MDS mode all variables declared as object initializers are undefined!

    FYI, I added all JS files as Custom Actions.

    The variable that I want to use is declared as:

    var ProjectImplementation = {
    callBack: {},
    initSettings: function () { … },
    saveSettings: function(callback) { … }
    }

    Now if I want to call ‘ProjectImplementation.initSettings()’ in MDS mode then ProjectImplementation is undefined.

    However if I use Constructor Function then it works after initialize it again if undefined.

    I also used “RegisterModuleInit” in top of the JS file like below:

    RegisterModuleInit(“/SPFiles/js/SP/SPCommon.js”, ProjectImplementation.initSettings);

    But it states error like “ProjectImplementation” is undefined.

    If I call simple function like:

    RegisterModuleInit(“/SPFiles/js/SP/SPCommon.js”, TestFunc);

    function TestFunc() { alert(‘hello’); }

    It works every time and show me alert!

    Please suggest

    Thanks!

    Shahnawaz Khan

    • Paulo Fagundes June 25, 2014 at 2:21 pm #

      Hi Shahnawaz

      try it like this:

      function $_global_MySharePointUtils() {
      Type.registerNamespace(“MySharePoint”);

      MySharePoint.Utils = MySharePoint.Utils || {
      Init: function () {
      try {
      if (typeof TGFSharePoint.MasterPageUtils == “object”) {
      MySharePoint.MasterPageUtils.SetLogoUrl();
      }
      }
      catch (e) {
      console.error(“MySharePoint.Utils.Init: \n” + e.message);
      }
      },

      InitMDS: function () {
      try {

      ExecuteOrDelayUntilScriptLoaded(
      function () {
      RegisterModuleInit(_spPageContextInfo.siteServerRelativeUrl + “/Style Library/Scripts/MasterPage_InjectedJSUtils.js”, TGFSharePoint.Utils.MDSUpdate);
      }
      , “core.js”);
      }
      catch (e) {
      console.log(“MySharePoint.Utils.InitMDS: \n” + e.message);
      }
      },

      MDSUpdate: function () {
      try {

      if (typeof TGFSharePoint.MasterPageUtils == “object”) {
      TGFSharePoint.MasterPageUtils.SetTopLinks();
      }
      }
      catch (e) {
      console.error(“MySharePoint.Utils.MDSUpdate: \n” + e.message);
      }
      },
      };
      MySharePoint.MasterPageUtils = MySharePoint.MasterPageUtils || {
      SetLogoUrl: function(){
      alert(“SetLogoUrl”);
      },
      SetTopLinks: function(){
      alert(“SetTopLinks”);
      }
      };
      }

      jQuery(document).ready(function () {

      $_global_MySharePointUtils();

      MySharePoint.Utils.InitMDS();
      });

      Ping me if you have questions.

      Paulo Fagundes

      • Shahnawaz Khan June 26, 2014 at 10:18 am #

        Hi Paulo,

        Thanks for your instant help, I am trying that out, but I can’t figure out ‘TGFSharePoint’ is there anything I missed in code sample or it just miss spelled, I think it should be ‘MySharePoint’.

        Please suggest!

        Kind Regards!

    • Justin Fentress June 25, 2014 at 2:24 pm #

      Hi Shahnawaz,

      Sorry to hear you are having troubles getting things to work. I can’t see anything that initially jumps out to me from what you posted so I am going to email you directly so we can work through your code together to iron out the kink(s) and then we can post the resolution on the site.

      • Paulo Fagundes June 26, 2014 at 10:21 am #

        Yes, Shahnawaz Khan, it should be ‘MySharePoint’ whenever you see ‘TGFSharePoint’.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: