dynamically creating script: readyState never "complete"
Asked Answered
P

3

11

I'm trying to do something AFTER a script is completely loaded. (IE8)

Script I use for testing: http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js
and the invalid one: http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.minaaaaaaaa.js

The code...

var script = create the element and append to head...

// this works fine with FF/Chrome/...
script.onload = function() {alert('script loading complete');}
script.onerror = function() {alert('error loading script');}

// and for IE
script.onreadystatechange = function() {
    // this will alert 1.loading 2.loaded
    alert(this.readyState);

    // this never works
    if(this.readyState == 'complete') {alert('script loading complete');}

    // this works with either a valid or INVALID url
    else if(this.readyState == 'loaded') {alert('script loaded');}
};

In my case, the "complete" never shows, the "loaded" shows even if a url is invalid. So there's no way to tell if a script is CORRECTLY loaded UNDER IE.

Am I doing something wrong? How come I never get the complete state?

UPDATE

OK, I just read some articles and it seems that readystate is not a reliable way to detect script loading.

So is there another way to do so? without jQuery, but pure Javascript.

Pamulapan answered 4/8, 2011 at 18:27 Comment(3)
I'm not sure what you want to do, but I suggest looking into jQuery#getScript for an idea of how to dynamically load scripts. Most importantly, using an AJAX pattern will let you test for HTTP errors as you seem to have in mind. That cannot -- afaik -- be accomplished using DOM Events on the script tag.Celenacelene
Thanks for the input, FK82. I just want to be 100% sure that the script is loaded. As I said, in FF/Chrome, onload and onerror works perfectly. But under IE, even if a script url is invalid (or maybe timed out), readyState still returns loaded. The reason I want to use pure Javascript is becasue I want to make sure jQuery is loaded correctly from a CDN first, and then use jQuery to do stuff. And yes, I too find that this should go the AJAX way. (haven't tried tho)Pamulapan
Please have a look at my answer. You will be able to catch various HTTP errors using the AJAX/XHR (XMLHttpRequest) pattern. Also, I believe this would be more stable in terms of cross-browser compatibility.Celenacelene
C
5

As per your comment, here's a schematic of how to dynamically add a script tag using XHR (XMLHttpRequest):

var handleRequest = function( ) { //!! set up the handleRequest callback

     if(this.status != undefined) {

         /* do something with the status code here */

     }

     if(this.readyState == 4) {

          var script = document.createElement("script") ;
              script.setAttribute("type","text/javascript") ;
          var text = document.createTextNode(this.responseText) ;
              script.appendChild(text) ;

          var head = document.getElementsByTagName("head")[0] ;
              head.insertBefore(script,head.firstChild) ;

     }

} ;

var request ; //!! supposing you have a way to get a working XHR Object

//.. set the XHR Object

request.open("GET",url,true) ;
request.overrideMimeType("text/javascript") ;
request.onreadystatechange = handleRequest ;
request.send(null) ;

Please keep in mind that this is only to give you an idea of what I mean. A working example would have to be way more elaborate judging from the jQuery source code.


Links:

Celenacelene answered 5/8, 2011 at 8:27 Comment(2)
your solution rocks, but to be 100% it should be enhanced with CORS handling so it can also load scripts outside of your domain, otherwise the scripts you can load are limited to the same origin policy :)Cistern
Thanks. ( : You're right in as far as CORS implementations are supported cross-browser. The fallback for cross domain script access is using the SCRIPT tag however.Celenacelene
A
12

I've found out a trick how to make script node become 'complete' in IE7 and IE8. And also how to detect when error really happens when loading script (node.onerror working only in IE9+). The hack is in

  1. NOT inserting the newly created element into DOM (at once).
  2. Calling node.children property and checking node.onreadystate property right after it. If the property changes to 'complete' - you have loaded script, it it changes to 'loading' - it's for sure script loading error.

Try it!

var node = document.createElement('script');
node.src = 'some-[un]existing-url.js';
node.type = 'text/javscript';

node.onreadystatechange = (function(node) { 
    return function () {
        if (/loaded|complete/.test(node.readyState)) {
            _finish();
        }
    };
})(node);

var _finish=function() {

        if (node.readyState == 'complete') {
            // insert node only after completing the request, though this is not necessary
            var head = document.head;
            head || (head = document.getElementsByTagName('head')[0]);
            head.appendChild(node);
            // call success function
            _success();
            return;
        }

        var firstState = node.readyState;

        // hack: calling 'children' property changes node's readyState from 'loaded' to complete
        // (if script was loaded normally) or to 'loading' - if error detected
        node.children;

        // error detected
        if (firstState == 'loaded' && node.readyState == 'loading') {
            // custom error code
            _error();
        }
}
Anhydrite answered 17/9, 2013 at 2:54 Comment(3)
This is by far the best solution for script injection in IE8 with success and error handlers. It is far better then loading the script via AJAX because by script injection the AJAX CORS limits do not apply. Great find with the node.children trick! Btw. var node = createNode( 'script', ... is of course in plain vanilla JavaScript var node = document.createElement('script'); ...Enki
I'm not sure that the node.onerror is working in IE9.Retrochoir
@Semmel, thank you for your reply and notes - I've updated my answer with pure JS.Anhydrite
C
5

As per your comment, here's a schematic of how to dynamically add a script tag using XHR (XMLHttpRequest):

var handleRequest = function( ) { //!! set up the handleRequest callback

     if(this.status != undefined) {

         /* do something with the status code here */

     }

     if(this.readyState == 4) {

          var script = document.createElement("script") ;
              script.setAttribute("type","text/javascript") ;
          var text = document.createTextNode(this.responseText) ;
              script.appendChild(text) ;

          var head = document.getElementsByTagName("head")[0] ;
              head.insertBefore(script,head.firstChild) ;

     }

} ;

var request ; //!! supposing you have a way to get a working XHR Object

//.. set the XHR Object

request.open("GET",url,true) ;
request.overrideMimeType("text/javascript") ;
request.onreadystatechange = handleRequest ;
request.send(null) ;

Please keep in mind that this is only to give you an idea of what I mean. A working example would have to be way more elaborate judging from the jQuery source code.


Links:

Celenacelene answered 5/8, 2011 at 8:27 Comment(2)
your solution rocks, but to be 100% it should be enhanced with CORS handling so it can also load scripts outside of your domain, otherwise the scripts you can load are limited to the same origin policy :)Cistern
Thanks. ( : You're right in as far as CORS implementations are supported cross-browser. The fallback for cross domain script access is using the SCRIPT tag however.Celenacelene
P
-1

Detecting Load Completion

[..] One web page suggested setting up some event handlers that will be called when the load is complete. We do that by adding the following lines to the previous code:

var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.onreadystatechange= function () {
  if (this.readyState == 'complete') helper();
}
script.onload= helper;
script.src= 'helper.js';
head.appendChild(script);

Here we set up two different event handlers on the newly created script tag. Depending on the browser, one or the other of these two handlers is supposed to be called when the script has finished loading. The onreadystatechange handler works on IE only. The onload handler works on Gecko browsers and Opera.

The "this.readyState == 'complete'" test doesn't actually entirely work. The readyState theoretically goes through a series of states:

  • 0 uninitialized
  • 1 loading
  • 2 loaded
  • 3 interactive
  • 4 complete

But in fact, states may be skipped. In my experience with IE 7, you get either a loaded event or a completed event, but not both.

It may have something to do with whether you are loading from cache or not but there seem to be other factors that influence which events you get. Sometimes I get loading or interactive events too, and sometimes I don't. It's possible the test should be "this.readyState == 'loaded' || this.readyState == 'complete'", but that risks triggering twice.

http://unixpapa.com/js/dyna.html

UPDATE

Please, can you sobstitute your script.onreadystatechange callback with this:

newjs.onreadystatechange = function () {
   if (newjs.readyState === 'loaded' || newjs.readyState === 'complete') {
      newjs.onreadystatechange = null;
      alert('script is complete or loaded.");
   }

};

An other thing i've noticed is that you're doing string comparison with == operator. This can be misleading. Use === instead.

Papyrus answered 4/8, 2011 at 18:33 Comment(4)
Come on........did you just copy and paste? You got exactly the same code as mine (I mean the structure), and this is what I'm talking about...the "readyState" is never "complete"!!! And I'm trying to figure it out.Pamulapan
It seems that there is no way to know if loaded or complete will be fired. Look javascript pattern "Loading on Demand". The author wrote this: • In IE you subscribe to the readystatechange event and look for a readyState “loaded” or “complete.” All other browsers will ignore this.Papyrus
He's saying that load IS fired, but it fires even when the script is invalid and fails (404, etc.). He wants to know how to detect that.Malevolent
@your update: well, that's not the point. == and === in this case are the same. readyState returns a string, and loaded/complete are surrounded with quotes, so, we're not comparing types. The problem is that IE just can't do the work with DOM event. I'm switching to AJAX... Thanks anyway.Pamulapan

© 2022 - 2024 — McMap. All rights reserved.