tags:

views:

130

answers:

2

I'm having some issues with the ordering of javascript execution in the following page:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>

<script type="text/javascript">
    var blah = 0;

    function scriptDoneRunning()
    {
     alert(blah);
    }

    function addExternalScript(src, parentNode)
    {
     var sc = document.createElement('script');
     sc.setAttribute('type', 'text/javascript');
     sc.setAttribute('src', src);
     parentNode.appendChild(sc);
    }

    function addEndingScript(parentNode)
    {
     var theDiv = document.createElement('div');
     var sc = document.createElement('script');
     sc.setAttribute('type', 'text/javascript');
     sc.text = "scriptDoneRunning();";
     theDiv.appendChild(sc);
     parentNode.appendChild(theDiv);
    }
</script>
</head>

<body>
<div id="theparent">
    &nbsp;
</div>

<script type="text/javascript">
    addExternalScript("blah.js", document.getElementById("theparent"));
    addEndingScript(document.getElementById("theparent"));
</script>
</body>
</html>

And the blah.js file looks like this....

blah=3;

Essentially the code calls "addExternalScript()" which adds a script element for "blah.js" to the "theparent" element. "blah.js" sets the variable blah to 3.

Then we call "addEndingScript()" which appends a script block to "theparent". All that script block does is call scriptDoneRunning() which alerts the variable blah.

The idea is that scriptDoneRunning() only gets called AFTER the blah.js has finished executing. Because it is appended to "theparent" after the external script element I would expect it to wait for that to complete then it would call scriptDoneRunning(). This is the behavior I expected, however it only works that way in firefox. In IE the value alerted is 0, which means the code in blah.js has not executed yet.

This is a slimmed down version of what the page actually does so obviously this doesn't make a ton of sense. But is there a way to append the inline script in such a way that the code in blah.js always executes first in both browsers?

Also, when this is deployed I will not have control over the external js. So keep that in mind ;)

Thanks!

+3  A: 

The script loads asynchronously, you can know exactly when the external script has been loaded,using the load (and readystatechange for IE) events, but you should take care of removing the script elements and nullify the handlers after the load is done to avoid known memory leaks.

I would also recommend you to insert your script elements on the head, there is no really a reason (unless you use document.write which I don't think is a good idea nowdays) to add the script elements anywhere inside the body...

I use the following library independent function, inspired by the jQuery's $.getScript method:

loadScript("blah.js", function () {
  addEndingScript(document.getElementById("theparent"));
  // or why not simply scriptDoneRunning(); ???
});

function loadScript(url, callback) {
  var head = document.getElementsByTagName("head")[0],
      script = document.createElement("script"),
      done = false;

  script.src = url;

  // Attach event handlers for all browsers
  script.onload = script.onreadystatechange = function(){
    if ( !done && (!this.readyState ||
      this.readyState == "loaded" || this.readyState == "complete") ) {
      done = true;
      callback(); // execute callback function

      // Prevent memory leaks in IE
      script.onload = script.onreadystatechange = null;
      head.removeChild( script );
    }
  };
  head.appendChild(script);
}
CMS
That works great, and I dig the functional style ;) Thanks!
ChrisDiRulli
+2  A: 

The reason of such behaviour is the fact that the IE loads scripts asynchronous, when you append the script tag dynamically (check the link MSDN). To wait till the script is loaded you should use "onload" (onreadystatechange) event.

sc.onload = sc.onreadystatechange = function(){};

You can use the loader, which I posted as an solution to the question or write your own.

P.S: Using, setAttribute for src attribute is bad practice when you want to support IE6< browsers, better use direct property, like

sc.src = "http://bla.js";
nemisj