Un pyjama efficace pour Internet Explorer

En termes d’ergonomie, on conseille souvent d’implanter dans ses tables HTML des « pyjamas », c’est-à-dire des couleurs de lignes alternées. Cela permet de faciliter la lecture : l’œil peut plus facilement suivre les lignes.

Une table avec des lignes de couleurs alternées

Une table avec pyjama

En CSS 3, il existe une solution tout à fait satisfaisante, utiliser la pseudo classe nth-child qui permet de sélectionner le nième fils d’un nœud DOM.

Cette pseudo classe accepte une syntaxe du type an+b où n représente le fils sélectionné et accepte deux mots clefs odd (pour les fils dont l’index est impair) et even (pour les fils dont l’index est pair). On a :

  • nth-child(2n) correspond à nth-child(even)
  • nth-child(2n+1) correspond à nth-child(odd)

On peut donc directement utiliser cette pseudo classe pour sélectionner les lignes paires et impaires (c’est-à-dire les nœuds de type TR) en fonction de la parité de leur index. Il est bien sûr possible d’utiliser nth-child pour mettre en place des couleurs alternées de plus de 2 couleurs voire mettre en place des couleurs de colonnes alternées (en utilisant la balise COL).

Si on veut ne mettre des couleurs de lignes alternées qu’aux lignes situées dans les balises TBODY, et pas sur celles qui sont situées dans le THEAD et le TFOOT , on utilisera des CSS telles que :

TBODY tr:nth-child(odd) {
background-color: #aaa;
}

TBODYtr:nth-child(even) {
background-color: #9CF;
}

Une feuille de style de ce type marche parfaitement sur les navigateurs récents… sauf sous Internet Explorer, où même dans la version IE9, le sélecteur nth-child n’est toujours pas implanté.

Du coup, il existe même des librairies javascript pour résoudre de façon générique ce problème, par exemple : http://selectivizr.com/.

Mise en œuvre d’une solution pour Internet Explorer

Nous nous contenterons ici, d’une solution ad hoc simplifiée.

Pour Internet Explorer, une solution est d’associer un attribut class différent selon qu’il s’agit d’une ligne paire ou d’une ligne impaire. Par exemple:

<TR class="impair"><TD>…</TD></TR>

<TR class="pair"><TD>…</TD></TR>

<TR class="impair"><TD>…</TD></TR>

Il est même plus simple de n’associer que les attributs pairs (ou impairs) et de faire gérer l’autre valeur par le style par défaut.

