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);
};
})($);