<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Border-Labs &#187; optimisation</title>
	<atom:link href="http://border-labs.fr/?cat=20&#038;feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://border-labs.fr</link>
	<description>... souvent à la limite</description>
	<lastBuildDate>Fri, 04 Mar 2011 16:36:17 +0000</lastBuildDate>
	<language>fr-FR</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=3.8.41</generator>
	<item>
		<title>Interagir avec le DOM au cours du chargement</title>
		<link>http://border-labs.fr/?p=133</link>
		<comments>http://border-labs.fr/?p=133#comments</comments>
		<pubDate>Tue, 15 Feb 2011 14:35:57 +0000</pubDate>
		<dc:creator><![CDATA[BlackAdder]]></dc:creator>
				<category><![CDATA[framework]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[optimisation]]></category>
		<category><![CDATA[chargement]]></category>
		<category><![CDATA[DOM]]></category>
		<category><![CDATA[librairie]]></category>
		<category><![CDATA[onload]]></category>

		<guid isPermaLink="false">http://border-labs.fr/?p=133</guid>
		<description><![CDATA[Problématique En termes d’ergonomie, il est souhaitable qu’une page HTML se charge le plus vite possible ou en tout cas que les composants graphiques qu’elle comporte soient manipulables dès que possible par l’utilisateur. Lorsqu’on utilise un framework javascript (et qu’on &#8230; <a href="http://border-labs.fr/?p=133">Continuer la lecture <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<h2>Problématique</h2>
<p>En termes d’ergonomie, il est souhaitable qu’une page HTML se charge le plus vite possible ou en tout cas que les composants graphiques qu’elle comporte soient manipulables dès que possible par l’utilisateur. Lorsqu’on utilise un <em>framework</em> javascript (et qu’on n’utilise pas la méthode du partage de code cf. <a title="partage de code inter fenetre" href="http://border-labs.fr/?p=57">dernier article</a>), il faut charger le code correspondant. Sur la plupart des navigateurs actuels, l’interprétation du code javascript est bloquante vis-à-vis de l’interprétation du DOM. Autrement dit, si la page est en train de se charger, qu’elle rencontre une balise SCRIPT, l’HTML cesse d’être interprété pendant que le contenu de la balise SCRIPT (le code javascript) est exécuté.</p>
<p>Pour cette raison, il est préférable d’interpréter le code javascript à la fin de la page ou après le onload (le DOM étant totalement chargé à ce moment). Les seules choses qu’on peut paralléliser sont le déclenchement du chargement de fichier javascript en parallèle. Néanmoins, une fois que le fichier est récupéré sur le poste client, son interprétation devient bloquante à son tour.</p>
<p>Ainsi, si on déclenche le chargement dynamique d’un fichier javascript dans le head ; si l’interprétation de ce fichier prend une seconde et a lieu avant la fin de l’affichage de la page HTML, on a une interruption de une seconde dans l’affichage.</p>
<p>Pour une question d’ergonomie, on en serait donc réduit à ne faire aucun traitement javascript lors du chargement de la page (le chargement de la librairie et les traitements associés étant déclenchés par le onload). Cela aurait des conséquences en termes … d’ergonomie. En effet, l’utilisateur risque de voir une première version (purement HTML) de la page, puis des corrections visuelles réalisées par le javascript.</p>
<p>Prenons l’exemple d’une page comportant des éléments de formulaire HTML. Cette page est entièrement statique et c’est sur le poste client, en javascript, que l’on souhaite initialiser les valeurs des éléments de formulaire (dans la page HTML, les attributs value sont vides).</p>
<p>La solution la plus simple si on veut charger la librairie javascript dynamiquement est :</p>
<ul>
<li>Chargement de la page HTML (les value sont vides)</li>
<li>Chargement asynchrone de la librairie (déclenchée en bas de la page ou dans le onload)</li>
<li>Interprétation de la librairie (1 seconde)</li>
<li>Modification des value des composants (uniquement lorsque la librairie est chargée)</li>
</ul>
<p>Cette solution se traduit par une page HTML qui se charge très vite, mais les champs de saisie restent vides pendant plus d’une seconde. C’est donc tout à fait perceptible par l’utilisateur.</p>
<h2>Introduction</h2>
<p>Nous abordons dans cet article une méthode qui permet de concilier les avantages d’un chargement dynamique des librairies javascript tout en garantissant une certaine ergonomie d’utilisation.</p>
<p>Lors du chargement de la page, de façon à ce que l’utilisateur ne puisse pas percevoir les changements dus à javascript, on va les faire au fil de l’eau, dès que chaque nœud HTML sur lesquels on veut faire un changement apparaît.</p>
<h2>Comment un nœud DOM prévient quand il est prêt</h2>
<p>On souhaite donc pouvoir déclencher un traitement javascript sur un nœud DOM au plus tôt, c&rsquo;est-à-dire dès que le nœud existe.</p>
<p>On sait que :</p>
<ul>
<li>les balises SCRIPT <em>inline</em> sont lues séquentiellement dans l’ordre du code source HTML.</li>
<li>un script javascript a immédiatement accès au DOM du document, même si celui-ci est encore en court de chargement.</li>
</ul>
<p>Par ailleurs, on souhaite être conforme aux préconisations de l’<em>unobtrusive javascript</em> : on veut séparer les balises HTML du code javascript. Il n’y aura donc pas de handler d’événement javascript déclaré dans une balise HTML.</p>
<p>Une solution la simple (et portable) est donc d’insérer une balise SCRIPT à proximité immédiate de la balise DOM sur laquelle on souhaite réaliser un traitement.</p>
<p>Pour une balise auto-fermante comme INPUT, par exemple :</p>
<pre>&lt;INPUT value="Smith" /&gt;</pre>
<p>çà donne:</p>
<pre>&lt;INPUT value="Smith" /&gt;
&lt;SCRIPT&gt;…&lt;/SCRIPT&gt;</pre>
<p>On est certain que</p>
<p>Pour une balise classique, telle que :</p>
<pre>&lt;DIV&gt;
&lt;balise&gt;…&lt;/balise&gt;
&lt;balise&gt;…&lt;/balise&gt;
&lt;/DIV&gt;</pre>
<p>on peut insèrer la balise SCRIPT immédiatement à l’intérieur de la balise HTML :</p>
<pre>&lt;DIV&gt;
&lt;SCRIPT&gt;…&lt;/SCRIPT&gt;
&lt;balise&gt;…&lt;/balise&gt;
&lt;balise&gt;…&lt;/balise&gt;
&lt;/DIV&gt;</pre>
<p>Dans ce cas, le script a accès à la balise englobante (d’identifiant c1) mais pas aux autres balises.</p>
<p>On peut aussi insérer la balise SCRIPT à la fin de la balise HTML :</p>
<pre>&lt;DIV&gt;
&lt;balise&gt;…&lt;/balise&gt;
&lt;balise&gt;…&lt;/balise&gt;
&lt;SCRIPT&gt;…&lt;/SCRIPT&gt;
&lt;/DIV&gt;</pre>
<p>Dans ce cas, le script a accès à la totalité des balises filles de la balise englobante.</p>
<p>On peut aussi (ce qui a priori ne change pas grand-chose par rapport à la solution précédente), utiliser la même approche que pour les balises auto-englobantes, c&rsquo;est-à-dire insérer le script après :</p>
<pre>&lt;DIV&gt;
&lt;balise&gt;…&lt;/balise&gt;
&lt;balise&gt;…&lt;/balise&gt;
&lt;/DIV&gt;
&lt;SCRIPT&gt;…&lt;/SCRIPT&gt;</pre>
<h2>Mise en œuvre</h2>
<p>Nous allons exclure de cet article les solutions consistant à interroger le body du document pour déterminer quelle est la balise courante en cours de lecture. Nous prendrons donc une solution plus simple. Cette solution impose la contrainte d’associer un attribut id unique à chaque nœud HTML concerné. Même si la balise SCRIPT est située à l’intérieur de la balise en cours de lecture, on peut déjà accéder à cette balise.</p>
<p>Ainsi, la forme suivante est parfaitement fonctionnelle :</p>
<pre>&lt;DIV&gt;
&lt;SCRIPT&gt;
// Récupération du noeud DOM
var domEl=this.document.getElementById("c1");
&lt;/SCRIPT&gt;
&lt;DIV&gt;…&lt;/DIV&gt;
&lt;DIV&gt;…&lt;/DIV&gt;
&lt;/DIV&gt;</pre>
<p>domEl contient bien le nœud DOM correspondant à la balise DIV d’identifiant c1, même si la balise SCRIPT est située à l’intérieur du DIV et que les balises suivantes n’ont pas encore été évaluées. Bien entendu, si on fait domEl.childNodes, on ne récupère que la balise SCRIPT, pas les balises d’identifiants a et b.</p>
<p>Cette solution utilisant les identifiants est parfaitement adaptée à une génération côté serveur. Par exemple, en JSP, on pourrait avoir une balise</p>
<pre>&lt;skynet:input id="c1" name="${path}"&gt;</pre>
<p>qui générerait le fragment complet suivant :</p>
<pre>&lt;INPUT id="c1" name="p1.prenom" /&gt;
&lt;SCRIPT&gt;
// Récupération du noeud DOM
var domEl=this.document.getElementById("c1");
… // Actions à faire sur le DOM
&lt;/SCRIPT&gt;</pre>
<h2>Exemple</h2>
<p>Cet exemple est disponible en ligne <a title="exemple interaction DOM" href="http://border-labs.fr/Samples/InteractionsDOM/Nouveau2.html">ici</a>.</p>
<p><span style="color: #000000; font-size: 18px; line-height: 27px;">Ajout d’un handler d’événement</span></p>
<p>On veut séparer le code de la balise HTML et la déclaration de son <em>handler</em> d’événement javascript.</p>
<p>Il s’agit ici d’un bouton de type submit :</p>
<pre>&lt;INPUT value="Valider"&gt;</pre>
<p>Si javascript n’est pas activé, le bouton a le comportement normal d’un bouton submit (envoi du formulaire sur le serveur).</p>
<p>Si javascript est activé, on associe au bouton un handler d’événement ouvrant un dialogue demandant si on veut vraiment soumettre le formulaire.</p>
<p>Le code résultant est le suivant :</p>
<pre>&lt;INPUT value="Test"&gt;
&lt;SCRIPT&gt;
// Récupération du dom element (le bouton)
var domEl=this.document.getElementById("button1");
/** Le handler du bouton
*  Il empêche la validation du formulaire (returnValue=false)
* @param ev {Event}
**/
function verifierValidation(ev){
if (confirm("Voulez vous vraiment valider ?")) {
ev.returnValue=true;
} else {
ev.returnValue=false;
}
};
// Ajout du handler d’événement
if (domEl.addEventListener) {  // Firefox, Google Chrome, Safari, Opera
domEl.addEventListener ("click", verifierValidation, false);
}
else {
if (domEl.attachEvent) {   // IE
domEl.attachEvent ("onclick", verifierValidation);
}
}
&lt;/SCRIPT&gt;</pre>
<p>Il est facile d’utiliser ce mécanisme pour que les handlers d’événements ne soient plus déclarés en tant qu’attributs HTML (comme onclick par exemple) mais dans un code javascript séparé. En faisant cette déclaration immédiatement, on s’assure que certains comportements javascript seront déjà actifs avant le chargement de la librairie du framework. Dans notre exemple, l’utilisateur ne pourra pas cliquer sur le bouton « <strong>Valider </strong>» sans passer par la vérification javascript, même si la librairie n’a pas encore été chargée.</p>
<h3>Ajouter un fragment HTML au DOM si javascript est activé</h3>
<p>On veut générer un fragment HTML à attacher au nœud DOM existant.</p>
<p>On applique la première solution à la génération d’un icône de calendrier permettant de lancer une <em>pop-up</em> de saisie des dates.</p>
<p>Si javascript est désactivé, seul le champ de saisie textuel est présent.</p>
<pre>&lt;INPUT type="text" value=""&gt;
&lt;SCRIPT&gt;
var domEl=this.document.getElementById("calendar1");
domEl.insertAdjacentHTML("afterEnd",<BR>
"&lt;IMG src='./Agenda-Icon.gif' onclick='launchCalendar()' /&gt;");
&lt;/SCRIPT&gt;</pre>
<p>Noter qu’il faut bien évidemment que la fonction launchCalendar soit définie à ce moment. Parce que sinon, si l’utilisateur clique immédiatement dessus, il y aura une erreur.</p>
<p>Cela implique qu’il faudra identifier et différencier :</p>
<ul>
<li>les traitements à réaliser AVANT le chargement de la librairie (leur code doit être défini avant leur utilisation, on peut par exemple les définir dans le head du document),</li>
<li>les traitements standards (qui nécessitent le chargement de la librairie complète)</li>
</ul>
<p><span style="color: #000000; font-size: 23px; line-height: 35px;">Autres utilisations</span></p>
<p>On peut multiplier à loisir les utilisations de cette approche.</p>
<p>Dans le cadre de l’<em>unobtrusive javascript</em>, on a vu l’ajout des <em>events listeners</em> ou l’insertion conditionnelle de fragments html, on pourrait aussi rendre visible ou changer l’état d’éléments HTML générés sur le serveur.</p>
<p>Cette approche permet aussi de <em>profiler</em> les temps d’interprétation du DOM : il suffit que chaque balise SCRIPT insérée fasse des mesures du temps.</p>
<p>De façon générale, cette approche permet de réaliser des modifications du code HTML de la page sans pour autant que l’utilisateur puisse les percevoir. Çà permet par exemple d’utiliser une page HTML complètement statique et de modifier au chargement les attributs value et l’état (disabled, readonly) de chaque composant. Cette solution ne fonctionne évidemment plus sans javascript (les composants graphiques n’ont pas la bonne value) mais elle peut être utilisée pour réutiliser la même page avec des valeurs différentes sans appel serveur (devient très intéressante en terme de performance : le serveur d’application se contentant de générer valeurs et états et pas la page HTML qui peut être gérée par un serveur web.</p>
<p><span style="color: #000000; font-size: 23px; line-height: 35px;">Constat</span></p>
<p>Cette approche permet de réaliser des modifications javascript sur la page en cours de chargement sans pour autant qu’on soit obligé d’avoir chargé avant la librairie javascript gérant les composants graphiques. Cette approche permet en effet d’afficher très rapidement le contenu de la page HTML (sans être retardé par des chargements de code javascript, ceux-ci peuvent par exemple avoir lieu au onload de la page). Cela permet de garantir à l’utilisateur qu’il a accès immédiatement (en cours de chargement) à certaines fonctionnalités ou tout du moins que les modifications réalisées par javascript ne sont pas visuellement perceptibles.</p>
<p>Bien sûr, l’interprétation de l’HTML de la page est légèrement plus lente (mais çà reste à peine perceptible tant que les traitements javascript réalisés sont simples – et que ceux-ci ne nécessitent pas à une évaluation longue de code javascript –).</p>
<p>Les composants semblent être correctement initialisés.</p>
]]></content:encoded>
			<wfw:commentRss>http://border-labs.fr/?feed=rss2&#038;p=133</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Partage de code javascript inter-fenêtres : les pop-up</title>
		<link>http://border-labs.fr/?p=57</link>
		<comments>http://border-labs.fr/?p=57#comments</comments>
		<pubDate>Fri, 28 Jan 2011 13:37:59 +0000</pubDate>
		<dc:creator><![CDATA[BlackAdder]]></dc:creator>
				<category><![CDATA[framework]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[optimisation]]></category>

		<guid isPermaLink="false">http://border-labs.fr/?p=57</guid>
		<description><![CDATA[Cet article étudie, dans le cas des fenêtres pop-up, comment on peut éviter le rechargement de code javascript en le partageant d&#8217;une fenêtre à l&#8217;autre. Cet article fait partie d&#8217;une série de plusieurs articles. Plan de l&#8217;article Chargement du framework &#8230; <a href="http://border-labs.fr/?p=57">Continuer la lecture <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>Cet article étudie, dans le cas des fenêtres <em>pop-up</em>, comment on peut éviter le rechargement de code javascript en le partageant d&rsquo;une fenêtre à l&rsquo;autre. Cet article fait partie d&rsquo;une série de plusieurs articles.</p>
<h1>Plan de l&rsquo;article</h1>
<ul>
<li><a href="#chargementAChaquePage">Chargement du framework par chaque page</a>
<ul>
<li><a href="#etape1">étape 1 : chargement synchrone du framework par chaque page</a></li>
<li><a href="#etape2">étape 2 : chargement asynchrone du framework par chaque page</a></li>
</ul>
</li>
<li><a href="#partageFramework">Partage du framework avec une page <em>pop-up</em> </a>
<ul>
<li><a href="#partageFramework"></a><a href="#rappels">Rappels (affichage d&rsquo;une page en html et en javascript)</a></li>
<li><a href="#etape3">étape 3 : la fenêtre <em>pop-up</em> charge elle-même le framework</a></li>
<li><a href="#etape4">étape 4 : la fenêtre <em>pop-up</em> partage le framework</a></li>
<li><a href="#algoFinal">Algorithme final</a></li>
</ul>
</li>
</ul>
<h1>Chargement du <em>framework</em>pour chaque page</h1>
<p><a name="chargementAChaquePage"></a></p>
<h2>Étape 1 : chargement synchrone du <em>framework</em></h2>
<p><a name="etape1"></a></p>
<h3>Mise en œuvre</h3>
<p>Dans cette étape, la première page de l’application, <span class="file">page1.html</span> charge explicitement le <em>framework</em>. Le chargement du fichier <span class="file">skynetlib.js</span> est fait <em>inline</em> via une balise <span class="tag">SCRIPT</span> :</p>
<pre>&lt;SCRIPT src="skynetlib.js"&gt;&lt;/SCRIPT&gt;</pre>
<p>La balise <span class="tag">SCRIPT</span> respecte l’ordonnancement, donc il suffit de lancer la méthode init après le chargement du fichier <span class="file">skynetlib.js</span> :</p>
<pre>&lt;SCRIPT src="skynetlib.js"&gt;&lt;/SCRIPT&gt;
&lt;SCRIPT&gt;
function init(){
// Création d’une instance de Probe et affectation à une variable
var a=new Skynet.tools.Probe(1337) ;
} ;
init() ;
&lt;/SCRIPT&gt;</pre>
<h3>Mesures</h3>
<p>Nos mesures sont faites en javascript. Les chiffres annoncés sont des moyennes.</p>
<table>
<tbody>
<tr>
<td>début de l&rsquo;interprétation du fichier <span class="file">skynetlib.js</span></td>
<td><span class="number">162ms</span></td>
</tr>
<tr>
<td>début de l&rsquo;évaluation de la méthode <span class="js">init</span></td>
<td><span class="number">1221ms</span></td>
</tr>
<tr>
<td>Lancement de l&rsquo;événement <span class="js">onload</span></td>
<td><span class="number">1280ms</span></td>
</tr>
</tbody>
</table>
<p>Noter que l&rsquo;interprétation du fichier <span class="file">skynetlib.js</span> s’arrête à <span class="number">1168ms</span>, cette interprétation dure bien de l&rsquo;ordre de la seconde.</p>
<h3>Constat</h3>
<p>La méthode <span class="js">init</span> est lancée après chargement de <span class="file">skynetlib.js</span> et ce chargement prend une seconde. La page ne peut donc utiliser le <em>framework</em> qu’après ce temps de chargement.</p>
<p>On voit que le <span class="js">onload</span> de la page est aussi en attente du chargement du fichier javascript. C’est visible sur cette copie d’écran de firebug :</p>
<p><a href="http://border-labs.fr/WordPress/wp-content/uploads/2011/01/three-sync.png"><img class="aligncenter size-large wp-image-61" title="Chargement synchrone du framework" src="http://border-labs.fr/WordPress/wp-content/uploads/2011/01/three-sync-1024x76.png" alt="Chargement synchrone du framework" width="640" height="47" /></a></p>
<h2>Étape 2 : chargement asynchrone du <em>framework</em></h2>
<p><a name="etape2"></a></p>
<h3>Mise en œuvre</h3>
<p>On sait (cf. par exemple <a href="http://stevesouders.com/docs/google-20090305.ppt">les transparents « Even Faster Web Sites</a> » de Steve Souders) qu’il vaut mieux charger les fichiers javascript en asynchrone qu’en <em>inline</em>.</p>
<p>Dans cette étape, on veut que la première page, <span class="file">page1.html</span> charge dynamiquement le fichier javascript. C’est assez facile à faire, par exemple via le DOM par création d’une balise <span class="tag">SCRIPT</span> dans le <span class="tag">HEAD</span> :</p>
<pre>function loadScript(filename){
var targetTag=document.getElementsByTagName("head")[0];
var nodeS=document.createElement("SCRIPT");
nodeS.src=filename;
targetTag.appendChild(nodeS);
}</pre>
<p>Néanmoins, ce chargement dynamique ne permet pas de garantir l’ordre d’exécution des scripts. Ainsi même si l’appel de loadScript est effectué AVANT l’appel de la méthode <span class="js">init()</span> ; on ne peut pas garantir que le code du <em>framework</em> aura été interprété avant cet appel.</p>
<p>La méthode <span class="js">init()</span> échoue donc puisqu’au moment où elle est appelée, elle ne peut pas accéder au code du <em>framework</em> (la variable <span class="js">Skynet</span>).</p>
<p>On pourra se référer aux transparents de Steve Souders pour les différents moyens d’ordonnancer des scripts chargés dynamiquement. En ce qui me concerne, j’ai directement pris la librairie EFWS qu’il utilise dans son livre « <a href="http://oreilly.com/catalog/9780596522315">Even faster Web Sites</a> ».</p>
<p>Après l’initialisation de cette librairie (150 lignes en mode non minifié), il suffit de faire</p>
<pre>EFWS.Script.loadScripts(["skynetlib.js"], init);</pre>
<p>pour que le fichier <span class="file">skynetlib.js</span> soit chargé dynamiquement puis que la méthode init soit appelée.</p>
<p>Attention, ce chargement dynamique garantit que la méthode <span class="js">init</span> est appelée après que le chargement du fichier <span class="file">skynetlib.js</span>,. Il n’y a par contre aucune garantie rien vis-à-vis de l’exécution du <span class="js">onload</span> de la page (qui peut avoir lieu avant ou après).</p>
<h3>Mesures</h3>
<table>
<tbody>
<tr>
<td>début de l&rsquo;interprétation du fichier <span class="file">skynetlib.js</span></td>
<td><span class="number">209ms</span></td>
</tr>
<tr>
<td>début de l&rsquo;évaluation de la méthode <span class="js">init</span></td>
<td><span class="number">1266ms</span></td>
</tr>
<tr>
<td>Lancement de l&rsquo;événement <span class="js">onload</span></td>
<td><span class="number">107ms</span></td>
</tr>
</tbody>
</table>
<p>Le chargement du fichier <span class="file">skynetlib commence à <span class="number">209ms</span> (donc un peu plus tard que le chargement par balise &lt;<span class="tag">SCRIPT</span>&gt; de l’étape précédente) et s’arrête à <span class="number">1221ms</span>.</span></p>
<h3>Constat</h3>
<p>L’utilisation d’un chargement dynamique se traduit dans notre exemple par un léger surcoût (l’évaluation de la fonction <span class="js">init</span> commence à <span class="number">1266ms</span>&gt; à la place de <span class="number">1221ms</span>). Il est clair cependant que la page d’exemple n’est pas significative : elle ne comporte pratiquement pas de balises HTML, or le chargement dynamique est surtout intéressant parce qu’il ne bloque pas l’évaluation de la page. On voit néanmoins que le <span class="js">onload</span> lors d’un chargement asynchrone :</p>
<p><a href="http://border-labs.fr/WordPress/wp-content/uploads/2011/01/two.png"><img class="aligncenter size-large wp-image-63" title="Chargement asynchrone" src="http://border-labs.fr/WordPress/wp-content/uploads/2011/01/two-1024x76.png" alt="Chargement asynchrone du fichier" width="640" height="47" /></a></p>
<p>est lancé plus d’une seconde (<span class="number">1280</span> – <span class="number">107</span>) avant celui de la solution synchrone (étape 1).</p>
<h1>Partage du <em>framework</em> avec une page <em>pop-up</em></h1>
<p><a name="partageFramework"></a><br />
Nous allons maintenant comparer le temps de chargement d’une fenêtre si au lieu d’être ouverte directement par son url, elle est ouverte en javascript par une page parente.</p>
<h2>Rappels</h2>
<p><a name="rappels"></a></p>
<h3>Affichage d’une page en HTML</h3>
<p>En html, il suffit d’utiliser un hyperlien pour afficher une page.</p>
<p>Si on ne précise pas l’attribut target, la page courante est remplacée par la page cible :</p>
<pre>&lt;A href="Page1.html"&gt;Page1.html&lt;/A&gt;</pre>
<p>Si on précise l’attribut target à la valeur _blank, on ouvre une nouvelle fenêtre. On peut aussi préciser comme target le name d’une frame ou d’une window existante.</p>
<pre>&lt;A href="Page1.html" target="_blank"&gt;Page1.html&lt;/A&gt;</pre>
<h3>Affichage d’une page en javascript</h3>
<p>Il existe plusieurs méthodes en javascript pour afficher une page dans fenêtre.</p>
<p>La première méthode est d’utiliser document.location=&nbsp;&raquo;pageCible.html&nbsp;&raquo; qui remplace la page courante par pageCible.html.</p>
<p>La seconde méthode utilise <span class="js">window.open</span>. Cette méthode est plus puissante : elle permet d’ouvrir d’autres fenêtres (et pas seulement de remplacer le contenu de la fenêtre courante).</p>
<p>La méthode <span class="js">window.open</span> a la syntaxe suivante :</p>
<p><span class="js">window.open(url, windowName, windowFeatures)</span></p>
<ul>
<li><span class="js">url</span> est l’url de la page à ouvrir,</li>
<li><span class="js">windowName</span> est le nom de la fenêtre (ce nom peut être utilisé pour rouvrir une nouvelle page dans la même fenêtre)</li>
<li><span class="js">windowFeatures</span>, une chaîne avec différentes propriétés que l’on peut donner à la nouvelle fenêtre.</li>
</ul>
<p>Cela donne par exemple :</p>
<pre><span class="js">window.open("eventEditionPage.html","Event editor",

"location=1,status=0,scrollbars=1, width=100,height=100")</span></pre>
<p>Sur Chrome comme Firefox, si on veut ouvrir la nouvelle page dans une fenêtre qui lui est propre, il suffit de mettre une chaîne non vide dans <span class="js">windowFeatures</span>, sinon, la fenêtre s’ouvre dans un onglet du navigateur.</p>
<p>Si l’on souhaite remplacer la fenêtre courante, il suffit d’utiliser comme windowName le nom de la fenêtre courante. On se retrouve alors avec un comportement identique à l’affectation de document.location.</p>
<h3>Cas particulier : le chargement dans la même fenêtre</h3>
<p>Il est possible de réutiliser la même fenêtre pour plusieurs fenêtres. soit par balise <span class="tag">A</span> avec pour <span class="tag">target</span> <span class="tag">_self</span>, soit par affectation de <span class="js">document.location</span>, soit par l’utilisation de <span class="js">window.open</span> en précisant pour <span class="js">windowName</span> le même nom de fenêtre que pour la première page.</p>
<p>Même dans ce cas où l’on réutilise la même fenêtre, par exemple si la page <span class="file">page1.html</span> charge la page un <span class="file">page2.html</span>, la page <span class="file">page2.html</span> possède toujours un <span class="js">window.opener</span>. Par contre, on a <span class="js">window.opener==window</span>, et <span class="file">page1.html</span> est inaccessible. Il est donc impossible de récupérer les variables initialisées dans <span class="file">page1.html</span>. Dans ce cas, il est obligatoire de charger <span class="file">skynetlib.js</span> dans <span class="file">page2.html</span>. La seule présence d’un <span class="js">window.opener</span> ne suffit donc pas à garantir que le <em>framework</em> est présent dans ce <span class="js">window.opener</span>.</p>
<p>Autrement dit, on ne peut pas partager un code javascript situé dans la même fenêtre et chargé par une page que la page courante a remplacé.</p>
<h2>Mise en oeuvre</h2>
<p>Dans la suite de l’exemple, nous introduisons une page <span class="file">master1.html</span>. Cette page charge elle-même le fichier <span class="file">skynetlib.js</span> (en synchrone via une balise <span class="tag">SCRIPT</span>).</p>
<p>C’est <span class="file">master1.html</span> qui ouvre la fenêtre <em>pop-up</em> par un appel javascript tel que :</p>
<pre><span class="js">window.open("mapage.html")</span></pre>
<h2>Étape 3 : la fenêtre <em>pop-up</em> charge le framework</h2>
<p><a name="etape3"></a></p>
<h3>Mise en œuvre</h3>
<p>La page <span class="file">master1.html</span> lance la page <span class="file">page1-frm.html</span> en tant que fenêtre <em>pop-up</em>. La page1-frm fait elle-même un chargement synchrone du fichier <span class="file">skynetlib.js</span>.</p>
<h3>Mesures</h3>
<p>Au niveau de <span class="file">page1-frm.html</span> (les temps sont mesurés à partir du chargement de cette page) :</p>
<table>
<tbody>
<tr>
<td>début de l&rsquo;interprétation du fichier <span class="file">skynetlib.js</span></td>
<td><span class="number">175ms</span></td>
</tr>
<tr>
<td>début de l&rsquo;évaluation de la méthode <span class="js">init</span></td>
<td><span class="number">1146ms</span></td>
</tr>
<tr>
<td>Lancement de l&rsquo;événement <span class="js">onload</span></td>
<td><span class="number">1176ms</span></td>
</tr>
</tbody>
</table>
<p>Noter que comme on peut s’y attendre l’utilisation de la méthode de chargement dynamique du <em>framework</em> (avec la <span class="file">page1-dyn.html</span>) ne change que la date de d’événement <span class="js">onload</span> qui se déclenche vers <span class="number">42ms</span> (comme dans l’étape 2).</p>
<h3>Constat</h3>
<p>Avec cette méthode, on a chargé deux fois le fichier <span class="file">skynetlib.js</span> : une fois dans la page <span class="file">master1.html</span>, une autre fois dans la page <span class="file">page1-frm.html</span>.</p>
<p>Autrement dit, à chaque fois qu’on ouvre une fenêtre <em>pop-up</em>, on est obligé de recharger le <em>framework</em> dans cette fenêtre <em>pop-up</em>.</p>
<p>Ainsi, si vous avez un composant comme un calendrier et que vous utilisez une page <em>pop-up</em> pour créer ou modifier un événement de calendrier, vous êtes obligés de recharger le <em>framework</em> dans cette fenêtre <em>pop-up</em>. Si vous avez une vue détail de cet page <em>pop-up</em>, par exemple pour pouvoir choisir le fuseau horaire, vous êtes là-aussi obligés de charger le <em>framework</em> pour la vue détail.</p>
<h2>Étape 4 : window.open avec partage de framework</h2>
<p><a name="etape4"></a><br />
On va maintenant chercher à ne charger qu’une seule fois le fichier <span class="file">skynetlib.js</span>. la page <span class="file">master1.html</span> ouvre maintenant <span class="file">page1.html</span> en tant que fenêtre <em>pop-up</em>.</p>
<h3>Mise en œuvre</h3>
<p>Quand on ouvre une fenêtre via javascript (via document.location ou via window.open), elle comporte une variable window.opener qui pointe sur la window qui l’a ouverte. Dans notre cas, le window.opener de <span class="file">page1.html</span> pointe sur la window de <span class="file">master1.html</span>. Or <span class="file">master1.html</span> a chargé le fichier <span class="file">skynetlib.js</span>.</p>
<p>Il suffit donc de faire <span class="js">Skynet=window.opener.Skynet</span> dans <span class="file">page1.html</span> pour que le <em>framework</em> soit accessible directement, sans aucune modification du code utilisant le <em>framework</em>.</p>
<p>Noter que cela nécessite d’être sur un site web, pour des questions de sécurité (liées au <em>cross scripting</em>), il n’est pas possible d’utiliser le protocole <span class="tag">file://</span>.</p>
<p>Ainsi, après cette affectation, dans <span class="file">page2.html</span>, le code de la fonction <span class="js">init</span> :</p>
<pre>var a=new Skynet.tools.Probe(1337) ;</pre>
<p>fonctionne parfaitement sans chargement du fichier <span class="file">skynetlib.js</span>.</p>
<h3>Remarque</h3>
<p>Noter que si on ferme la fenêtre parente (alors que la fenêtre pop-up est toujours ouverte), la variable capturée dans la fenêtre parente, ainsi que tout le code afférant reste valide. Ainsi, le framework est toujours accessible via la variable <span class="js">Skynet</span> alors même que la page qui a chargé ce fichier a disparu. </p>
<h3>Mesures</h3>
<p>Les temps sont inchangés au niveau de <span class="file">master1.html</span>.</p>
<p>Au niveau de <span class="file">page1.html</span> (les temps sont mesurés à partir du chargement de cette page) :</p>
<table>
<tbody>
<tr>
<td>début de l&rsquo;évaluation de la méthode <span class="js">init</span></td>
<td><span class="number">8ms</span></td>
</tr>
<tr>
<td>Lancement de l&rsquo;événement <span class="js">onload</span></td>
<td><span class="number">44ms</span></td>
</tr>
</tbody>
</table>
<h3>Constat</h3>
<p>Autrement dit, les gains sont très importants au niveau de la fenêtre <em>pop-up</em> qui n’a plus à charger le fichier <span class="file">skynetlib.js</span> ce qu’on peut vérifier dans la vue réseau de firebug :</p>
<p><a href="http://border-labs.fr/WordPress/wp-content/uploads/2011/01/g-page1-par-window.open-3.png"><img class="aligncenter size-large wp-image-64" title="Partage de framework dans le cas d'une pop-up" src="http://border-labs.fr/WordPress/wp-content/uploads/2011/01/g-page1-par-window.open-3-1024x79.png" alt="Partage de framework dans le cas d'une pop-up" width="640" height="49" /></a></p>
<p>Dans l’étape précédente, la méthode <span class="js">init</span> était appelée au bout de <span class="number">1100ms</span>, en partageant le <em>framework</em>, elle est appelée au bout de 8ms.</p>
<p>La méthode init de la fenêtre <em>pop-up</em> peut être exécutée très rapidement : la fenêtre <em>pop-up</em> peut utiliser pratiquement immédiatement le code du <em>framework</em> chargé dans la fenêtre parente <span class="file">master1.html</span>.</p>
<h2>Algorithme final</h2>
<p><a name="algoFinal"></a></p>
<h3>Principe</h3>
<p>On peut maintenant récupérer le <em>framework</em> sans le charger de nouveau dans <span class="file">page1.html</span>. Cependant, pour le moment, la <span class="file">page1.html</span> ne peut plus être chargée directement. Il faut obligatoirement qu’elle ait été chargée via un window.open. et que la fenêtre qui a déclenché cette ouverture ait elle-même chargé le <em>framework</em>. Il faut corriger ce défaut.</p>
<p>On peut facilement détecter si une fenêtre a été ouverte via un window.open ou un document.location, il suffit de regarder si elle possède un <span class="js">window.opener</span> et si <span class="js">window.opener != window</span> (cf. le paragraphe sur le chargement dans la même fenêtre)</p>
<p>L’idée est donc de :</p>
<p>1)      Détecter si la fenêtre a été ouverte directement</p>
<p>2)      Si la fenêtre a été ouverte directement, ou a remplacé la page qui l’a ouverte, déclencher un chargement dynamique du <em>framework</em></p>
<h3>Mise en oeuvre</h3>
<p>L’algorithme final reste assez simple :</p>
<ul>
<li>soit la fenêtre courante a été ouverte via une fenêtre parente et on récupère la variable Skynet via window.opener.Skynet</li>
<li>soit on charge dynamiquement le fichier <span class="file">skynetlib.js</span> qui initialise (localement) la variable <span class="js">Skynet</span>.</li>
</ul>
<p>Les exemples en ligne sont disponibles <A href="http://border-labs.fr/Samples/Article%20pop-up/master1.html">ici</A><BR></p>
<p>Le fichier compressé comprenant toutes les pages de l&rsquo;exemple sont <A href="http://border-labs.fr/Samples/Article%20pop-up/Article pop-up.rar">ici</A><BR></p>
<p><span style="color: #000000; font-size: 31px; line-height: 46px;">Conclusion</span></p>
<table>
<tbody>
<tr>
<td></td>
<td>Début de <span class="js">init</span></td>
<td>Début de <span class="js">onload</span></td>
</tr>
<tr>
<td>Chargement synchrone par la page</td>
<td><span class="number">1221ms</span></td>
<td><span class="number">1280ms</span></td>
</tr>
<tr>
<td>Chargement asynchrone par la page</td>
<td><span class="number">1266ms</span></td>
<td><span class="number">107ms</span></td>
</tr>
<tr>
<td>Partage du framework</td>
<td><span class="number">8ms</span></td>
<td><span class="number">44ms</span></td>
</tr>
</tbody>
</table>
<p>Nos mesures montrent que lorsqu’on veut utiliser une fenêtre <em>pop-up</em>, il est très intéressant de partager le code du <em>framework</em>.</p>
<p>C’est d’autant plus important que généralement les fenêtres <em>pop-up</em> sont des fenêtres très utilisées : elles servent par exemple à éditer les objets manipulés par la fenêtre principale. Par exemple on peut avoir une page principale comportant un calendrier et une page <em>pop-up</em> qui permet d’éditer ces événements. Il est clair que le chargement de la page <em>pop-up</em> doit être extrêmement rapide. Dans notre exemple, nous avons vu qu’en partageant le <em>framework</em>, on peut utiliser son code au bout de quelques millisecondes (au lieu d’attendre que son code soit chargé puis évalué).</p>
<p>En plus, nous n’avons compté dans les mesures effectuées que le temps d’interprétation du fichier javascript. Nous n’avons pas tenu compte du temps nécessaire à la récupération des fichiers eux-mêmes. Le gain issu du partage du <em>framework</em> est donc encore supérieur. Sans ce partage, l’utilisateur est donc confronté à un délai d’attente plus important.</p>
]]></content:encoded>
			<wfw:commentRss>http://border-labs.fr/?feed=rss2&#038;p=57</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Partage de code javascript inter-fenêtres : introduction</title>
		<link>http://border-labs.fr/?p=51</link>
		<comments>http://border-labs.fr/?p=51#comments</comments>
		<pubDate>Fri, 28 Jan 2011 13:37:32 +0000</pubDate>
		<dc:creator><![CDATA[BlackAdder]]></dc:creator>
				<category><![CDATA[framework]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[optimisation]]></category>
		<category><![CDATA[iframe]]></category>
		<category><![CDATA[pop-up]]></category>
		<category><![CDATA[window]]></category>

		<guid isPermaLink="false">http://border-labs.fr/?p=51</guid>
		<description><![CDATA[Problématique Quand on réalise une application web, il est fondamental d’optimiser les temps de chargement de chacune des pages. C’est un impératif en termes d’ergonomie. Cette série d’articles étudie le partage de code javascript inter-fenêtres ou comment éviter de recharger &#8230; <a href="http://border-labs.fr/?p=51">Continuer la lecture <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<h1>Problématique</h1>
<p>Quand on réalise une application web, il est fondamental d’optimiser les temps de chargement de chacune des pages. C’est un impératif en termes d’ergonomie. Cette série d’articles étudie le partage de code javascript inter-fenêtres ou comment éviter de recharger un <em>framework</em> javascript qui est utilisé dans chacune des pages de l’application web.</p>
<p>Les contraintes sont les suivantes :</p>
<ul>
<li>On doit pouvoir accéder directement à chaque page html de l’application via son url (on ne peut pas facilement empêcher l’utilisation des menus ou des raccourcis des navigateurs ; de plus, en termes d’ergonomie, il est conseillé que toute fonctionnalité d’une application possède sa propre url).</li>
<li>L’application peut ouvrir ses pages html dans d’autres fenêtres via des appels javascript ou intégrer ces pages dans des balises <span class="tag">IFRAME</span></li>
<li>Dans chacun de ces cas, le code qui dans chaque page utilise le <em>framework</em> doit rester identique. Il est par exemple exclus de se préoccuper de la façon dont la page a été ouverte : directement, sous forme d’un dialogue modal simulé avec des balises <span class="tag">DIV</span> ou sous forme d’un <span class="tag">IFRAME</span>.</li>
</ul>
<p>Dans les exemples, nous étudierons des pages statiques html mais elles pourraient être générées dynamiquement (jsp ou php par exemple), sans que çà change quoi que ce soit à l’approche utilisée.</p>
<p><span style="color: #000000; font-size: 31px; line-height: 46px;">Plan de la série d&rsquo;articles</span></p>
<p>Un <A href="http://border-labs.fr/?p=57">premier article</A> étudiera le cas de l’ouverture de fenêtres <em>pop-up</em> et comment il est possible de partager le code chargé par la fenêtre qui les a ouvertes.</p>
<p>Un second article étudiera le cas des fenêtres incluses dans un <span class="tag">IFRAME</span> et comment partager le code chargé par la fenêtre parente.</p>
<p>Un troisième article étudiera comment optimiser cette utilisation des <span class="tag">IFRAME</span> pour partager le code</p>
<p>Un quatrième article fera le point sur les contraintes dues à ce partage de code : par exemple au niveau de la conception du code lui-même, en termes de sécurité (<em>cross-scripting</em>), d’ergonomie, de robustesse.</p>
<h1>L’exemple</h1>
<p>D’un point de vue pratique, nous allons simuler l’utilisation d’un <em>framework</em> en utilisant un unique fichier javascript intitulé <span class="file">skynetlib.js</span>. Ce fichier contient une boucle d’attente d’1 seconde pour simuler le temps d’interprétation d’un « gros » <em>framework</em>.</p>
<p>Ce temps d’1 seconde n’est absolument pas délirant. Il est courant d’avoir des <em>frameworks</em> du marché, qui ayant fait le choix d’utiliser un fichier javascript unique (rassemblant tous les composants) ont un temps de chargement de cet ordre (voir pire) et qui peuvent aller jusqu’à retarder l’événement <span class="js">onload</span> de 5 à 6 secondes même sur une machine décente. On peut voir ce mécanisme dans cette copie d’écran de firebug où le <span class="js">onload</span> (trait rouge) est très tardif.</p>
<div id="attachment_54" style="width: 513px" class="wp-caption aligncenter"><a href="http://border-labs.fr/WordPress/wp-content/uploads/2011/01/gros-framework.png"><img class="size-full wp-image-54" title="gros framework" src="http://border-labs.fr/WordPress/wp-content/uploads/2011/01/gros-framework.png" alt="Temps de charment d'un gros framework" width="503" height="123" /></a><p class="wp-caption-text">Chronologie du chargement d&#39;un gros framework</p></div>
<p>Le <em>framework</em> de l’exemple est sensé être conforme au <a href="http://yuiblog.com/blog/2007/06/12/module-pattern/">design pattern « module »</a> et l’ensemble de son code est donc accessible sous forme d’une variable globale. Dans les exemples suivants, cette variable, qui sert aussi d’espace de nommage, sera intitulée Skynet. Ce <em>framework</em> d’exemple comporte un constructeur appelé <span class="js">Skynet.tools.Probe</span>.</p>
<p>Une fois le code du <em>framework</em> chargé, chaque page de l’application lance une méthode d’initialisation qui lui est propre (dans notre exemple, elle s’appelera <span class="js">init</span>). Voilà par exemple une fonction init qui réalise l’instanciation du constructeur <span class="js">Probe</span> définit par le <em>framework</em> :</p>
<pre>function init(){
// Création d’une instance de Probe et affectation à une variable
var a=new Skynet.tools.Probe(1337) ;
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://border-labs.fr/?feed=rss2&#038;p=51</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