TR { background-color: #9CF }

Et

TR.impair { background-color: #AAA;}

En ne marquant que les lignes impaires

<TR class="impair"><TD>…</TD></TR>

<TR><TD>…</TD></TR>

<TR class="impair"><TD>…</TD></TR>

On obtient alors un affichage identique à celui procuré en CSS 3 par :

TR:nth-child(odd) { background-color: #9CF }

TR:nth-child(even) { background-color: #AAA }

Il reste à déterminer comment marquer ces lignes impaires. Si la table HTML est générée par le serveur (par exemple une taglib), le plus simple est que le serveur génère lui-même les attributs class correspondant.

Par contre, si c’est javascript qui doit rajouter le « pyjama » à une table préexistante, il faut itérer sur les lignes pour rajouter le nom de class correspondant. Noter que çà veut dire que dans un premier temps, la table s’affiche sans pyjama, celui-ci étant créé plus tard via javascript.

Cela donne par exemple une boucle telle que :

function createPyjama(table) {

// Récupération des tbodies de la table
var tableBodies = table.getElementsByTagName("TBODY");

// Itération sur chaque TBODY
for (var i = 0; i < tableBodies.length; i++) {
var tableRows = tableBodies[i].rows;

// Marquage des lignes impaires
for (var j = 1; j < tableRows.length; j+=2) {
tableRows[j].className += " impair";
}
}
}

Une telle méthode donne le résultat attendu si la table à laquelle elle s’applique ne possède pas des rangées possédant déjà la classe impair.

Gérer les suppressions de ligne

Malheureusement, si on donne la possibilité de modifier au runtime la table en question, par exemple en supprimant ou en rajoutant une ligne, il est nécessaire de mettre à jour le « pyjama » (sous Internet Explorer, les browsers supportant les CSS 3 n’ont pas ce problème puisque le pseudo sélecteur nth-child fait tout le travail).

Dans ce cas, si on a ajouté (ou supprimé) un nombre impair de lignes, il est nécessaire de recalculer les attributs class de la table pour toutes les lignes suivantes.

Version simplifiée : on recalcule toute la table

La solution la plus simple consiste à recalculer l’intégralité du pyjama. Il suffit de prendre la précaution pour les lignes qui doivent être modifiées de :

  • Retirer la class impair des lignes qui le contenait
  • Ajouter la class impair aux autres lignes

Cela donne par exemple :

function alternate(table) {

var tableBodies = table.getElementsByTagName("TBODY");
for (var i = 0; i < tableBodies.length; i++) {
var tableRows = tableBodies[i].rows;
var pair=true;
for (var j = 0; j < tableRows.length; j++) {
var theTableRow=tableRows[j];
var theClassName=theTableRow.className;
if ( pair  ) {
if ( (theClassName.indexOf('impair') != -1) ) {
theTableRow.className = theClassName.replace('impair', '');
}
} else {
if ( (theClassName.indexOf('impair') == -1) ) {
theTableRow.className += " impair";
}
};
pair=!pair;
}
}
}

Version optimisée

Pour être encore plus efficace, l’idée est de ne modifier que les lignes qui sont situées en dessous de la ligne supprimée. On peut ainsi ajouter un argument qui est l’index de la première ligne supprimée (ajoutée). Cela permet de ne pas refaire le calcul pour les premières lignes, qui n’ont pas besoin d’être touchées. Sur des navigateurs très peu performants comme Internet Explorer 6, cela peut faire une réelle différence.

La première étape est de déterminer l’index de la ligne supprimée. Il ne semble pas avoir de méthode standard pour déterminer quel est l’index d’un nœud DOM parmi tous ses frères (les autres nœuds fils du même nœud père). On peut enchaîner des appels à nextSibling pour itérer de ligne en ligne (balise TR) mais çà n’est pas forcément le plus performant. Cela peut poser problème par exemple quand on sélectionne une ligne à la souris pour la détruire. Il est facile de récupérer le nœud DOM TR correspondant à la ligne cliquée via le handler d’événement. Mais comment connaître son index parmi tous les TR ? En plus, la méthode indexOf, introduite avec Javascript 1.6 n’est disponible que sur une instance d’Array javascript, pas sur une HTMLCollection (les nœuds fils d’un nœud DOM forment une HTMLCollection, pas une Array). Et de toutes façons indexOf n’est pas disponible sous Internet Explorer 6.

Heureusement, il existe deux attributs supportés par la plupart des navigateurs et en tout cas, par les différentes versions d’Internet Explorer. Il s’agit des attributs rowIndex et sectionRowIndex. Ici, c’est la propriété sectionRowIndex qui nous intéresse : elle donne l’index de la ligne au sein de la section courante, ici un TBODY.

On peut donc implanter une méthode utilisant cet index pour ne recalculer que le « pyjama » des lignes suivantes. Mais attention, il faut tenir compte du fait qu’une table peut avoir plusieurs balises TBODY. La parité des lignes doit donc être transmise d’un TBODY à l’autre.

Au chargement, les lignes ont la couleur définie par la CSS s’appliquant par défaut sur les TR :

TR { color: black ; background-color: #9CF }

Il y a aussi la déclaration de la classe impair :

TR.impair { color: white ; background-color: #aaa }

C’est seulement via javascript que l’attribut class sera positionné à impair. Cela veut dire que si javascript n’est pas actif, les lignes restent de la couleur d’origine. Il vaut donc mieux que le style par défaut correspond à la couleur la plus claire. On garantit ainsi que la table sera plus facilement lisible même si javascript est désactivé.

Démonstration

Dans cette démonstration, un clic sur une cellule de la table supprime la ligne correspondante.

La table comporte un THEAD, un TFOOT et deux balises TBODY.

La démo comporte 2 boutons permettant de supprimer par programme la deuxième et la troisième ligne.

Publié dans CSS, Javascript | Marqué avec , | Commentaires fermés

Comment concevoir des composants déclenchant des missions d’expertise

En regardant le très bon reportage « Prêt à jeter » passé dans le Théma d’Arte mardi soir (le 15 février 2011), je me disais que la même démarche s’applique depuis bien longtemps  à l’informatique…

Pour ceux qui n’ont pas eu la chance de le voir, le reportage décrivait l’approche consistant à intégrer volontairement dans la conception des produits de grande consommation des éléments limitant leur durée de vie : c’est l’obsolescence programmée.
L’amoralité de ce type d’approche reste la même pour les prestations intellectuelles et le développement logiciel. Bien entendu, en informatique, le contexte est différent, et n’a pas de conséquence directe sur l’environnement. Par contre, la démarche reste la même : le fournisseur fait une action délibérée qui se traduit pour le client par le fait de payer plus cher.
Dans ce billet d’humeur, nous ne nous intéresserons pas à l’obsolescence programmée des logiciels (les principes étant connus et restant très proches que ceux utilisés pour les produits de consommation industriels) mais à d’autres possibilités annexes offertes aux fournisseurs de logiciels. Par la suite, nous utiliserons le terme « composant » mais « logiciel » ou « produit informatique » conviendrait tout aussi bien. Quant au terme « fournisseur », il désigne tout éditeur informatique qui réalise des prestations d’accompagnement technique (consulting, assistance et expertise technique).

Imaginons ce dialogue fictif entre Vipère Noire (consultant J2EE de la vieille école) et Priscilla (jeune développeuse J2EE encore naïve).

Priscilla : tu as vu le reportage d’hier « Prêt à jeter » sur Arte ? ça fait froid dans le dos

Vipère Noire : oui je l’ai vu, mais ça ne m’étonne pas, on fait pareil en informatique. Il y a des années, j’ai un de mes directeurs techniques qui disait « non, on ne va pas mettre des boucles d’attente exprès pour ralentir la v1 du soft, mais bon, il serait bien qu’il reste une marge d’optimisation pour qu’on nous achète la v2 ou qu’on nous passe un contrat de maintenance évolutive ».

Priscilla : oui, c’est un sujet à part entière qui est bien connu : il s’agit de vendre la version suivante des composants parce que les fonctionnalités (ou performances) ne sont volontairement pas complètement couvertes dans la version courante.

Vipère Noire : oui, mais je veux te parler d’un autre moyen exploité moins ouvertement par les fournisseurs de produits informatiques. Il s’agit – dans le cadre de la vente de composants – de déclencher des prestations d’expertises supplémentaires. En fait,  Il s’agit d’inclure des effets induits (non désirables) dans la conception des composants vendus.

Priscilla : ça me parait un peu dangereux pour le fournisseur. Il doit constamment naviguer entre « amener des revenus récurrents supplémentaires (expertise technique) autour du produit » et le point de non retour où le client abandonne le produit ou composant informatique. En plus, le client peut légitiment considérer qu’il s’agit de bogues du composant.

Vipère Noire : non, parce qu’en fait je ne parle pas d’un composant qui a des bogues flagrants ou dont les effets induits se manifestent systématiquement dès qu’on l’utilise. Je te parle d’un composant qui est conçu pour fonctionner correctement dans son cadre d’utilisation normal et qui ne pose problème que si on sort de ces conditions.

Priscilla : ok, et comme ça, la prestation déclenchée est complémentaire, elle ne rentre pas dans le cadre d’un éventuel contrat de maintenance existant.

Vipère Noire : c’est tout à fait ça. Et pour te le prouver, il y a quelques années, mon directeur technique m’a passé ce document que je vais te montrer :

Règles de conception permettant de déclencher des prestations de services récurrentes

Il s’agit de règles utiles pour amener le client à déclencher des prestations complémentaires tout en garantissant une zone de sécurité au fournisseur. Le principe est de déclencher un effet induit qui ne peut être directement qualifié de bogue. Il s’agit par exemple de rétention mémoire, de mauvaise performance, de non résistance à la montée en charge… En plus, cet effet induit doit apparaître comme étant du à une mauvaise utilisation du composant (la responsabilité du client doit être engagée). Il s’agit donc en fait de « Règles de conception de composants dont l’utilisation sans précautions a des effets induits ».

Règle 1.

Pour éviter tout reproche de la part du client, il faut accompagner le composant d’une documentation technique complète indiquant les précautions d’utilisation (s’il y a un problème, c’est que le client n’a pas lu la documentation).

  • Pour bien montrer qu’il ne cache rien, le fournisseur peut aussi fournir le code source du composant. Les probabilités que le client final puisse en tirer des conclusions sont très faibles. En plus, le client n’est sensé y trouver que des explications confirmant des règles d’utilisation qui sont déjà correctement documentées.

Règle 2.

Les précautions d’utilisation doivent paraître conformes à « l’état de l’art » (s’il y a un problème, c’est bien parce que les développeurs du client sont mauvais ou pas assez expérimentés).

Règle 3.

Ces précautions ne doivent pas avoir un caractère évident (elles ne doivent pas dépendre de façon trop évidente des fonctionnalités inhérentes au composant). Sinon, les développeurs pourraient immédiatement en déduire quels pièges ils doivent éviter.

Règle 4.

Les précautions d’utilisation ne doivent pas être « tout ou rien » : on doit pouvoir continuer à utiliser le composant (avec toutes ses fonctionnalités) ; les effets cachés ne doivent pas être visibles immédiatement. Il faut qu’il y ait déjà eu un maximum d’utilisations effectives du composant. C’est très important parce que plus le nombre d’utilisations du composant est important, plus il est difficile pour le client d’envisager de s’en séparer. En plus, ça augmente les opportunités d’expertise technique (pour couvrir toutes les applications où le composant a été utilisé)

Règle 5.

L’effet induit ne doit pas être systématique pour éviter une levée de bouclier du client, il vaut mieux que l’effet induit n’ait lieu que dans certains cas d’utilisation.

Règle 6.

Les effets induits d’une mauvaise utilisation ne doivent pas facilement être détectables automatiquement. Par exemple, ils doivent difficilement être vérifiables dans l’environnement de développement ou de test standard. Au contraire, ils doivent nécessiter un outillage important (ex : effet détectable seulement lors d’un test de montée en charge), un temps de test important (ex : effet caché qui ne se déclenche que lors d’une utilisation récurrente, c’est par exemple le cas pour une rétention mémoire) ou des interventions manuelles (mesures non standards).

  • Les meilleurs effets induits dus à une mauvaise utilisation sont donc souvent : mauvaises performances, rétention mémoire accrue, utilisation CPU trop importante…

Règle 7.

Il n’y a pas de niveau de performance qui soit universellement reconnu (par le marché), encore moins garanti par le fournisseur : il faut que ce soit le client qui les impose et de préférence a posteriori. Si le client reproche au fournisseur que son composant TABLE met plus de 10 secondes sous IE6 pour afficher 40 colonnes de 10.000 lignes et qu’il ne l’a pas précisé dans le cahier des charges, il n’a qu’à s’y prendre à lui-même. Dans le même ordre d’idées, il ne faut pas qu’on puisse facilement comparer les performances du composant du fournisseur avec celui d’un concurrent. Les métriques associées ne doivent donc pas être disponibles.

Priscilla : arrête, je n’imagine pas un instant qu’un manager prenne le risque de faire circuler ce genre de document. Le risque est bien trop grand d’avoir une fuite.

Vipère Noire : si tu as bien regardé le reportage d’Arte, tu as bien vu qu’il y avait des documents de ce type (pour interdire aux fabricants de vendre des ampoules qui durent plus de 1000 heures).

Priscilla : oui, mais ces documents étaient tout à fait confidentiels.

Vipère Noire : bon. Effectivement, je raconte des histoires. Ce document est bidonné. Par contre, le conseil de mon directeur technique était bien réel.

Priscilla : tant qu’à faire, tu pourrais aussi parler des trucs permettant de rendre plus difficile l’identification des problèmes dans les applications (par exemple en favorisant la non répétabilité de ces problèmes) et donc de limiter la capacité pour les équipes du client d’identifier par eux-mêmes le problème, c’est là aussi un sujet à part entière.

Vipère Noire : ça n’est pas le sujet, là, je te parle uniquement de la façon dont un fournisseur peut jouer sur les conditions d’utilisation

Priscilla : ok

Vipère Noire : il n’empêche que j’ai rencontré des composants qui semblaient tout à fait obéir à ces règles. Par exemple, je peux te parler d’un composant destiné à faciliter la mise à jour dynamique (via AJAX) d’une portion d’une page jsp. Pour le développeur de page JSP, l’implantation est très simple : il faut juste insérer une balise JSP pour indiquer le début du fragment d’HTML à mettre à jour, et une autre balise pour en indiquer la fin. Au runtime, ces balises JSP génèrent simplement des balises de marquage dans la page HTML (par exemple des balises DIV avec des identifiants spécifiques). Ces balises sont ensuite utilisées lors de la mise à jour dynamique de la zone.

Au runtime, on peut déclencher via javascript une mise à jour du fragment HTML limité par les balises de marquage. Cette mise à jour consiste à effectuer un appel AJAX sur le serveur, appel qui réévalue totalement la page JSP sur le serveur. Sur le serveur, on ne conserve du code HTML résultant de cette évaluation que la partie située entre les balises de marque générées ultérieurement. Ce fragment HTML situé entre les balises de marquage est alors renvoyé en réponse de l’appel AJAX . Dans le navigateur, javascript remplace ensuite le DIV de marquage, par le nouveau fragment HTML contenu dans le retour AJAX.

Priscilla : Un composant de ce type est super, il est facile à utiliser et pratique pour des développeurs qui ne veulent surtout pas avoir à gérer directement du javascript.

Vipère Noire : Néanmoins, si tu étudies les choix d’implémentation. La page JSP est réévaluée totalement lorsqu’on veut mettre à jour dynamiquement une partie de la page dans le navigateur. Le seul intérêt de cette réévaluation totale, c’est que si la page comporte des scriptlets ou des balises JSP d’affectation de beans en dehors du fragment qu’on souhaite réévaluer, leurs effets de bords pourront être utilisés par la partie mise à jour dynamiquement. Si un développeur a dans sa page un scriptlet qui fait des appels en base de données, l’appel aura lieu à chaque fois que l’on veut remettre à jour la zone que cela soit souhaité ou pas.

Priscilla : La documentation du composant précise que la totalité de la page est réévaluée à chaque fois ?

Vipère Noire : oui, et donc la règle 1 est respectée.

Priscilla : En plus, le fournisseur peut se réfugier derrière l’état de l’art : « il est clair qu’il est déconseillé d’utiliser des scriplets ou des balises d’affectation dans les pages JSP, tout le monde sait çà depuis le MVC 2 de Sun » (règle 2).

Vipère Noire : Oui. Mais comme, la tendance générale des acheteurs des grands comptes est de baisser le tarif journalier des développeurs, les clients se retrouvent avec des développeurs juniors qui n’ont pas forcément le recul et les connaissances nécessaires.

Priscilla : oui, et comme la fonctionnalité du composant est « une mise à jour dynamique d’une portion de JSP » ; tu vas me dire que le développeur ne peut pas en conclure que sur le serveur, toute la page JSP est réévaluée ce qui est conforme avec ta règle 3.

Vipère Noire : en effet. En plus, le composant continue à fonctionner, même s’il y a des scriptlets dans la page. La règle 4 est donc aussi respectée.

Priscilla : Oui parce que c’est seulement si ces scriptlets font des traitements importants sur le serveur (appels en base de données par exemple) que l’effet se fait sentir en termes de performance. L’effet caché n’est donc pas systématique, et il dépend de l’utilisateur, donc la règle 5 est couverte.

Vipère Noire : quant à la règle 6, elle nécessite que les effets induits d’une mauvaise utilisation ne doivent pas facilement être détectables automatiquement. C’est en effet le cas ici, parce que les mauvaises performances dépendent du traitement fait par les scriptlets qui n’a pas du tout les mêmes performances en test et en production.

En ce qui concerne la règle 7, le composant en question n’est pas standard, il n’y a donc pas de niveau de performance qui soit universellement reconnu (par le marché).

Priscilla : tout ça n’est pas une preuve. En plus, là, le rasoir d’Ockham s’applique totalement : l’explication la plus simple reste une mauvaise conception initiale du composant, pas une volonté malfaisante de la part du fournisseur.

Vipère Noire : arrête ! je suis sûr que tu as appris ce qu’est le rasoir d’Ockham dans Big Bang Theory !

Priscilla : c’est ça, et toi tu développais en Occam sur transputer, peut être ?

Vipère Noire : tu as raison, ce composant a sans doute été juste mal conçu. Peut-être mais en tout cas, il fait carton plein ! Il vérifie la plupart des règles précédentes et effectivement, il a l’avantage pour le fournisseur d’entraîner des missions d’expertises assez récurrentes. Par exemple, mon équipe a du intervenir sur une page développée par le client qui utilisait ce composant et comportant 600 lignes de scriplet en haut de la page JSP (lignes comportant des appels en base de données).

Priscilla : et vous avez corrigé le problème ?

Vipère Noire : oui, par une refactoring complet de la page qui a d’ailleurs amené à se passer de ce composant. Le client avait cependant une excuse : il n’avait même pas utilisé ce composant explicitement, il était inclus d’office dans un autre composant du même fournisseur .

Priscilla : et comment a réagi le client ?

Vipère Noire : Ce qui est intéressant, c’est que le client lui-même, en tout cas ses équipes techniques, a pris conscience du problème. Un responsable technique du client en question m’expliquait que « le niveau des développeurs ayant tellement baissé (à cause des baisses des grilles de tarifs journaliers) » que l’utilisation d’un tel composant devrait maintenant être interdite.

Priscilla : C’est marrant, parce qu’en faisant ce constat, le client ne remettait en cause ni les choix d’implantation du composant ni le fournisseur, mais bien le comportement de sa direction générale vis-à-vis des prestations informatiques.

La morale de l’histoire

Bien entendu, si beaucoup d’éléments de ce dialogue comportent des éléments existants (entre autres choses, l’exemple est réel), il n’en n’est pas de même en ce qui concerne la suspicion d’une mauvaise foi du fournisseur.

En fait, l’amoralité potentielle de l’utilisation de telles règles n’a aucune importance pour le client. Le client ne voit que les problèmes réels auxquels il est confronté, que ceux-ci soient issus d’une volonté délibérée ou simplement d’une conception maladroite des composants que ses équipes utilisent.

Les aspects positifs

Il y a quand même un aspect positif. En effet, pour arriver à déclencher des missions d’expertise (et pas un rejet pur et simple du composant par le client), le fournisseur doit quand même assurer un certain niveau de qualité à son composant. Nous avons en effet exclu de cette discussion le cas des bogues explicites mais aussi les composants qui ont des effets cachés, même dans le cas d’une utilisation nominale. Cela implique déjà un certain niveau d’exigence :

  • Un fonctionnement conforme dans le cas de l’utilisation préconisée
  • L’existence d’une documentation technique de bon niveau (règle 1),
  • Une utilisation « conforme à l’état de l’art » (règle 2),
  • Une bonne résistance (fonctionnement en mode dégradé) (règle 4)

Comment le client peut contrer ce problème

Par ailleurs, la règle 6 permet au client de savoir quels aspects il doit regarder en priorité :

  • Les performances,
  • La rétention mémoire,
  • L’utilisation CPU,
  • La résistance à la montée en charge

Néanmoins, il n’y a pas que des bonnes nouvelles : le test de ces aspects peut être difficile, voire même peu compatible avec l’utilisation d’une plateforme d’intégration continue (la durée des tests peut en effet être très importante). La problématique de ces tests est générale, et n’est pas liée à la bonne ou mauvaise utilisation, encore moins au respect des bonnes pratiques. Et le coût de la mise en place de ces tests est important, surtout si on souhaite couvrir tout le périmètre du composant :

  • Axe ressources : temps, occupation mémoire (instantanée et en rétention), charge CPU, taille (en transport sur le réseau), nombre de transactions par seconde
  • Axe fonctionnalités (par exemple, pour un composant graphique TABLE, l’affichage initial, la pagination, le tri sur une colonne…),

Plutôt que de faire ces tests exhaustivement, le client peut être tenté de recourir à une couverture contractuelle en demandant au fournisseur de garantir le niveau de performance. Par exemple, au niveau des temps, le client pourrait demander au fournisseur une garantie, pour chaque fonctionnalité :

  • De mesure absolue (ex : le temps maximal pour une taille de données précisée)
  • De complexité des algorithmes utilisés par rapport aux données (ex : pour un composant graphique de type TABLE, donner la complexité de l’algorithme en fonction du nombre de lignes et du nombre de colonnes).

C’est assez intéressant pour le client parce que du coup, il peut se contenter de faire des tests ponctuels (juste pour vérifier que les engagements du fournisseur sont suivis et demander des corrections le cas échéant). Cependant, on imagine mal un fournisseur prendre ce genre d’engagement, surtout que s’il veut garantir un prix faible des composants ; il ne peut répercuter le coût afférant à la garantie. Bien sûr, en ce qui concerne les engagements contractuels, le client peut éventuellement faire jouer la concurrence en incluant dans le cahier des charges de choix des composants cette dimension d’engagement.

Dans l’hypothèse prise ici où les règles d’utilisation recouvrent en fait les bonnes pratiques, le client peut commencer par s’assurer que ces bonnes pratiques sont suivies par les équipes de développement. D’un point de vue RH, çà implique :

  • D’augmenter le niveau d’expertise des développeurs internes,
  • De conserver ces développeurs plus longtemps sur ces projets (pour qu’ils puissent acquérir les connaissances liées à l’utilisation des composants en question)

Ces deux options ont un coût qui ne semble pas très compatible avec la tendance actuelle. La baisse des tarifs journaliers implique de ne pas avoir ce niveau d’expertise, en plus, la simple augmentation automatique des salaires des juniors implique un renouvellement fréquent des développeurs sur ces projets (les contraintes financières imposant de conserver constamment des débutants).

Si le client ne se donne pas les moyens de mettre en place les tests nécessaires, ne réussit pas à obtenir des engagements contractuels du fournisseur et ne peut pas conserver des équipes techniques avec le niveau d’expertise suffisant, lui reste t’il des solutions ?

Bien entendu, le client peut demander au fournisseur de concevoir des composants pouvant être utilisés dans n’importe quel contexte et par n’importe quel niveau de compétences. Néanmoins, des composants de ce type sont beaucoup plus complexes à concevoir même en limitant drastiquement leur fonctionnalité (et donc leur intérêt même). Et même si le fournisseur prend des précautions légitimes telle que « ne pas préjuger du niveau technique des utilisateurs », jusqu’à quel niveau de non technicité doit il les appliquer ?

Pour une solution répondant à cette problématique

L’idéal serait une solution ne touchant ni au périmètre du composant, ni à sa capacité d’être utilisé par des experts aussi bien que par débutants, et qui permettrait au client de :

  • s’assurer que ses équipes utilisent le composant dans son cadre d’utilisation nominal,
  • avoir une garantie générale (qui soit technique et pas contractuelle) du comportement du composant dans le cas d’une utilisation nominale

Pour des détails sur cette solution « miracle » et bien…. il faudra attendre un nouveau billet…

Publié dans Billet d'humeur, framework | Commentaires fermés

Interagir avec le DOM au cours du chargement

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 n’utilise pas la méthode du partage de code cf. dernier article), 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é.

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.

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.

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.

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).

La solution la plus simple si on veut charger la librairie javascript dynamiquement est :

  • Chargement de la page HTML (les value sont vides)
  • Chargement asynchrone de la librairie (déclenchée en bas de la page ou dans le onload)
  • Interprétation de la librairie (1 seconde)
  • Modification des value des composants (uniquement lorsque la librairie est chargée)

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.

Introduction

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.

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.

Comment un nœud DOM prévient quand il est prêt

On souhaite donc pouvoir déclencher un traitement javascript sur un nœud DOM au plus tôt, c’est-à-dire dès que le nœud existe.

On sait que :

  • les balises SCRIPT inline sont lues séquentiellement dans l’ordre du code source HTML.
  • un script javascript a immédiatement accès au DOM du document, même si celui-ci est encore en court de chargement.

Par ailleurs, on souhaite être conforme aux préconisations de l’unobtrusive javascript : 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.

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.

Pour une balise auto-fermante comme INPUT, par exemple :

<INPUT value="Smith" />

çà donne:

<INPUT value="Smith" />
<SCRIPT>…</SCRIPT>

On est certain que

Pour une balise classique, telle que :

<DIV>
<balise>…</balise>
<balise>…</balise>
</DIV>

on peut insèrer la balise SCRIPT immédiatement à l’intérieur de la balise HTML :

<DIV>
<SCRIPT>…</SCRIPT>
<balise>…</balise>
<balise>…</balise>
</DIV>

Dans ce cas, le script a accès à la balise englobante (d’identifiant c1) mais pas aux autres balises.

On peut aussi insérer la balise SCRIPT à la fin de la balise HTML :

<DIV>
<balise>…</balise>
<balise>…</balise>
<SCRIPT>…</SCRIPT>
</DIV>

Dans ce cas, le script a accès à la totalité des balises filles de la balise englobante.

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’est-à-dire insérer le script après :

<DIV>
<balise>…</balise>
<balise>…</balise>
</DIV>
<SCRIPT>…</SCRIPT>

Mise en œuvre

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.

Ainsi, la forme suivante est parfaitement fonctionnelle :

<DIV>
<SCRIPT>
// Récupération du noeud DOM
var domEl=this.document.getElementById("c1");
</SCRIPT>
<DIV>…</DIV>
<DIV>…</DIV>
</DIV>

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.

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

<skynet:input id="c1" name="${path}">

qui générerait le fragment complet suivant :

<INPUT id="c1" name="p1.prenom" />
<SCRIPT>
// Récupération du noeud DOM
var domEl=this.document.getElementById("c1");
… // Actions à faire sur le DOM
</SCRIPT>

Exemple

Cet exemple est disponible en ligne ici.

Ajout d’un handler d’événement

On veut séparer le code de la balise HTML et la déclaration de son handler d’événement javascript.

Il s’agit ici d’un bouton de type submit :

<INPUT value="Valider">

Si javascript n’est pas activé, le bouton a le comportement normal d’un bouton submit (envoi du formulaire sur le serveur).

Si javascript est activé, on associe au bouton un handler d’événement ouvrant un dialogue demandant si on veut vraiment soumettre le formulaire.

Le code résultant est le suivant :

<INPUT value="Test">
<SCRIPT>
// 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);
}
}
</SCRIPT>

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 « Valider » sans passer par la vérification javascript, même si la librairie n’a pas encore été chargée.

Ajouter un fragment HTML au DOM si javascript est activé

On veut générer un fragment HTML à attacher au nœud DOM existant.

On applique la première solution à la génération d’un icône de calendrier permettant de lancer une pop-up de saisie des dates.

Si javascript est désactivé, seul le champ de saisie textuel est présent.

<INPUT type="text" value="">
<SCRIPT>
var domEl=this.document.getElementById("calendar1");
domEl.insertAdjacentHTML("afterEnd",
"<IMG src='./Agenda-Icon.gif' onclick='launchCalendar()' />"); </SCRIPT>

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.

Cela implique qu’il faudra identifier et différencier :

  • 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),
  • les traitements standards (qui nécessitent le chargement de la librairie complète)

Autres utilisations

On peut multiplier à loisir les utilisations de cette approche.

Dans le cadre de l’unobtrusive javascript, on a vu l’ajout des events listeners 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.

Cette approche permet aussi de profiler les temps d’interprétation du DOM : il suffit que chaque balise SCRIPT insérée fasse des mesures du temps.

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.

Constat

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.

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 –).

