Wednesday, September 26th, 2007

More fun with DOMContentLoaded

Category: JavaScript, Tip

<p>It is amazing how much chatter we have about wanting to know when the darn DOM is ready, and finding the right place to start doing your work. We just posted about IEContentLoaded and already Stuart Langridge has taken that and the other approaches and come up with this 7 liner (if you aren’t using a library that already does it for you):

javascript
< view plain text >
  1. (function(i) {var u =navigator.userAgent;var e=/*@cc_on!@*/false; var st =
  2. setTimeout;if(/webkit/i.test(u)){st(function(){var dr=document.readyState;
  3. if(dr=="loaded"||dr=="complete"){i()}else{st(arguments.callee,10);}},10);}
  4. else if((/mozilla/i.test(u)&&!/(compati)/.test(u)) || (/opera/i.test(u))){
  5. document.addEventListener("DOMContentLoaded",i,false); } else if(e){     (
  6. function(){var t=document.createElement('doc:rdy');try{t.doScroll('left');
  7. i();t=null;}catch(e){st(arguments.callee,0);}})();}else{window.onload=i;}})(YOUR_FUNCTION);

John Resig is taking jQuery in a different direction though:

I have another technique that I posted to the list a while back, that I’ll be switching jQuery to. If you attempt to insert into the document.body before the document is fully loaded, an exception is thrown. I take advantage of that to determine when the document is fully loaded. I like that particular technique better because it actually tells you when you can manipulate the DOM – as opposed to this scroll thing which may, or may not, correspond to the document being loaded.

Related Content:

Posted by Dion Almaer at 11:30 am
23 Comments

+++--
3 rating from 25 votes

23 Comments »

Comments feed TrackBack URI

What’s about the script defer technology used in other areas. What are the benefits of polling the DOM instead? Ok, in non IE clients you need to do this anyway. But isn’t script defer for IE the better technique?

Comment by Sebastian Werner — September 26, 2007

It is too bad that we are left to create hacks such as these. Detecting the DOM’s ready status shouldn’t be a try … catch exception sort of technique – thats just a dirty hack.

Since HTML and CSS aren’t getting updated any time soon – why dont we get a JS update? I can just imagine how much better optimized our JS can be with updated specs and engines.

Comment by Bryan Migliorisi — September 26, 2007

Script defer has never been guaranteed to work. It’s nothing more than a hint to the browser that it *may* defer loading the script, because the script promises not to do a document.write. But the browser is not required to defer the script loading, and there are cases where it loads the script before the DOM is fully ready.

I’d have to go back to a test case to verify this, but I’m pretty sure I recall seeing a script with the defer attribute get loaded earlier than expected when another script tries to manipulate the DOM before it’s ready.

I’m really not too keen on any of the techniques that deliberately throw exceptions – and repeatedly! – until the DOM is ready. What do you if you want to use the “Break on All Errors” option in Firebug to catch a legitimate exception?

I’d rather put a script at the end of the BODY. That always works and doesn’t require any hackery. Maybe it’s a little more “obtrusive” in terms of the HTML code, but it sure is clean and simple.

Comment by Michael Geary — September 26, 2007

“…why don’t we get a JS update?”

We will. And then we will *still* have to support old browsers for another five years after that! :-(

Comment by Michael Geary — September 26, 2007

Ext uses that, and I hate it. There! I said it! Oh, I *love* Ext by the way, I was just talking about the IE defer thing. DEFER is an advisory to script load, _not_ something the browser _guarantees_. There are things that will stop this behavior.

And have we forgotten how easy it is just put a one line script after the body to say ‘body done!’?? Its like all the CSS hacks so one file will hold the CSS for every browser. I like the Ext way of just adding an “isGecko” class to the body and making CSS rules that depend on a browser broken out as such.

Comment by Steven — September 26, 2007

If you attempt to insert into the document.body before the document is fully loaded, an exception is thrown.

John, it’s not true.

If You check document.body (back to origin?) when it’s ready You can append everything You want but as You can read on Dean Edwards site when document.body is presente this doesn’t mean that document has been totally loaded and it’s quite obvious that if document.body is present You can append a child node … the same behaviour of this code that will always work:

(document.getElementsByTagName(“head”)[0] || document.getElementsByTagName(“*”)[0]).appendChild(document.createElement(“link”));

This is my trick to create a syncronous globalEval function, descrived in my blog as is descrived why this twice posted solution is buggy.

Finally, this is a John proposal example and this doesn’t work.

Comment by Andrea Giammarchi — September 26, 2007

P.S. this is my proposal … based on new doScroll behaviour without elements creation and usable with many browsers.

Comment by Andrea Giammarchi — September 26, 2007

P.S.2 .. i hope I understund what John said about body … in other case, I’ll wait curiously its trick (however doScroll seems to work quite perfectly, doesn’t require a dedicated script deferred tag and a fake source … so, it’s quite cool and thank You Hedger Wang) :-)

