Today I was researching ways to provide Mozilla’s DOMContentLoaded functionality in Internet Explorer. Dean Edwards has already demonstrated two ways that this can be done, using the script “defer” trick and behaviors. Both methods require that an external file be included–either “ie_onload.js” or “loaded.htc”.
I need this feature for a JavaScript library, so I specifically want it to be available without forcing the user to include these external files. I also want it to be self-contained so that a developer can drop a single script into a page or site to get the DOMContentedLoaded-like functionality.
Approach A: JavaScript URL
After a lot of hunting, it finally occurred to me that the script’s “src” attribute can reference JavaScript code. The simplest approach is to replace ie_onload.js with a JavaScript URL. Instead of this…
<!--[if IE]><script defer src="ie_onload.js"></script><![endif]-->
…you use this:
<!--[if IE]><script defer src="javascript:'init()'"></script><![endif]-->
Dean Edwards commented on his blog that the “defer” technique only works with external scripts. At first glance, this seems to contradict my findings. But I think he only meant that the “defer” technique requires that the script use the “src” attribute (vs. an inline script) and not that the code must be contained in an external file.
It is important to note that the init function is included in quotes. Internet Explorer will evaluate the JavaScript expression in the src attribute and use the result as the contents of the <script> tag. Because Internet Explorer evaluates the src attribute immediately, the init function must be called from the script itself.
The biggest problem I discovered is that this approach is broken in Internet Explorer 7 Beta 2. However, there is a bug filed for it, so if you’d like to use this, go vote for a fix!
Approach B: Using the script’s onreadystatechange
I also pursued an alternative approach. While I was perusing MSDN’s documentation on the SCRIPT element, I found that it also supports the onreadystatechange event. Because Internet Explorer waits to load these scripts until the DOM has loaded, the page can watch for changes to the script’s readyState. When the script starts loading, the page can trigger DOMContentLoaded initialization.
Even though IE7b2 does not execute the JavaScript in the src attribute, it does trigger onreadystatechange correctly (fortunately!). Unlike the previous example, this code does not need any conditional comments since onreadystatechange will only be called in IE.
Scripts created with createElement do not respect the defer attribute, apparently because they are loaded (although not executed) before the script is attached to the document. Scripts created with innerHTML must be preceded by a node of a certain type, and they appear to suffer the same problems as createElement.
Given these findings, the init.js script would look something like this:
document.write('<script id="__init_script" defer="true" src="//[]"></script>');
function registerInit(callback) {
/* for Mozilla */
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", callback, false);
}
/* for Internet Explorer */
if (document.getElementById) {
var deferScript = document.getElementById('__init_script');
if (deferScript) {
deferScript.onreadystatechange = function() {
if (this.readyState == 'complete') {
callback();
}
};
/* check whether script has already completed */
deferScript.onreadystatechange();
/* clear reference to prevent leaks in IE */
deferScript = null;
}
}
/* for other browsers */
window.onload = callback;
}
The code in the HTML file would look like this:
function init() {
// quit if this function has already been called
if (arguments.callee.done) return;
// flag this function so we don't do the same thing twice
arguments.callee.done = true;
// do initialization here
};
registerInit(init);
Disclaimer
Although this script seems to work for me, I place no guarantee on it. I know that Dean Edwards has tried more things than I can imagine, so I’ll be slow to call this the final solution. I’m very interested in criticism.
I should also note that this is not the actual code that I will be using in the library. It needs to support multiple event handlers, and it should call the event handlers only once. I have not included these details for simplicity.
Update: I fixed both approaches, which were broken. Although the onreadystatechange approach is not as robust as it could be (it would be nice not to depend on document.write), it does seem to be a step in the right direction.
Update: Applying fixes for HTTPS.