Les composants semblent être correctement initialisés.

Publié dans framework, Javascript, optimisation | Marqué avec , , , , | Commentaires fermés

Partage de code javascript inter-fenêtres : les pop-up

Cet article étudie, dans le cas des fenêtres pop-up, comment on peut éviter le rechargement de code javascript en le partageant d’une fenêtre à l’autre. Cet article fait partie d’une série de plusieurs articles.

Plan de l’article

Chargement du frameworkpour chaque page

Étape 1 : chargement synchrone du framework

Mise en œuvre

Dans cette étape, la première page de l’application, page1.html charge explicitement le framework. Le chargement du fichier skynetlib.js est fait inline via une balise SCRIPT :

<SCRIPT src="skynetlib.js"></SCRIPT>

La balise SCRIPT respecte l’ordonnancement, donc il suffit de lancer la méthode init après le chargement du fichier skynetlib.js :

<SCRIPT src="skynetlib.js"></SCRIPT>
<SCRIPT>
function init(){
// Création d’une instance de Probe et affectation à une variable
var a=new Skynet.tools.Probe(1337) ;
} ;
init() ;
</SCRIPT>

Mesures

Nos mesures sont faites en javascript. Les chiffres annoncés sont des moyennes.

début de l’interprétation du fichier skynetlib.js 162ms
début de l’évaluation de la méthode init 1221ms
Lancement de l’événement onload 1280ms

