views:

655

answers:

5

I am including some related content on misc. web pages by adding a <script> tag near the end of the <body> tag, which then loads other javascript files. The flow is a little convoluted, so I’ll try to explain it before asking my question:

  • A browser loads a page with our <script> element near the end of the <body> element
  • The src attribute of the script tag points to a javascript file which (in some cases) injects a second <script> element
  • The src attribute of the injected <script> element points to yet another javascript file, which finally injects some content on the appropriate part of the page.

We are using this two-stage approach to be able to do some basic processing before deciding whether to include the final content, which could take some time to load.

The problem is that IE8 (and maybe older versions) loads the last javascript twice. It appears that the act of setting the src attribute will trigger a load, but so will appending the script tag to the DOM. Is there any way to avoid this?

I have created a bare-bones demo of the problem. If you have some way of tracing the HTTP requests, you will see that IE8 loads js_test2.js twice.

+2  A: 

It would seem that jquery is fetching the second instance of js_test2 using XmlHttpRequest.

Why I don't know but its the behaviour of JQuery you need to investigate.

AnthonyWJones
That’s just bizarre, but it seems you are right. If I use appendChild instead of jQuery’s append it does not load js_test2.js twice. I guess if I had made the demo /really/ bare-bones (without jQuery), I would have discovered this myself. Thanks!
stiang
I've done some investigation. The results (below) are not nice.
bobince
+2  A: 

I've seen that behavior happening on other versions of IE under similar circumstances (such as cloning of <script> nodes) and I never got to know how to stop the script from executing twice, so what I ended up doing was adding a guard on the script code for it not to run twice. It was something like:

if (typeof(loaded) == 'undefined') {
    // the whole code goes in here
    var loaded = true;
}

If you can't find a way of preventing the script to execute twice, you may want to try that approach instead.

Miguel Ventura
+2  A: 

You can check to see if a script is already in the document before loading it-

function fetchscript(url, callback){
 var S= document.getElementsByTagName('script'), L= S.length;
 while(L){
  if(S[--L].src== url) return true;
 }
 //create and append script and any callbacks
}
kennebec
+7  A: 

The root difference is that IE executes a script element the first time it is added to a parent element's childNodes, regardless of whether the parent element is actually in the document. Other browsers only execute script when it is added to a node inside the document's tree of childNodes.

jQuery's domManip function (line 524 of jQuery 1.3.2), which is called by append and other similar jQuery methods, tries to be clever and calls evalScript for any <script> elements it finds in the final parsed HTML, to execute the script (by doing AJAX requests if necessary for external scripts). (The actual script elements have been removed from the parsed childNodes to stop them getting executed on insertion into the document, presumably so that scripts are only executed once when content containing them is appended into multiple elements at once.)

However, because the previous clean function, whilst parsing the HTML, appended the script element to a wrapper div, IE will have already executed the script. So you get two executions.

The best thing to do is avoid using domManip functions like append with HTML strings when you're doing anything with scripts.

In fact, forget putting your content in a serialised HTML string and getting jQuery to parse it; just do it the much more reliable plain DOM way:

var s= document.createElement('script');
s.type= 'text/javascript';
s.charset= 'UTF-8';
s.src= 'js_test2.js';
document.getElementById('some_container').appendChild(s);

(To be honest, after viewing the stomach-churning source code of the clean function, I'm having serious doubts about using jQuery's HTML-string-based DOM manipulations for anything at all. It's supposed to fix up browser bugs, but the dumb-regex processing seems to me likely to cause as many problems as it solves.)

Incidentally with this initial call:

document.write(unescape("%3Cscript src='js_test1.js' type='text/javascript'%3E%3C/script%3E"));

You don't need to unescape here; I don't know why so many people do. The sequence </ needs to be avoided in an inline script as it would end the <script> tag, and if you're doing XHTML < and & should be avoided too (or ]]> if you're using a CDATA wrapper), but there's an easier way of doing all that with just JavaScript string literals:

document.write('\x3Cscript src="js_test1.js" type="text/javascript">\x3C/script>"));
bobince
Thank you for this very thorough answer. I have indeed reverted to the more traditional DOM way, and it works well now.Regarding the unescaping in the initial call: If I remember correctly I copied the code that Google Analytics uses to include their script, under the (perhaps false) assumption that if anybody knows how to robustly include a remote javascript, it’s the GA people. Thanks for the clarification.
stiang
A: 

It's possible that this is because you use document.write when you're already in a <script> element. Have you tried appending to <head> instead of document.write?

orip