Wednesday, January 31st, 2007
Eval’ing with IE’s window.execScript
Plaxo’s Joseph Smarr has been playing with on-demand javascript, i.e. downloading extra JS code after the page has already loaded. When you grab the code via a remote call and eval() it, it doesn’t get into global scope. So here’s how he dealt with it.
Here’s a simplified version of the situation we faced:
function loadMyFuncModule() { // imagine this was loaded via XHR/etc var code = 'function myFunc() { alert(\"myFunc\"); }'; return eval(code); // doesn’t work in FF or IE } function runApp() { loadMyFuncModule(); // load extra code “on demand” myFunc(); // execute newly loaded code }The thing to note above is that just calling eval() doesn’t stick the code in global scope in either browser. Dojo’s loader code solves this in Firefox by creating a dj_global variable that points to the global scope and then calling eval on dj_global if possible:
function loadMyFuncModule() { // imagine this was loaded via XHR/etc var code = 'function myFunc() { alert(\"myFunc\"); }'; var dj_global = this; // global scope object return dj_global.eval ? dj_global.eval(code) : eval(code); }This works in Firefox but not in IE (eval is not an object method in IE). So what to do? The answer turns out to be that you can use a proprietary IE method
window.execScriptto eval code in the global scope (thanks to Ryan “Roger” Moore on our team for figuring this out). The only thing to note about execScript is that it does NOT return any value (unlike eval). However when we’re just loading code on-demand, we aren’t returning anything so this doesn’t matter.The final working code looks like this:
function loadMyFuncModule() { var dj_global = this; // global scope reference if (window.execScript) { window.execScript(code); // eval in global scope for IE return null; // execScript doesn’t return anything } return dj_global.eval ? dj_global.eval(code) : eval(code); } function runApp() { loadMyFuncModule(); // load extra code “on demand” myFunc(); // execute newly loaded code }And once again all is well in the world.












Randy Webb, a regular on Usenet’s comp.lang.javascript, has done quite a bit of work on dynamic script insertion to get a script to evaluate in the global scope. The following page may load slowly but is good research.
http://members.aol.com/_ht_a/hikksnotathome/loadJSFile/index.html
In our qooxdoo framework we use “window.eval” instead. IMHO the above workaround is not needed because window.eval works quite well and should do exactly the same (filling the global scope) in all browsers.
Another thing which should work well is the following:
(new Function(code))();This should also perform better than window.eval because with this solution the browser does not need to merge the different scopes which is one thing which makes eval slow.
In my experience, window.eval() does the same thing as eval() in all cases. For non-IE browsers, I’ve found that using eval.call(window, jscode) works brilliantly.
My general approach, though, is to make sure that all of my dynamically-loaded code is namespaced to an already-existing global object. For example, to load a generic function, the on-demand JavaScript file could attach it directly to the window object:
window.myNewFunction = function() {
};
This way, it doesn’t matter where eval() is called, it will always load correctly. (Of course, adding stuff to window isn’t necessarily the best way, I prefer using another global object, such as YAHOO if you’re using YUI.)
You should be able to do this with
eval.call(window, stringToEval), but sadly it seems like IE7 isn’t giving any love on that front either.However it appears that you can do
with(window){eval("this.foo='bar'")}which will set a window variable named “foo” to “bar” in IE and Firefox, but that’s not really much of an improvement.To understand this fully, you need to see Joseph’s original article with references to a Dean Edwards and a Jeff Watkins article. Those two are the basis for this fix. Then, possibly Dojo notes on eval(), which shows the framework we’ve been using. Our version was older, but apparently we developed the same solution independently. Finally, you should see a comparison of window.eval() and eval() from hedgerwow on Y!360, you’ll get inconsistent behavior depending on how you use it. All of these can be eliminated by namespacing everything you create into a single object, but our point was to be able to declare
function blah(x){...}and use that in an innerHTML statement instead of hooking up events to DOM nodes one-by-one.What about creating a script element and setting its innerHTML or add a text node to it? It has some browser-specific quirks but works well. Oh, apparently it’s one of the methods in the first comment.
This is not directly related to the article, but it is related to external javascript “on demand”.
Im currently trying to develop a method to include an *external* javascript file (a module) with the DOM script method. I pass a callback function to the included module, which the module itself calls with .apply. This almost works… From the outset, it appears that the module has loaded, and the callback has called correctly. The callback begins to execute, but it isn’t able to access methods within the module - its still undefined.
Now, im no expert on closures and scope, but I was wondering if anyone could point me in a direction to other peoples work on this sort of thing. Perhaps what I am trying to do is impossible.
This article seems to only be applicable for inline functions/script or script pulled in via XHR.
Hello
I resolve my problem with this function:
function globalEval(code) {
if(window.execScript) {
window.execScript(code);
} else {
window.setTimeout(code, 0);
}
}
I have not proven it in IE7, but for IE6 and Firefox is ok.
jgarces, your function does not behave the same for all browsers.
globalEval(foo);
bar(); // defined in the just eval’d code
This will work in IE, but it will not work in other browsers, as the timeout will not be triggered until the current script block finishes.
How would I implement this for JavaScript that I’m placing in a DIV from an AJAX.Updater call? I’m returning a Dojo widget and it does not render. However, viewing a page with the same code renders the widget correctly. But I can’t figure out how to get it to render after the AJAX.Updater call using Prototype. Thanks!
I agree with Sebastian’s comment about using pre-defined namespaces. I think it’s just a bad practice to dynamically load functions into global scope. No matter how careful you are about colliding function names or making sure each page loads correct functions, it is still possible that you can override functions you need by mistake or calling another page’s function.
Here’s my “Missing Image Hack”:
http://www.ajaxprogrammer.com/?p=17
Any HTML “module” that gets pulled in via XHR can run its own JavaScript. The script executes in the global namespace, but won’t pollute it.
It’s ugly, but it works.
Ryan - Don’t quite understand what you are dealing with innerHTML. Are you dealing with the fact that tags don’t get evaluated when included as part of innerHTML?
Eval also works in Firefox and IE.
But it seems that: function outer(){ function inner(){}; }
is handled like: function outer(){ var inner = inner(){}; }
but not as: function outer(){ inner = inner(){}; }
The last way exposes inner to the world.
You can see this in the jsolait-library and you can test it with an example found in http://planet.openjsan.org/
function loadMyFuncModule() {
// imagine this was loaded via XHR/etc
var code = ‘function myFunc() { alert(\”myFunc\”); }’;
return eval(code); // doesn’t work in FF or IE
}
function runApp() {
loadMyFuncModule(); // load extra code “on demand”
myFunc(); // execute newly loaded code
}
myFunc (); // produces an error
With a little change it works:
loadMyFuncModule = function() {
// imagine this was loaded via XHR/etc
var code = ‘myFunc = function() { alert(\”myFunc\”); }’;
return eval(code); // d o e s work in FF and IE
}
runApp = function() {
loadMyFuncModule(); // load extra code “on demand”
myFunc(); // execute newly loaded code
}
runApp()
execScript() is particularly useful when implementing Multiple Document Interfaces (MD). The sample below describes this in detail using the prototype windows class.
http://www.codeproject.com/Ajax/AjaxMDIWeb.asp
I’ve noticed that the sample works like a charm in IE, but that it degrades in Firefox because of its exclusive use of execScript. I’ve tried replacing it with window.eval() and surprisingly, that works in IE as well; but not firefox. eval.call() doesn’t work either. And this leads me to believe that in FF, I’m not get a proper reference to the win object?
Tried everything to get this sample to work in FF, but have had no luck. A real brick wall. I’d truly appreciate any ideas you might have.
Guys here is a solution to run a script in global scope:
Found as a bug reported, but seems will help us. Works fine in IE and fire fox.
a.html:
——————————-
//Can make it an HIDDEN IFRAME.
b.html
——————————-
function loadTest()
{
window.parent.eval(”function test(){return ’success’;}”)
}
After loading a.html, click on “Test” button it doesn’t work, now click on Load in the iframe and then click on “Test”. Hurray it works :-). Tested on IE6 and FF2
sorry HTML tags lost in the previous comments:
a.html
—————————
//HIDDEN IFRAME.
b.html
—————————
function loadTest()
{
window.parent.eval("function test(){return 'success';}")
}
Sorry Again … I wish i could delete the previous comment. Anyways here is what i had to post.
a.html
———————-
<input type=’button’ value=’Test’ onclick=’alert(test())’>
<iframe src=’b.html’></iframe>//HIDDEN IFRAME.
b.html
———————-
<script language=’javascript’>
function loadTest()
{
window.parent.eval(”function test(){return ’success’;}”)
}
</script>
<input type=’button’ value=’Load’ onclick=’loadTest()’>
Not having read all of the comments above, the problem is solved for CSS, TEMPLATE and JS CODE files in the twoBirds lib (with framework addon lib). No eval() needed (!) and easy to debug…
Look at the following code snippet, it will explain everything:
application.index_body = {
init: function (pDivId) {
tb.element.require(
“[ [ ‘css’, ‘application’, ‘index_body’ ], ” +
” [ ‘tpl’, ‘application’, ‘index_body’ ], ” +
” [ ‘js’, ‘tb’, ‘effect.fadeTo’ ] ]”,
‘application.index_body.display( “‘ + pDivId + ‘” )’ ,
true
);
},
display: function (pDivId) {
var myHtml = tb.loader.tplget(’application’,'index_body’);
tb.div.replace( pDivId, myHtml );
tb.element.require(
“[ [ ‘js’, ‘application’, ‘menu’ ], ” +
” [ ‘css’, ‘application’, ‘menu’ ], ” +
” [ ‘tpl’, ‘application’, ‘menu’ ], ” +
” [ ‘js’, ‘application’, ‘user_greeting’ ], ” +
” [ ‘css’, ‘application’, ‘user_greeting’ ], ” +
” [ ‘tpl’, ‘application’, ‘user_greeting’ ], ” +
” [ ‘js’, ‘application’, ’submenu’ ], ” +
” [ ‘css’, ‘application’, ’submenu’ ], ” +
” [ ‘tpl’, ‘application’, ’submenu’ ], ” +
” [ ‘js’, ‘application’, ‘window’ ], ” +
” [ ‘css’, ‘application’, ‘window’ ], ” +
” [ ‘tpl’, ‘application’, ‘window’ ] ]”
);
tb.element.show( ‘toprightcontainer’ , ‘application’, ‘user_login’ );
}
};
This is productive code. I was working on that special problem for more than 1 year, now it works on every browser thats halfway modern. This is only a fraction of the libs abilities. I can recursively load objects consisting of css,tpl and js object code files, it all works asynchronous and executes out-of-order. In my opinion this allows unlimited complexity while having managable code.
twoBirds V2.0 has just been declared stable, it works easily with other libs. And it is open source.
Im working on my HP now to make a download and documentation area (had no time for that so far), but the libs are pretty self-explaining just in case you want to rip the code from the prototype. The window management system is NOT OPEN SOURCE though. And it is a work in progress, permanent beta.
Cheers from Germany, Frankie.
Additional comment:
The twobirds system documentation will be about 1 page for each “core” and “framework”. There is nothing more required to fully explain the system.
If you want to contact me, my eMail is frank dot thuerigen -at- phpbuero justAnotherDot de. Please add “ajaxian” to the topic line so I know where it comes from - I get a lot of spam in english language and I´d like to see the comments, not find them after a year in the spam bin ;-)
Have fun!
For those who didn´t find the code snippet that self-explaining, it simply translates to this:
-
“On object initialization, load the three files named from their module directories. Do this asynchronous (true). Once you are done, execute the display callback function (application.index_body.display).”
-
The display function starts loading some more files required later (asynchronous by default) and in the meanwhile displays a login form topright.
-
This works no matter how many files are in the loading queue.
-
If anyone can come up with a solution that is simpler, I´d like to see it. ;-)
-
Frankie
-
PS: to get to the chat on the prototype site you have to enter whatever user name and return, no pass required…
And one last comment: a dynamic web-object programmed with twobirds is getting displayed using only one statement, for the element above that is:
-
tb.element.show( ‘toprightcontainer’ , ‘application’, ‘index_body’ );
-
This lib then performs everything necessary to display said element. It also caches everything, so once loaded no server access is required to display the same element again, except for sheer data, which I usually convey in a JSON container.
-
Frankie
correcting the above comment (sry):
tb.element.show( ‘bodyDiv’ , ‘application’, ‘index_body’ );
Just to clarify since I´ve been asked:
-
twobirds.js IS open source
twobirds_fw.js IS open source
twobirds_application.js IS NOT open source
-
The two open source parts fully allow on-demand-everything crossbrowser, application add nothing to that.
-
Frankie
Thank u very much and u did a great job
i think it iwould be better to use a textarea then apply the source formatting behind it.
nice article
i love it very much
wow power leveling
Thank you, execScript was exactly what I was looking for.
How about:
try { window.execScript(code); } catch (e) {}
try { eval(code); } catch (e) {}