Noter que l’interprétation du fichier skynetlib.js s’arrête à 1168ms, cette interprétation dure bien de l’ordre de la seconde.

Constat

La méthode init est lancée après chargement de skynetlib.js et ce chargement prend une seconde. La page ne peut donc utiliser le framework qu’après ce temps de chargement.

On voit que le onload de la page est aussi en attente du chargement du fichier javascript. C’est visible sur cette copie d’écran de firebug :

Chargement synchrone du framework

Étape 2 : chargement asynchrone du framework

Mise en œuvre

On sait (cf. par exemple les transparents « Even Faster Web Sites » de Steve Souders) qu’il vaut mieux charger les fichiers javascript en asynchrone qu’en inline.

Dans cette étape, on veut que la première page, page1.html charge dynamiquement le fichier javascript. C’est assez facile à faire, par exemple via le DOM par création d’une balise SCRIPT dans le HEAD :

function loadScript(filename){
var targetTag=document.getElementsByTagName("head")[0];
var nodeS=document.createElement("SCRIPT");
nodeS.src=filename;
targetTag.appendChild(nodeS);
}

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 init() ; on ne peut pas garantir que le code du framework aura été interprété avant cet appel.

La méthode init() échoue donc puisqu’au moment où elle est appelée, elle ne peut pas accéder au code du framework (la variable Skynet).

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 « Even faster Web Sites ».

