Going Global
So far, the most popular posts I’ve written (if you discount the rant about syncing my mobile phone on Windows) are the two about getting dynamically loaded JavaScript code to execute in the global context.
A Bit of History
Just to recap, in Ajaxian Limitation I complained about the difficulty I encountered trying to get the JavaScript returned from an XmlHttpRequest to function correctly. After poking around a bit, I encountered a clever solution to this problem which I documented in The Magic Eval. Thanks to the help of lots of people, I think I can offer a final reduction of the problem and solution.
When you’re building an Ajax application, it can be extremely convenient to generate HTML on the server which gets sent back to the client. This is often faster and certainly more convenient, because you can share the same content templates as well as leverage the data in its native format rather than converted into JavaScript objects. But I often find that I want to send some code back in addition to the HTML. When you update the innerHTML attribute of an element, any script tags are not evaluated. So you have to manually evaluate the scripts.
Installing a Script Globally
When you receive your HTML snippet, you’ll need to extract the scripts — I typically use the String method extractScripts from prototype, but you can use whatever you’d like. Actually, extractScripts isn’t exactly perfect for the job, because it doesn’t seem to work with scripts with a src attribute. But that’s OK for this example.
Once I have the JavaScript that I’d like to add to the current browser context, I need to evaluate it. Unfortunately, while calling eval with the text of the script will execute the code, it won’t work quite as you expect: the code will execute in the scope of the eval statement and any functions you define won’t be available in the global page scope. There are some clever tricks around this, but they all require you to write your scripts in a slightly funky way. There’s one really clever trick that allows you to write your scripts just like you always do, but have everything work.
/** Execute a script in the global context. This installs all functions
defined in this script into the global scope, unless they are
explicitly created in different scopes.
@param script the source of the JavaScript to evaluate
**/
function installScript( script )
{
if (!script)
return;
// Internet Explorer has a funky execScript method that makes this easy
if (window.execScript)
window.execScript( script );
else
window.setTimeout( script, 0 );
}
This function takes advantage of a proprietary extension to the window object in Internet Explorer: the execScript method. There’s not much information on Microsoft’s MSDN page for the execScript method. But it seems to execute the script in the global scope and installs new functions into that scope, which is fortunate, because the approach used for every other browser doesn’t work in Internet Explorer.
For other browsers, the installScript function uses the setTimeout method of the window object to execute the script. Essentially, when setTimeout executes, it will evaluate the JavaScript in its first parameter. Fortunately, it evaluates the script in the global (or window) scope. This allows any functions you defined in that script to be available throughout the rest of the page.
Handling Script Tags with src Attributes
Sometimes returning inline JavaScript just isn’t the right solution. That means you’ll need to deal with script tags that include a src attribute. The gist of the solution is to pull out the value of the src attribute (either using regular expressions or straight string parsing) and load the JavaScript using an XmlHttpRequest. Once you have the script source, pass it to installScript and you’re done.
Comments
I have played with your solution, and have made it a little better. Essentially I took your installScript function, and made it so that it will work with any script tag including those which use a src= element to refer to an external script file. My function now takes the actual script DOM element as a parameter instead of the script code.
The inserting the element into the head tag of the document, was taken from liferay portal open source. They do it like that. The rest of the code comes from you.
I wanted to say thank you very much. Your post saved me mucho time, and will allow me to have a successful product launch. It may also end up in the Eclipse.org BIRT project.
PS: I have only tested the function in Internet Explorer 6, however liferay is cross-browser and it didn’t have anything in there to indicate browse compatibility issues.
Pierre, I’m really glad this was able to help you out. There are too many times that we encounter a problem that we know must have a clean solution, but Googling for it doesn’t work, because no one has written about the solution.
That’s why I wrote up this particular post: solving the problem was such a pain in the arse, that I didn’t want anyone else to have to go through it. Sort of like my post on building Mozilla SpiderMonkey with NSPR support. These are the sort of things you should never have to solve for yourself, because you know someone else must have already solved it.
P.S. I hope you don’t mind me reformatting your code a bit… WordPress really isn’t the easiest tool for posting code samples.
Hi
I came across your solution in time!
Thanks for the execScripts trick in IE.
However for other browsers, I found another trick.
I managed to make eval work in global context in FF and apparently in other browsers by using
eval.call(window,jscode)
Let me know if this works for you.
Ashutosh — your eval.call approach worked wonders!! thanks. also thanks to Jeff for this googleable article. i was searching with: javascript eval js within a function into global context. -tim
Unfortunately, the
eval.calltrick doesn’t work in Safari. And I’ve no data on whether it works in Opera, since I’ve never run Opera (it’s just not a significant target for any of the Web apps I’ve built).For cross-browser support, I’d stick with the
execScriptsIEism combined withwindow.setTimeout.Oh HELL yes — I was giving up hope on IE’s setTimeout implementation where loading libraries like prototype.js etc…
This execScript crap TOTALLY fixed my problem. :^) Thanks!
great tip.
Thanks.
Hi guys - this article saved me a lot of time - I have the same problem with ajax - the scripts returned by the xmlhttpRequest are not working in ie. Can anyone tell how can I decide if certain script is installed or not without knowing what is inside?
Just found this SOLUTION to my problems…thanks a lot!!!
Just like to give a little code back that I am using…Not checked for browsers other than IE! Maybe could do with tidying up…but it works!!
a.jsp:
b.jsp:
I use XML only….but easy to fix!
Thanks again
…oops
div and script tags missing from post!
a.jsp has standard script tags…if you add src= then that is retrieved else body is used.
b.jsp is surounded by any tag with an id (div span etc) the code replaces any matching id in the dom…good for use with timers. Also, can run multiple threads at the same time!
Lewis, I think I’ve fixed your formatting. I can’t quite get markdown to process the ASP tags correctly, but that’s life.
sorry …
though the explanation given above is prety much clear, but i still can’t apply to my problem
The problem is :
I have a page inside which i have a ‘div’ with a divID. I load evryth from the Ajax response object inside the div with the help of divId.
Now the scripts inside the response text is not working when the the page is loaded or even when that div is loaded.
How can i use execScript() in my this case. From where can I call this gloabal function ??
Plz help me !!
Plz !
Hi Jeff,
Your solution and find of the execScript function is excellent. I’m not sure if you’ve heard of or use the jQuery javascript library, but your fix has generated quite a discussion on the mailing list because the IE bug has been around for a while.
Since you helped in finding the cure, I just wanted to give a little back and tell you that a problem was discovered in your function. As it turns out, window.setTimeout(data,0) will be delayed for 10ms in Opera because there is a 10ms minimum for setTimeout. Granted, 10ms isn’t much time, but it could easily cause something you’re expecting to work fail due to the delay. In jQuery there is browser detection so that we can isolate safari if need be and the solution below uses that, but you could use any safari detection technique you wish.
Thanks again.
Paul, thanks for the note. I’m always glad to help. I had no idea that Opera was doing anything wacky like forcing a 10ms delay. That’s not nice. But, you probably have as good a solution as any — and I think the next Safari will fix the
evalbug so the special case won’t be necessary.Actually, I need to update this article to include the code I really use which distinguishes between browsers once and then always uses the same code.
That’d be excellent. Thanks again Jeff.
Thank you very much for the execScript idea Jeff. That saved my day:-)
Just a quick note on the Opera browser.
Opera doesn’t support innerHTML for the script tag. Instead you have to use the “text” attribute.
scriptTag.text
instead of
scriptTag.innerHTML
Alf, was just about to say same thing about IE7 ; mine throws exceptions when using scriptTag.innerHTML, and runs fine with scriptTag.text
However, it stills a problem : getElementsByTagName doesn’t return ALL script objects… only one of five is returned (in IE7 always, FF 2 reports 5 scripts, and everything’s ok)
PS : Alf, i’m precisely working on some of your scripts, i’ll send you the result of my work if you want :)
Hi Jeff,
First of all, I’d like to thank you for this discussion. I was really going crazy about how to make objects or functions defined through eval defined in global scope. I was trying to improve the security of the scripts on my site, some playing with Javascript when I was getting bored with my usual collage life, lolz. Unfortunately, the answer I found in this discussion did not work out fine for me!!
I used the following script, just a matter of testing.
function iS( script ) { if (!script) return; if (window.execScript) window.execScript(script); else window.setTimeout(script,0); } function evalGS() {iS("function bar(){return 'bar';}");} function tF() {eval('bar();');} evalGS(); tF();Which, unfortunately, did not work out fine for me on my Firefox (v2.0.0.1). Well, tried working with
… eval(”bar=function(){return ‘bar’;}” …
No luck with that either.
And I don’t give up just like you… :P… So I just worked out a little. Tried out some other alternatives.Started with window._content.eval(), tried to check out the compatibility of this statement with other browsers. However, I found out later that to execute eval in Global Scope, we can just use window.eval() in Opera and this works out pretty fine on Firefox too.
So, my script now goes like this…
function iS( script ) { if (!script) return; if (window.execScript) window.execScript(script); else if (navigator.userAgent.indexOf('Safari')==-1) window.setTimeout(data,0); else window.eval(script); } function evalGS() {iS("bar = function(){return 'bar';}");} function tF() {eval('alert(bar)');} evalGS(); tF();This worked fine for me on IE, FF, Opera.
Please verify it for yourself :).
I havent tested it on Safari, However, I would be glad if you could test it for me.
Anyway thanks again for this discussion. It was great :).
Sorry, but I posted that wrong.
Heres the script. Lolz.
function iS( script ) { if (!script) return; if (window.execScript) window.execScript(script); else if (navigator.userAgent.indexOf('Safari')!=-1) window.setTimeout(script,0); else window.eval(script); } function evalGS() {iS("bar = function(){return 'bar';}");} function tF() {eval('alert(bar)');} evalGS(); tF();Just a ’small’ mistake. :P
[…] (PS: Having found the magic term execScript, I was then able to find some related articles on this topic by Dean Edwards and Jeff Watkins. However much of the details are buried in the comments, so I hope this article will increase both the findability and conciseness of this information). […]
This is exactly what I need, however, I cannot determine where it needs to be executed. I have a page with a varity of DIVs that get populated via AJAX.Updater calls using Prototype.
I populate one DIV with another DIV and when I try to populate that with a Dojo widget; it doesn’t render and I just get plain text. Between this and the recent article on Ajaxian, I know the solution is right under my nose, I just can’t get it implemented.
A bit new to JavaScript but frameworks such as Prototype, Moo Tools, OpenRico and Dojo make it so easy to make a really slick site.
If you’d like to offer a helping hand, please contact me directly:
matthew.d.williams [at] gmail.com
all code is not really compatible with Safari.
all variable declared in global is not accessible directly after (in the function who call the eval global).
exec this with safari :
global eval
var screentest = function (s, w){ var t = 'bug'; if(w){ var Start = (new Date()).getTime(); while(typeof test != 'function') { if( ((new Date()).getTime() - Start) > 6000 ){ addconsole('infinite while: test() is '+typeof(test) +' after '+ ((new Date()).getTime() - Start) +'ms on '+s); break; } } } try{ t = test(s); addconsole(t); return true; } catch (e) { addconsole('error: '+e+ ' -- on -- '+s); return false; } } var addconsole = function (s){ document.getElementById('console').innerHTML += '' +s; } function globaleval (){ var s = "var test = function (s){ return 'test() running in global scope: '+s;};"; if(window.execScript){ window.execScript(s); if(screentest('window execScript')) return; } if(window.eval) { window.eval(s); if(!screentest('window eval')){ window.setTimeout(s,0); if(!screentest('setTimeout eval',true)){ addconsole('last try with setTimeout(screentest)...'); setTimeout('screentest("setTimeout eval and called with setTimeout");',0); } } } }– console –
or view online at http://www.xorax.info/test/eval.php
The information I found here was rather helpful. Thank you for this.
Im not in the mood to write a long explanation, so those who where experiencing the problem that IE didnt exec the extrernal ‘ajaxed’ javascripts, well, I found something interesting (It is something with the innerHTML).
If you add something like this to the innerHTML=responseText line will fix the problem (im not an expert so i really dont know why a newline added to the innerHTML fixes it):
...
container.innerHTML = “”+ajax.responseText
…
Hope this helps anybody, it worked for me… after l
LONG LONG hours(DAMN IE).
sorry, im kinda new here so, i didnt know how to write html code.. thats why the “”+ajax.responseText is wrong written, it should display:
…
“<br style=’line-height:0px !important;’ />”+ajax.responseText
…