1
votes

I'm implemeting a very simple use case, and yet not only do I not find a solution, but I can't find any article that talks about it, as if I was the only one.

I want my custom Javascript to execute on every page of a given SharePoint site.

Easy, you'll say. Well, no. Far from it, like always with SharePoint.

Steps to reproduce :

  • Create a out-of-the-box publishing site
  • Include the custom javascript below using any of the means I describe below
  • Go to the site, to the home page. It's a publishing site, so by default you should have the left navigation pane with at least "Home" and "Documents" by default.
  • The first time you load the page, the javascript executes. Now, click on "documents". The page changes but the Javascript is not executed.

That's because SharePoint uses Ajax. Even if the MDS is disabled. It uses Ajax through the hash ( # ) in the URL.

For example, it transforms a very inocuous link like this one :

< a href src="/SitePages/Home.aspx">

into this URL when you click it:

https://your-url/sites/your-site/_layouts/15/start.aspx#/SitePages/Home.aspx

Here is my Javascript :

if (ExecuteOrDelayUntilScriptLoaded && _spBodyOnLoadFunctionNames) {
    _spBodyOnLoadFunctionNames.push(ExecuteOrDelayUntilScriptLoaded(
        function () {
            alert("It's working!");
        }, "sp.js"));
} 

So, I've tried the following ways of including the Javascript :

  1. Through a User Custom Action. I've used this very handy page to add it, but that's not relevant. The action is added to the site and I can see the JS in the DOM on first load. But then after I click on a link in the page and after SP uses Ajax, it does not execute it again.
  2. By modifying the master page -- namely: seattle.html. at first I included it this way, simply under other native inclusions :

<head runat="server">

...

<!--SPM:<SharePoint:ScriptLink language="javascript" name="suitelinks.js" OnDemand="true" runat="server" Localizable="false"/>-->

<!--SPM:<SharePoint:ScriptLink language="javascript" Name="~sitecollection/SiteAssets/MYJAVASCRIPT.js" runat="server"/>-->

But then I read about AjaxDelta (here : https://msdn.microsoft.com/fr-fr/library/office/dn456543.aspx ) , and I moved my inclusion (still in the header) into < AjaxDelta >, like this :

<head runat="server">

...

<!--SPM:<SharePoint:AjaxDelta id="DeltaPlaceHolderAdditionalPageHead" Container="false" runat="server">-->

<!--SPM:<asp:ContentPlaceHolder id="PlaceHolderAdditionalPageHead" runat="server"/>-->

<!--SPM:<SharePoint:DelegateControl runat="server" ControlId="AdditionalPageHead" AllowMultipleControls="true"/>-->

<!--SPM:<SharePoint:ScriptLink language="javascript" Name="~sitecollection/SiteAssets/MYJAVASCRIPT.js" runat="server"/>-->

<!--SPM:</SharePoint:AjaxDelta>-->

...and yet nothing works. The Javascript is never executed when switching between pages of the same site by clicking on SharePoint's "managed" links.

I'm looking for a solution that handles elegantly SharePoint's Ajax, not something heavy and risky that hijacks every hyperlink on a page. For example I've tried to hook my code onto ajaxNavigate methods (for example : addNavigate) but I'm not sure I understand what's actualy going on there and if it could be of any help to me.

EDIT :

  • There seems to be a consensus (for example, here at the very bottom) that User Custom Actions get executed no matter what -- because SharePoint allegedly places their ScriptLink into the AjaxDelta for some reason. Well, that's not what I witnessed.

  • There's another consensus that this issue can be adressed by using "RegisterModuleInit". This doesn't work for me either.

I'm extermely puzzled. I think those two solutions do address navigation issues when the user clicks on a link and then clicks "back". But it does NOT address SharePoint's clever "managed", Ajax-riddled, hyperlinks.

1

1 Answers

0
votes

I've finally found a solution that never seems to fail so far. That's a real relief.

Short answer: use asyncDeltaManager.add_endRequest

This MSDN discussion suggests a simple way to implement it: https://social.msdn.microsoft.com/Forums/office/en-US/1ae292b4-3589-46f6-bedc-7bd9dc741f1b/javascript-code-to-execute-after-all-the-elements-and-css-are-loaded?forum=appsforsharepoint

$(function () {
  ExecuteOrDelayUntilScriptLoaded(function () {
      if (typeof asyncDeltaManager != "undefined")
           asyncDeltaManager.add_endRequest(MYCUSTOMCODE); //execute it after any ajax event
      else 
           MYCUSTOMCODE(); //execute it at first load
  }, "start.js");
});

This shows how to include it properly in SharePoint's cycle (with ExecuteOrDelayUntilScriptLoaded ) https://sharepoint.stackexchange.com/questions/171490/javacript-only-executed-on-first-page-load

Full-blown solution (objet "LefeCycleHelper"), by Mx https://sharepoint.stackexchange.com/questions/192974/where-to-place-a-js-script-with-whom-i-need-to-get-an-div-id/193009#193009

//use an IIFE to create a scope and dont dirty the global scope
(function (_) {

// use strict to ensure we dont code stupid
'use strict';

var initHandlers = [];
var initMDSHandlers = [];

var ensureSharePoint = function (handler) {
    var sodLoaded = typeof (_v_dictSod) !== 'undefined' && _v_dictSod['sp.js'] != null && _v_dictSod['sp.js'].state === Sods.loaded;

    if (sodLoaded) {
        handler();
    } else {
        SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function () { });
        SP.SOD.executeOrDelayUntilScriptLoaded(handler, 'sp.js');
    }
};

var initMDS = function () {
    for (var i = 0; i < initMDSHandlers.length; i++) {
        initMDSHandlers[i]();
    }
};

var init = function () {
    // Register MDS handler
    if ('undefined' != typeof g_MinimalDownload && g_MinimalDownload && (window.location.pathname.toLowerCase()).endsWith('/_layouts/15/start.aspx') && 'undefined' != typeof asyncDeltaManager) {
        asyncDeltaManager.add_endRequest(initMDS);
    } else {
        for (var i = 0; i < initHandlers.length; i++) {
            initHandlers[i]();
        }
    }
};

var registerInit = function (handler) {
    initHandlers.push(handler);
};

var registerInitMDS = function (handler) {
    initMDSHandlers.push(handler);
};

var domReady = (function (handler) {
    var fns = [];
    var listener;
    var loaded = (document.documentElement.doScroll ? /^loaded|^c/ : /^loaded|^i|^c/).test(document.readyState);

    if (!loaded) {
        document.addEventListener('DOMContentLoaded', listener = function () {
            document.removeEventListener('DOMContentLoaded', listener);
            loaded = 1;
            while (listener = fns.shift()) listener();
        });
    }

    return function (fn) {
        loaded ? setTimeout(fn, 0) : fns.push(fn);
    };
})();


var attachToLoad = function (functionToAttach) {
    registerInit(functionToAttach);
    registerInitMDS(functionToAttach);
   domReady(function () {
       init();
    });
};

_.AttachToLoad = attachToLoad;

// THIS WILL PROTECT YOUR GLOBAL VAR FROM THE GARBAGE COLLECTOR
window.LifeCycleHelper = _;
if (window.Function != 'undefined' && typeof (Function.registerNamespace) == 'function') {
    Function.registerNamespace('LifeCycleHelper');
}
})({});

var theCodeYouWantToRun = function () {
    alert('theCodeYouWantToRun');
};

window.LifeCycleHelper.AttachToLoad(theCodeYouWantToRun);