Après l’initialisation de cette librairie (150 lignes en mode non minifié), il suffit de faire

EFWS.Script.loadScripts(["skynetlib.js"], init);

pour que le fichier skynetlib.js soit chargé dynamiquement puis que la méthode init soit appelée.

Attention, ce chargement dynamique garantit que la méthode init est appelée après que le chargement du fichier skynetlib.js,. Il n’y a par contre aucune garantie rien vis-à-vis de l’exécution du onload de la page (qui peut avoir lieu avant ou après).

Mesures

début de l’interprétation du fichier skynetlib.js 209ms
début de l’évaluation de la méthode init 1266ms
Lancement de l’événement onload 107ms

Le chargement du fichier skynetlib commence à 209ms (donc un peu plus tard que le chargement par balise <SCRIPT> de l’étape précédente) et s’arrête à 1221ms.

Constat

L’utilisation d’un chargement dynamique se traduit dans notre exemple par un léger surcoût (l’évaluation de la fonction init commence à 1266ms> à la place de 1221ms). 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 onload lors d’un chargement asynchrone :

Chargement asynchrone du fichier

est lancé plus d’une seconde (1280107) avant celui de la solution synchrone (étape 1).

Partage du framework avec une page pop-up


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.

Rappels

Affichage d’une page en HTML

