My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Sunday, September 24, 2006

a better DOMContentLoaded ?

a better DOMContentLoaded ?



The first line of this post is dedicated to Dean Edwards: thank you very much for your DOMContentLoaded implementation !!!
Without Dean solution I'll probably never create the code I'm going to show, than thanks again Dean.

Next step is my solution code concept:
if we need to write runtime a script tag, why should we use conditional comments if we could implement the solution directly with that tag for other browsers too ?

Since onreadystatechange inside a script tag is called only from IE and Safari, we could write this properties directly inside the script tag, then we don't need any anonimous function and we don't need any webkit dedicated interval too.

<script id=__ie_onload defer src=javascript:void(0) onreadystatechange=ourFunction><\/script>

If we write a javascript source using void(0) as undefined source we could use undefined source using a function with an undefined return.

<script id=__ie_onload defer src=javascript:ourFunction2() onreadystatechange=ourFunction><\/script>

Then if we need to use defer with uncompatible browsers, we could set its value as generic value (I think it's more clean and elegant) and if we use our functions with the script we don't need to get it then we don't need it's id too.

<script src="javascript:ourFunction2()" defer="defer" onreadystatechange="ourFunction()"><\/script>


This code is the base for my onContent callback, that's based on a single global object, used fromthe tag to call functions.
It's time to show my lightweight function (less than 500 bytes), compatible with FireFox, Opera 9, IE5+ and Safari (thank you guidoz) and compatible with other browsers too (IE4, Opera < 9, others), using "old" window.onload method.

function onContent(callback){
__onload__={
$:function(){window.onload=null;callback()},
IES:function(e){if(e.readyState==="complete")this.$()},
FO:document.addEventListener?"document.addEventListener('DOMContentLoaded',__onload__.$,false)":"void(0)"
};
onload=callback;
if(!window.opera||parseInt(window.opera.version())>=9)
document.write(''));
};

Let me explain what this function does :)

__onload__ is the global object used by script tag and has 2 methods and just 1 parameter.
The dollar method $ is a shortcut to delete window.onload callback with every "DOMContentLoaded" compatible browser and then $ calls the callback too.

The second function, called IES, is the IE/Safari dedicated callback, used inside onreadystatechange event.
This function recieves the this (window) object and then check if its readyState is "complete", then call the dollar $ function to delete onload and to call callback.
The last FO parameter is dedicated to FireFox and Opera but preserve javascript errors using addEventListener only if this document method is available.
In other cases, this parameter will be the string "void(0)".
We don't need to care about addEventListener compatible browsers (i.e. Safari) because if they don't support the "DOMContentLoaded" event they'll necer use that listener.
FireFox as Opera will use that method directly (onerror, for FireFox, and onload, for Opera, script functions have a delay and if you add DOMContentLoaded event after page is loaded (cached) this will nevver be called).

Next line is the window.onload method, used from every browser ... then if __onload__ "hack" fails, callback will be called without problems.
The next line contains an if, this is necessay for Opera version older than 9 because Opera 8.5, for example, doesn't show the page if you write a script tag while page is downloading.
Final line writes the script tag, adding event or void inside src, adding defer="defer" for IE and setting onreadystatechange for IE and Safari.


What about addLoadEvent for multiple onload callbacks ?
I think that a single callback, if we don't use external libraries, is sufficient to call every other event on page complete or on dom ready (just calling multiple functions inside another one).
However this should be a simple solution to add multiple events using onContent function.

function addContentEvent(callback) {
if(!window.__onload__)
onContent(function(){__onload__.callEvents()});
__onload__.callEvents = function() {
for(var i = 0; i < __onload__.events.length; i++)
__onload__.events[i]();
};
if(!__onload__.events)
__onload__.events = [callback];
else
__onload__.events[__onload__.events.length] = callback;
};

// Example:
// addContentEvent(function(){alert("hello dom 1")});
// addContentEvent(function(){alert("hello dom 2")});

First line calls onContent if __onload__ object doesn't exists then creates the __onload__ object too and add a callback to call an __onload__ method.
callEvents is this one and it's used on page complete.
It loops over every callback and calls them.
Last if/else creates the __onload__.events array, adding first callback too.

Finally You could create a single function moving onContent inside addContentEvent (at the top), if addContentEvent is your favourite way to add callbacks.

The last thing ? The Example Page, where you should read event: onContent with compatible browsers (and please let me know if some browser is no compatible).

P.S. this blog doesn't require an account to reply ;)




UPDATE
For some reason Safari (at least some versionof Safari) doesn't accept an undefined source script and shows an error when this function write the script tag.
To solve this problem I've changed the function that now has the same snif used by Dean Edwards plus Konqueror snif (for KDE 3.X).

This is my updated solution, now tested with a real big range of browsers with a size of 650 bytes.

function onContent(callback){
__onload__={
E:function(){window.onload=null;callback()},
IES:function(e){if(e.readyState==="complete")this.E()},
FO:document.addEventListener?"document.addEventListener('DOMContentLoaded',__onload__.E,false)":"void(0)"
};
onload=callback;
if(/WebKit|Konqueror/i.test(navigator.userAgent))
(function(){/loaded|complete/.test(document.readyState)?__onload__.E():setTimeout(arguments.callee,1)})();
else if(!window.opera||parseInt(window.opera.version())>=9)
document.write('');
};

4 comments:

Andrea Giammarchi said...

Final DOMCOntentLoaded Solution

Anonymous said...

on IE, I was getting the
"This page contains both secure and nonsecure items" warning.
I changed the following:

script id=__ie_onload defer src=/dis/js/void.js

Unknown said...

awesome work Andrea. Can u tell me the way bu which i can do my javascriping after my form or table is loaded instead of img (as show in above example)

?

Anonymous said...

Well, I have to say this is script is not complete, at least for my project.
I had a simple requirement to change appearance of few elements on the page dynamically when
its loaded. 'OnContent' callback worked most of the pages and failed on few pages with Inline Javascript.
I was not able retrieved DOM elements in such pages on OnContent callback, i.e elements are not
loaded into DOM when 'OnContent' callback fired.