How do you determine when your document is ready to be manipulated by the javascript in your web application? In the course of a recent project, I discovered that the answer is perhaps not so simple as the question. In fact, the conclusion I've come to is that "it depends".
The most elegant solution I've come across (in my opinion) is the DOMContentLoaded event. This event has been hidden in Mozilla browsers since I don't know when (???) but has now been adopted in recent versions of WebKit and Opera as well. As the name suggests, this event fires when the document tree (DOM) is completely loaded. This may fire before images and other external objects are loaded. The only problem is that IE 6, 7 and 8 don't support it, so there goes the market share. The rest of this article is about various "hacks" which attempt to emulate this behavior in IE.
The current most popular workaround (and newest I believe) is a "hack" discovered by Diego Perini. It is based on the ondocumentready event that is available only to Behaviors in IE. Diego discovered a paragraph in an MSDN document and ran with it.
A few methods, such as doScroll, require the primary document to be completely loaded. If these methods are part of an initialization function, they should be handled when the ondocumentready event fires.
Taking the reverse of this statement, Diego tried polling doScroll until there were no more errors as a means to achieve a "poor man's" ondocumentready. And guess what? It works! ...Sort of. If the loading document's window is not top level (ie. if in an iframe), doScroll never errors and the hack falls apart.
// Should return true only if the document is ready, false otherwise
// Works unless in an IFRAME
function documentready() {
try {
window.doScroll('top');
return true;
} catch (e) {
return false;
}
}
function init() {
}
// Poll until documentready
(function() {
if (documentready()) return init();
setTimeout(arguments.callee, 0);
})();
Another popular method of DOMContentLoaded emulation in IE is the deferred SCRIPT method made popular by Dean Edwards.
function firedomload() {
}
// A deferred, external script is executed after the DOM is available in IE.
// I know of no formal specification for this behavior.
// However, it appears consistent across IE 6 through 8.
// MUST use a document.write to emit the SCRIPT (if being placed dynamically)
// or readyState never reaches 'complete'.
// (I don't know why, but that's just the way it is.)
document.write('<' + 'SCRIPT src="//:" defer><' + '/SCRIPT>');
var script = document.documentElement.lastChild.lastChild;
script.onreadystatechange = function() {
if (this.readyState == 'complete') {
this.onreadystatechange = null;
this.parentNode.removeChild(this);
firedomload();
}
};
The problem with this method is, of course, is that it requires a call to document.write which will obliterate the current document if called too late.
Dean also shows us how to take advantage of the ondocumentready event directly, but this method requires an external HTC Behavior file, making it less friendly for inclusion within libraries.
Lastly is the most obvious option. Place a script after all of the content in the BODY of your document and use it to bootstrap the initialization calls.
<html>
<head>
<script type="text/javascript">
if (typeof ondomload == 'undefined') ondomload = [];
ondomload.push(function() {
alert('This will execute on DOM load');
});
</script>
</head>
<body>
... some content ...
<script src="firedomload.js"></script>
</body>
</html>
And the firedomload.js script looks like this...
/**
* This SCRIPT should be placed just before the close of the BODY element
* in your (X)HTML document. It may be deferred.
*
* Scripts that take advantage of 'ondomload' may come before or after
* this SCRIPT and may be either deferred or not deferred.
*
* USAGE:
* if (typeof ondomload == 'undefined') ondomload = [];
* ondomload.push(function() {
* alert('This will execute on DOM load');
* });
*/
if (typeof ondomload == 'undefined') {
ondomload = {};
} else {
while (ondomload.length)
setTimeout(ondomload.shift(), 0);
}
ondomload.push = function(cb) { return cb() };
In summary, each method has its pros and cons. Even the library authors can't agree on what is best. At the time of this writing, jQuery uses the doScroll method and Prototype uses the deferred SCRIPT method. I've written a small function using the deferred SCRIPT method. You can find it here. The source code for the tests is also available here. And there is a live test here.
What you use may depend on your needs.