En html, il suffit d’utiliser un hyperlien pour afficher une page.

Si on ne précise pas l’attribut target, la page courante est remplacée par la page cible :

<A href="Page1.html">Page1.html</A>

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.

<A href="Page1.html" target="_blank">Page1.html</A>

Affichage d’une page en javascript

Il existe plusieurs méthodes en javascript pour afficher une page dans fenêtre.

La première méthode est d’utiliser document.location= »pageCible.html » qui remplace la page courante par pageCible.html.

La seconde méthode utilise window.open. 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).

La méthode window.open a la syntaxe suivante :

window.open(url, windowName, windowFeatures)

  • url est l’url de la page à ouvrir,
  • windowName est le nom de la fenêtre (ce nom peut être utilisé pour rouvrir une nouvelle page dans la même fenêtre)
  • windowFeatures, une chaîne avec différentes propriétés que l’on peut donner à la nouvelle fenêtre.

Cela donne par exemple :

window.open("eventEditionPage.html","Event editor",

"location=1,status=0,scrollbars=1, width=100,height=100")

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 windowFeatures, sinon, la fenêtre s’ouvre dans un onglet du navigateur.

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.

Cas particulier : le chargement dans la même fenêtre

Il est possible de réutiliser la même fenêtre pour plusieurs fenêtres. soit par balise A avec pour target _self, soit par affectation de document.location, soit par l’utilisation de window.open en précisant pour windowName le même nom de fenêtre que pour la première page.

Même dans ce cas où l’on réutilise la même fenêtre, par exemple si la page page1.html charge la page un page2.html, la page page2.html possède toujours un window.opener. Par contre, on a window.opener==window, et page1.html est inaccessible. Il est donc impossible de récupérer les variables initialisées dans page1.html. Dans ce cas, il est obligatoire de charger skynetlib.js dans page2.html. La seule présence d’un window.opener ne suffit donc pas à garantir que le framework est présent dans ce window.opener.

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é.

Mise en oeuvre

Dans la suite de l’exemple, nous introduisons une page master1.html. Cette page charge elle-même le fichier skynetlib.js (en synchrone via une balise SCRIPT).

C’est master1.html qui ouvre la fenêtre pop-up par un appel javascript tel que :

window.open("mapage.html")

Étape 3 : la fenêtre pop-up charge le framework

Mise en œuvre

La page master1.html lance la page page1-frm.html en tant que fenêtre pop-up. La page1-frm fait elle-même un chargement synchrone du fichier skynetlib.js.

Mesures

Au niveau de page1-frm.html (les temps sont mesurés à partir du chargement de cette page) :

début de l’interprétation du fichier skynetlib.js 175ms
début de l’évaluation de la méthode init 1146ms
Lancement de l’événement onload 1176ms

Noter que comme on peut s’y attendre l’utilisation de la méthode de chargement dynamique du framework (avec la page1-dyn.html) ne change que la date de d’événement onload qui se déclenche vers 42ms (comme dans l’étape 2).

Constat

Avec cette méthode, on a chargé deux fois le fichier skynetlib.js : une fois dans la page master1.html, une autre fois dans la page page1-frm.html.

Autrement dit, à chaque fois qu’on ouvre une fenêtre pop-up, on est obligé de recharger le framework dans cette fenêtre pop-up.

Ainsi, si vous avez un composant comme un calendrier et que vous utilisez une page pop-up pour créer ou modifier un événement de calendrier, vous êtes obligés de recharger le framework dans cette fenêtre pop-up. Si vous avez une vue détail de cet page pop-up, par exemple pour pouvoir choisir le fuseau horaire, vous êtes là-aussi obligés de charger le framework pour la vue détail.

Étape 4 : window.open avec partage de framework


On va maintenant chercher à ne charger qu’une seule fois le fichier skynetlib.js. la page master1.html ouvre maintenant page1.html en tant que fenêtre pop-up.

Mise en œuvre

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 page1.html pointe sur la window de master1.html. Or master1.html a chargé le fichier skynetlib.js.

Il suffit donc de faire Skynet=window.opener.Skynet dans page1.html pour que le framework soit accessible directement, sans aucune modification du code utilisant le framework.

Noter que cela nécessite d’être sur un site web, pour des questions de sécurité (liées au cross scripting), il n’est pas possible d’utiliser le protocole file://.

Ainsi, après cette affectation, dans page2.html, le code de la fonction init :

var a=new Skynet.tools.Probe(1337) ;

fonctionne parfaitement sans chargement du fichier skynetlib.js.

Remarque

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 Skynet alors même que la page qui a chargé ce fichier a disparu.

Mesures

Les temps sont inchangés au niveau de master1.html.

Au niveau de page1.html (les temps sont mesurés à partir du chargement de cette page) :

début de l’évaluation de la méthode init 8ms
Lancement de l’événement onload 44ms

Constat

Autrement dit, les gains sont très importants au niveau de la fenêtre pop-up qui n’a plus à charger le fichier skynetlib.js ce qu’on peut vérifier dans la vue réseau de firebug :

Partage de framework dans le cas d'une pop-up

Dans l’étape précédente, la méthode init était appelée au bout de 1100ms, en partageant le framework, elle est appelée au bout de 8ms.

La méthode init de la fenêtre pop-up peut être exécutée très rapidement : la fenêtre pop-up peut utiliser pratiquement immédiatement le code du framework chargé dans la fenêtre parente master1.html.

Algorithme final

Principe

On peut maintenant récupérer le framework sans le charger de nouveau dans page1.html. Cependant, pour le moment, la page1.html 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 framework. Il faut corriger ce défaut.

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 window.opener et si window.opener != window (cf. le paragraphe sur le chargement dans la même fenêtre)

L’idée est donc de :

1)      Détecter si la fenêtre a été ouverte directement

2)      Si la fenêtre a été ouverte directement, ou a remplacé la page qui l’a ouverte, déclencher un chargement dynamique du framework

