Categories:

Relying on DOM readiness to invoke a function (instead of window.onload)

One of the most common event web developers check for before running their JavaScript is whether the page has completely loaded. For example:

window.onload=function(){
  walkmydog()
}

This technique has become very popular as a way of defining our scripts in a central location without having to worry about the availability of the page content it seeks to manipulate. The bottom line is, when the page has fully loaded, whatever we want on the page has as well, ready to be molded. However, it's not without its flaws- waiting for the entire page to load literally means that, which can include slow loading images/iframes present on the page, and isn't always necessary. That's why Dean Edwards, JavaScript extraordinaire, came up with an alternate technique to detect page DOM readiness instead, which just means finding out when the document's DOM tree has fully initialized, versus the entire document and every object in it. A classic example is the insertion/ deletion of nodes via the DOM, which can in fact be done sooner rather than later, when the DOM has loaded versus the document itself (window.onload). In this tutorial, let me recap and perhaps refine a bit Dean Edward's technique for calling a function as soon as the document DOM is ready!

Checking DOM readiness in Firefox and Opera 9+

In Firefox and Mozilla, checking for DOM readiness and executing a function when it's ready is simple, as a special "DOMContentLoaded" keyword is supported that can be used in place of an element when calling document.addEventListener():

if (document.addEventListener)
  document.addEventListener("DOMContentLoaded", walkmydog, false)

In this case the function walkmydog() will run as soon as the DOM for the document has fully initialized, which depending on the contents of your page can occur a lot sooner than window.onload.

Checking DOM readiness in Internet Explorer

In IE (includes IE7), there's no intrinsic way to detect DOM readiness. Dean Edwards came up with a roundabout solution instead, which relies on the behaviour of the defer attribute when added to an external JavaScript tag. In short this attribute causes IE to "defer" loading the referenced external JS file until the DOM has fully loaded, a behaviour that we can take advantage of here to accomplish what we want:

if (document.all && !window.opera){ //Crude test for IE
	//Define a "blank" external JavaScript tag
	document.write('<script type="text/javascript" id="contentloadtag" defer="defer" src="javascript:void(0)"><\/script>')
	var contentloadtag=document.getElementById("contentloadtag")
	contentloadtag.onreadystatechange=function(){
		if (this.readyState=="complete")
			walkmydog()
	}
}

As you can see, this technique centers around defining a "blank" external JavaScript tag with the "defer" attribute added. In IE, most elements support a "onreadystatechange" event that fires during each stage of the element's loading process, ending with "complete". So by probing the onreadystatechange event of our deferred blank JavaScript tag, we can in effect call the desired function when the DOM is ready.

Putting it all together, plus a fall back plan

You just saw the two divergent techniques for detecting DOM readiness. It's time now to put the two halves together, but more importantly, implement a fall back mechanism for browsers that don't support any of the two "detect DOM readiness techniques" to default to using window.onload instead. With that said:

var alreadyrunflag=0 //flag to indicate whether target function has already been run

if (document.addEventListener)
	  document.addEventListener("DOMContentLoaded", function(){alreadyrunflag=1; walkmydog()}, false)
else if (document.all && !window.opera){
	  document.write('<script type="text/javascript" id="contentloadtag" defer="defer" src="javascript:void(0)"><\/script>')
	  var contentloadtag=document.getElementById("contentloadtag")
	  contentloadtag.onreadystatechange=function(){
		if (this.readyState=="complete"){
			alreadyrunflag=1
			walkmydog()
		}
	}
}

window.onload=function(){
	setTimeout("if (!alreadyrunflag) walkmydog()", 0)
}

Note the highlighted code- this is our fallback mechanism that shall account for the scenario in which the target function wasn't run at the DOM ready stage for any reason, and run it via the traditional window.onload event instead. With it in place, no browser should fall through the cracks at the end of the day. Two things I should point out with this scheme that makes it work:

  • A global flag "alreadyrunflag" is defined to let the fallback code know whether it should run the target function via window.onload. If the flag evaluates to true, it means the target function has already been run at the DOM readiness stage (which proceeds window.onload).

  • In IE and for very basic pages that contain just a few lines of text, there is the possibility that window.onload could fire at the same time the readyState property of the external JavaScript tag evaluates to "complete", instead of the former always following the later . In such a unique case, the flag alreadyrunflag won't evaluate to true in time for our fall back function to know that the target function has already been run in IE, and will run it again. This problem can easily be fixed by embedding a setTimeout() within window.onload, ensuring the code attached to this event will in fact always be the very last thing run on the page.

And there you have it- a way to launch functions as soon as the DOM is ready and not leave non capable browsers in the dust. Using the exact same technique, the below example shows launching two functions this time around, the later with a parameter:

var alreadyrunflag=0 //flag to indicate whether target function has already been run

if (document.addEventListener)
	document.addEventListener("DOMContentLoaded", function(){alreadyrunflag=1; walkmydog(); feedcat('grapes')}, false)
else if (document.all && !window.opera){
	document.write('<script type="text/javascript" id="contentloadtag" defer="defer" src="javascript:void(0)"><\/script>')
	var contentloadtag=document.getElementById("contentloadtag")
	contentloadtag.onreadystatechange=function(){
		if (this.readyState=="complete"){
			alreadyrunflag=1
			walkmydog()
			feedcat('grapes')
		}
	}
}

window.onload=function(){
	setTimeout("if (!alreadyrunflag){walkmydog(); feedcat('grapes')}", 0)
}

In conclusion, and checking for  DOM readiness in Safari

The advantage of running a function as soon as the DOM is ready versus window.onload materializes on pages that are heavy on mages or iframe tags. Ultimately you have the weigh the benefits versus the additional code and work needed to implement such a technique. Before I conclude this tutorial, there's an elephant in the room I've been ignoring until now. Apparently in Safari it's also possible to check for DOM readiness, and Dean Edward referred to John Resig in coming up with a way to do so in that browser:

if(/Safari/i.test(navigator.userAgent)){ //Test for Safari
	var _timer=setInterval(function(){
		if(/loaded|complete/.test(document.readyState)){
			clearInterval(_timer)
 			walkmydog() // call target function
		}}
	, 10)
}

It's the obsessive DOM readiness checker! In Safari, apparently, what you need to do is manually probe the document.readyState property at fast paced intervals to see when it returns either "loaded" or "completed" (usually the former proceeds the later, but to be safe, check both). That's when you know the DOM is ready and to fire your desired function. Personally I'm not keen on implementing it, as this is where the technique crosses the line and becomes a burden in my opinion considering the small group it benefits. And yes we're all entitled to our opinions!