Comment by Andrea Giammarchi — September 26, 2007

Re: support old browsers for another five years…

IE6 is 7years old… and we HAVE to support it, because more than 60% users still using it… ARGH!#@!

Comment by YALD — September 27, 2007

I think users are now moving to the newer version of IE or firefox instread of using the older IE 6

Thanks

Comment by DESIGNEXPANSE.COM — September 27, 2007

@Andrea: You’re correct – I haven’t had the opportunity to significantly test the appendChild() test, but it does, in fact fail when you flush out to the browser (as you showed).

I really like your revised technique – especially with no DOM element creation. I’ll probably look to that as a viable solution for jQuery. Thanks!

Comment by John Resig — September 27, 2007

John,
you better use the REAL “doScroll()” original solution for jQuery:

http://javascript.nwbox.com/IEContentLoaded/

The above two modifications of my trick are both useless from my point of view, Hedger Wang doesn’t solve any issue in my original code, he just made it worst.

Andrea’s modifications to my code can still fail because he is trying to use “document.firstChild” instead of “document.documentElement”, and sometimes that “firstChild” will be a “#textnode” or at best a DOCTYPE declaration. I wouldn’t believe these nodes have a “doScroll()” method…

I see a lot of confusion both about the origin of this trick and how it is to be used…
I could also had taken jQuery/Mootools, then add my IE trick to it and say that I have a better framework. :-)

Cheers

Comment by Diego Perini — September 27, 2007

Just chiming in here, on the script defer hack… it should go away IMHO. This technique is poorly tested and as mentioned unreliable, and it simply breaks in some cases where the toolkits get loaded on-demand (for instance, well after the DOM has indeed been loaded) in an environment designed to support such cases, like our WebWidgetry platform… (although I think that could be avoided if the toolkit devs did some simple checks first to see if the DOM has been loaded already or not before even worrying how to emulate this event)… sigh.

Comment by Ryan Gahl — September 27, 2007

@Diego: Good call, I tested your code and it seems to work fine – I reverted jQuery to use yours instead.

@Ryan: If only it were that easy. There’s, currently, no way to determine if the page is already “ready” in Firefox. Every other browser has ways to check and see if the document has been loaded (Opera, IE, Safari). I’ve been wanting to do this for a while now but have yet to find an appropriate hack. I think I might dedicate some more time to it here soon as having a solution for it would be really really nice. You can see the relevant jQuery ticket here:
http://dev.jquery.com/ticket/904

Comment by John Resig — September 27, 2007

Diego, I tested my proposal before posting them and it seems to work correctly with every DTD but I didn’t test them with spaces before one element (it seems the same problem of people who don’t know ho to use session_start() with PHP … imho).

At this point I’ll test your proposal as soon as I can :-)
(but I’m not sure about readyState behaviour)

Comment by Andrea Giammarchi — September 28, 2007

Ehr … Diego, I’ve just tested my proposal with more than a space before page and every DTD … it seems to work perfectly.

That’s why page source is not the internal IE source … the same thing why FireFox and others contains every new line inside DOM while any IE has them … simply these don’t exists.

At this point, could You please show only one example where my proposal doesn’t work with IE?

and sometimes that “firstChild” will be a “#textnode” or at best a DOCTYPE declaration.

not in IE ;-)

I see a lot of confusion both about the origin of this trick and how it is to be used…

I hope You’re not talking about my confusion … however, if You prefere to try to scroll documentElement instead of a non scrollable one (html, head, script) at this point is your choice, not the only way.

At the same time, if script is executed and its goal is to do something before body, it will simply be the first found Child, do You agree?