Mise en oeuvre

L’algorithme final reste assez simple :

  • 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
  • soit on charge dynamiquement le fichier skynetlib.js qui initialise (localement) la variable Skynet.

Les exemples en ligne sont disponibles ici

Le fichier compressé comprenant toutes les pages de l’exemple sont ici

Conclusion

Début de init Début de onload
Chargement synchrone par la page 1221ms 1280ms
Chargement asynchrone par la page 1266ms 107ms
Partage du framework 8ms 44ms

Nos mesures montrent que lorsqu’on veut utiliser une fenêtre pop-up, il est très intéressant de partager le code du framework.

C’est d’autant plus important que généralement les fenêtres pop-up 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 pop-up qui permet d’éditer ces événements. Il est clair que le chargement de la page pop-up doit être extrêmement rapide. Dans notre exemple, nous avons vu qu’en partageant le framework, on peut utiliser son code au bout de quelques millisecondes (au lieu d’attendre que son code soit chargé puis évalué).

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 framework est donc encore supérieur. Sans ce partage, l’utilisateur est donc confronté à un délai d’attente plus important.

Publié dans framework, Javascript, optimisation | Commentaires fermés

Partage de code javascript inter-fenêtres : introduction

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 un framework javascript qui est utilisé dans chacune des pages de l’application web.

Les contraintes sont les suivantes :

  • 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).
  • L’application peut ouvrir ses pages html dans d’autres fenêtres via des appels javascript ou intégrer ces pages dans des balises IFRAME
  • Dans chacun de ces cas, le code qui dans chaque page utilise le framework 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 DIV ou sous forme d’un IFRAME.

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.

Plan de la série d’articles

Un premier article étudiera le cas de l’ouverture de fenêtres pop-up et comment il est possible de partager le code chargé par la fenêtre qui les a ouvertes.

Un second article étudiera le cas des fenêtres incluses dans un IFRAME et comment partager le code chargé par la fenêtre parente.

Un troisième article étudiera comment optimiser cette utilisation des IFRAME pour partager le code

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é (cross-scripting), d’ergonomie, de robustesse.

L’exemple

D’un point de vue pratique, nous allons simuler l’utilisation d’un framework en utilisant un unique fichier javascript intitulé skynetlib.js. Ce fichier contient une boucle d’attente d’1 seconde pour simuler le temps d’interprétation d’un « gros » framework.

Ce temps d’1 seconde n’est absolument pas délirant. Il est courant d’avoir des frameworks 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 onload 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 onload (trait rouge) est très tardif.

Temps de charment d'un gros framework

Chronologie du chargement d'un gros framework

Le framework de l’exemple est sensé être conforme au design pattern « module » 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 framework d’exemple comporte un constructeur appelé Skynet.tools.Probe.

Une fois le code du framework chargé, chaque page de l’application lance une méthode d’initialisation qui lui est propre (dans notre exemple, elle s’appelera init). Voilà par exemple une fonction init qui réalise l’instanciation du constructeur Probe définit par le framework :

function init(){
// Création d’une instance de Probe et affectation à une variable
var a=new Skynet.tools.Probe(1337) ;
}
Publié dans framework, Javascript, optimisation | Marqué avec , , | Commentaires fermés

Mettre son site wordpress à la racine de son site OVH

M’étant enfin décidé à ne plus avoir le répertoire WordPress pour accéder à mon blog (l’url border-labs.fr/WordPress dans mon cas), j’ai décidé de donner accès à mon blog directement à la racine de mon site, c’est à dire à border-labs.fr.

La mauvaise solution

Dans la page « réglages » de l’administration de wordpress, il y a deux url différentes. La première s’appelle Adresse web de WordPress. Et bien, il ne faut surtout pas la changer !
C’est la seconde qu’il faut changer : Adresse web du site (URL)

Si vous changez par erreur la première, ce qui fut mon cas, WordPress ne retrouve pas son propre répertoire et il est alors impossible de réaccéder à la console d’administration. De plus, cette information est stockée en base de données, donc ne comptez pas pouvoir vous en sortir en la trouvant dans un fichier .php. Il faut accéder à la base de données de WordPress.

N’ayant pas la possibilité d’accéder à cette base, j’ai utilisé une méthode super violente : copier l’intégralité du répertoire WordPress à la racine du site (le répertoire www).
En faisant çà, on met le repertoire de WordPress en conformité avec l’Adresse web de WordPress, on a alors de nouveau accès à la console d’administration.
Mais là surprise, le password doit dépendre de l’emplacement physique de wordpress, puisque je n’arrive plus à m’authentifier…
Heureusement, wordpress donne la possibilité d’envoyer un nouveau mot de passe. Ce que je fais.
Je peux alors accéder à la console d’admin, remettre correctement la propriété Adresse web de WordPress. WordPress me redemande alors de m’authentifier et bien… il faut de nouveau lui demander d’envoyer un nouveau mot de passe.
çà y est çà marche.

La bonne solution

La bonne solution est beaucoup plus rapide… :-)

1) modifier la propriété Adresse web du site (URL) en la mettant à la valeur de la racine de son site (border-labs.fr dans mon cas)
2) cliquer sur l’url qui donne les explications supplémentaires : il faut copier le fichier index.php situé dans WordPress et le coller dans la racine du site (www sur OVH). Il faut ensuite l’éditer pour remplacer
require('./wp-blog-header.php');
par
require('./WordPress/wp-blog-header.php');
3) ne pas oublier de renommer ou supprimer la page d’accueil html mise en place par OVH (index.html)

çà marche, on peut maintenant accéder à votre blog à la racine de vote site (à border-labs.fr dans mon cas)

Publié dans Installations | Marqué avec | Commentaires fermés

Mettre à jour WordPress sur OVH

Je l’avoue, je n’avais pas l’intention d’installer directement et manuellement WordPress sur mon nouvel hébergement chez OVH.

J’ai donc installé le module standard OVH en me connectant via le manager OVH puis via Mutualisé/Hébergement/Gestion des modules.

Au bout de 5 mn, je reçois donc un courriel d’OVH m’indiquant que le module WordPress est désormais disponible sur mon site.

Après quelques vérifications d’usage, j’ai donc décidé de mettre à jour WordPress pour une version plus récente (OVH installe une 2.9.1), je voulais une 3.0.4 francisée.

Et bien, pour une raison qui m’échappe, la mise à jour automatique de WordPress ne semble  pas fonctionner (même si les répertoires et les fichiers associés, les .maintenance par exemple sont effectivement créés, ce que j’ai pu vérifier via FTP).

