outbreak

Me

My name is Marko Mrdjenovič. I’m a web developer, manager and an entrepreneur from Ljubljana, Slovenia.

Bio

I like solving problems. I do that by writing code, managing projects and people. I like creating good experiences. And going to conferences.

Availability

I work full time on CubeSensors so I'm currently not available for freelance work (UX, frontend, backend).

Elsewhere:
LinkedIn
Twitter
Facebook
Quora
Flickr
Marela
Last.fm
Dopplr
Delicious
Huffduffer

Archives

jQuery.readyOrDone

There’s a really small probability that someone might need something like this but I did and I’d like to share it.

At Zemanta we have a few different ways of loading our scripts and we cannot always control when they do. The Firefox extension will load the scripts on DOM ready, WordPress plugin will load them somewhere in the middle of the HTML, Drupal and MovableType plugins will load them in the head and IE extension will load them sometime while loading the page.

This all means that we have to delay some of our code execution to when DOM is ready and scripts are loaded. Which is where the problem kicks in.

jQuery has this nice way of doing this with $(document).ready(fn) or short $(fn) which waits until the document is ready and executes the passed fn function. If the document is ready it will execute the function immediately. Our issue lies in what “document is ready” means to jQuery – it means different thing in different browsers.

In browsers that support DOMContentLoaded (Firefox, Webkit, Opera – let’s call them modern browsers) “document is ready” means that either DOMContentLoaded event fired on the document or the load event fired on its window. On IE “document is ready” means that either onreadystatechange fired with readyState === 'complete' on the document or document.documentElement.doScroll("left") is successful (Diego Perini hack). To make this short – if you load jQuery after all the events fired in modern browsers jQuery will never know that the document is ready.

To get around this (we really don’t like having our own hacked version of jQuery) I wrote this little plugin:

(function ($) {
$.readyOrDone = function (fn) {
	var s = document.readyState;
	if (s === 'complete') {
		$.ready();
	}
	$(fn);
};
})(jQuery);

As you can see this will check if document is in a “complete” state and fire the ready method on jQuery which usually fires when DOM is ready – if it fired before it will do nothing. It will then add the function to the ready queue which also has this nice feature of firing immediately if DOM is ready.

All you have to do is change your $(fn) calls to $.readyOrDone(fn) and you have a bulletproof solution for executing functions when DOM is ready even if jQuery was late to the party and has no idea if the document is really ready.

Update: Filed a bug and hoping for the best.

Update 2: I found out that not all browsers provide the readyState property – Firefox on Ubuntu for example. Devised a new version that tries to smartly handle such cases:

(function ($) {
	var time = setTimeout(function () {}, 0),
		lastelm = null;
	$.readyOrDone = function (fn) {
		var s = document.readyState, getLast = function () {
			var elms = document.getElementsByTagName('*');
			return elms[elms.length - 1];
		};
		if (s === 'complete') {
			$.ready();
		} else if (typeof s === 'undefined') {
			lastelm = getLast();
			clearTimeout(time);
			time = setTimeout(function () {
				if (getLast() === lastelm && typeof document.readyState === 'undefined') {
					 $.ready();
				}
			}, 1000);
		}
		$(fn);
	};
})($);
Reblog this post [with Zemanta]

Opinions

express yours below
  1. gasper_k

    Ah, the sweet DOMLoaded problem. So simple, yet so frustrating. :)

    I’ve been researching this a year ago, and again a few weeks ago. A while ago, a special hack for Opera was required, because Opera triggered the event before all CSS files were downloaded and applied. This caused numerous problems, because manipulating DOM at that moment usually caused strange side-effects. jQuery used to solve this by detecting style tags, and checking them whether they’re loaded. I guess this isn’t necessary anymore, because jQuery doesn’t do that anymore. I also prefer the scroll method instead of those weird defered script src=//0 for IE, which generally works, but is “hackier”, and causes some problems under certain circumstances.

    Anyway. If you embed jQuery in a script tag, it will be executed before the events are fired, so ready() will work fine. How do you load jQuery after the document is fully loaded? Are you dynamically adding script tags with timers or what?

  2. Marko Mrdjenovic

    As I said before – when you’re adding scripts to the page via Greasemonkey or browser extensions you can easily hit these problems. With bookmarklets you can usually know that the DOM is ready, though even that is not guaranteed in apps such as WordPress which take a long time to load (while the content is visible and in users mind editable).

Express your opinion