Wednesday, September 26, 2007

What's wrong with new IEContentLoaded solution?

Update
This post talk about old implementation posted in Ajaxian before this one.

After some interesting comment, posted by Diego Perini, it seems that His last proposal is logically the best We can use today to solve this IE problem.

What I missed in my one is an onreadystatechange alternative to be sure function is called before onload one.

In my case this is not a problem but if You use third party code libraries it should be a big problem.
At this point Diego solution seems to be perfect for every case so just use them and thank You Diego and every other developer that helped Him to find this cool, portable, efficient solution :-)


---------------------------------------------


The solution proposed in this page to solve in another way DOMContentLoaded IE problem, ir really interesting.

I didn't test them so much but I suppose this is a valid alternative.

I didn't know doScroll behaviour too, so it's quite a surprise for me and I'm happy if this will work as expected simply because alternatives uses a script with a particular source string that one day should be a problem for every secure site: src="://"

ok guys, but what's wrong with this solution?


Firsto point is that it's logic is quite bugged.

In fact, since this line:

tempNode = null;

should be useful to free memory, I can't understand why author choosed to put them inside try.

try, as You know, works as a sentinel ... when a single error occurs, catch is instantly called and every other piece of code inside try will never be executed.

If the goal was to free memory (but You'll read here that's even not important) it should be wrote after catch statement:

var tempNode = document.createElement('document:ready');
try {
tempNode.doScroll('left');
alert('window.onDocumentReady()');
}catch (err){
setTimeout(arguments.callee, 0);
};
tempNode = null;


The other point is wrote inside first linked page:
“… A few methods, such as doScroll, require the primary document to be completely loaded. If these methods are part of an initialization function, they should be handled when the ondocumentready event fires. …”


Well, at this point, if this is true ... why do you create an element each timeout?

(function (){
if(!document.uniqueID && document.expando) return;
try {
document.firstChild.doScroll('left');
alert('window.onDocumentReady()');
}catch (err){
setTimeout(arguments.callee, 0);
}
})();


At this point You can see that solution is even simpler than original one.
document.firstChild, if You know how to write an (x)HTML page, is never a scrollable element.

This mean that You can use it directly, instead of create every time a new element.

The last point is that the best secure way to use a piece of code only with IE it to use conditional comment while last point is that a setTimeout with 0 as delay is never respected by browser because 0 means the minimum possible delay and not 0!

/*@cc_on
(function(){
try{
document.firstChild.doScroll('left');
alert("window.onDocumentReady()")
}catch(e){
setTimeout(arguments.callee, 10)
}
})();
@*/


Is that's all? none

You can use conditional comment to create an window.onReady portable and cross-browser function:

onReady = (function(ie){
var d = document;
return ie ? function(c){
var n = d.firstChild,
f = function(){
try{
c(n.doScroll('left'))
}catch(e){
setTimeout(f, 10)
}
}; f()
} :
/webkit|safari|khtml/i.test(navigator.userAgent) ? function(c){
var f = function(){
/loaded|complete/.test(d.readyState) ? c() : setTimeout(f, 10)
}; f()
} :
function(c){
d.addEventListener("DOMContentLoaded", c, false);
}
})(/*@cc_on 1@*/);

onReady(function(){

alert("Hello DOM");

});


That should be packed in few bytes:

onReady=(function(ie,d){d=document;return ie?
function(c){var n=d.firstChild,f=function(){try{c(n.doScroll('left'))}catch(e){setTimeout(f,10)}};f()}:/webkit|safari|khtml/i.test(navigator.userAgent)?
function(c){var f=function(){/loaded|complete/.test(d.readyState)?c():setTimeout(f,10)};f()}:
function(c){d.addEventListener("DOMContentLoaded", c, false)}
})(/*@cc_on 1@*/);

onReady(function(){

alert("Hello DOM");

});

window.onReady(function(){

alert("Hello one more time");

});


Seems cool?

12 comments:

Oliver Kühn said...

the thought rethought!

thx, this idea was generelly a bit strange (timeout=0) for me... but now it's like a realy nice attempt to solve this DOMShitWhatever-problem in a "Not-Dean-Edwards-Way" :D nice work, compliment!

Some other question: I'am asking me why your beatiful JPU is going crazy if I scroll in Opera? I haven't notice this behavior in other Browsers... btw. thx, again for this great work!

