10

I have a script that knows to load dynamiclly scripts that contains javascript classes. i'm loading the class script using the following code:

var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "myscript.js";
head.appendChild(script);

i'm then trying to create the new class using eval:

var classObj = eval(" new MyClass()" );

the problem is that the code of the eval is being executed bofre the script has been loaded into memory and i get an error that the MyClass is undefined.

Is there a way to synch these events? i need to make sure the script is fully being loaded into memory before i can start allocating classes from it.

Amir
  • 1,219
  • 3
  • 17
  • 31
  • Check this related answer out: **[Load ordering of dynamically added script tags](http://stackoverflow.com/a/38840724/2247494)** – jherax Aug 09 '16 at 17:05

6 Answers6

5

You need to attach an event handler to either the onload method, in browsers compliant with Web standards, or the onreadystatechange, checking for the script.readyState property getting equal to "loaded" or "complete", in Internet Explorer.

Before you get notified that the script was loaded, you are probably trying to access objects, functions and properties that have not been declared or created yet.

Here is an example function, extracted from the module bezen.dom.js in my Javascript library, bezen.org:

var appendScript = function(parent, scriptElt, listener) {
    // append a script element as last child in parent and configure 
    // provided listener function for the script load event
    //
    // params:
    //   parent - (DOM element) (!nil) the parent node to append the script to
    //   scriptElt - (DOM element) (!nil) a new script element 
    //   listener - (function) (!nil) listener function for script load event
    //
    // Notes:
    //   - in IE, the load event is simulated by setting an intermediate 
    //     listener to onreadystate which filters events and fires the
    //     callback just once when the state is "loaded" or "complete"
    //
    //   - Opera supports both readyState and onload, but does not behave in
    //     the exact same way as IE for readyState, e.g. "loaded" may be
    //     reached before the script runs.

    var safelistener = catchError(listener,'script.onload');

    // Opera has readyState too, but does not behave in a consistent way
    if (scriptElt.readyState && scriptElt.onload!==null) {
      // IE only (onload===undefined) not Opera (onload===null)
      scriptElt.onreadystatechange = function() {
        if ( scriptElt.readyState === "loaded" || 
             scriptElt.readyState === "complete" ) {
          // Avoid memory leaks (and duplicate call to callback) in IE
          scriptElt.onreadystatechange = null;
          safelistener();
        }
      };
    } else {
      // other browsers (DOM Level 0)
      scriptElt.onload = safelistener;
    }
    parent.appendChild( scriptElt );
};

To adapt it to your needs, you may replace the call to catchError, which wraps the listener to catch and log errors, and use the modified function:

var appendScript = function(parent, scriptElt, listener) {
    // append a script element as last child in parent and configure 
    // provided listener function for the script load event
    //
    // params:
    //   parent - (DOM element) (!nil) the parent node to append the script to
    //   scriptElt - (DOM element) (!nil) a new script element 
    //   listener - (function) (!nil) listener function for script load event
    //
    // Notes:
    //   - in IE, the load event is simulated by setting an intermediate 
    //     listener to onreadystate which filters events and fires the
    //     callback just once when the state is "loaded" or "complete"
    //
    //   - Opera supports both readyState and onload, but does not behave in
    //     the exact same way as IE for readyState, e.g. "loaded" may be
    //     reached before the script runs.

    var safelistener = function(){
      try {
        listener();
      } catch(e) {
        // do something with the error
      }
    };

    // Opera has readyState too, but does not behave in a consistent way
    if (scriptElt.readyState && scriptElt.onload!==null) {
      // IE only (onload===undefined) not Opera (onload===null)
      scriptElt.onreadystatechange = function() {
        if ( scriptElt.readyState === "loaded" || 
             scriptElt.readyState === "complete" ) {
          // Avoid memory leaks (and duplicate call to callback) in IE
          scriptElt.onreadystatechange = null;
          safelistener();
        }
      };
    } else {
      // other browsers (DOM Level 0)
      scriptElt.onload = safelistener;
    }
    parent.appendChild( scriptElt );
};
Eric Bréchemier
  • 1,908
  • 3
  • 18
  • 30
2

Since you seem to be able to edit the external script (since you tested it with an alert), why not just put this code in that script?

If you can't do that (maybe the extra code is generated or the first file is shared perhaps), just add a function call at the end of the script you're loading like this:

load_complete();

and then put your extra code in that function:

function load_complete() {
    var classObj = eval(" new MyClass()" );
}

It's a lot simpler and foolproof than any kind of onload trigger. Also, if the js file is shared, then you can have different load_complete functions on every page that uses it (just be sure to always define a load_complete, even if it is empty).

Rob Van Dam
  • 7,812
  • 3
  • 31
  • 34
1

I believe that this can actually be remedied by making sure you put the code loading the external script and the code using the external script separate script blocks like this:

<script>
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "myscript.js";
head.appendChild(script);

//this is in the same script block so it wouldn't work
//var classObj = eval(" new MyClass()" );
</script>

<script>
//this is in a separate script block so it will work
var classObj = eval(" new MyClass()" );
</script>
Zaptree
  • 3,763
  • 1
  • 31
  • 26
1

Use the jQuery (a JavaScript library) getScript function to load your script async. Use the callback function to create your objects. Example:

$.getScript("script.js", function () {
  var classObj = new MyClass();
});
JefClaes
  • 3,275
  • 21
  • 23
knut
  • 4,699
  • 4
  • 33
  • 43
  • This is unreliable, according to the `getScript()` documentation: "The callback is fired once the script has been loaded but not necessarily executed." – Bennett McElwee Jul 21 '15 at 03:49
0

Are you sure that adding a <script> element to the DOM will cause the browser to actually evaluate the script at all? I have a vague memory of reading somewhere that it doesn't, but perhaps I inhaled a bit too much oven cleaner yesterday.

Rob
  • 47,999
  • 5
  • 74
  • 91
  • 1
    yes. its called On-Demand JavaScript. you can read more about it here: http://www.webreference.com/programming/javascript/rg29/ – Amir Apr 21 '09 at 21:46
0

Amir, it looks to me as if the script is still sitting on the server, that is to say, has not been loaded by the browser. So your head.appendChild(script);is appending null. You can't fetch a script from the server just by saying its name, you need to request it and inject it into the page, using ajax, or by loading it in using <script> tag.

karim79
  • 339,989
  • 67
  • 413
  • 406
  • i thought it at first. i debbuged it and saw that the script is becoming available after a few milliseconds. i added a alert("i'm loaded"); into the script and saw it executed. – Amir Apr 21 '09 at 21:56
  • -1 It appends the script _element_ immediately, but it loads and executes the script _code_ asynchronously, which is why the class is not available immediately. – Bennett McElwee Jul 21 '15 at 03:46