One thing has always annoyed me about the script tag. Script tags that reference external resources (via the src attribute) are no longer able to execute script embedded within the tag itself.
It doesn’t make sense to me that we’re forced to write:
<script src="some-lib.js"></script> <script> var foo = use_some_lib(); foo.do.stuff(); </script>
when this is so much more elegant:
<script src="some-lib.js"> var foo = use_some_lib(); foo.do.stuff(); </script>
Only one tag – and the semantics would work like this: The external script would attempt to be loaded (as normal). If the load and execution was error-free then the internal script would be executed as well. If the load or execution failed then the internal script would not be executed (this being the important distinction from the first example, shown above).
We can verify these two facts with some tests:
- Test 1: Verify that internal scripts aren’t executed, even if an external src is loaded.
- Test 2: Verify that internal scripts aren’t executed, even if an external src is not loaded.
Running the tests in Firefox, Opera, Safari, and IE show them all passing (the internal scripts are unable to execute).
With this knowledge in place now, let’s get to the juicy part: How do we actually implement the above style of script-tag-writing in a way that melds well with our desired style of development?
It’s actually terribly simple. I took a copy of jQuery and tacked the following two lines on to the end of the script:
var scripts = document.getElementsByTagName("script"); eval( scripts[ scripts.length - 1 ].innerHTML );
Taking a look at a demo shows it working, using only one script tag:
<script src="jquery1.js"> jQuery(document).ready(function(){ jQuery("p").addClass("pretty"); }); </script>
We have succeeded! but how does the above code work? The short snippet takes advantage of the way in which script tags are loaded and executed. Each script tag within a page may be downloaded in any order (for example, Internet Explorer 8 and Firefox 3.1 are starting to download many scripts in parallel). BUT their execution must come in order AND they must be executed in their exact place in the DOM.
This means that when the script finally executes that it’ll be the last script in the DOM – and even the last element in the DOM (the rest of the DOM is built incrementally as it hits more script tags, or until the end of the document).
This particular behavior occurs because scripts are able to document.write, affecting the very nature of the document. The following is perfectly legal (even if you can imagine the trauma that it must cause browser vendors – preventing them from performing some perfectly good optimizations):
<script>document.write('<' + '!--');</script> <script src='some_external_script.js'></script> <script>document.write('-->');</script>
With the above taken in mind – let’s consider a further optimization to our code. While the above code snippet is generic to any external source file, what if we were to take advantage of the nature of jQuery, simplifying its behavior?
Virtually all jQuery-using code is encased within a call to the document ready function (which delays execution of attached handlers until the DOM is full loaded).
If we were to make some adjustments to our script addition, like so:
var scripts = document.getElementsByTagName("script"); var script = scripts[ scripts.length - 1 ].innerHTML; if ( script ) { jQuery(document).ready(function(){ jQuery.globalEval( script ); }); }
We can now get the full advantage of having our scripts inline and not having to write out a document ready call, like so:
<script src="jquery2.js"> jQuery("p").addClass("pretty"); </script>
Pretty sexy, huh?
The only thing holding me back from blindly adding the above snippet directly in to jQuery, today, is a small possibility: The chance that someone could copy the full library and stick it in an embedded script tag (rather than calling it from an external file).
Right now if you were to stick either of the above snippets in an embedded script tag you would end up with a never-ending loop. Consider this code:
<script> var scripts = document.getElementsByTagName("script"); eval( scripts[ scripts.length - 1 ].innerHTML ); </script>
This code would look for the last script on the page, find it (it would be the script itself), and execute – causing a piece of recursion to occur. It would become necessary to flag the script to prevent it from executing again.
With a little bit of extra work, we can make it possible (demo):
(function(){ var scripts = document.getElementsByTagName("script"); var curScript = scripts[ scripts.length - 1 ]; if ( curScript.executed ) return; // ... jQuery ... curScript.executed = true; var script = curScript.innerHTML; if ( script ) { jQuery(document).ready(function(){ jQuery.globalEval( script ); }); } })();
I think this particular technique could serve to simplify a number of web pages, even if it’s only because:
- There’s one less script tag on the page.
- The dependency link between the script being loaded and the script being executed becomes exceedingly obvious.
- The dependent script won’t execute (and subsequently cause errors) if the external script is unable to load.
- The need for user-authored window load, document ready, etc. functions are no longer necessary (can be hidden within the library).
It’s definitely something that’ll need to be mulled over as it’s pretty fundamentally changing the existing behavior of the script tag (although, it is being done in a completely safe and backwards-compatible manner). But if there’s sufficient interest it seems as if this is one area in which external scripts could stand to offer an obvious improvement to the developer.
Chris (August 27, 2008 at 12:33 am)
Maybe something to go in the core?
Malte (August 27, 2008 at 12:53 am)
Hey,
I didn’t check, but does this also work, if the loaded script does document.write(‘…<script>…</script>…’);
henrah (August 27, 2008 at 1:03 am)
One problem-case I can imagine would be if jQuery was loaded via xhr and eval’d dynamically, maybe by a package system like dojo’s. In that case the library wouldn’t correspond to any actual script tag, and you might end up re-evaluating arbitrary code from whatever tag was last in the document.
Instead of executing code inside the remote script tag, a possible compromise might be putting library-dependent js in <script type=”text/javascript+jquery”> tags, which will not be automatically executed by the browser because the mimetype is unrecognized; then putting something like this at the end of your jQuery build:
jQuery(function(){
$('script[type="text/javascript+jquery"]').each(function(){
eval(this.textContent);
});
});
Jake Archibald (August 27, 2008 at 1:52 am)
In ‘replaced content’ type things in HTML, the pattern is
<object ...>
Fallback content (object isn't supported or failed to load)
</object>
So it may be slightly misleading that you’re doing it:
<script ...>
Success content (script loaded and executed)
</script>
But I like the idea. It’d be great if I could do something like:
<script scope="private" include="myScript.js" >
var hello = "world";
</script>
Where both myScript.js and the inner script run, and global variables created by either are scoped to that script block. If the developer wanted to send something global they could still do window.hello = hello.
If you developed a snippet which used a js library, you could get people to include it like above without worrying about the user having a different version of the library already on their page.
ilya (August 27, 2008 at 2:18 am)
Really useful, especially dependence on external js load. But as for me keeping js outside of html is much clear and semantic solution.
V1 (August 27, 2008 at 2:41 am)
Hi,
Thats actually, a great solution! And it will actually increase performance of the page to, as that will not “count” as inline scripts (if im correct), so it might actually help in parallelisation of your page, and u have one extra script tag to worry about.
Brad (August 27, 2008 at 2:49 am)
What about 1 page and optionally just 1 script tag with an array of src’s executed in order (especially useful when loading plugins) so something along the lines of
<script src="myScript.js,myNextScript.js,myFinalScript.js">
var hello = "world";
</script>
Jens Meiert (August 27, 2008 at 3:13 am)
Nit-picking (well), but it looks like you’re referring to the
script
element, not “tag”.A Nony Mouse (August 27, 2008 at 3:44 am)
Neat I like it. I always thought that script tags should know where they are, which leads me to this minor change,
jQuery(document).ready(function(){
jQuery.globalEval( script );
});
becomes
jQuery(document).ready(function(){
Function(script).call($(script).parent);
});
which would make things adding events easy. eg
<div>click me
<script src=jquery.js>
this.click(finction(event){event.tagert.hide('slow');});
</script>
</div>
Jörn Zaefferer (August 27, 2008 at 4:02 am)
I like the idea, and would take it a step further. Instead of loading only jQuery, the script tag would reference a versioned and minified file with cache-headers properly set. The script-element’s content would contain any page-specific code.
<script src="/lib/js-20080826-001.js">
$("#someForm").setupValidation();
</script>
The JS file contains the necessary code to execute the content, and will be loaded just once until the URL changes. Put that at the bottom of the page and loading times would be quite nice.
G_Gus (August 27, 2008 at 4:13 am)
John, you can add this functionality to jQuery by parsing only the script tags that have a specific parameter, like autoload=”autoload”
just check against the existence of such parameter prior to executing the script innerHTML
This way you can add the functionality in jQuery and developers can use if they want, by simply adding a attribute that helps them also to understand why there’s such a strange script tag with both src and code, and why it is working.
Andrea Giammarchi (August 27, 2008 at 4:18 am)
Hi John, nice stuff as usual.
I wonder if something like this could be interesting as well.
(function(script){
(script=script[script.length-1]).src=""; // IE
if(script.src) // Others
script.parentNode.appendChild(
document.createElement("script")).innerHTML=script.innerHTML;
})(document.getElementsByTagName("script"));
Kind Regards
alf (August 27, 2008 at 4:41 am)
I was thinking about using onload, for simple cases like this:
<script src="http://www.google.com/jsapi" onload="google.load('jquery', '1')"></script>
alsanan (August 27, 2008 at 6:42 am)
Why such a change in non-standard behaviour? Why not:
<script>
include("some-lib.js"); // load library by code and waits until loaded
var foo = use_some_lib();
foo.do.stuff();
</script>
More simple. More standard.
Starfish (August 27, 2008 at 6:47 am)
Yeah, this surely falls into the too-clever category. Sure you _can_ do it, but why would you want to? The benifits are pretty minimal – and the danger is that it introduces a syntax that looks like it does something straightforward but totally doesn’t. It’s not apparent that you have to do clever innerHTML stuff in the background to get it to work.
No votes from me! :)
John Resig (August 27, 2008 at 7:22 am)
@Chris: Maybe, I still want to think of the possible ramifications, first.
@Malte: Good point, the external script would probably need to gather the internal script before any document.writes occurred (well, ones that write out additional script tags, at least).
@henrah: That’s a great point. I’ll probably leave it out of jQuery for that reason.
I’ve actually thought about having the text/javascript+jquery (or similarly-named) script tags – but I’m hesitant. To me the semantics of the structure are much less clear – along with the performance implications (especially since this new check has to wait for document ready before running – making sure that it captures all the script elements).
@Jake Archibald: Good ideas – it’d be interesting to see what extra layers of semantic behavior could be added to the script tag (the scope that you mention is one interesting point – but there could, absolutely, be others.
@V1: I’m not sure if it’ll help page load because the entire page is still blocking on the parsing and execution of the script tag (it really doesn’t matter if there’s one more tag after, or not – since that code will still be executing anyway). It’d be cool if there were some performance speed-ups, but I doubt it.
@Brad: The problem with what you describe is that it breaks the current script tag model.
@Jens Meiert: Not sure about this case – I’m referring to the actual syntactical representation of the tags (hence using the term script tags). If I were referring to it in aggregate, or as its DOM representation, then the term element would seem to be more appropriate.
@A Nony Mouse: I like what you propose – it would certainly make the global ‘this’ more useful within individual tags (although it would certainly hamper the portability of the code itself – along with the typical unobtrusive nature that most developers strive for).
@Jörn Zaefferer: Correct – it does seem like a very common case of including some simple page-specific code, so anything to make it simpler would help. We’ll have to see if this technique gets any legs. Although I’m hesitant, since jQuery could be loaded dynamically (executing some script twice).
@G_Gus: One issue with supporting more than one script tag is that jQuery (or any other library) would now have to wait until the DOM is ready before locating all the scripts. Adding overhead to the eventual execution of all the page JavaScript.
@Andrea Giammarchi: Nice code – I like the re-use of the script element – though is dancing through those extra steps really much better than just calling an eval?
@alf: That’s also a possible piece of code. I like it, it tends to support the existing models presented by the browser.
@alsanan: A problem is that, in your example, you have to 1) Load the script synchronously via Ajax (breaking any possible use of cross-domain scripts). 2) Lose out on any background pre-loading of the scripts normally done by browsers. Since the script is now forced to wait until all previous scripts load, before executing and loading itself, it’ll end up with a much slower experience.
@Starfish: It’s a two-sided coin. It’s changing the model of how the script tag works – I would argue for the better – but at the same time it’s changing it in a way that isn’t supported in all environments or all scripts (which is where the stickiness comes in). It seems like some publication of the idea would yield some good results.
Andrea Giammarchi (August 27, 2008 at 8:01 am)
Hi John, the re-use of the script element is to avoid usage of eval, and to instantly execute the content of the tag.
This is because in a scenario where JS “has to” write a tag with document.write, in my opinion the DOMContentLoaded event is not a good idea.
In my case IE leave the code tag as is, and simply thread it as a regular script, while other browsers will simply append a new tag to evaluate globally the entire content of the script.
I like the “scope=private” proposal as well, so my idea is not enough, and I have tested some truly weird behaviour that I will describe better later, after more debug (a sort of global ghost scope …)
Regards
Jake Archibald (August 27, 2008 at 8:17 am)
@alsanan: In addition to what John said, your include function could document.write the script element for the external script. However, you’d still need to close the current script block and open a new one before you could rely on the included script being present.
In fact, I think the only browser that would make the script available after the include line (if it were using document.write) is Opera 9 prior to 9.5 (windows only?). This behaviour was considered a bug and changed.
So, if you’re document writing script element, do it within setTimeout(docWriteScriptElement, 0); to keep Opera consistent.
Jake.
Asbjørn Ulsberg (August 27, 2008 at 8:20 am)
It’s a great idea, but the problem is that I like to have all JavaScript in external files to improve the cachability of my web applications. Since there’s no inline JavaScript, there’s no good way to declare that one script block is dependent on another. Or is there?
John Resig (August 27, 2008 at 8:22 am)
@Jake Archibald: It’s important to not place document.write’s in an asynchronous callback (like in a setTimeout) as it’ll overwrite the entirety of the document (not append to the document, or anything non-destructive like that). document.write can be incredibly dangerous.
@Asbjørn Ulsberg: Cacheability works for a number of things. Note what Jörn pointed out above: You could cache your copy of jQuery (for example) but then have page-specific code inline. That way it limits the inline code to the behavior that is only relevant to this page (and isn’t desirable to cache anyway). I think it makes sense in this context.
Jake Archibald (August 27, 2008 at 8:44 am)
@John: Good point, that would be especially noticeable on a gzipped page, where the dom is ready in IE after a setTimeout(…, 0).
The Opera bug actually occurs when a script element is included with elm.appendChild(…), which the setTimeout trick successfully works around. I had my brain on backwards for a moment.
Has your suggesting of processing the inside of a script element with a src been proposed to HTML5?
Jake
Luke (August 27, 2008 at 9:17 am)
Couldn’t you just do this:
var foo = use_some_lib();
foo.do.stuff();
Then you could move on to other things :)
Paul van Dam (August 27, 2008 at 9:23 am)
Doesn’t
scripts[ scripts.length - 1 ]
fail when you have multiple inline script-blocks?
a (August 27, 2008 at 9:38 am)
I prefer to have all my external JS libraries combined and compressed into one file. Hopefully multiple libraries don’t start doing this.
Tim Ryan (August 27, 2008 at 9:38 am)
I actually like the concept of doing:
<script src="init-script.js">
include('jquery.js');
include('some-library.js');
// ... other code here
</script>
Where include() would document.write() or generate <script> tags to get the benefits of caching, and the number of your script tags are basically cut down to one.
In other words, I think use of this technique should be flexible, since different people could find very different uses for it. If you were to implement a simple callback for the script element, people would eventually develop their own microformats for scope=””, depends=””, and more.
V1 (August 27, 2008 at 9:46 am)
I have been racing it over and over again with the IBM page detailer, and fixed3, actually loads faster than test 1.
It might not be much, BUT its faster ;)
ddunlop (August 27, 2008 at 9:58 am)
Last week I was actually playing with a similar idea, to pass in parameters to a function via the attributes to the script tag that worked on the same idea. I ran into an hopefully obscure problem in ie that I haven’t come up with a super eloquent solution to yet.
if the script is included on the page via a document.write but is preceded by another script that inside of it document.writes a script out the last script element is actually that doubly nested script even though it is processing next script in this case jQuery.
here is an example:
http://www.cronochrome.com/degrade/
here is the structure of how scripts are included:
index.html
|-- docwritescript.js
| -- otherscript.js
| | -- blank.js (turns ps blue)
| -- jquery2.js (turns ps red)
in IE 6 and 7 you will see blue, in ff you will see red
Tim Gossett (August 27, 2008 at 10:14 am)
Awesome idea! I’d love to start using it right away… how would I? Plugin?
John Resig (August 27, 2008 at 10:35 am)
@Jake Archibald: I haven’t proposed this to the HTML 5 people as of yet. We’ll have to see, since this is pretty-solidly changing the existing behavior (e.g. doing nothing) into something (executing the script).
@Paul van Dam: No, as I explained in the blog post it’ll always retrieve the last script on the page (which would be the desired script tag itself).
@a: That’ll always be the preferred technique for sites that have the same JavaScript being loaded on every single page, no doubt about it. But what about the case where you have some page-specific script only being loaded once? Or if you’re loading an external library (like, say, Google Adsense, or jQuery from the Google Ajax API) that you don’t have control over – it’s better to use this technique instead.
@Tim Ryan: So you would synchronously load the script tags, inline, then? As I mentioned before, I’m hesitant to use methods like include() since you start to lose out on some of the implicit caching/retrieval abilities of the browser.
@V1: That’s great to hear – I don’t think I would’ve expected that result. Excellent. How much was it faster by?
@ddunlop: Yeah, if you’re in a situation that involves a lot of document.writes I’m sure things will get confusing pretty quickly.
@Tim Gossett: I think just adding the two line snippet above to your copy of jQuery would do the trick. Or try out the embedded copy of jQuery 1.2.6 that I put up.
Dean Edwards (August 27, 2008 at 11:11 am)
I actually use this technique in the IE7 script. I allow certain parameters inside the script tags just as you describe.
Thomas Hansen (August 27, 2008 at 1:09 pm)
I must completely agree with Starfish here that this is “too clever”…
One could argue that e.g. some “custom attribute x’s existence” and value defines some y thingie which is probably really awesome and does something fantastisc. Then when you pull that string long enough; swosh – and you’re in Silverlight (ActiveX2.0) land…
Though as a CHANGE proposal for the next HTML standard, that’s another thing. THERE it does belong :)
Guy Fraser (August 27, 2008 at 2:39 pm)
I vote in favour of John’s idea.
I remember when people were saying that the chainability of jQuery functions was “too clever” and now none of us could imagine life without it. I think this approach to running page specific script once the library has loaded will turn out the same way.
I’m not sure that it would need to go in to HTML5 (partly because I could well imagine Internet Exploder managing to make a mess of the implementation – sorry, no faith in M$ any more) – having the required code in the library should suffice for any libraries that want to do this.
Leo Horie (August 27, 2008 at 3:44 pm)
It’d be a good idea to make sure the eval can’t bomb on html comments. Their presence (for conformance with real XHTML, for example) could cause js to throw errors, or, in browsers with E4X, to not do anything.
On a similar note, it’d be nice to filter script elements by type, so as to not eval things like html templates hidden in script tags, hotruby.js, etc.
Jonas Sicking (August 27, 2008 at 4:02 pm)
My recollection of the inline content inside the <script> when a src attribute was specified was that it was the ‘fallback’ script that was supposed to execute if loading the src failed.
Looking at our implementation that doesn’t seem to be what we are doing though.
Sounds like your testing is showing the same thing.
Do note though that doing
var scripts = document.getElementsByTagName("script");
eval( scripts[ scripts.length - 1 ].innerHTML );
is going to fail if the script tag is inserted using the DOM though.
An alerternative is to look for the script tag whose src attribute points to jQuery, or who has a specific id specified.
jason kuhn (August 27, 2008 at 4:46 pm)
i’ve always been peeved about this. i can’t believe i’ve never bothered to try fixing it. forgot that script tags also have innerHTML.
what’s uglier is you have to have the closing </script> tag instead of just saying <script src=”js.js” />.
this technique is rather slick, but i’m not sure if this really has a place in a jQuery release since it redefines how the browser is supposed to interpret the tag and it forces a developer to break a rule. *gasp* (here’s to HTML5 and the Open Web!)
according to w3c recommendations:
“If the src attribute is not set, user agents must interpret the contents of the element as the script. If the src has a URI value, user agents must ignore the element’s contents and retrieve the script via the URI.”
surprisingly, it still validates.
i’ve just implemented this into my website. it doesn’t really do much other than pleases me. i’m okay with that. :) sometimes being pragmatic trumps semantics.
Chris (August 27, 2008 at 7:41 pm)
> sometimes being pragmatic trumps semantics.
Indeed.
Iraê (August 27, 2008 at 8:22 pm)
This is indeed very cleaver and a nice technique.
I’m just worried with forward compatibility. One thing is to implement an future HTML5 spec in jQuery and conforming to the new spec. This is totally awesome if it is the case. But I think if the trick will not ever become a standard then it’s better to use something in the fashion of jQuery.noConflict()… maybe jQuery.contextualScript() ?? This would enable the code only for the ones aware of it.
Tim Ryan (August 27, 2008 at 10:42 pm)
@John: My mistake. You’re right in that an include() function could only load them synchronously—I was thinking more along the lines of a preprocessor.
So I experimented with adding an #include directive to JavaScript. Your script callback parses the code, generates <script> elements for each of the dependencies, and then a final one containing the script. By using <script src=”…”> tags, it should retain all the benefits of browser caching, too.
Of course, IE doesn’t like setting the innerHTML of a <script> element (to evaluate the #include-stripped code), so it needs some hacks to be useful…
Just Me (August 28, 2008 at 11:30 am)
Interesting. First came across this and other interesting ideas on Kent Brewster’s site here:
http://kentbrewster.com/case-hardened-javascript/
He then later created a presentation describing it here:
http://kentbrewster.com/badges/
I suspect he’s not the first to consider this, but perhaps this idea will get more attention being mentioned by someone as recognizable as jQuery’s creator.
Steve Souders (August 28, 2008 at 1:47 pm)
Let’s explore how this works when loading scripts in a non-blocking way.
My recent work has focused on loading scripts without blocking the browser. (See slides from OSCON or Google I/O.) Even though newer browsers (IE8, FF3.1, etc.) download scripts in a non-blocking way, there are still advantages in using these non-blocking techniques, not the least of which is better performance for current browsers.
When evangelizing these non-blocking script loading techniques, the issue of inline (aka, internal) scripts being dependent on the (now asynchronously loaded) external scripts is an issue. As John points out, the typical path is to tie the inline script to a window onload handler. The downside of this is the inlined JS code executes later than necessary.
The pattern proposed here is a big help, but we need to make it work when the script is loading asynchronously (eg, inserted using the DOM as Jonas mentions). I wrote an example that that uses this pattern and works for scripts inserted using the DOM:
http://stevesouders.com/tests/inex-dom.php
This is much more complicated, but for people who are venturing into these advanced techniques to load scripts without blocking, it provides a way of attaching inline code that waits for dependencies, but still executes as early as possible.
moé (August 28, 2008 at 4:50 pm)
sorry but i find it useless… I agree that internal script should be executed even if script tag has a src, but it doesn’t matter for me…
Garrett (August 29, 2008 at 3:31 am)
Using eval to execute fallback content. I can’t say I like this idea.
I suppose I should take this post seriously, since so many others have
The benefits of this technique;
* Only one tag
* If the load and execution was error-free then the internal script would be executed as well.
If you fix those load errors, then the only remaining issue would be: Not one script tag, but two.
A side effect is that the fallback-content of the script tag is executed when the page is “ready”. That doesn’t seem very intuitive, but the approach itself seems strange to me. So, this would not work:-
<script src=”jquery-1.2.6-embed.js”>
document.write(‘<p style=”color:green” id=”xxx”>PASS</p>’);
</script>
The Drawbacks:
* use of eval will cause problems with comments, make debugging harder, and impact performance.
* Requiring jQuery to be the last script in the page with:-
eval( scripts[ scripts.length - 1 ] )
Using an extra script tag:
<script type='text/javascript''>
</script>
– would be less code that the two lines at the end of the script:
var scripts = document.getElementsByTagName("script");
eval( scripts[ scripts.length - 1 ].innerHTML );
The script tag is more widely supported, does not rely on eval, does not rely on the jQuery.ready function, and it in fact requires less code.
Garrett
Kyle Simpson (August 29, 2008 at 8:57 am)
It’s a great idea. I wonder how successful we’ll be in getting it to work robustly.
I’ve recently been experiencing (with IE, big surprise!) some frustrating, difficult to debug/address issues with inspecting/manipulating script-tags during page load (to basically dynamically load dependent assets hanging off a library).
The hitch seems to be introduced by something that Dojo is doing (I know that’s a ‘bad word’ around here!).
Just thought I’d mention that a technique like this should be tested even when the dojo.js file is included *before* the script tags in question, as this is the sequence that I saw causing me all my problems (still haven’t figured out why). So for anyone who’s trying out advanced stuff like this, or more appropriately, for those who are advocating it’s wide-spread use, I personally suggest running it through the ‘dojo is present’ test and make sure their script doesn’t screw you over like it did me. :)
TNO (August 29, 2008 at 9:03 pm)
*Cross posting from Ajaxian*
Hmm, I guess I’m a bit too concerned with some unforeseen issues with browser vendors to like this too much:
First, this is invalid HTML according to the specs:
http://www.w3.org/TR/REC-html40/interact/scripts.html#h-18.2.1
http://www.w3.org/TR/html401/interact/scripts.html#h-18.2.1
http://www.w3.org/html/wg/html5/#script
Second, HTML5 offers the defer, and async attributes
Third, shouldn’t we as good web developers have all our scripts in external files anyway?
Also, wouldn’t the use of “curScript.innerHTML;” run into to trouble with inline tag strings?
Angus Turnbull (August 29, 2008 at 10:28 pm)
Clever hack! One idea that would get around your infinite-loop problem without executing inline scripts twice would be:
var scripts = document.getElementsByTagName("script"),
script = scripts[ scripts.length - 1 ];
if ( script.innerHTML && script.innerHTML.indexOf('jQ-core-unique-string') == -1 ){
eval( scripts[ scripts.length - 1 ].innerHTML );
}
jseros (September 1, 2008 at 11:42 am)
is very obstructive John!!!
JP (September 4, 2008 at 9:48 am)
For some reason this post made me think of something I was dreaming about the other day, and that was how nice it would be if JS knew what context it was called from. I.e you had some sort of reference to the parent script tag from within the JS that the script tag was running. It would make things like JS widgets so much easier to operate. I guess there are ways round it like add a var with the Scripts file name in the script then hunting through the scripts to find the one with that src value, or even parsing out the actual innerHTML of the inline script tag to find the one that contains a text key you could match against. It’s all starting to sound very scary. Maybe I’ll go off and mock something up just to really terrify myself.
JP (September 4, 2008 at 10:33 am)
var el = document.createElement("H4");
el.innerHTML = "THIS IS CREATED CONTENT";
el.style.color = "red";
var scripts = document.getElementsByTagName("script");
scripts[ scripts.length - 1 ].parentNode.insertBefore(el, scripts[ scripts.length - 1 ].nextSibling);
I do like that, goodbye document.write, goodbye dumb dom nodes that do nothing but act as an anchor, a very nice idea John. +1
Alan Hogan (September 4, 2008 at 2:46 pm)
I can’t even express how awesome that sounds!
Richard Fink (October 7, 2008 at 10:46 am)
I’ve found this technique (been using it for quite some time) very helpful in amending variables on a page-by-page basis as necessary while still retaining the same underlying .js file from page to page.
Saves a tag and keeps things which belong together, together.
I’ve been working for some time on a small special-purpose script library which not only fixes but also enhances IE’s rather quirky Text Size and Zoom features. (Essentially, it uses javascript to add window.zoom and window.textsize properties and window.ontextresize and window.onzoom events. In so doing, IE’s Text Size can become a true Text Zoom like in FF and Safari. Switch style sheets, etc…)
Anyway, pages styled differently may require different “tweaks” for sizing and resizing; a Form page versus a Hompage, for example; and I’ve found this a good way to do it. Not much can go wrong, in that the innerHTML consists of just variable assignments.
I had never seen it done by anyone else but I see I’m not alone!
Ricardo (October 9, 2008 at 1:55 pm)
A very stupid way of doing this is by using
//code
Ricardo (October 9, 2008 at 1:56 pm)
beh. didn’t read the note.
A very stupid way of doing this is by using
<script src="..." onload="eval(innerHTML)"> //code <script>
Andy VanWagoner (May 21, 2009 at 12:53 pm)
@Paul van Dam – I’ve had problems with the last script tag not being the currently running script on WebKit when I dynamically add the script tags. However, when the script tags are included in the html file, this technique works in all browsers.