ok

Andrea Giammarchi said...

Oliver, my last 2 functions doesn't use timeout 0 ... and if You read entire post You'll see that I'm not sure about doScroll behaviour and that Dean solution, actually my favourite one and the only one I use, should have problems with script source.

However, thank You but please read post before another comment, ok? :D

Andrea Giammarchi said...

P.S. JPU is just an experiment (a toy if You prefere) ... if You can understand its logic, You can understand why it will never be perfect.

However, if Opera use more CPU while You scroll a page, JPU will grow up :-)

Francesco Sullo said...

Ciao Andrea.
Seguo di tanto in tanto le tue soluzioni e trovo che siano sempre molto interessanti. Volevo solo farti i complimenti e per pigrizia te li faccio come commento ad un interessante post.
Tante belle cose,
Francesco

Andrea Giammarchi said...

You are welcome Francesco and thank You too for this comment :-)

Anonymous said...

Andrea,
you took the code from a place where it was slightly modified from my original code posted here:

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

My original solution was also posted on Dean blog and it was using the "document.documentElement" as a source for this trick since that node is the only node available during startup and we can be sure it always exists even in wrongly written pages.

I wanted to keep this as simple as possible without creating/extending anything.

Beside that, scrolling "documentElement" to the left seemed safe to me because the HTML node is normally not used to scroll the page "left"...

I have a better solution based on the same trick but acting like an event instead of polling, but I prefer to keep that on test for some time before releasing it.

Diego Perini

Diego Perini said...

Andrea,
use "document.documentElement" as in the original solution (see Dean blog) and my lab site, instead of "document.firstChild". Your solution is also likely to break...

I may be wrong but if the "document" is a well written (x)HTML it will probably fail because the "firstChild" will then be a "DOCTYPE" declaration with node type 8.

If there is a line-feed or even a space before the HTML tag it will also break.
I would be surprised if it doesn't.

So all you said about Hedger Wang "bad" modifications to my original "test" case ( see http://javascript.nwbox.com/IEContentLoaded/ ) are completely correct, he probably just believed the document will scroll !?! I did also believe that initially...

For a cross-browser solution, included this IE trick/hack, my take to it has always been here:

http://javascript.nwbox.com/ContentLoaded/

I know everybody will take that code and rebuild their own, but that was the objective :-)

If I can help slice down lost testing hours for you and other developers, forget trying other methods like "document.recalc()" (that also works the same for our objective).

Really Microsoft says "there are a few methods like doScroll()"...but what I claim about being my idea is to join this with a try/catch construct and "documentElement".

The next idea is to completely avoid the setTimout or setInterval and use it like an event to check the "doScroll()".

My real trick (which is an extension of this basic one) will be published as soon, as time permits...or as soon somebody find flaws with this method. I am only a test cases guy, no fancy website sorry...And yes I also have bugs in my code.

The "red", "green" test case I have on my lab site is a good test for the "onload" moment we are looking for, so you may use it with other solutions and compare.

I also suggest some reading here, which is slightly related to this and presents still another solution to the problem.

http://peter.michaux.ca/article/3752

which is also based on my "NWEvents" and related event Delegates in a new strange way.

Diego Perini

oliver kühn said...

Sry, but you have missunderstood me, with timeout 0 I meant this original code listed in your post (+ajaxian) - not yours!

I'am only using the "Dean"-way for production-releases, too. But I like clever __attempts__ to solve common problems like this in another way... ;)

ok

Jake Archibald said...

Problem with the solution above: If the callback throws an error, it'll send IE into an infinite loop.

Eg.

onReady(function() { alert("hi!"); lalala(); });

Andrea Giammarchi said...

quite obvious Jake but I hope your DOMReady code, one day, will work without exceptions :D

P.S. don't call them infinite loop, it's just an interval

Jake Archibald said...

When I discovered the issue I was trying to throw a helpful error in an application, not because I'd written buggy code.

Either way, gobbling errors is never a good idea.

How about...

f=function(){try{n.doScroll('left')}catch(e){setTimeout(f,10);return}c()}

Or does that create problems I'm missing?

Andrea Giammarchi said...

it should prevent just setTimeout Jake but if You don't throw errors inside try catch your code will never work.

However, You can choose to write this trick as You prefere and I suppose there's any problem with your last example :-)

Best Regards