Regards

Comment by Andrea Giammarchi — September 28, 2007

@Andrea,
appreciate your comments, if you re-read the above I didn’t say your solution don’t work I just said your modifications to my original solution are nonsense from my point of view. Also Hedger Wang made it work in a bizarre way…(creating too many elements for nothing).

Both modifications to my code, yours and Hedger Wang’s, just show that the concept of this method have some foundation and works where other solutions may fail, it also works when used improperly…

You are correct about IE discarding “#text” nodes before the HTML tag, however DTD are not discarded.

These are (some) of the reason I would stay with “document.documentElement”:

- “documentElement” is an exact and known reference to a precise node in the document tree and it is the only node that will always be present, even if the page has no HTML tag and even when you open a new “iframe” with a src=about:blank, or a new “_blank” window/document

- “document.firstChild” is ambiguous, it will be a different node in differently written pages sometime it will be a “DOCTYPE” sometime it will be the “documentElement”, it is a shame that Microsoft allow for the “doScroll()” method on “DOCTYPE”, it may change in future release and relying on an ambiguous node is something I will try to avoid if possible

- we could as well have used “document.lastChild”, this has still more chances to be “documentElement”, but that still doesn’t make it a different solution, it does not solve any issue in my original code, and it doesn’t add any new concept to the solution.

If “doScroll(‘left’)” is what freaks you, just use “doScroll(‘none’)”, the behavior will be the same. You could also say “doScroll(‘left’);doScroll(‘right’)” or simply use “doScroll()” without an argument.

All these options have been evaluated and tested to work, also you could use “document.recalc()” method as I pointed out in your blog, it will do the job but in my tests seemed to consumes more CPU time.

Now could you please show the problem you see in using “document.documentElement” or at least say the reason you changed to “document.firstChild”, or tell me why “document.firstChild” should be preferred to “document.lastChild” ?

Comment by Diego Perini — September 29, 2007

Hi Diego, as first point, interesting arguments (Ajaxian “callee” too :P )

We are talking about a function usable to do something before DOM is loaded … usable in every part of document, not only inside its “headers”.

If You call this function inside (or after) body, lastChild should be body itself so lastChild is dynamic while document.firstChild will be always the same, do You agree?

That’s why I choosed firstChild instead of last one, to use correctly my proposal even after body tag.

At the same time, You’re right when You say:

Microsoft allow for the “doScroll()” method on “DOCTYPE”, it may change in future release and relying on an ambiguous node is something I will try to avoid if possible

But I hope that IE.Next will have a native support for DOMContentLoaded like event (it has it in core since it can choose when doScroll can be executed. We don’t have IE source code so We can only look for hacks in every way …)

If You think documentElement is the best solution, at this point, I can agree with You … but only for this reason, just because:

even if the page has no HTML tag and even when you open a new “iframe” with a src=about:blank, or a new “_blank” window/document

if a page is empty, document.body will be available (automatically “generated” by DOM)
onload=function(){alert(document.body)}
and my code works perfectly as your one :-)

but that still doesn’t make it a different solution, it does not solve any issue in my original code, and it doesn’t add any new concept to the solution.

Who read your solution? I wrote my post before Ajaxian wrote it … feel free to think You found the perfect one, I’ll never say a different thing :-)

Regards

Comment by Andrea Giammarchi — October 1, 2007

@Andrea,

Who read your solution? I wrote my post before Ajaxian wrote it …

it seems that at least Hedger Wang says he has read my post on Dean Edwards blog. I wrote there mainly because I believe Dean knowledge on the “onload” problem would have been precious advice, but I know he is busy with his “base2-baby”, and I was patiently waiting for tips or question/answers from other known experts normally there…The work on the “doScroll()” is from February/March.

I should point out that I am not trying to convince anybody about “document.lastChild” being better. In fact it is not. I insist about “document.documentElement” being the only usable node for this trick.

If You call this function inside (or after) body, lastChild should be body itself so lastChild is dynamic while document.firstChild will be always the same, do You agree?

NO! Please do not confuse “document.lastChild” with “document.documentElement.lastChild”. There is only ONE node of type “1″ in the “document” and that is the HTML node (“document.documentElement”), which also happen to be “document.lastChild” in general and “document.firstChild” sometimes.

