views:

10809

answers:

7

I have the following script, where the first and third document.writeline are static and the second is generated:

<script language="javascript" type="text/javascript">
document.write("<script language='javascript' type='text/javascript' src='before.js'><\/sc" + "ript>");
document.write("<script language='javascript' type='text/javascript'>alert('during');<\/sc" + "ript>");
document.write("<script language='javascript' type='text/javascript' src='after.js'><\/sc" + "ript>");
</script>

Firefox and Chrome will display before, during and after, while Internet Explorer first shows during and only then does it show before and after.

I've come across an article that states that I'm not the first to encounter this, but that hardly makes me feel any better.

Does anyone know how I can set the order to be deterministic in all browsers, or hack IE to work like all the other, sane browsers do?

Caveats: The code snippet is a very simple repro. It is generated on the server and the second script is the only thing that changes. It's a long script and the reason there are two scripts before and after it are so that the browser will cache them and the dynamic part of the code will be as small as possible. It may also appears many times in the same page with different generated code.

+1  A: 

Slides 25/26 of this presentation talk about the characteristics of different methods for inserting scripts. It suggests that IE is the only browser that will execute those scripts in order. All other browsers will execute them in the order that they finish loading. Even IE won't execute them in order if one or more have inline js instead of a src.

One of the methods suggested is to insert a new DOM element:

var se1 = document.createElement('script');
se1.src = 'a.js';

var se2 = document.createElement('script');
se2.src = 'b.js';

var se3 = document.createElement('script');
se3.src = 'c.js';

var head = document.getElementsByTagName('head')[0]
head.appendChild(se1);
head.appendChild(se2);
head.appendChild(se3);

To make the second script section generated you could use a script to generate that content and pass the parameters:

se2.src = 'generateScript.php?params=' + someParam;

EDIT: In spite of what the article I sited says, my testing suggests that most browsers will execute your document.write scripts in order if they each have a src, so while I think the method above is preferred, you could do this as well:

<script language="javascript" type="text/javascript">
document.write("<script type='text/javascript' src='before.js'><\/sc" + "ript>");
document.write("<script type='text/javascript' src='during.php?params=" + params + "'><\/sc" + "ript>");
document.write("<script type='text/javascript' src='after.js'><\/sc" + "ript>");
</script>

EDIT AGAIN (response to comments to myself and others): You are already generating the script on your page. Whatever you are doing can be moved to another server-side script that generates the same block of code. If you need parameters on your page then pass them to the script in the query string.

Also, you can use this same method if, as you suggest, you are generating the inline script multiple times:

<script language="javascript" type="text/javascript">
document.write("<script type='text/javascript' src='before.js'><\/sc" + "ript>");
document.write("<script type='text/javascript' src='during.php?params=" + params1 + "'><\/sc" + "ript>");
document.write("<script type='text/javascript' src='during.php?params=" + params2 + "'><\/sc" + "ript>");
document.write("<script type='text/javascript' src='during.php?params=" + params3 + "'><\/sc" + "ript>");
document.write("<script type='text/javascript' src='after.js'><\/sc" + "ript>");
</script>

However, this is starting to look as though you are approaching this the wrong way. If you are generating a large block of code multiple times then you should probably be replacing it with a single js function and calling it with different params instead...

Prestaul
'during' is a very long piece of script, so I can't summarize it to a simple get request...
Omer van Kloeten
@Omer: You've already summarized it to a "simple get request"... How are you generating it now to put it inline? Whatever you are doing now you could do on another page but only return the js and not the rest of the page.
Prestaul
the demo code I gave is a very simplified repro of the problem. the code I wrote is the one being generated and the file includes are so that the browser will cache them and need less time to load the page.
Omer van Kloeten
+1  A: 

Okay...during...

// During.js
during[fish]();

after...

// After.js
alert("After");
fish++

HTML

