0

For example I have a solution with three files in module:

enter image description here

Loader.html:

<div id="exampleDiv" style="background-color: yellowgreen">click here..</div>

<script type="text/javascript" language="javascript" src="../Style%20Library/TestSpSOD/jquery-3.1.1.min.js"></script>
<script type="text/javascript" language="javascript" src="../Style%20Library/TestSpSOD/consoleHello.js"></script>

jQuery and my javascript file, addClickEvent.js:

"use strict";

function addClickEvent() {
    jQuery("#exampleDiv").on("click", function () { (alert("Hello!")) });
}

// if scripts are loaded from hardcoded root site collection
_spBodyOnLoadFunctionNames.push("addClickEvent");

I refer this script in CEWP on root site collection like

/sites/SiteColection/Style Library/TestSpSOD/Loader.html

and all this works.

But it will not work on subsites, I need to write something like "src="../../../Style%20Library/TestSpSOD/consoleHello.js" and change the path every time

I want to load jQuery.js and my javascript file dynamically:

<div id="exampleDiv" style="background-color: yellowgreen">click here..</div>

<script type="text/javascript" language="javascript">

    function moduleLoader() {

        var url = _spPageContextInfo.webServerRelativeUrl + "/Style%20Library/TestSpSOD/";

        var jqueryScript = document.createElement('script');
        jqueryScript.src = url + "jquery-3.1.1.min.js";
        jqueryScript.type = "text/javascript";
        document.getElementsByTagName('head')[0].appendChild(jqueryScript);

        var myScript = document.createElement('script');
        myScript.src = url + "consoleHello.js";
        myScript.type = "text/javascript";
        document.getElementsByTagName('head')[0].appendChild(myScript);
    }

    // if scripts are loaded dynamically
    //
        SP.SOD.executeOrDelayUntilScriptLoaded(moduleLoader, 'sp.js');
    //
    </script>

But than my _spBodyOnLoadFunctionNames.push("addClickEvent"); does not work. Also if I check this code in FF it sometimes says "jQuery not defined", and sometimes doesn't say it. Also tried to do something like

    // if scripts are loaded dynamically
    SP.SOD.executeOrDelayUntilScriptLoaded(moduleLoader, 'sp.js');
    SP.SOD.executeOrDelayUntilScriptLoaded(addClickEvent, 'sp.js');

but it does not work.

How to load these scripts correctly?

upd.: Thank You all guys for answers! All of them are very interesting! I used method that Cecilia reccomended (added loading html and css):

<script type="text/javascript" language="javascript">
    var url = _spPageContextInfo.siteAbsoluteUrl + "/Style%20Library/TestSpSOD/";

    // add html markup
    document.write("<div id='exampleDiv'>");
    document.write("click here..");
    document.write("<\/div>");

    // add css
    document.write("<link rel='stylesheet' type='text\/css' href='" + url + "" + "\/styles\/stylesheet.css'><\/script>");

    // add js
    document.write("<script src='" + url + "jquery-3.1.1.min.js'><\/script>");
    document.write("<script src='" + url + "addClickEvent.js'><\/script>");

    ExecuteOrDelayUntilScriptLoaded(function () {
        addClickEvent();
    }, "sp.js");

</script>

It works on root and on subwebs on my dev and test and in the cloud (Mohamed Derhalli commented that using will break MDS if it's used). Now I don't need to change code every time

4
  • 1
    Is your feature Web or Site scoped ?
    – Cecilia
    Commented Dec 28, 2016 at 15:41
  • Site scoped. Think it should be Site-Scoped. If I switch to Web-scoped then this feature will try to search "Style Library" on child web, but there is no "Style Library" on child web.
    – Gennady G
    Commented Dec 28, 2016 at 15:50
  • 1
    Note that document.getElementsByTagName('head')[0] is 'I don't know Jack about the DOM' code for document.head ... it was needed in IE8 because Microsoft is always late with implementing standards Commented Dec 28, 2016 at 16:06
  • Thank You all very much for great answers! I will play with all the tips you provided and keep you posted! Suggested it is asynchronious but didn't think it it so.. nontrivial to load js scripts in SharePoint.. :)
    – Gennady G
    Commented Dec 28, 2016 at 17:06

3 Answers 3

3

Even if this is not the best method to link the js files, I think you could replace the webServerRelativeUrl property with siteAbsoluteUrl like this:

var url = _spPageContextInfo.siteAbsoluteUrl + "/Style%20Library/TestSpSOD/";

Instead of appending a child to the header you can directly write on the document like this:

document.write("<script src='" + url + "jquery-3.1.1.min.js'><\/script>");
document.write("<script src='" + url + "addClickEvent.js'><\/script>");

and call your custom functions like this:

ExecuteOrDelayUntilScriptLoaded(function () {
    moduleLoader();
    addClickEvent();
}, "sp.js");

Or you can use another solution like this:

<div id="exampleDiv" style="background-color: yellowgreen">click here..</div>

<script type='text/javascript'>

    ExecuteOrDelayUntilScriptLoaded(function () {

        var url = _spPageContextInfo.siteAbsoluteUrl + "/Style%20Library/TestSpSOD/";

        SP.SOD.registerSod("addClickEvent.js", url + "addClickEvent.js");
        SP.SOD.registerSod("jquery-3.1.1.min.js", url + "jquery-3.1.1.min.js");

        //Tell SOD to load jquery first
        SP.SOD.registerSodDep("addClickEvent.js", "jquery-3.1.1.min.js");

        SP.SOD.executeFunc('addClickEvent.js', null, function () {
            //This will run, but not until SP loads its jquery dependency!
            addClickEvent();
        });

    }, "sp.js");

</script>
2
  • Note that referencing scripts using <script> will break MDS if it's used. Commented Dec 28, 2016 at 18:33
  • Cecilia thanks much for great answer! First method works like a charm, in all environments! :) I'll update my question with Your answer
    – Gennady G
    Commented Dec 29, 2016 at 10:40
