View Full Version : [OPEN-604] Ext.onReady modernization.
hendricd
06-23-2009, 08:45 AM
Well, I'll offer this up again.
This time, for 3.0/Core. :>
The attached version of EventManager.js uses the latest popular IE detection scheme used successfully by most all other JS frameworks (and gets rid of that nasty <script defer="defer"> tag that prevents Ext/Core from being loaded in the <BODY> tag (think startup logos) on IE).
And, it:
- is IE-FRAME/IFRAME safe.
- adds better WebKit coverage for latest browsers.
- permits Ext/Core to be loaded after the page onload event has fired. (dynamically loaded eg. Google API)
It also corrects false onReady events on Opera when the CSS engine is not quite ready for action.
Condor
06-23-2009, 09:03 AM
Some remarks:
1. The load event handler is never removed.
2. Should fireDocReady kill all alternative timers and event handlers instead of waiting for them to also fire (and do nothing)?
3. Should onReady also execute when the document is already loaded?
hendricd
06-23-2009, 09:09 AM
1. The load event handler is never removed.,
Good point! I'll add that in also. See #2 below. :-?
2. Should fireDocReady kill all alternative timers and event handlers instead of waiting for them to also fire (and do nothing)?
It does, see lines 46-51. (for anything that uses setInterval for polling -- which BTW, setInterval timer resolution is poor on most browsers). The setTimeouts (in conjunction with Ext.isReady) are designed to be self-expiring, anyway.
3. Should onReady also execute when the document is already loaded?
It must. Ext needs the event raised to setup the <HTML, BODY> classNames.
That's why it adds a public :fireDocReady method. It must be called after dynamic/late loading.
Condor
06-23-2009, 09:26 AM
fireDocReady does not kill:
- The doScrollChk timer (shouldn't it be using docReadyProcId?)
- The onreadystatechange listener for IE
- The DOMContentLoaded listener for Opera
hendricd
06-23-2009, 09:36 AM
fireDocReady does not kill:
- The doScrollChk timer (shouldn't it be using docReadyProcId?)
- The onreadystatechange listener for IE
- The DOMContentLoaded listener for Opera
See my revised comments above for #2 : timers.
IE's onreadystatechange handler removal is handled line ~96.
Opera DOMContentLoaded removal is handled line ~114.
What version you lookin at? ;)
Condor
06-23-2009, 09:50 AM
See my revised comments above for #2 : timers.
IE's onreadystatechange handler removal is handled line ~96.
Opera DOMContentLoaded removal is handled line ~114.
What version you lookin at? ;)
I think you are missing my point:
Those event handlers are only removed when the event fires. But what if the document 'load' event fires first? Shouldn't fireDocReady remove the listeners and timers so they don't also execute?
hendricd
06-23-2009, 10:15 AM
I think you are missing my point:
Those event handlers are only removed when the event fires. But what if the document 'load' event fires first? Shouldn't fireDocReady remove the listeners and timers so they don't also execute?
Actually it would, but, I see your edge-case. See the latest zip.
Since all roads end at fireDocReady -- including the load event, I added an additional Ext.isReady check to any polling loop to exit and call fireDocReady for timer/event cleanup.
To add, in scenarios where Ext is loaded after window.onload, no DOM events will ever be raised, which is why calling fireDocReady is very important. It cleans up any setInterval timers and most-importantly sets Ext.isReady=true, which as now coded, expires all/any running polling methods.
Condor
06-23-2009, 10:49 AM
I was more thinking in the line of:
function doScrollChk(){
/* Notes:
'doScroll' will NOT work in a IFRAME/FRAMESET.
The method succeeds but, a DOM query done immediately after -- FAILS.
*/
if(window != top){ return; }
try{
DOC.documentElement.doScroll('left');
}catch(e){
return Ext.isReady || !!!setTimeout(arguments.callee,5);
}
fireDocReady();
return true;
}
function checkReadyState(){
if(DOC.readyState == COMPLETE){
fireDocReady();
return true;
}
}
var styles;
function checkStyleSheets(){
styles || (styles = Ext.query('style, link[rel=stylesheet]'));
if(styles.length == DOC.styleSheets.length){
fireDocReady();
return true;
}
Ext.isReady || setTimout(arguments.callee, 5);
}
function OperaDOMContentLoaded(){
if(checkStyleSheets()){
return true;
}
//poor timeout resolution, 'load' may fire before 'ready'
docReadyProcId = setInterval(checkStyleSheets, 10);
}
function fireDocReady(){
docReadyState = true;
if(DETECT_NATIVE) {
DOC.removeEventListener(DOMCONTENTLOADED, fireDocReady, false);
}
if(docReadyProcId){
clearInterval(docReadyProcId);
}
if(Ext.isIE){ //We don't know wether this was actually set ??
DOC.detachEvent('onreadystatechange', checkReadyState);
}
if(Ext.isOpera){
DOC.removeEventListener(DOMCONTENTLOADED, OperaDOMContentLoaded, false);
}
if(docReadyEvent && !Ext.isReady){
Ext.isReady = true;
docReadyEvent.fire();
docReadyEvent.clearListeners();
}
E.un(WINDOW, "load", arguments.callee);
};
function initDocReady(){
var COMPLETE = "complete";
docReadyEvent = new Ext.util.Event();
if (DETECT_NATIVE) {
DOC.addEventListener(DOMCONTENTLOADED, fireDocReady, false);
}
/*
* Handle additional (exceptional) detection strategies here
*/
if (Ext.isIE){
// setInterval == poor timeout resolution, 'load' often fires before
/* if(window == top){ //non-frames only
if(doScrollChk()) return;
docReadyProcId = setInterval(doScrollChk, 5);
} */
checkReadyState() ||
doScrollChk() ||
//Use readystatechange as a backup AND primary detection mechanism for a FRAME/IFRAME
DOC.attachEvent('onreadystatechange', checkReadyState);
} else if(Ext.isOpera ) {
/* Notes:
Opera needs special treatment needed here because CSS rules are NOT QUITE
available after DOMContentLoaded is raised.
*/
if(OperaDOMContentLoaded()) return; //must wait for DOM for this test.
DOC.addEventListener(DOMCONTENTLOADED, OperaDOMContentLoaded, false);
} else if (Ext.isWebKit){
//Fallback for older Webkits without DOMCONTENTLOADED support
if(checkReadyState()) return;
docReadyProcId = setInterval(checkReadyState, 10); //not ideal, but relatively harmless at this stage
}
// no matter what, make sure it fires on load
E.on(WINDOW, "load", fireDocReady);
};
hendricd
06-23-2009, 11:37 AM
@Condor -- I like the structure!
But, the setInterval timer resolution has proven time and again just how bad it is for this purpose. You'll often miss the desired timing because of it. setTimeout handles these scenarios much better. :-?
See the inlines in your last version.
I'll start testing out your revs..
hendricd
06-23-2009, 05:48 PM
@Condor --
Using a tweaked version of our discussion: :-?
//Diagnostic Harness
window.evloop =[];
var evl = function(){
evloop.push(Array.prototype.slice.call(arguments,0).concat(new Date().getTime()));
};
function doScrollChk(){
/* Notes:
'doScroll' will NOT work in a IFRAME/FRAMESET.
The method succeeds but, a DOM query done immediately after -- FAILS.
*/
if(window != top){ return false; }
try{
DOC.documentElement.doScroll('left');
}catch(e){
evl('doScroll failed',Ext.isReady, null , DOC.readyState)
return false;
}
evl('doScroll OK'+(!Ext.isReady?', WINS!':''),Ext.isReady,null , DOC.readyState);
fireDocReady();
return true;
}
function checkReadyState(e){
Ext.isReady || (Ext.isIE && doScrollChk());
if(DOC.readyState == COMPLETE){
evl('onReadyState'+(!Ext.isReady?', WINS!':''),Ext.isReady, null, DOC.readyState)
fireDocReady();
return true;
}
evl('onReadyState = ' + DOC.readyState, Ext.isReady, e? e.type: '', DOC.readyState);
Ext.isReady || setTimeout(arguments.callee, 2);
}
var styles;
function checkStyleSheets(e){
styles || (styles = Ext.query('style, link[rel=stylesheet]'));
if(styles.length == DOC.styleSheets.length){
evl('styleSheets OK'+(!Ext.isReady?', WINS!':''),Ext.isReady, e? e.type: '', DOC.readyState)
fireDocReady();
return true;
}
Ext.isReady || setTimeout(arguments.callee, 2);
}
function OperaDOMContentLoaded(){
evl('Opera:DOMContentLoaded'+(!Ext.isReady?', WINS!':''),Ext.isReady, e? e.type: '', DOC.readyState)
DOC.removeEventListener(DOMCONTENTLOADED, arguments,callee, false);
checkStyleSheets();
}
function fireDocReady(e){
docReadyState = true;
evl('on fireDocReady',Ext.isReady, e? e.type: e, DOC.readyState);
if(DETECT_NATIVE) {
DOC.removeEventListener(DOMCONTENTLOADED, fireDocReady, false);
}
if(docReadyProcId){
clearInterval(docReadyProcId);
}
if(Ext.isIE && checkReadyState.bindIE){ //was this was actually set ??
DOC.detachEvent('onreadystatechange', checkReadyState);
}
if(Ext.isOpera){
DOC.removeEventListener(DOMCONTENTLOADED, OperaDOMContentLoaded, false);
}
if(docReadyEvent && !Ext.isReady){
Ext.isReady = true;
evl('Firing documentready',Ext.isReady,'',DOC.readyState);
docReadyEvent.fire();
docReadyEvent.clearListeners();
}
//E.un(WINDOW, "load", _onload); //remarked for logging/diag
};
function initDocReady(){
docReadyEvent = new Ext.util.Event();
if (DETECT_NATIVE) {
DOC.addEventListener(DOMCONTENTLOADED, fireDocReady, false);
}
/*
* Handle additional (exceptional) detection strategies here
*/
if (Ext.isIE){
//Use readystatechange as a backup AND primary detection mechanism for a FRAME/IFRAME
if(!checkReadyState()){
checkReadyState.bindIE = true;
DOC.attachEvent('onreadystatechange', checkReadyState);
}
} else if(Ext.isOpera ) {
/* Notes:
Opera needs special treatment needed here because CSS rules are NOT QUITE
available after DOMContentLoaded is raised.
*/
DOC.addEventListener(DOMCONTENTLOADED, OperaDOMContentLoaded, false);
} else if (Ext.isWebKit){
//Fallback for older Webkits without DOMCONTENTLOADED support
checkReadyState();
}
// no matter what, make sure it fires on load
E.on(WINDOW, "load", _onload);
};
//used for the diagnostic only.
function _onload(e){
evl('onload (fallback)'+(!Ext.isReady?' WINS!':''),Ext.isReady, 'load', DOC.readyState)
fireDocReady();
}
I have this diagnostic build running here (http://demos.theactivegroup.com/domready.html). Try on all your browsers of interest. ;)
It dumps the diagnostic results to a table after the window.load event. Pay particular attention to the doScroll handler for IE with this large image embedded in the page.
Tested IE 7,8, Safari 4, Chrome 1, FF(2, 3.1.11)
IE observations: For a lightly loaded page, the document.readyState trap usually wins; for a heavily loaded page (simulated here), the doScroll trap works well (without the need for the pesky <SCRIPT> tag).
mystix
06-23-2009, 10:36 PM
+1e5!
p.s. @condor -- what's with the triple negation in your suggested doScrollChk() method?
function doScrollChk(){
/* Notes:
'doScroll' will NOT work in a IFRAME/FRAMESET.
The method succeeds but, a DOM query done immediately after -- FAILS.
*/
if(window != top){ return; }
try{
DOC.documentElement.doScroll('left');
}catch(e){
return Ext.isReady || !!!setTimeout(arguments.callee,5);
}
fireDocReady();
return true;
}
JeffHowden
06-24-2009, 02:46 AM
Tested IE 7,8, Safari 4, Chrome 1, FF(2, 3.1.11)
IE observations: For a lightly loaded page, the document.readyState trap usually wins; for a heavily loaded page (simulated here), the doScroll trap works well (without the need for the pesky <SCRIPT> tag).
Tested in IE6 and works well with doScroll winning.
Condor
06-24-2009, 03:01 AM
@mystix: That's not my code. You'll have to ask hendricd.
@hendricd:
Your new revised code still clears the docReadyProcId interval timer (which isn't used anymore).
Instead it should be clearing the checkReadyState timeout timer.
hendricd
06-24-2009, 10:19 AM
@et al --
OK, my hair is about gone. (:|
But after some further rigorous testing, I've made some new observations and changes.
Here is the latest version of the detection strategy (only relevant methods shown, with debugging harness still in place). I have removed the worrisome triple negation for clarity (and @Mystix), and, for completeness -- reset any setTimeout timers that happen to be in effect. ;)
Any thing highlighted in red, IMO, should be fixed sooner as it's not directly related to the proposed detection strategy.
//Diagnostic Harness
window.evloop =[];
window.evl = function(){
var args = Array.prototype.slice.call(arguments,0).concat(new Date().getTime());
evloop.push(args);
};
function doScrollChk(){
/* Notes:
'doScroll' will NOT work in a IFRAME/FRAMESET.
The method succeeds but, a DOM query done immediately after -- FAILS.
*/
if(window != top){ return false; }
try{
DOC.documentElement.doScroll('left');
}catch(e){
evl('doScroll failed',Ext.isReady, null , DOC.readyState)
return false;
}
evl('doScroll OK'+(!Ext.isReady?', WINS!':''),Ext.isReady,null , DOC.readyState);
fireDocReady();
return true;
}
/**
* @return {Boolean} True if the document is in a 'complete' state (or was determined to
* be true by other means). If false, the state is evaluated again until canceled.
*/
function checkReadyState(e){
if(Ext.isIE && doScrollChk()){
return true;
}
if(DOC.readyState == COMPLETE){
evl('onReadyState'+(!Ext.isReady?', WINS!':''),Ext.isReady, null, DOC.readyState)
fireDocReady();
return true;
}
evl('onReadyState = ' + DOC.readyState, Ext.isReady, e? e.type: '', DOC.readyState);
docReadyState || (docReadyProcId = setTimeout(arguments.callee, 2));
return false;
}
var styles;
function checkStyleSheets(e){
styles || (styles = Ext.query('style, link[rel=stylesheet]'));
if(styles.length == DOC.styleSheets.length){
evl('styleSheets OK'+(!Ext.isReady?', WINS!':''),Ext.isReady, e? e.type: '', DOC.readyState)
fireDocReady();
return true;
}
docReadyState || (docReadyProcId = setTimeout(arguments.callee, 2));
return false;
}
function OperaDOMContentLoaded(e){
evl('Opera:DOMContentLoaded',Ext.isReady, e? e.type: '', DOC.readyState)
DOC.removeEventListener(DOMCONTENTLOADED, arguments.callee, false);
checkStyleSheets();
}
function fireDocReady(e){
if(!docReadyState){
docReadyState = true; //only attempt listener removal once
evl('on fireDocReady',Ext.isReady, e? e.type: e, DOC.readyState);
if(docReadyProcId){
clearTimeout(docReadyProcId);
}
if(DETECT_NATIVE) {
DOC.removeEventListener(DOMCONTENTLOADED, fireDocReady, false);
}
if(Ext.isIE && checkReadyState.bindIE){ //was this was actually set ??
DOC.detachEvent('onreadystatechange', checkReadyState);
}
E.un(WINDOW, "load", arguments.callee);
}
if(docReadyEvent && !Ext.isReady){
Ext.isReady = true;
evl('Firing documentready',Ext.isReady,'',DOC.readyState);
docReadyEvent.fire();
docReadyEvent.clearListeners();
}
};
function initDocReady(){
evl('initDocREady', Ext.isReady, '','')
docReadyEvent || (docReadyEvent = new Ext.util.Event());
if (DETECT_NATIVE) {
DOC.addEventListener(DOMCONTENTLOADED, fireDocReady, false);
}
/*
* Handle additional (exceptional) detection strategies here
*/
if (Ext.isIE){
//Use readystatechange as a backup AND primary detection mechanism for a FRAME/IFRAME
//See if page is already loaded
if(!checkReadyState()){
checkReadyState.bindIE = true;
DOC.attachEvent('onreadystatechange', checkReadyState);
}
} else if(Ext.isOpera ) {
/* Notes:
Opera needs special treatment needed here because CSS rules are NOT QUITE
available after DOMContentLoaded is raised.
*/
//See if page is already loaded and all styleSheets are in place
(DOC.readyState == COMPLETE && checkStyleSheets()) ||
DOC.addEventListener(DOMCONTENTLOADED, OperaDOMContentLoaded, false);
} else if (Ext.isWebKit){
//Fallback for older Webkits without DOMCONTENTLOADED support
checkReadyState();
}
// no matter what, make sure it fires on load
E.on(WINDOW, "load", fireDocReady);
};
onDocumentReady : function(fn, scope, options){
if(Ext.isReady){ // if it already fired or document.body is present
docReadyEvent || (docReadyEvent = new Ext.util.Event());
docReadyEvent.addListener(fn, scope, options);
docReadyEvent.fire();
docReadyEvent.clearListeners();
} else {
if(!docReadyEvent) initDocReady();
options = options || {};
options.delay = options.delay || 1;
docReadyEvent.addListener(fn, scope, options);
}
},
//Initialize doc classes
(function(){
var initExtCss = function(){
// find the body element
var bd = document.body || document.getElementsByTagName('body')[0];
if(!bd){ return false; }
.....
return true;
};
// if the document.body is present (ie when Google-loading Core or simply including Ext somewhere in the <BODY> tag), the DOM is ready and should be reflected if CSS initialization succeeds.
if(!(Ext.isReady = initExtCss())){
Ext.onReady(initExtCss);
}
})();
I've got two pages up to examine both cases:
Standard Script-tags in the <head> section: domready.html (http://demos.theactivegroup.com/domready.html)
and
Script-tags in the <body> section: bodyready.html (http://demos.theactivegroup.com/bodyready.html) (for those fancy splash screens; you wont see any detection chatter here because Ext.isAlreadyReady. :-? )
[Clear those caches before attempting]
What this means, in short, is that late-loaded Ext/Core will not have to call a public fireDocReady method after all. ;)
stever
06-26-2009, 03:10 PM
Ext needs the event raised to setup the <HTML, BODY> classNames.
A month or so ago, I put up a version of that code that would only add the classes if they were not already there (in the case that the sever does it to prevent a document reflow).
http://www.extjs.com/forum/showthread.php?t=65106
Condor
07-23-2009, 10:14 AM
@hendricd:
According to this article (http://www.nczonline.net/blog/2009/07/09/firefox-35firebug-xmlhttprequest-and-readystatechange-bug/) you also would need to enable readyState polling when using Firefox 3.5 + Firebug 1.4/5.
hendricd
07-23-2009, 10:51 AM
@hendricd:
According to this article (http://www.nczonline.net/blog/2009/07/09/firefox-35firebug-xmlhttprequest-and-readystatechange-bug/) you also would need to enable readyState polling when using Firefox 3.5 + Firebug 1.4/5.
@Condor -- Thanks for heads up. ;)
But, what I garner from that thread is that it's an XHR readystatechange event problem, not a onReady detection issue.
I'm running Fx 3.5.1 and FB 1.4/1.5x now and I'm not seeing any interference with domready detection (DOMCONTENTLOADED event).
FYI: The latest ext-basex does not suffer from those reported symptoms as it does not rely solely on readystatechange events. And Ext uses a timer-based readyState polling strategy in lib.Ajax, so it too should not be adversely affected by the problem.
hendricd
09-24-2009, 07:47 AM
Here is the latest rendition of EventManager.js (without all the diagnostics) that's SVN compat with #5402.
Been using for months now.
Condor
09-24-2009, 07:47 AM
If this thread (http://www.extjs.com/forum/showthread.php?t=79640) is correct, then you can no longer rely on the document being ready when the IFRAME 'load' event fires in Opera 10.
This impacts Ext.data.Connection.doFormUpload and maybe also ManagedIframe (you are deferring the event, so it might already work). Not sure if this can also affect Ext.onReady...
mprice
01-15-2010, 12:29 PM
Here is the latest rendition of EventManager.js (without all the diagnostics) that's SVN compat with #5402. Been using for months now.
Is there a 3.1 compatible version of this? Are there plans to ever incorporate this into a release? We experience what is described here (http://www.extjs.com/forum/showthread.php?p=427809#post427809) often and we are on 3.1.
cginzel
01-22-2010, 08:09 PM
I'm using 3.1.1 rev 5856 w/ core 129 and I tried to reapply hendricd changes and the result I have is that I now hang in both IE 6 and FF 3.5! :D So obviously I'm missing something somewhere...
I've debugged it down to the docReadyEvent.fire() call in where it has two listeners but my onready function never fires for some reason.
function fireDocReady(e){
if(!docReadyState){
docReadyState = true; //only attempt listener removal once
if(docReadyProcId){
clearTimeout(docReadyProcId);
}
if(DETECT_NATIVE) {
DOC.removeEventListener(DOMCONTENTLOADED, fireDocReady, false);
}
if(Ext.isIE && checkReadyState.bindIE){ //was this was actually set ??
DOC.detachEvent('onreadystatechange', checkReadyState);
}
E.un(WINDOW, "load", arguments.callee);
}
if(docReadyEvent && !Ext.isReady){
Ext.isReady = true;
docReadyEvent.fire();
docReadyEvent.clearListeners();
}
};
Maybe someone more knowledgeable might know what differences exist in 3.1 that might confound my rather mindless copy effort or see an even dumber copying error! :">
vBulletin® v3.8.4, Copyright ©2000-2010, Jelsoft Enterprises Ltd.