When is it Safe to Modify the DOM?
This is a pretty important question when in comes to DOM programming. Start manipulating the DOM to early and bad things happen. Do it too late and the users may see that annoying flicker. So what's the best technique for figuring this out? Well, I was curious about this myself so I took a look at how some of the popular Ajax libraries (jQuery, MooTools, YUI, Prototype and ASP.NET AJAX) are doing this. Here is what I found out ...
Update: 08/11/2008: I came across an article that sheds more light on ASP.NET AJAX's init technique. There is a lot of good content in the comments as well (look for the ones left by Dave Reed). Here are a few snippets of what Dave has to say ...
His comments with respect to the location of where the ScriptManager injects the call to Application.initialize ...
ScriptManager does not put the call to initialize in a “random” location. It is in fact, always the very last thing in the form, just before the closing form tag. There shouldn’t be anything after that (for better or for worse, form is a major part of an asp.net page), and if there is, it could only be because the dev put content after the closing form tag, and all they must do is move it inside
And what he had to say regarding why they chose this approach instead of what some of the other Ajax libraries are using ...
To address your follow up post that questions why we used this technique rather than a “proven” technique developed by the community — when we release the framework, we always have to consider the shifting browser space. New browsers may come along. Updates for all the existing browsers come out all the time, sometimes causing bugs, like this one:
So when we come up against a browser quirk, there are a few rules we try to stick to if it makes sense. The primary one is SIMPLICITY. Because a simple solution is less likely to break than a complex one. Another one is CONSISTENCY. If one technique works in all browsers, it is better to use it in all browsers, otherwise you are more likely to wind up with subtle bugs in one browser but not in another. Another example in the framework of that rule in action is the requirement for the Sys.Application.notifyScriptLoaded call at the end of script references (required, at least, for them to load successfully during a partial update in an update panel). We really didn’t want to require it, but it was the best way to make it work in Safari at the time. So what if you accidentally left out the call in your script? We detect this, even if you are in IE or FF or Opera, and throw an exception! It can work in those browsers without, but its better to warn you — hey, this is wrong, it won’t work in Safari.
You don't need to wait for window.onload ...
Its definitely safe to muck with the DOM in the window's onload event, but there is really no need to wait that long. The onload event doesn't fire until the entire page has loaded (DOM plus images) and often that ends up being too late. The user ends up seeing the page in potentially 3 different states: DOM without images, DOM with images, and finally the DOM with images and what ever changes your script has applied. There is no reason why your script can't run just after the DOM has loaded, but before the images are downloaded.
FireFox, Opera 9+ and recent Safari builds all support a DOMContentLoaded event that fires at precisely this moment (after loading the DOM, but before the images are downloaded). Opera even fires it even before all of the stylesheets have loaded which actually turns out to be a bit too early. But the bottom line is that you wire an event handler, add your code and be on your way ...
Wait - What about IE?
Internet Explorer is a little different. There is no DOMContentLoaded event so we have to rely on IE specific
hacks tricks. There seem to be generally two different approaches to simulate the DOMContentLoaded event ...
The first is to exploit an IE's non-standard defer attribute located on the script tag. When IE comes across this it will hold off on executing the script until after the DOM is loaded. So you can simulate the DOMContentLoaded event by including the script you need executed (include it in onload.js in the example below).
The second workaround (which seems to be a little more popular) includes setting up a function that attempts to scroll an element. If an error is raised, the DOM isn't quite ready for us yet and we try it again later. If no exception is raised we know it is safe to run our script. Here is an example of what this looks like (taken from ajaxian) ...
jQuery 1.2.6, MooTools 1.2 and YUI 2.5.2 all use variations of the second technique to simulate the DOMContentLoaded in Internet Explorer. Prototype 126.96.36.199 includes a variation of the first technique.
Will IE8 Support DOMContentLoaded?
I am not sure. I found this gem in the transcripts for the Windows Internet Explorer 8 Expert Zone Chat (March 20, 2008)
Marc Silbey [MSFT] (Expert):
Q: Will IE8 support HTMLElement? addEventListener? DOMContentLoaded?
A: We are working on improving our standards support to be more interoperable with other browsers. That said, we can't really promise support for features or standards in future releases. You can look for the features you want on the IE8 Beta Feedback site and vote on them. Check out the IE blog post here for more information: http://blogs.msdn.com/ie/archive/2008/03/05/ie8-beta-feedback.aspx
What Does ASP.NET AJAX Do?
I found it interesting that ASP.NET AJAX doesn't attempt any browser specific optimizations (like attaching to the DOMContentLoaded event). Instead, ASP.NET AJAX simply queues its initialization function to be executed after the DOM is loaded by using window.setTimeout. Here is the bit of code that does this ...
I tried to find some documentation explaining why this works, but I couldn't find any. Apparently queuing a function like this ensures it will execute after the DOM has loaded. This is real simple, but unless I am mistaken, not taking advantage of the other browsers DOMContentLoaded event causes ASP.NET AJAX's initialization function to execute just a bit later than it could in FireFox, Opera and Safari. Not the end of the world, but it could maybe use some optimization as well.
Another thing I was curious about was if this approach would work if the call to Sys.Application.initialize(); wasn't located so far down in the page (I believe the ScriptManager injects this guy way down by the body's closing tag). Anyone know anything more about this?
Some Real Simple Tests
To play around with these I created a real simple page where I used each of the Ajax libraries initialization functions to run a bit of code that hides a big red banner. Then I loaded the pages in the different browsers to see how well they performed. I know, nothing too complicated, but just enough to get a feel for it.
In each of these pages I have wired the Ajax libraries init function to hide an element that contains the text 'Did this flicker?'. Ideally you will never see this text in any of the demos. Under the header I print out roughly the number of milliseconds it took for the init function to be invoked and finally at the bottom of the page I have links to the other demo pages.
Depending on the what browser/Ajax library combination you are using will depend on how well the page performs.
That's it. Enjoy!