Un po' di tempo fa mi sono imbattutto in un post dell' IEBlog in cui viene mostrato come l'aggiunta "prematura" di elementi al DOM tramite javascript ( ovvero prima che il parser rilevi la corretta chiusura del tag padre dell'elemento aggiunto dinamicamente ) può indurre IE7 alla visualizzazione di un messageBox "Operation aborted" che preclude addirittura la visualizzazione della pagina. Viceversa, se il DOM viene acceduto "troppo tardi" - ad esempio per nascondere degli elementi della pagina come immagini o oggetti embedded - si possono ottenere degli effetti indesiderati di lampeggiamento ("flickers"). Dunque, sorge spesso il dubbio circa quando e come modifcare il DOM della pagina in base al browser per evitare inconsistenze/risultati non ottimali. Certamente la questione non è banale!
Anzitutto, modificare il DOM dopo che si è scatenato l'evento window.onload costituisce un'operazione sicura, ma talvolta ci troviamo di fronte all'esigenza di non poter attendere il completo caricamento della pagina (DOM + oggetti) in quanto può avvenire troppo tardi rispetto alle nostre esigenze: potremmo infatti desiderare che il nostro script venga eseguito subito dopo che il DOM viene parserizzato dal browser ma prima che le immagini vengano scaricate.
Prima di vedere come questo scenario sia gestito automaticamente (e in maniera cross-browser) dal framework client-side di ASP.NET AJAX, spendiamo un attimo due parole sull' attuale supporto fornito dai principali browser per la manipolazione client-side del DOM.
Firefox, Opera e Safari prevedono un evento DOMContentLoaded che viene scatenato subito dopo il caricamento del DOM ma immediatamente prima del download dei vari oggetti della pagina. Quindi, l'unica cosa da fare è aggiungere un EventListener per l'evento DOMContentLoaded e il gioco è fatto.
if (document.addEventListener)
{
document.addEventListener("DOMContentLoaded", function() {
// Codice che accede al DOM in maniera sicura...
}, false);
}
Internet Explorer (almeno fino alla versione 7) non rende la vita così semplice, in quanto l'evento DOMContentLoaded non è supportato. Siamo dunque costretti ad affidarci a qualche workaround che lo emuli. Nello specifico, i due workaround più conosciuti sono:
- utilizzare l'attributo defer nel tag script: <script type="text/javascript" defer="defer" src="onload.js" />
- implementare un meccanismo di controllo alternativo come questo per attendere il corretto caricamento del DOM.
In framework client-side di ASP.NET AJAX invece risolve il problema alla radice!
Anzitutto, per ovvi motivi non è prevista alcuna ottimizzazione dedicata ad un browser specifico (es. agganciamento dell'evento DOMContentLoaded a seguito dello sniffing del browser), tuttavia l'inizializzazione dei vari componenti viene sempre eseguita dopo il caricamento del DOM. Come avviene tutto questo? Vediamo.
Nel codice sorgente dell' AJAX Library viene definita una classe Sys.Application che viene istanziata automaticamente in ogni pagina:
Sys.Application = new Sys._Application();
Il costruttore
_Application() al suo interno include il seguente codice:
Sys.UI.DomEvent.addHandler(window, "load", this._loadHandlerDelegate);
Sys.UI.DomEvent.addHandler(window, "unload", this._unloadHandlerDelegate);
La prima istruzione aggancia l'evento Application.load all'evento window.load del browser mentre la seconda istruzione associa l'evento Application.unload all'evento window.unload. Dunque, quando viene scatenato l'evento window.load viene invocato il metodo _loadHandlerDelegate() di ASP.NET AJAX, il quale a sua volta invoca il metodo _Application.initialize():
function Sys$_Application$initialize()
{
if(!this._initialized && !this._initializing)
{
this._initializing = true;
window.setTimeout(Function.createDelegate(this, this._doInitialize), 0);
}
}
A cosa serve l'ultima istruzione window.setTimeout(...)? Da quanto ho avuto modo di apprendere, impostare l'invocazione di un metodo a seguito di un timeout di 0 millisecondi forza il browser ad eseguire la funzione prima possibile. In questo caso, si suppone che il metodo _doInitialize venga eseguito dopo che il DOM viene caricato ma prima che venga scaricato il contenuto binario di immagini, oggetti flash/silverlight etc. In questo modo il runtime di javascript non permette lo scadere del timeout e forza l'esecuzione di _doInitialize finché il DOM non sarà completamente caricato.
Di conseguenza il metodo _doInitialize verrà eseguito solo quando sarà sicuro modificare il DOM e nello stesso tempo ci preserverà dall'attesa della completa esecuzione dell' evento window.load (il quale - ricordiamolo ancora - attende il download degli oggetti binari nella pagina).
In conclusione, ASP.NET AJAX fornisce un proprio meccanismo analogo al DOMContentLoaded che permette il corretto accesso al DOM indipendentemente dal browser utilizzato. A questo punto la domanda finale è: dove viene posizionata la chiamata Sys.Application.initialize() ?
Lo ScriptManager si prenderà cura di posizionarla appena prima della chiusura del tag </form> in fondo alla pagina, assumendo ovviamente che non ci siano altri script "a rischio" dopo di esso.
Technorati tags: AJAX, DOM