Friday, November 24, 2006

My Last DOMContentLoaded Solution

Update 28/11/2006 Thanks to anonymous for its perfect suggest!!!
Update 01/12/2006 Thanks to Stephen Elson for debug
Update 01/13/2007 This is my last version that doesn't use global window.__onContent__ variable
Update 13/02/2007
Sorry guys for my choosed old title, thanks to Pierluigi that explained me what did it mean




function onContent(f){//(C)webreflection.blogspot.com
var a=onContent,b=navigator.userAgent,d=document,w=window,c="onContent",e="addEventListener",o="opera",r="readyState",
s="");
a[c]=(function(o){return function(){a[c]=function(){};for(a=arguments.callee;!a.done;a.done=1)f(o?o():o)}})(a[c]);
if(d[e])d[e]("DOMContentLoaded",a[c],false);
if(/WebKit|Khtml/i.test(b)||(w[o]&&parseInt(w[o].version())<9))(function(){/loaded|complete/.test(d[r])?a[c]():setTimeout(arguments.callee,1)})();
else if(/MSIE/i.test(b))d.write(s);
};



Here is where this "story" began, inside a Dean Edwards post.
After that there was a big list of comments, posts and tests ... for a second post and a better solution !

Dojo adopted that solution ... then came back to old one ... but someone has never stopped to test, to try or to find a "perfect" way to add DOMContentLoaded with every browser and expecially with Internet Explorer.

Mark Wubben and Paul Sowden did it, they've created a complete and portable solution that works with http and https pages too !

Wonderful work guys and ... as first point, thank you very much !


Now, I can show my personal DOMCOntentLoaded solution, explaining them as better as I can.


The first step, the real key of this code, is the source of used script.

It's not void, it's not //0 ... it's exactly this one: //:
and is what I had not to create my tiny, simple and portable solution.

Here is the func, called onContent ...

function onContent(f){//(C)webreflection.blogspot.com
var a,b=navigator.userAgent,d=document,w=window,
c="__onContent__",e="addEventListener",o="opera",r="readyState",
s="<scr".concat("ipt defer src='//:' on",r,"change='if(this.",r,"==\"complete\"){this.parentNode.removeChild(this);",c,"()}'></scr","ipt>");
w[c]=(function(o){return function(){w[c]=function(){};for(a=arguments.callee;!a.done;a.done=1)f(o?o():o)}})(w[c]);
if(d[e])d[e]("DOMContentLoaded",w[c],false);
if(/WebKit|Khtml/i.test(b)||(w[o]&&parseInt(w[o].version())<9))
(function(){/loaded|complete/.test(d[r])?w[c]():setTimeout(arguments.callee,1)})();
else if(/MSIE/i.test(b))d.write(s);
};


... and this is the http test page, while this is the https test page.



Let me explain that unreadable function with a clear commented version :)


function onContent(callback){ // (C) webreflection.blogspot.com
// [please note that this code doesn't work]

// private scope variable

var IEStringToWrite = // this is IE dedicated string

"<script defer src='//:' onreadystatechange='
(function(element){

// if readystate is complete
if(element.readyState === "complete") {

// remove the element
element.parentNode.removeChild(element);

// call the global variable
window.__onContent__();
}
})(this);
'></script>";

// the above string is necessary to use onreadystatechange property
// with an undefined page. In this way IE tell us the readyState
// of the current document



// to call callback function IE need a global scope variable
// this variable could call one or more callback
// then if it's already created we need to call the old callback
// then this new callback
window.__onContent__ = (function(oldCallback){

// returns a function that will set every callback fired
// [thanks to Stephen Elson for its suggest]
// to remove multiple callbacks with different
// events and different ways for each browser

return function(){

// set window.__onContent__ as empty function
// required by IE5
window.__onContent__ = function(){};

// verify that callee wasn't fired before
if(!arguments.callee.done) {

// set calle.done 1 as true fired value
arguments.callee.done = 1;

// checks if oldCallback isn't null or undefined
if(oldCallback)
oldCallback(); // call them to preserve the right order

callback(); // call this scope callback function
// (sent calling onContent)
}
}

})(window.__onContent__); // undefined if is the first time we use __onContent__



// __onContent__ is my function to use as callback

// I need to add this function as event

// Opera 9 and FireFox both support DOMContentLoaded as well as
// addEventListener document method
if(document.addEventListener)
document.addEventListener("DOMContentLoaded", __onContent__, false);

// if some browser supports addEventListener but doesn't support DOMContentLoaded
// event I don't need to care about that because this event will never be fired

// at the same time if Safari or KDE one day will support DOMContentLoaded
// I prefere use this dedicated in-core
// event instead of next trick that's quite horrible but works with Safari,
// KDE as Opera 8.5 and lower too

// that's why I don't use an else if but an if ... because the first time
// event will be fired __onContent__
// became an empty function ... then calling them twice is not a problem

if(
// Safari and KDE
/WebKit|Khtml/i.test(navigator.userAgent) ||

// Opera less than 9
(window.opera && parseInt(window.opera.version())<9)
)
// runtime anonymous function
(function(){

// checks if document.readyState is loaded or complete
/loaded|complete/.test(document.readyState) ?

// then call __onContent__ , stopping internal loop
window.__onContent__() :

// or loops itself with the faster timeout
setTimeout(arguments.callee, 1);
})();

// at this point I've setted the DOMContentLoaded event for every browser
// but not for Inernet Explorer.
else if (/MSIE/i.test(navigator.userAgent))

// I can write dedicated string
document.write(IEStringToWrite);
};



My solution doesn't use conditional comments (I hate them !!!) ... and this is the compatibility list:

- FireFox 1 or greater
- Opera 8 or greater (I don't know 7)
- Safari 2 or greater (I don't know 1)
- KDE 3.4 or greater
- Internet Explorer 5 or greater (I don't know IE 5.2 for Mac)

No comments:

Post a Comment