You can peek at “document.lastChild” from wherever you want, from inside the BODY, from the HEAD or from inside the HTML node itself, there will be no difference, it will always be the HTML node, even if a DOCTYPE declaration is present (contrary to “document.firstChild”). I may be wrong, I have been so many times. But then please send a link where you get BODY instead.

If there are no comments and no DOCTYPE declarations in your document this equation is true:

document.documentElement == document.firstChild == document.lastChild

If there are comments and/or DOCTYPE declarations in the document the same equation is false.

Please understand it is not about “my idea is better than others”, if you read on my site I clearly say that the IEContentLoaded should be used in other “fine scripts”, my main idea was replace the “defer” trick in Dean Edwards script. Only in this way it will be really useful to everybody as it was meant when I wrote about it.

And yes, I should have my blog/site, it has been said (maybe one day…), thank you to Ajaxian for the bandwidth :-)

Comment by Diego Perini — October 2, 2007

I read about doScroll only here few days ago, I didn’t know this trick before and I have not read Dean’s blog recently (I was sure its defere solution was perfect and I was working on something else …).

As You wrote I didn’t test lastChild because firstChild worked perfectly so the point is that I don’t need to show You a page where lastChilkd is BODY just because You can’t show me a page where my solution fails in IE 5, 5.5, 6 and 7 :-)

However You’re right, lastChild is document.lastChild and not last child of nested document elements too and I suppose it can’t be body.

Finally, It’s not a race (IMHO), I’m just happy to find a solution, not mine or your one, just a solution … and as I’ve said, documentElement at this point is potentially the best element to check using doScroll, ok?

In my post, I commented old code showed here too … and I suggested firstChild alternative after few minutes and few tests but if You want I’ll change that post, just tell me how. Regards.

Comment by Andrea Giammarchi — October 3, 2007

Andrea,
the amendments on your blog where not my priority, but are really appreciated.
Your comments gave me a chance to go through some of the insight of this “hack” once again, and to have somebody confirm that it works also in their environment.

As a result of the talks, I have also added a correction to my IEContentLoaded that was suggested by “Jake Archibald” on Andrea blog. I moved the callback out of the try/catch, after it, and with a return in the catch part. This is needed to avoid hiding useful errors to developers or possible ever lasting intervals, for example in case the callback has errors…

My priority was having developers look at this code in the way you have actually seen it. Seeing so many delicious bookmarks pointing to Hedger “not very helpful” and wrongly modified IEContentLoaded was not nice for me, and not a very helpful tip for the users in general. At some point I also thought it was a bad idea having let that particular bit of information slip out in this “wrongly emphasized” way.

Ok. To follow up on this I have setup a simple DOMContentLoaded Emulation Tester where people can drop in their code and see if it is able to pass the tests. It is an automated procedure written in few hours for my own testing needs, so it may fail in different situations/environment. The pasted in code must call init() when DOM is ready and should not use the window.onload event.

In this way also Stuart and Hedger can test their own code and compare the results.

It would be helpful to see comments and suggestion on possible improvements to this tester and the IEContentLoaded itself.

Cheers…

Comment by Diego Perini — October 9, 2007

I have found out that the most practical way of initializing scripts after the dom is ready is to add an extra script tag after the tag.

Example:

initMyScripts();
// Or with Prototype you could do Event.fire(document, ‘dom:ready’);

It’s not valid, but hey, it works without any complicated hacks with all the browsers I tested (IE6+7, FF2+3, Opera 9, Safari 3) and I don’t see any reason why it woun’t work with every browser.

Any comments?

Comment by sakarit — August 26, 2008

Darn, it stripped the tags. Here again:

I have found out that the most practical way of initializing scripts after the dom is ready is to add an extra script tag after the </body> tag.

Example:

</body>
<script type=”text/javascript”>
initMyScripts();
// Or with Prototype you could do Event.fire(document, ‘dom:ready’);
</script>

It’s not valid, but hey, it works without any complicated hacks with all the browsers I tested (IE6+7, FF2+3, Opera 9, Safari 3) and I don’t see any reason why it woun’t work with every browser.

Any comments?

Comment by sakarit — August 26, 2008

Leave a comment

You must be logged in to post a comment.