Fixing eval() to use global scope in IE
[Note: This is the first in what I hope will become a series of technical articles on the lessons I’ve learned “from the trenches” of my web development work at Plaxo. Non-techy readers are invited to skip any articles categorized under “Web development”.
]
Update: This article has been picked up by Ajaxian, and it’s sparked an interesting discussion there.
At Plaxo I’ve been working on a new (soon to be released) version of Plaxo Online (our web-based address book, calendar, and more) that is very ambitious both technically and in terms of user experience. We’re currently deep into performance tuning and bug fixing, and we’ve already learned a lot of interesting things, most of which I hope to share on this blog. The first lesson is how to correctly eval() code in the global scope (e.g. so functions you define inside the eval’d code can be used outside).
When we built the first version of the new site, we combined all the JavaScript into one giant file as part of our deployment process. The total codebase was huge and it had the predictable effect that initial page-load time was terrible because the user’s CPU was solidly spiked for several seconds while the poor browser choked through the massive amount of code it had to parse. So we started loading a lot of our code on-demand (packaging it into several logical chunks of related files and using dojo’s package/loader system to pull in the code as needed).
All was well until we started defining global functions in the loaded JavaScript. (We did this mainly for event handler code so we didn’t have to spend time walking the DOM and finding all the clickable nodes after injecting innerHTML to hook them up to the right scoped functions.) In Firefox, everything kept working fine, but in IE, none of the global functions were callable outside of the module being loaded on-demand (you would get a typically cryptic IE error that in effect said those global functions weren’t defined). It seemed clear that when the code being loaded got eval’d, the functions weren’t making it into the global scope of the page in IE. What was unclear was how to fix this.
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. Hopefully this is the type of thing that will be hidden under the hood in future versions of dojo and similar frameworks, but for the time being it may well impact you if you’re loading code on demand. So may this article save you much time scratching you head and swearing at IE.
(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).
Posted: January 31st, 2007 under Plaxo, Web development.
Comments: 7
Comments
Comment from dave t
Time: February 1, 2007, 10:30 pm
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 from jsmarr
Time: February 2, 2007, 8:41 am
Dave-the issue is that I’m building my HTML by building up a big string of HTML and then injecting it into the DOM using innerHTML, because that’s much faster that building using DOM methods (appendChild, etc.). But one problem with using innerHTML is that if I have instantiated objects with methods I want to call when an event happens (e.g. when you click on a contact, I want to call something like this.getContactManager().setContactSelected(true)) you have to wire those calls up AFTER you inject the innerHTML (because you’re just injecting HTML, so there’s no scope or object reference available).
We used to solve this problem by giving each node we wanted to attach an event handler to a unique className, and then after we called innerHTML we would use getElementsByClassName to find the newly created element in the DOM and hook up the event with JavaScript. That’s an elegant solution, but it’s too slow if you’re doing it a lot. So we decided to use global functions (e.g. g_plx_setContactSelected(’contact-id-here’)) instead, since those could be written directly into the innerHTML and the global function could then find the instantiated object to call the method on when it was invoked.
This was great until we realized that it wasn’t working in IE, because the code where the global function was defined was being loaded on-demand, and since we weren’t using the global-scope eval (execScript) trick I mention above, those functions weren’t ending up defined as event handlers. The global-eval trick fixes the problem though, and I’m now quite happy with our setup (load code on-demand, build the UI with innerHTML, no walking the DOM to hook up events, still get to use instantiated objects and their instance methods).
I hope that clears things up!
Thanks, js
Comment from Daniel
Time: February 4, 2007, 8:36 am
Don’t swear at IE too much. What you’re doing isn’t standard ECMAscript and is deprecated in Javascript.
Comment from Trevin
Time: February 7, 2007, 1:42 am
Hey Joseph, long time no see. Good to see you’re still up to your old tricks and hacking away at code
Saw some great coverage on the Plaxo widget so congrats!
Pingback from Joseph Smarr » Handling GeoCoding Errors From Yahoo Maps
Time: March 20, 2007, 7:54 am
[…] PS: Special thanks to Mark Jen for finding me a decent code-writing plug-in for Windows Live Writer! Boy did I struggle with getting WordPress not to mangle my code in the eval post! […]
Comment from mano
Time: May 1, 2008, 2:45 am
the loader is async…
Comment from infous
Time: May 22, 2008, 7:57 am
Why don’t you use window.eval(code) instead of dj_global.eval(code)? thinks, it can be use even inside object’s method, but if you use “dj_global = this”, “this” is a current object, not window.
BTW, thanks a lot! good article


Write a comment