Bref, le seul moyen que j’ai trouvé, c’est de :

  • sauvegarder le wp-config.php d’origine (qui se trouve dans le répertoire WordPress),
  • télécharger sur mon PC le zip correspondant à la version 3.0.4, (disponible sur http://fr.wordpress.org/
  • décompresser ce zip,
  • supprimer tous les fichiers du répertoire WordPress sur mon hébergement OVH,
  • y copier ceux téléchargés sur mon PC,
  • télécharger le wp-config.php à la racine du site.

Et là, tout à marché…

Publié dans Installations | Marqué avec , , | Commentaires fermés

JSON-P

JSON-P est une utilisation particulière de JSON.

Lorsqu’on fait un appel ajax via une XHR (XMLHttpRequest), on est soumis à la règle de sécurité dite du « same origin policy » qui précise qu’une page dans le navigateur n’accepte des requètes XHR que si elles interrogent des serveurs situés dans ce domaine.

Ceci commence à changer sur les navigateurs récents avec le CORS(cross-origin resource sharing, norme W3C dont le support ne s’est pas encore généralisé).

Cette restriction du « same origin policy » ne s’appliquent pas sur la baliseSCRIPT. Une page HTML peut en effet comporter des balises SCRIPTtéléchargeant des fichiers sources provenant de n’importe quel domaine.

L’idée est donc d’utiliser des balises SCRIPT pour télécharger du JSON de façon à s’affranchir de la contrainte du « same origin policy ». Néanmoins, charger du JSON de cette façon ne fonctionne pas directement. En effet, un fragment tel que

<SCRIPT SRC="www.mondomaine.com/getJSON"></SCRIPT>

téléchargera effectivement le flux JSON rendu par le serveur,  mais n’en fera rien ; il ne sera ni mémorisé dans une variable javascript, ni utilisable par une fonction javascript.

C’est ainsi qu’est né, en 2005, l’idée du JSON-P ou JSON Padding, il s’agit de demander au serveur d’encapsuler sa réponse JSON dans un appel de fonction, fonction qui est précisée dans l’url d’appel (cette fonction joue le rôle de callback), . Cela pourra donner par exemple (si le serveur utilise l’attribut jsonp_callback pour préciser le callback) :
<SCRIPT SRC="www.mondomaine.com/getJSONP?jsonp_callback=maFonction"></SCRIPT>
Lors de l’appel de l’url précédente, le serveur générera alors le code javascript suivant :

maFonction({ .. le code JSON proprement dit })

qui sera appelé lorsque la balise aura terminé le téléchargement de ce code. La fonction maFonction aura un unique argument qui est le code JSON ramené

function maFonction(jsonData){
...
}

Avantages

Un avantage de cette approche c’est que chaque client du serveur peut préciser sa propre fonction et donc utiliser le code du serveur de la façon qu’il souhaite : le serveur ne choisit pas comment il encapsule le code JSON, c’est bien la page dans le navigateur qui précise la fonction qu’il faudra appeler.

Un second avantage est qu’il est possible d’utiliser une politique de cache sur l’url en question (le fragment JSONP pouvant être conservé dans le cache du navigateur).
Un autre avantage est lié au nombre de connexions simultanées qu’autorise le serveur. Les navigateurs actuels limitent le nombre de connexions simultanées sur le même domaine. Cette contrainte s’applique donc sur les appels XHR qui sont bloqués tant qu’ils restent d’autres requêtes sur le même domaine.

Cette contrainte ne s’applique pas sur la balise SCRIPT : les appels JSONP peuvent donc avoir lieu simultanément à partir du moment où ils appellent des hosts différents.

Inconvénients

Problèmes liés à la sécurité

JSON-P pose de sérieux problèmes de sécurité liées au attaques du type cross-site request forgery. Par exemple, une page malveillante peut récupérer des données JSON appartenant à un autre domaine, ces données (par exemple des données privées de l’utilisateur logué sur l’autre domaine) sont alors évaluées dans le contexte de la page malveillante.

Pour éviter ce genre de problème, il faut que le serveur ne fasse pas de réponse quand il détecte que la requête d’appel n’est pas valide, par exemple en effectuant une vérification du referrer de la request ou en effectuant une vérification syntaxique du callback utilisé.

Un autre problème, lié à l’injection javascript est qu’un serveur malveillant introduise dans sa réponse du code javascript permettant de récupérer des informations personnelles de l’utilisateur du navigateur. Il existe plusieurs tentatives pour éviter ce problème. Par exemple on pourrait introduire au niveau du navigateur un parser spécifique qui vérifierait la syntaxe de la réponse JSON-P, potentiellement en spécifiant un type mime spécifique à la balise SCRIPT. (cf. http://www.json-p.org/).

Gestion d’erreur

La gestion d’erreur avec JSONP, dans le cas où la requête n’arrive pas, est plus complexe que pour les appels XHR. Il existe à cet effet les événementsonerror, mais ils ne sont pas supportés par tous les navigateurs (en fait, la plupart des navigateurs supporte l’événement onerror sur la window, mais sous IE, l’événement onerror sur la window n’est déclenché que sur une erreur javascript, pas sur une erreur de chargement).

Optimisations

En effet, l’utilisation la plus courante de JSONP se fait via l’insertion dynamique de balises SCRIPT. Ainsi, plutôt que de créer dynamiquement une XHR, on insère une balise SCRIPT avec une url JSON-P. Dans ce type d’utilisation de JSON-P, Google, dans son implantation de Google Instant Preview précise deux conseils intéressants :

  • Si on insère la balise SCRIPT directement (viadocument.createElement), certains navigateurs afficheront que la page est encore en cours de chargement même si toutes les requêtes sont terminées. Pour éviter ce problème, il faut encapsuler la création de la balise dans un window.setTimeout.
  • Une fois que l’appel est terminé et la fonction de callback appelée, il est préférable de mettre à null l’attribut SRC de la balise SCRIPT et de retirer la balise. Sur certains navigateurs, l’accumulation de balisesSCRIPTsemble ralentir l’exécution au fur et à mesure.

Exemple

Un programme minimum en PHP pourrait être :

<?php
data = « { name: \ »kate\ » } »;
echo $_GET['jsonp_callback'] . ‘(‘ . $data . ‘);’;
?>

où jsonp_callback est le paramètre de la requête qui contient le nom de la méthode à appeler.

On peut par exemple appeler le serveur dans une page HTML contenant :

<SCRIPT SRC="http://localhost/json_sample1.php?jsonp_callback =alert"></SCRIPT>

évidemment, on utilisera plutôt une création dynamique de la balise SCRIPT, par exemple via un fragment javascript tel que :

var url="http://localhost/json_sample1.php?param=12";
url+="&jsonp_callback=myCallback";

// Création dynamique d'un tag SCRIPT avec la bonne url
var tagScript=document.createElement("SCRIPT");
tagScript.src=url;
var cible=document.getElementById("monAppel");
cible.appendChild(tagScript);

Les fichiers de test correspondants sont disponibles dans cette archive.

<?php
data = « { name: \ »kate\ » } »;
echo $_GET['jsonp_callback'] . ‘(‘ . $data . ‘);’;
?>

<?phpdata = « { name: \ »kate\ » } »;echo $_GET['jsonp_callback'] . ‘(‘ . $data . ‘);’;?>

Publié dans Javascript, JSON | Marqué avec , , , , , , , | Commentaires fermés

Bonjour, occupants des frontières

Vipère noire vous parle

Publié dans Langages | Commentaires fermés

Hello to the people of borders…

Yes

Publié dans Langages | Commentaires fermés