2

The sure way to get scriptfiles loaded is to load them as UserCustomAction ScriptLinks

See all ScriptLink posts here on SO: https://sharepoint.stackexchange.com/search?q=scriptlink

This technique has been around since SP2010, yet most blogs continue with SOD or stuffing links in the MasterPage

https://chrome.google.com/webstore/detail/chrome-sp-editor/ecblfcmjnbbgaojblcpmjoamegpbodhd

Is a Chrome Browser extension that helps you assign Site or Web Scoped ScriptLinks without any coding (you only need to do this once)

These scripts load way before most SharePoint scriptfiles, so great for jQuery and the like scripts.

Note that JSLink on SharePoint Online can also reference any https link, so you could use a JSLink setting on a WebPart as well to load jQuery from a CDN
Files in JSLink connections are loaded fairly late, check the head of any page

All this stuff does not work on the new Modern Experiences,
Microsofts "We are working" on it response, has not changed since May 2nd
You can cast your "C'mmon Microsoft, you gotta fix this!!!" vote here:

https://sharepoint.uservoice.com/forums/329214-sites-and-collaboration/suggestions/13385364-allow-javascript-customization-and-css-branding-th

3
  • Thank You Danny for answer! I have voted :) In my case I try to use csripts only in my webpart and do not affect the entire page. Thanks for chrome extension link. I knew only about "Cisar" extension by Andrey Markeev before: chrome.google.com/webstore/detail/cisar/… (it allows to add custom CSR as I understand)
    – Gennady G
    Commented Dec 29, 2016 at 10:57
  • github.com/andrei-markeev/cisar
    – Gennady G
    Commented Dec 29, 2016 at 10:59
  • 1
    Cisar is the best thing since sliced bread. It inspired me to create a DEV environment using 100% browser only technologies for the upcoming SPFx release Commented Dec 29, 2016 at 11:49
1

If you want to dynamically load your scripts into CEWP and don't want to load them for every page of the site (JSLink) you can use one of the following approaches.

Let me first explain why your code doesn't work.

All scripts you are adding after page load with document.getElementsByTagName('head')[0].appendChild(myScript); by default are loaded asynchronously.

That's very easy to check - create a file index.html:

<html>
    <head>
        <script type="text/javascript">
            var s = document.createElement("script");
            s.setAttribute("src","script.js");
            document.head.appendChild(s);
            console.log("done");
        </script>
    </head>
    <body>
    </body>
</html> 

Inside script.js put console.log('Hi'). Output will be done hi.
That's why sometimes jquery is undefined for you - when the consoleHello.js loads before jquery (because of async nature explained above).

Now approaches:

  • Use XMLHttpRequest to get your file and provide text property instead of src:
// get some kind of XMLHttpRequest
var xhrObj = createXMLHTTPObject();
xhrObj.open('GET', "script.js");
xhrObj.send();
.....
// add the returned content to a newly created script tag
var se = document.createElement('script');
se.type = "text/javascript";
se.text = xhrObj.responseText;
document.getElementsByTagName('head')[0].appendChild(se); 

You need to load your scripts one by one or simultaneously, but add jquery to document first.

  • set async=false for script tag:
function loadScriptSync (src) {
    var s = document.createElement('script');
    s.src = src;
    s.type = "text/javascript";
    s.async = false;                                 // <-- this is important
    document.getElementsByTagName('head')[0].appendChild(s);
} 

Caveats: sync loading is generally bad idea in modern web development. IE9 and below doesn't support async

  • In the past I've created my own async script loader. Here the full code:
var JSLoader = function (pathToScript, successCallback) {
        var script = document.createElement("script");
        script.async = true;
        script.type = "text/javascript";
        script.src = pathToScript;
        var loaded = false;
        var succcessFunction = successCallback;
        this.load = function () {
            script.onload = script.onreadystatechange = function () {
                if ((script.readyState && script.readyState !== "complete" && script.readyState !== "loaded") || loaded) {
                    return;
                }
                loaded = true;
                script.onload = script.onreadystatechange = null;

                if (succcessFunction && typeof succcessFunction === "function") {
                    succcessFunction();
                }
            };
            var s = document.getElementsByTagName("script")[0];
            s.parentNode.insertBefore(script, s);
        };
};

Sample using (in your case):

var jqloader = new JSLoader(_spPageContextInfo.webServerRelativeUrl + "/Style%20Library/TestSpSOD/jquery.js", function () {
    // jquery loaded! Let the party begin! :)
    new JSLoader(_spPageContextInfo.webServerRelativeUrl + "/Style%20Library/TestSpSOD/consoleHello.js").load();
});
jqloader.load();
2
  • Thank You much for the answer! Interesting! What I noticed that if I use Your JSLoader and there are few CEWP webparts on the page - it breaks the others (if they load js in 'standard way' using <script src="..">)
    – Gennady G
    Commented Dec 29, 2016 at 10:51
  • 1
    Right, that's common problem. Ideally you should use some of the module systems (ie. CommonJS or AMD), and use module bundlers (webpack, browserify, etc.) in order to build isolated bundle without polluting global scope. Commented Dec 29, 2016 at 11:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.