Wednesday, January 31st, 2007

Eval’ing with IE’s window.execScript

Category: JavaScript, Programming, Remoting

<p>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.execScript to 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.

Posted by Michael Mahemoff at 3:40 pm
29 Comments

+++--
3.9 rating from 71 votes

29 Comments »

Comments feed TrackBack URI

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

Comment by Peter Michaux — January 31, 2007

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.

Comment by Sebastian Werner — January 31, 2007

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.

Comment by Sebastian Werner — January 31, 2007

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.)

Comment by Nicholas C. Zakas — January 31, 2007

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.

Comment by Mark Kawakami — January 31, 2007

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.

Comment by Ryan Moore — January 31, 2007

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.

Comment by Bertrand Le Roy — January 31, 2007

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.

Comment by Karl — January 31, 2007

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.

Comment by jgarces — February 1, 2007

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.

Comment by Martin — February 1, 2007

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!

Comment by Matthew Williams — February 1, 2007

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.

Comment by Min — February 1, 2007

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.

Comment by Pete Frueh — February 1, 2007

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?

Comment by dave t — February 2, 2007

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()

Comment by Johann Flori-HImpel — February 2, 2007

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.

Comment by max — March 5, 2007

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

Comment by Abhiraj — March 12, 2007

sorry HTML tags lost in the previous comments:
a.html
—————————

//HIDDEN IFRAME.

b.html
—————————

function loadTest()
{
window.parent.eval("function test(){return 'success';}")
}

Comment by Abhiraj — March 12, 2007

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()’>

Comment by Abhiraj — March 12, 2007

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.

Comment by Frank Thuerigen — March 31, 2007

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!

Comment by Frank Thuerigen — March 31, 2007

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…

Comment by Frank Thuerigen — March 31, 2007

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

Comment by Frank Thuerigen — March 31, 2007

correcting the above comment (sry):

tb.element.show( ‘bodyDiv’ , ‘application’, ‘index_body’ );

Comment by Frank Thuerigen — March 31, 2007

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

Comment by Frank Thuerigen — March 31, 2007

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.

Comment by wow powerleveling — May 28, 2007

Thank you, execScript was exactly what I was looking for.

How about:
try { window.execScript(code); } catch (e) {}
try { eval(code); } catch (e) {}

Comment by lou — October 15, 2007

execScript() is evil.

If the script has an error when it runs, then execScript() catches the exception, and it creates a *new* exception: “Could not complete the operation due to error 80020101″. You lose all information about the original error…

If you get 80020101 on a client’s machine you have *no* information about the error – and it will be a bitch to diagnose (google 80020101 – lots of problems and incorrect answers). [PS: If you are reading this because you have got that error, then try replacing "execScript(code);" with "(window.eval || eval)(code, null);" !]

To see the error, try the following in the address bar IE6 (without a debugger installed – your clients don’t have one!): javascript:execScript(‘syntaxerror;’);

Calling eval(code, null); is the way to go for non-IE browsers, because the second parameter is the context and null means use global as the context.

Ryan Moore:
You had a link to window.eval and eval
which says window.eval is evil – however the evilness is easily avoided by writing a global function which:
a) has *no* local variables (e.g. only does eval and returns) and
b) is not declared inside another function (is *not* a closure – doesn’t capture variables of the outer function/s.
c) calls window.eval or eval appropriately

Sebastian Werner:
(new Function(code))() is not the same. If the code contains any var statements at top level, then declared variables should end up in the scope of the function, not at global scope e.g.
javascript:(new Function(‘var xxx=5′))(); alert(window.xxx);
alerts undefined in IE6 (hmmmm – alerts 5 in FF) versus:
window.eval(‘var xxx=5′); alert(window.xxx);
alerts 5.

lou:
try { window.execScript(code); } catch (e) {}
try { eval(code); } catch (e) {}
That will run the code twice on IE – not what you want at all.

Comment by morrisj — October 24, 2008

There is another Solution, seems like the code that gets executed when using eval, checks if you are evaluating a function, so the trick is tell it its an object like this
var x = eval(“({fn:function(){alert(54)}})”)

now you can easily get x.fn() // 54

this was testes on ie7

Comment by azendal — March 4, 2010

Leave a comment

You must be logged in to post a comment.