<!-- some html -->
<script language="javascript" type="text/javascript">
document.write("<script language='javascript' type='text/javascript' src='before.js'></sc" + "ript>");
document.write("<script language='javascript' type='text/javascript'>during[" + fish + "] = function(){alert('During!' + fish);}</sc" + "ript>");
document.write("<script language='javascript' type='text/javascript' src='during.js'></sc" + "ript>");
document.write("<script language='javascript' type='text/javascript' src='after.js'></sc" + "ript>");
</script>
<!-- some other html -->
<script language="javascript" type="text/javascript">
document.write("<script language='javascript' type='text/javascript' src='before.js'></sc" + "ript>");
document.write("<script language='javascript' type='text/javascript'>during[" + fish + "] = function(){alert('During!' + fish);}</sc" + "ript>");
document.write("<script language='javascript' type='text/javascript' src='during.js'></sc" + "ript>");
document.write("<script language='javascript' type='text/javascript' src='after.js'></sc" + "ript>");
</script>

I'm inclined to agree about the way this is starting to smell, though. In particular, why couldn't you codegen the "during" into a dynamically created js file, and insert that?

Note that the dynamically generated script goes inside the function in the second document.write.
Tested in FF2, IE7

Dustman
+6  A: 

No, this is the behavior of Internet Explorer.

If you attach scripts dynamically, IE, Firefox, and Chrome will all download the scripts in an asynchronous manner.

Firefox and Chrome will wait till all of the async requests return and then will execute the scripts in the order that they are attached in the DOM but IE executes the scripts in the order that they are returned over the wire.

Since the alert takes less time to "retrieve" than an external javascript file, that likely explains the behavior that you are seeing.

From Kristoffer Henriksson's post on the subject of asynchronous script loading:

In this scenario IE and Firefox will download both scripts but Internet Explorer will also execute them in the order they finish downloading in whereas Firefox downloads them asynchronously but still executes them in the order they are attached in the DOM.

In Internet Explorer this means your scripts cannot have dependancies on one another as the execution order will vary depending on network traffic, caches, etc.

Timothy Lee Russell
A: 

Code to provide:

<script language="javascript" type="text/javascript">
document.write("<script language='javascript' type='text/javascript'>function callGeneratedContent() { alert('during'); }<\x2Fscript>");
document.write("<script language='javascript' type='text/javascript' src='before.js'><\x2Fscript>");
document.write("<script language='javascript' type='text/javascript' src='after.js'><\x2Fscript>");
</script>

In before.js:

alert("Before");
callGeneratedContent();

In after.js:

alert("After");

You have to put the generated line first, otherwise FF will complain because it executes before.js before seeing the function definition.

PhiLho
please see the caveat to Dustman's answer.
Omer van Kloeten
+1  A: 

You can force secuential execution by defining "onload" (or similar) events on the scripts and inject the next one in the event function. It is not trivial, but there are plenty of examples out there, here is one.

http://www.phpied.com/javascript-include-ready-onload/

I think popular libraries like jQuery or prototype can help with this.

Victor
A: 

I've found an answer more to my liking:

<script language="javascript" type="text/javascript">
document.write("<script language='javascript' type='text/javascript' src='before.js'><\/sc" + "ript>");
document.write("<script defer language='javascript' type='text/javascript'>alert('during');<\/sc" + "ript>");
document.write("<script defer language='javascript' type='text/javascript' src='after.js'><\/sc" + "ript>");
</script>

This will defer the loading of both during and after until the page has finished loading.

I think this is as good as I can get. Hopefully, someone will be able to give a better answer.

Omer van Kloeten
Internet Explorer runs inline scripts before external scripts. To get a consistent order, move the inline code to an external file.
Eric Bréchemier
A: 

How about that:

<script>
document.write("<script src='before.js'><\/script>");
</script>

<script >
document.write("<script>alert('during');<\/script>");
</script>

<script>
document.write("<script src='after.js'><\/script>");
</script>