Javascript external script loading strangeness
Asked Answered
N

4

0

I'm maintaining a legacy javascript application which has its components split into 4 JS files.

They are "Default.aspx", "set1.aspx", "set2.aspx" and "set3.aspx". The ASPX pages writes out compressed JS from multiple (all-different) source files belonged to their respective set and set content-type header to "text/javascript".

The application is invoked by adding a reference to the first set and creating the main entry object.

<script src="/app/default.aspx" type="text/javascript"></script>

<script type="text/javascript>

    var ax;  

    // <body onload="OnLoad()">
    function OnLoad() {
        ax = new MyApp(document.getElementById("axTargetDiv"));
    }

</script>

At the end of the first set of scripts (default.aspx) is the following exact code:

function Script(src) {
    document.write('<script src="' + src + '" type="text/javascript"></script>');
}

Script("set1.aspx?v=" + Settings.Version);

Which loads the second set of scripts (set1.aspx). And this works without any errors in all major browsers (IE6-8 Firefox Safari Opera Chrome).

However, as I've been working on this script for quiet sometime, I'd like to simplify function calls in a lot of places and mistakenly inlined the above Script function, resulting in the following code:

document.write('<script src="set1.aspx?v=' + Settings.Version + '" type="text/javascript"></script>');

Which, when tested with a test page, now throws the following error in all browsers:

MyApp is not defined.

This happens at the line: ax = new MyApp(... as Visual Studio JS debugger and Firebug reports it.

I've tried various methods in the first 4 answers posted to this question to no avail. The only thing that will enable MyApp to loads successfully is only by putting the actual "add script" code inside a function (i.e. the document.write('script') line):

If I put the document.write line inside a function, it works, otherwise, it doesn't. What's happening?

Splitting and/or escaping the script text does not work.

Nab answered 8/4, 2009 at 5:59 Comment(3)
you need to carefully inspect the order in which javascript files are included and evaluated. if you can post the document outline may be that would help.Assent
@Salman A I'll do it tonight... it's a little complicated.Nab
Revised the entire question... hope it make things clearerNab
P
4

To see the problem, look at that top line in its script element:

<script type="text/javascript">
    document.write('<script src="set1.aspx?v=1234" type="text/javascript"></script>');
</script>

So an HTML parser comes along and sees the opening <script> tag. Inside <script>, normal <tag> parsing is disabled (in SGML terms, the element has CDATA content). To find where the script block ends, the HTML parser looks for the matching close-tag </script>.

The first one it finds is the one inside the string literal. An HTML parser can't know that it's inside a string literal, because HTML parsers don't know anything about JavaScript syntax, they only know about CDATA. So what you are actually saying is:

<script type="text/javascript">
    document.write('<script src="set1.aspx?v=1234" type="text/javascript">
</script>

That is, an unclosed string literal and an unfinished function call. These result in JavaScript errors and the desired script tag is never written.

A common attempt to solve the problem is:

document.write('...</scr' + 'ipt>');

This is still technically wrong (and won't validate). This is because in SGML, the character sequence that ends a CDATA element is not actually ‘</tagname>’ but just ‘</’ — a sequence that is still present in the line above. Browsers generally are more forgiving and in practice will allow it.

Probably the best solution is to escape the sequence. There are a few possibilities, but the simplest is to use JavaScript string literal escapes ('\xNN'):

document.write('\x3Cscript src="set1.aspx?v=1234\x26w=5678" type="text/javascript"\x3E\x3C/script\x3E');

The above escapes all ‘<’, ‘>’ and ‘&’ characters, which not only stops the ‘</’ sequence appearing in the string, but also allows it to be inserted into an XHTML script block without causing errors.

(In XHTML, there's no such thing as a CDATA element, so these characters would have the same meaning as if included in normal content, and a string '<script>' inside a script block would actually create a nested script element! It's possible to allow <>& in an XHTML script block by using a <![CDATA[ section, but it's a bit ugly and usually better to avoid using those characters in inline script.)

Phillipphillipe answered 8/4, 2009 at 8:41 Comment(4)
The loader script is inside a JS file of its own. not inside a HTML page body.Nab
Ah, the term “inline” was misleading then. You'll need to show us the complete code then (best: as an online demo); we can't guess what order you're executing scripts from the isolated snippets above.Phillipphillipe
There is no reason document.write() at global level would behave differently to in an immediately-executed function, so something else is likely wrong.Phillipphillipe
@bobnice indeed... that's why I'm posting an SO question. And I'll find time to rewrite the question tonight. Thanks!Nab
A
1

1) Assure that you do not try to reference MyApp before the script is "actually" included in your page.

2) Try breaking the word "script" in your inline loader like this:

<script type="text/javascript">
document.write('<scr' + 'ipt src="set1.aspx?v=1234" type="text/javascript"></scr' + 'ipt>');
</script>

Alternatively, use this syntax which i borrowed from google analytics code and have been able to use successfully:

<script type="text/javascript">
document.write(unescape("%3Cscript src='set1.aspx?v=1234' type='text/javascript'%3E%3C/script%3E"));
</script>
Assent answered 8/4, 2009 at 6:17 Comment(1)
Tried, does not work. Please see the edit. The google analytics code was a nice trick, though.Nab
K
1

You could also try:

var script = document.createElement("script");
script.src = "set1.aspx?v=1234";
script.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(script);

Steve

Kilan answered 8/4, 2009 at 8:30 Comment(1)
Tried but the problem still persist. Only if I put it inside a function body is when it'll work.Nab
B
0

If you could use JQuery you could use the following:

$.getScript("set1.aspx?v=1234");

This loads the script into the global javascript context. Make sure you set contenttype of the response to "text/javascript".

Hope this helps...

Buster answered 8/4, 2009 at 7:52 Comment(1)
Thanks... but jQuery is not an option.Nab

© 2022 - 2024 — McMap. All rights reserved.