Common patterns when handling promises

Promises are becoming a top feature of JavaScript. They’re already part of most libraries and frameworks (jQuery, WinJS and AngularJS among others) and will soon be included natively in all browsers with the upcoming delivery of ECMAScript 6 (the standard behind JavaScript). They are heavily used every time you have to deal with asynchronous calls, like in Ajax, or in Single Page Applications which rely a lot on them.

I’ve been using them proficiently for different projects, and I noticed there are common patterns that keep popping in my code, and are not necessarily well known for the time being. I propose we take a look at three of them. Lire la suite

Retour sur la conférence NCRAFTS 2014

Pendant que certains de mes collègues participaient au hackaton organisé par Microsoft pour l’évènement //publish, j’ai pour ma part eu le plaisir de me rendre à la première édition de la conférence NCRAFTS organisée le 16 mai dernier par l’association ALT.NET. Cette association promeut les solutions alternatives en .NET et une vision du développement informatique axée autant sur l’artisanat que sur l’ingénierie. Je vous propose donc un compte-rendu de ce que j’ai pu glaner au cours des session auxquelles j’ai pu assister. Lire la suite

Les blogs pour faire de la veille technologique en .NET

Les équipes de MCNEXT espèrent que ce blog vous permet d’améliorer toujours un peu plus vos connaissances des technologies Microsoft. Il existe néanmoins d’autres blogs qui valent le détour sur ce sujet. En l’occurrence, je vais vous parler des blogs que je consulte régulièrement – via leur flux RSS – pour me tenir au courant des dernières avancées en matière de développement en .NET.

Les auteurs de ces différents blogs, lorsqu’ils ne travaillent pas pour Microsoft, sont généralement des MVPs reconnus par la communauté pour leur expertise technique et leurs capacités à partager leurs connaissances. Par ailleurs ils ont tous un point commun : c’est un vrai plaisir de les lire.

Croyez bien que c’est tout à fait involontaire de ma part, mais tous ces blogs sont en anglais. J’espère donc que vous maîtrisez la langue de Shakespeare.

Scott Guthrie est le directeur de toute la stack web chez Microsoft, et même plus encore. Son blog lui sert à annoncer régulièrement les nouveautés dans le monde d’ASP.NET et d’Azure. Ses articles sont généralement pointus mais très bien expliqués, et il en publie de nouveaux à intervalle très régulier. Je lui reprocherais juste un côté un peu « corporate », et le fait que les sujets ont tendance à s’entrechoquer. En fonction de vos affinités, tout ne vous intéressera pas, mais cela reste néanmoins une source d’information incontournable si vous êtes du métier.

Scott Hanselman est le pendant plus « geek » de Scott Guthrie. Lui aussi travaille pour la stack web, à un degré de hiérarchie moindre, et lui aussi publie très régulièrement de nouveaux articles. Mais il est par ailleurs très drôle, a un don pour savoir mettre en avant des connaissances vraiment utiles, et il n’hésite pas à parler de sujets très variés tout en réunissant à les connecter au monde du développement informatique. A lire absolument : ses réflexions sur sa condition de diabétique de niveau 1 et l’état de la technologie dans ce domaine.

Ayende Rahien, de son vrai nom Oren Eini, est une sommité dans le monde Microsoft. Spécialiste du développement .NET, il publie au minimum un article par jour, et ceux-ci sont souvent passionnants. Il a la particularité de publier des articles à suivre sous forme de feuilleton, autour d’un sujet bien précis : NHibernate, le développement de son propre moteur de stockage de données (VORON), ou plus récemment le fonctionnement de Lucene.NET. Au fil des ans il est devenu de plus en plus pointu, et j’avoue avoir parfois du mal à suivre lorsqu’il explique les algorithmes à l’œuvre dans certains produits que commercialise sa société (Hibernating Rhinos), mais c’est aussi un très bon moyen de se remuer les méninges.

Rick Strahl est aussi un auteur très prolifique. Il n’est d’ailleurs pas rare de tomber sur son blog au détour d’une recherche depuis Google sur le monde ASP.NET. Cette technologie est sa grande spécialité et ses articles sont une vraie mine d’or en matière de bonnes pratiques, astuces diverses et solutions aux nombreux problèmes auxquels il a pu lui-même été confronté. Un gros défaut cependant : ce salaud habite à Hawaï.

Jeff Atwood et son blog Coding Horror. Jeff Atwood n’est autre que l’un des fondateurs de Stackoverflow que je ne vous ferai pas l’insulte de présenter. Son blog est moins technique et parle d’avantage de choses comme la motivation, comment recruter les bons collaborateurs, comment s’organiser, et c’est tout aussi passionnant, surtout sous sa plume.

K. Scott Allen est un vieux briscard du monde .NET qui partage ses astuces et réflexions en matière de développement informatique. Ses connaissances épousent tout un pan de la galaxie .NET, et il est n’est pas avare en exemples de code que vous passerez de nombreuses heures à étudier.

Kraig Brockschmidt est l’auteur du très bon livre Programming Windows Store Apps With HTML CSS and JavaScript, disponible gratuitement au téléchargement chez Microsoft Press, dont la seconde édition vient juste de sortir. Ce grand spécialiste de WinJS profite de son blog pour distiller des connaissances de base sur son sujet de prédilection, ou aborder des aspects qui ne trouvaient par leur place dans l’ebook.

Troy Hunt est une découverte récente pour ma part. Spécialiste d’Azure et de sécurité informatique (en particulier autour de la stack web .NET), Troy publie très régulièrement des articles longs et très détaillés. Il a par exemple abordé les bonnes pratiques à appliquer en matière de mot de passe informatique, de quelle manière selon lui une grande compagnie américaine s’est faite pirater sa base de comptes clients. Dans un billet plus récent, il explique avec brio comment fonctionne le bug qui frappe actuellement OpenSSL, pourquoi il faut s’en émouvoir, comment s’en protéger, et quels impacts celui-ci peut avoir sur un système informatique. A lire absolument.

La trousse à outils du développeur HTML5

Le monde du web est en pleine effervescence avec la mise au point en parallèle des normes HTML5, CSS3 et ECMAScript 6. Dans le même temps, les développements web se font plus diverses, intégrant maintenant des notions telles que le responsive design, la mobilité, ou l’hypermedia.

Les frontières bougeant rapidement, il devient parfois difficile de suivre l’actualité et de se former efficacement sur les nouveautés et sur l’utilisation des nouveaux outils, à moins d’avoir le courage d’aller lire le document de normalisation.

C’est pourquoi je vous propose aujourd’hui une liste de ressources qui vous permettront de vous tenir à jour, et peut-être de découvrir de quoi sera fait le monde HTML5 de demain.

Les sites web pour se documenter

Que vous vouliez vérifier la syntaxe d’une fonction JavaScript ou simplement apprendre à utiliser de nouvelles fonctionnalités, le web est maintenant riche de ressources qui vous permettront de creuser en profondeur aussi bien les anciens sujets que les toutes dernières nouveautés de la norme HTML5.

  • W3Schools, qu’on ne présente plus tant il a permis de mettre le pied à l’étrier de toute une génération de développeurs web. Le site propose toute une section consacrée à HTML5, qui parle essentiellement des éléments de la norme qui sont d’ores et déjà stabilisés. Pour l’anecdote, contrairement à ce que croient beaucoup de développeurs, le site W3Schools n’est en aucun cas associé au W3C, l’organisme qui régit la norme HTML5. Il s’agit d’une entité totalement indépendante.
  • Mozilla Developper Network (MDN), un effort de la fondation Mozilla, bien connue pour son navigateur Firefox. Ce site propose une documentation riche et très complète sur tout ce qui touche au HTML5, y compris ses aspects toujours en cours de normalisation. De nombreux tutoriaux permettent de mettre directement les mains dans le cambouis, avec des exemples de code clairs et pédagogiques, ce qui est toujours appréciable. MDN a par ailleurs le bon goût de ne pas se limiter aux fonctionnalités supportées par Firefox, mais au contraire de proposer pour chacune d’entre elles un tableau établissant quel navigateur les supporte, et les éventuels cas particuliers (fonctionnalité supportée partiellement).
  • HTML5 Rocks, un site supporté par Google qui propose des articles sur différents sujets autour du HTML5. Le site est conçu sur un principe de communauté ouverte où chacun est libre de proposer de nouveaux articles ou de corriger les articles plus anciens, par le biais d’un repo GitHub. On trouve quelques belles plumes sur ce site, comme Paul Irish ou Paul Lewis.

Les sites pour juger la compatibilité

Le principal problème avec lequel le développeur HTML5 doit composer, est la notion de compatibilité des navigateurs avec les fonctionnalités HTML5. De cette compatibilité va dépendre la nécessité d’utiliser ou non des polyfills, et d’appliquer les principes de graceful degradation et d’amélioration progressive.

  • Can I Use propose une matrice des fonctionnalités du monde HTML5, et des différents navigateurs du marché. Il vous permet donc d’un seul coup d’œil de savoir quel navigateur supporte quelles fonctionnalités. Ce site est rapidement devenu un incontournable, car nous sommes dans une période de transition où il est très important de faire attention à ce que les aspects de la norme HTML5 (ou de ses cousines CSS3 et ECMAScript 5) que nous souhaitons utiliser dans nos développements sont bien supportés par le parc de navigateurs visé. Dans ce domaine, Can I Use est la référence absolue. Il est très clair, facile à utiliser avec son moteur de recherche, et surtout il a le mérite d’être très régulièrement mis à jour, aussi bien avec de nouvelles fonctionnalités en cours de normalisation, qu’avec les nouvelles versions des navigateurs mises sur le marché.
  • QuirksMode est un bon complément à Can I Use. Le site existe depuis plus longtemps, et s’avère parfois plus précis pour expliquer comment sont supportées certaines vieilles fonctionnalités par de non moins anciens navigateurs. Le site est aussi très bavard pour qui s’intéresse à l’histoire du web. Par contre son interface commence à se faire vieillissante, et le site ne semble plus être mis à jour qu’épisodiquement.
  • Les matrices des fonctionnalités ECMAScript 5 et le futur ECMAScript 6 présentent les nouvelles fonctionnalités introduites dans ces deux normes du langage JavaScript, et indiquent quels navigateurs les supportent. Ces tableaux sont tout d’abord très utile d’un point de vue éducatif, car ils permettent de se faire une bonne idée des nouveautés du langage JavaScript. En terme de compatibilité, tous les nouveaux navigateurs supportent maintenant ECMAScript 5, mais il est intéressant de pouvoir déterminer lesquels ont commencé à supporter ECMAScript 6 (qui est toujours en cours de normalisation) et quelles nouvelles solutions cette norme va nous apporter.
  • Modernizr n’est pas à proprement parler un site, mais une librairie à la fois CSS et JavaScript, permettant de faire de la détection de fonctionnalités. Côté CSS, Modernizr ajoute automatiquement dans la balise <html> des classes qui vont indiquer si le navigateur courant supporte ou non, par exemple les coins arrondis (les classes borderradius et no-borderradius). Côté JavaScript, Modernizr propose une API permettant d’effectuer les mêmes tests, cette fois-ci déclarativement, pour s’en servir comme condition dans notre code. Tout cela permet d’appliquer les principes d’amélioration progressive dont nous avons parlé plus haut. Personnellement, j’évite généralement de charger cette librairie, en préférant en extraire simplement le morceau de code correspondant au test que j’ai besoin d’effectuer.

Les outils de développement

L’époque où l’on devait presser Ctrl+F5 toutes les 10 secondes est maintenant révolue. L’écosystème du développement web est maintenant riche de très nombreuses applications et plugins qui peuvent vous faire gagner un temps phénoménal.

  • Firebug a longtemps été un pionnier du débugage dans les navigateurs, mais aujourd’hui, ils disposent tous d’un tel outil. Quel que soit le choix que vous avez fait, il y a forcément une console qui accompagne votre navigateur préféré. De nos jours, elles permettent toutes, à minima, d’ausculter et de modifier à la volée le code HTML et styles CSS de la page, ainsi que de débuguer le code JavaScript (avec des points d’arrêt, des espions et l’accès à la stack) et d’afficher les requêtes HTTP. Firebug permet aussi d’examiner les cookies liés à la page courante. Chrome nécessitera pour se faire une autre extension : Edit this cookie.
  • YSlow est un très bon outil conçu par les développeurs web de Yahoo!, disponible sous forme de plugin pour la plupart des navigateurs (à l’exception notable d’Internet Explorer). Il vous permet de jauger la rapidité de rendu de vos pages web, et vous suggère différentes optimisations que vous pourriez effectuer afin d’améliorer votre score. Il dispose aussi d’outils complémentaires comme JSLint, un parser JavaScript permettant d’analyser votre code afin d’y décéler les mauvais usages, ou de Smush.it, un outil permettant d’optimiser la compression des images utilisées par votre site.
  • Autre très bonne extension pour Firefox, Tamper Data permet d’intercepter les formulaires POST et de les modifier à la volée. Très utile pour se retrouver plus rapidement en tête des highscores de votre jeu préféré. 🙂
  • Dans le même ordre d’idée, l’extension Postman pour Chrome permet de fabriquer vos propres requêtes HTTP, chose très utile pour tester des services REST.
  • Bien connu des développeurs web, racheté en 2012 par Telerik mais toujours gratuit, Fiddler est un outil de plus haut niveau que les consoles de développement. Il permet de sniffer le trafic HTTP qui circule sur votre machine, y compris sur la boucle Localhost, et de l’analyser, voire le modifier. C’est un incontournable, extrêmement complet (au point que je ne pense pas connaître les trois quart de ses fonctionnalités) qui se révèlera être votre meilleur ami lorsque vous aurez tôt ou tard besoin de débuguer au plus près les requêtes HTTP.
  • Encore plus haut niveau, WireShark (ex-Ethereal) est plutôt un outil pour administrateur réseau, mais il s’avère parfois nécessaire d’en arriver à cette extrémité lorsque le problème dépasse la seule couche applicative (où se trouve le protocole HTTP). Je vous souhaite d’avoir rarement à vous en servir car son utilisation est généralement synonyme d’un problème de réseau assez compliqué à résoudre pour un humble développeur web.
  • Il y a malheureusement encore beaucoup de vieilles versions d’Internet Explorer en circulation, ce qui implique la nécessité de tester nos sites web pour s’assurer de leur bon fonctionnement sur celles-ci. Pour se faire, vous pouvez utiliser les dernières versions de ce navigateur qui dispose d’émulateurs pour les versions antérieures. Malheureusement, ça ne marche pas toujours très bien. La meilleure alternative que j’ai pu trouver, en dehors de créer une machine virtuelle avec une vieille version de Windows (seule solution 100% fiable), est de passer par IETester, qui propose de reproduire le comportement des anciennes versions d’Internet Explorer, en réutilisant les moteurs originaux, gage de fidélité dans le comportement. Cet outil semble néanmoins sujet à de nombreux bugs, mais pour du débugage rapide c’est la meilleure solution que je connaisse.

Conclusion

Cette liste ne se veut pas exhaustive. Aussi, n’hésitez pas à partager vos propres recommandations dans les commentaires si le cœur vous en dit.

JavaScript, un langage single-thread

JavaScript, il faut le savoir, est un langage single-thread. Cela signifie que le code d’une page s’exécute sur un seul et unique thread, et qu’aucun appel n’est dispatché.

Prenons un exemple concret (on utilise jQuery pour simplifier) et essayons de déterminer dans quel ordre va s’exécuter le code suivant :

// 1. Code principal
console.log("Code principal");
var element = $("#mon-element");
element.on("click", onClick);

alert("Coucou!");

element.trigger("click");

someFunction();

// 2. Gestionnaire d'évènement
function onClick() {
  console.log("Gestionnaire d'évènement");
}

// 3. Méthode arbitraire
function someFunction() {
  console.log("Fonction arbitraire");
}

Et le résultat :

Capture console single-thread

On pouvait à priori s’y attendre, pourtant cet exemple mérite quelques commentaires.

Tout d’abord, l’appel à la fonction alert() a pour effet de bloquer l’exécution du code JavaScript. Tant que l’utilisateur n’aura pas cliqué sur le bouton de la fenêtre modale qui va s’afficher, le reste du code ne sera pas traité. On peut le voir facilement en mettant un point d’arrêt après la fonction.

Ensuite, il est intéressant de noter que le gestionnaire d’évènement sera exécuté dès l’appel à trigger(), et non pas mis en file d’attente comme on aurait pu le croire.

Une conséquence importante de la nature single-thread du JavaScript est que le code s’exécute sur le même thread que le thread graphique. Cela signifie que pendant ce temps l’interface utilisateur est bloquée (impossible de cliquer sur les boutons), et qu’aucun repaint de la page (rafraîchissement de l’affichage) ne peut être effectué.

Par exemple, si l’on essaie de faire tourner une boucle infinie, on constate qu’au bout de quelques secondes le navigateur nous propose d’arrêter le script qui selon lui « ne répond pas ». Tous les navigateurs modernes affichent ce comportement, qui vise à empêcher que des scripts malicieux ou mal conçus ne viennent perturber l’expérience utilisateur.

// Boucle infinie
while(true) {}

Capture firefox blocage

Mais nous allons voir maintenant que certaines fonctions peuvent faire varier l’ordre d’exécution du code JavaScript, et nous aider à résoudre les problèmes dus à des scripts trop longs à s’exécuter.

L’asynchronisme en JavaScript

Certaines tâches en JavaScript peuvent s’exécuter de manière asynchrone, c’est-à-dire de manière décalée par rapport au code principal. Cela peut être soit subi soit provoqué par le développeur lui-même.

Un exemple d’appel asynchrone subi est un appel Ajax. Lorsque l’on contacte le serveur pour récupérer des données, il est impossible de savoir combien de temps celui-ci mettra pour nous répondre, c’est pourquoi on indique une fonction de « callback » qui sera chargée de traiter le résultat retourné. Le navigateur va donc exécuter l’appel Ajax, attendre la réponse du serveur, puis exécuter le callback.

$.ajax("/UNE-URL-QUELCONQUE").done(function () {
  // Callback
});

// Ce code s'exécutera AVANT le callback
var toto = "toto";

Pendant le temps d’attente qui suit l’appel au serveur, deux faits remarquables vont se dérouler. Tout d’abord, le moteur JavaScript va traiter tout code en attente d’exécution, et notamment le code qui pourrait avoir été déclaré à la suite de l’appel Ajax. Ensuite, une fois que tout le code en attente aura été exécuté et si le serveur n’a toujours pas répondu, le moteur JavaScript rendra la main au thread graphique, permettant ainsi à l’utilisateur d’interagir normalement avec la page. Ce n’est que lorsque le serveur aura répondu que le thread graphique sera de nouveau interrompu afin de laisser le moteur JavaScript exécuter notre callback de traitement de la réponse.

Mais il est aussi possible de provoquer volontairement des appels asynchrones. Pour cela on utilise les méthodes JavaScript setTimeout() et setInterval(). Ces méthodes permettent respectivement de retarder l’exécution d’une fonction selon un temps d’attente défini, et de répéter l’exécution d’une fonction selon un intervalle de temps défini.

var timeoutId = setTimeout(function () {
  // Cette fonction s'exécutera dans 1 seconde (1000 millisecondes)
}, 1000);

Pendant le temps qu’attendra le navigateur avant d’exécuter le callback, l’interface utilisateur sera libérée. Ce sont ces fonctions que l’on utilise pour gérer l’affichage d’une animation (sur les vieux navigateurs) et surtout pour permettre au thread graphique de prendre une grande bouffée d’air avant de lancer une routine susceptible de mettre très longtemps à s’exécuter, anticipant ainsi l’affichage de l’avertissement « le script ne répond pas » que nous avons vu plus haut. Ces méthodes renvoient un identifiant unique (un nombre entier en fait) qui permet à tout moment d’annuler l’exécution retardée des callbacks avec respectivement les méthodes clearTimeout() et clearInterval().

// Finalement j'ai changé d'avis
// et je ne souhaite pas que le callback soit exécuté
clearTimeout(timeoutId);

Notez qu’Internet Explorer depuis la version 10 propose également la fonction setImmediate(), qui est l’équivalent d’un setTimeout() avec un délai d’attente de zéro. Son utilisation revient à dire au moteur JavaScript : termine de traiter tout ce qui est en attente, laisse le thread graphique faire un repaint, et ensuite seulement occupe-toi de ce bloc de code. Cette méthode, bien qu’elle n’ait pas été homologuée par le W3C, peut être très utile dans le cadre du développement d’applications Windows Store avec WinJS.

Au chapitre des fonctions introduisant de l’asynchronisme dans JavaScript, on peut aussi citer requestAnimationFrame(), une nouveauté HTML5 qui permet de demander au navigateur ne nous réserver une « fenêtre » avant le prochain repaint. C’est la méthode moderne pour qui veut gérer plus finement l’affichage de ses animations.

Les WebWorkers

Les WebWorkers sont une nouveauté HTML5 permettant d’exécuter du code JavaScript dans une tâche de fond, c’est-à-dire de faire du multi-thread. L’intérêt est de pouvoir effectuer côté client de lourds calculs dans un thread séparé, permettant ainsi de ne pas bloquer le thread graphique.

Pour cela, on doit déporter le code qui sera traité dans un fichier .js séparé. Le thread principal et le thread de tâche de fond que représente ce fichier pourront alors discuter par le biais d’une API de message.

// On créé un WebWorker en indiquant le fichier .js
var backgroundTask = new Worker("background-task.js");

// On s'abonne aux messages que va renvoyer le WebWorker
backgroundTask.addEventListener("message", function (event) {
  // Traitement des données renvoyées par le WebWorker
  var returnedData = event.data;
});

// On poste un message au WebWorker pour le lancer
backgroundTask.postMessage("");

Contenu du fichier « background-task.js » (le WebWorker) :

// Le fait de recevoir un message du code principal
// lance le traitement du WebWorker
onmessage = function (event) {
  var mainThreadData = event.data;

  // On simule une opération longue à s'exécuter
  setTimeout(
        function () {
            postMessage("Coucou du WebWorker!");
        },
        5000
    );
};

Le thread principal et le WebWorker peuvent s’échanger du JSON mais les données seront dupliquées. Comprendre : les modifier d’un côté ne les modifiera pas de l’autre, il faut les échanger grâce à l’API de message. Notez aussi que les WebWorkers reposent sur des threads gérés par le système d’exploitation lui-même.

Les WebWorkers ont toutefois une lourde limitation : ils s’exécutent dans une sorte de « bac à sable » conçu pour limiter les problèmes de sécurité, et à ce titre ils n’ont pas accès aux variables globales de l’objet window, notamment jQuery et document. Autrement dit les WebWorkers n’ont pas accès au DOM.

Ce dernier point peut s’avérer problématique, car JavaScript est souvent utilisé pour manipuler le DOM afin de modifier le rendu de la page web. Il est toutefois possible de résoudre cette difficulté en rusant. Par exemple, imaginons que nous ayons dans notre page un tableau contenant quelques centaines de lignes. Nous voulons trier celles-ci en fonction d’une donnée qu’elles contiennent afin de modifier ensuite leur ordre d’affichage. Le calcul permettant de faire le tri étant assez lourd, nous allons le déléguer à un WebWorker à qui nous enverrons un tableau contenant des paires « id de la ligne / donnée de tri ».

// On invoque un WebWorker et on lui envoie les données à trier
var tableSorter = new Worker("background-task.js");
tableSorter.postMessage([
  { "id": "line-1", "data": "Pierre" },
  { "id": "line-2", "data": "Paul" },
  { "id": "line-3", "data": "Jacques" }
]);

La responsabilité du WebWorker sera alors d’effectuer le plus gros du travail en triant le tableau, avant de renvoyer au thread principal un nouveau tableau contenant les ids dans l’ordre attendu. Il ne restera donc plus qu’à manipuler le DOM pour remettre les lignes dans le bon ordre.

// Le WebWorker se charge de l'opération la plus lourde : le tri
onmessage = function (event) {
  var tableData = event.data;
  tableData.sort(function (a, b) {
    if (a.data > b.data)
      return 1;

    if (a.data < b.data)
      return -1;

    return 0;
  });

  return tableData.map(function (line) {
    return line.id;
  });
};

Conclusion

Si vous voulez aller plus loin je vous invite à lire cette réponse sur StackOverflow, qui donne des détails intéressants sur l’aspect single-thread de JavaScript, notamment sur le fait qu’il est possible de « tricher » pour faire s’exécuter du code lorsque par exemple une fenêtre modale « alert » est affichée.

La délégation d’évènement avec jQuery

Une fonctionnalité méconnue et pourtant très utile de jQuery est la délégation d’évènement. Elle permet de brancher un gestionnaire d’évènement sur un élément parent de l’élément que l’on veut réellement écouter.

Pas assez clair? Pour mieux comprendre faisons d’abord un petit rappel.

La notion de « bulle »

Lorsque l’on clique sur un élément du DOM, l’évènement « click » est déclenché sur le nœud en question, puis est propagé sur tous ses nœuds parents jusqu’à remonter sur l’élément racine du document, c’est-à-dire « body ».

Dans l’exemple suivant, si vous cliquez sur le bouton, l’évènement « click » sera d’abord déclenché sur le <button>, puis sur la <div>, puis <article>, <section> et enfin <body>.

<body>
  <section>
    <article>
      <div>
        <button></button>
      </div>
    </article>
  </section>
</body>

Cette remontée du DOM aura lieu que vous traitiez ou non l’évènement à quelque niveau que ce soit – bien qu’il soit possible de bloquer la bulle, mais c’est un autre sujet. Il est donc possible d’intercepter l’évènement à plusieurs reprises en branchant des gestionnaires d’évènement à différents niveaux.

$(document).ready(function () {
  $("article").on("click", function (e) {
	// Gestionnaire d'évènement sur <article>
  });
  
  $("button").on("click", function (e) {
	// Gestionnaire d'évènement sur <button>
  });
});

Dans ce dernier exemple, on branche deux gestionnaires d’évènement, un sur <button> et un sur <article>. Si l’on clique sur le bouton, le gestionnaire d’évènement branché sur <button> sera déclenché en premier, puis suite à la remontée de la bulle le long du DOM, ce sera le tour de celui qui est branché sur <article>. On voit que l’ordre dans lequel ils ont été abonnés importe peu.

Comment mettre en place la délégation

La méthode on() de jQuery permet d’indiquer un paramètre supplémentaire : un sélecteur désignant les nœuds enfants que l’on souhaite écouter. Dans ce cas, le gestionnaire branché sera déclenché lorsque l’évènement se sera propagé jusqu’à l’élément, mais uniquement si la cible initiale – par exemple l’élément sur lequel on a vraiment cliqué – respecte le sélecteur indiqué en paramètre.

$(document).ready(function () {
  $("article").on("click", "button" /* Sélecteur supplémentaire */, function (e) {
	// Gestionnaire d'évènement sur <article>
	// mais qui ne se déclenchera que si on a cliqué sur <button>
  });
});

C’est donc la présence de ce paramètre supplémentaire qui induit la délégation d’évènement dans jQuery.

Vous avez compris le principe. Voyons maintenant trois cas d’utilisation concrets, issus de mon expérience, qui vous démontreront tout l’intérêt que peut revêtir cette fonctionnalité.

Beaucoup d’éléments à écouter

Imaginez que vous affichiez un tableau HTML sur votre page web, que celui-ci contienne un million de cellules, et que vous souhaitiez brancher un gestionnaire d’évènement pour écouter les clicks sur chacune d’entre elles.

<table id="big-table">
  <tr>
    <!-- Ici plein de cellules -->
    <td></td>
    <td></td>
  </tr>
</table>

Si vous le faites en vous branchant individuellement sur chacune des cellules, vous allez enregistrer un million de gestionnaires d’évènement en mémoire. Inutile de vous dire que ce n’est pas une très bonne idée.

En utilisant la délégation vous pouvez brancher un seul gestionnaire d’évènement au niveau du tableau lui-même. Ce gestionnaire sera alors en mesure de traiter les clicks sur chacune des cellules.

$(document).ready(function () {
  $("#big-table").on("click", "td", function (e) {
	// Gestionnaire d'évènement unique pour l'ensemble des <td>
  });
});

A ce stade, vous devriez commencer à vous poser la question suivante : comment savoir quelle cellule a été clickée?

Pour ce cas de figure, jQuery a tout prévu, et dans l’argument passé en paramètre au gestionnaire d’évènement – appelé « e » dans notre exemple – vous trouverez trois propriétés qui vous permettront d’obtenir des informations.

  • e.target désignera l’élément qui a déclenché initialement l’évènement, c’est-à-dire la cellule.
  • e.currentTarget désignera l’élément sur lequel l’évènement est en train de buller, c’est-à-dire le tableau.
  • e.delegateTarget désignera l’élément sur lequel est branchée la délégation, c’est-à-dire ici encore le tableau.

Notez aussi que le mot-clef this comme e.currentTarget désignera toujours l’élément sur lequel l’évènement est en train de buller, c’est-à-dire celui correspodant au sélecteur que l’on a indiqué en paramètre de la fonction on().

Les fragments de code HTML

Imaginez que vous affichez une liste d’items.

<ul id="list">
  <li class="list-item"><button type="button" class="delete">Supprimer</button></li>
  <li class="list-item"><button type="button" class="delete">Supprimer</button></li>
  <li class="list-item"><button type="button" class="delete">Supprimer</button></li>
  <li class="list-item"><button type="button" class="delete">Supprimer</button></li>
</ul>

Chacun de ces items peut être supprimé ou ré-ordonné dans la liste par glissé/déposé. Pour ce faire, on branche sur chacun des items des gestionnaires d’évènement chargés de traiter ces opérations.

$(document).ready(function () {
  $("#list .list-item").on("dragstart", function (e) {
	// Gestionnaire d'évènement de glissé/déposé
  });
  
  $("#list .list-item .delete").on("click", function (e) {
	// Gestionnaire d'évènement de suppression des items
  });
});

Une première remarque – inspirée de l’exemple précédent – est qu’il est possible de brancher les gestionnaires d’évènement par délégation sur la liste elle-même. Seulement voilà : cette liste est susceptible à tout moment d’être mise à jour par écrasement, suite par exemple à l’appel d’une requête Ajax.

function refreshList() {
  $.ajax("/UNE-URL-QUELCONQUE").done(function (html) {
  	$("#list").replaceWith(html);
	// Il faut rebrancher tous les gestionnaires d'évènements 😦
  });
}

Le problème, c’est qu’une fois cette opération d’écrasement effectuée, il faut rebrancher tous nos gestionnaires d’évènements car les éléments du DOM auxquels ils étaient rattachés ont été supprimés du document. Notez au passage qu’il est inutile de s’inquiéter du désabonnement de ces gestionnaires d’évènements, jQuery les nettoyant automatiquement lors de l’appel à la méthode replaceWith().

Pour pallier à l’inconvénient d’avoir à tout rebrancher à chaque rafraichissement de la liste, nous allons tout d’abord encadrer celle-ci avec un nouveau parent.

<div id="list-wrapper">
  <ul id="list">
    <!-- Ici les items -->
  </ul>
</div>

Nous allons ensuite brancher nos gestionnaires d’évènements par délégation sur ce nouvel élément.

$(document).ready(function () {
  $("#list-wrapper").on("dragstart", ".list-item", function (e) {
	// Gestionnaire d'évènement de glissé/déposé
  });
  
  $("#list-wrapper").on("click", ".list-item .delete", function (e) {
	// Gestionnaire d'évènement de suppression des items
  });
});

Le résultat de ces changements est que lorsque la liste sera écrasée, l’élément qui l’encadre lui ne le sera pas, et les gestionnaires d’évènements qui sont branchés seront par conséquent bien conservés. Par ailleurs, bien que le contenu de la liste soit mis à jour, ses items respectent toujours le sélecteur indiqué en paramètre de la délégation d’évènement. Comme ce sélecteur n’est appliqué que lorsqu’un évènement est capturé au niveau du délégué, les gestionnaires seront bien déclenchés.

Il est très courant de nos jours de mettre à jour des pans entiers d’une page web avec des appels Ajax, et ce procédé vous permettra de vous simplifier grandement la vie quant à la gestion des évènements sur ces fragments.

Gestion de la propagation d’évènements

Imaginez maintenant que lorsque l’utilisateur clique n’importe où sur la page, il faille escamoter certains morceaux de celle-ci, comme typiquement une popup.

<body>
  <div class="popup">
    <button type="button">Cliquez-moi</button>
  </div>
</body>

Pour cela le plus simple est de brancher un gestionnaire d’évènement au niveau le plus haut de la page, c’est à dire sur <body>.

$(document).ready(function () {
  $("body").on("click", function (e) {
	$(".popup").hide();
  });
});

Cette mécanique fonctionnera parfaitement, excepté qu’elle dissimulera également la popup lorsque l’on clique dans le contenu de celle-ci, par exemple sur le bouton qu’elle contient. Or ce n’est pas ce que nous souhaitons.

Encore une fois nous allons pouvoir utiliser la délégation pour corriger ce problème. Pour cela, nous allons enregistrer un nouveau gestionnaire d’évènement click sur le <body> de la page. Mais attention, nous allons l’enregistrer avant celui que nous avons déjà indiqué. Ce gestionnaire d’évènement agira par délégation sur notre popup.

$(document).ready(function () {
  $("body").on("click", ".popup", function (e) {
    e.stopImmediatePropagation();
  });

  $("body").on("click", function (e) {
	$(".popup").hide();
  });
});

Puisque nous avons branché deux gestionnaires d’évènements sur <body>, lors d’un click sur la page jQuery les traitera tous les deux dans l’ordre où nous les avons indiqué. Mais une différence importante est que le premier ne sera déclenché que lorsque le click aura été propagé depuis la popup ou son contenu. Comme on l’a déjà vu le second gestionnaire d’évènement sera lui déclenché quelque soit l’endroit que l’on cliquera sur la page.

Or dans le premier cas, on empêche la bulle de se propager aux autres gestionnaires d’évènement en utilisant la méthode stopImmediatePropagation(). Ainsi, on inhibe l’exécution de tous les autres gestionnaires d’évènements qui pourraient avoir été branchés sur le même élément – ici <body>.

Le résultat est que si l’on clique sur le bouton, le premier gestionnaire d’évènement sera en mesure de capturer ce click au moment où celui-ci atteindra la popup, et l’empêchera d’être capturé par l’autre gestionnaire d’évènement.

Au passage, si l’on place un point d’arrêt dans le premier gestionnaire d’évènement et que l’on clique sur le bouton, on constate que l’argument qui est passé en paramètre contient les informations suivantes :

  • e.target désigne le <button>.
  • e.currentTarget et this désignent <div class= »popup »>.
  • e.delegateTarget désigne <body>.

Notions fondamentales de JSON

Les formats de données

Lorsque l’on développe en Javascript, le format de données roi est le JSON (JavaScript Object Notation). Ce format supporte deux types d’entité : les tableaux (ou array) représentés par la notation littérale « [] », et les objets, aussi appelés dictionnaires ou hash représentés par la notation littérale « {} ».

Les tableaux ont une propriété length qui indique le nombre d’éléments qu’ils contiennent. Il est possible d’accéder à ces éléments par le biais d’un indexeur, et on utilise généralement une boucle for pour les parcourir.

var array = [ "Foobar", 42, true ];
for (var i = 0; i < array.length; i++) {
    var item = array[i];
}

Les hash quand à eux, contiennent des propriétés nommées auxquelles il est possible d’accéder directement, soit par une notation pointée, soit par le biais d’un indexeur nommé.

var object = {
    "Lastname":  "Guillot",
    "Firstname": "François"
};
var lastname = object.Lastname;      // Notation pointée
var firstname = object["Firstname"]; // Indexeur nommé

L’accès par indexeur nommé offre l’avantage de pouvoir être utilisé avec une variable contenant le nom de la propriété.

// Même chose que précédemment, mais le nom de la propriété "Firstname" est contenu dans une variable
var propertyName = "Firstname";
var firstname = object[propertyName];

Pour parcourir les propriétés d’un hash, on utilise une boucle for in. Chaque tour de boucle permet d’accéder au nom d’une propriété du hash, permettant ensuite d’accéder à la valeur de celle-ci par le biais de l’indexeur nommé.

var city = {
    "Name": "Marseille",
    "ZipCode": "13000"
};
for (var key in city) {
    // Au premier tour de boucle, key contiendra "Name", et value contiendra "Marseille"
    var value = city[key];
}

Il n’est pas possible de parcourir un tableau avec la boucle for in, car bien qu’à chaque tour de boucle la clef contiendrait une des valeurs contenue par le tableau, elle contiendrait également la valeur « length », car toutes les propriétés d’un objet Javascript sont inspectées.

La boucle for in inspecte également les propriétés d’un objet héritées par prototypage. Si vous ne souhaitez inspecter que les propriétés que porte l’objet lui-même, vous devez tout d’abord contrôler leur origine avec la méthode hasOwnProperty().

for (var key in city) {
    if (city.hasOwnProperty(key)) {
        // La clef contient bien le nom d'une propriété que l'objet city n'a pas reçu par héritage
        var value = city[key];
    }
}

Il est bien entendu possible de mixer les tableaux et les hash, pour concevoir des graphes de données complexes.

// Tableau contenant un hash, un booléen, et un autre tableau
var complexGraph = [
    { "Language": "C#", "Version": "4.5", "Year": 2012, "Creators": [ "Anders Hejlsbjerg" ] },
    true,
    [ 'Test', 12, { "Id": "4BB84287-B919-45DD-98BA-FFBC7D6F7BF2" } ]
];

Écrire du code JSON valide

JSON est un format de données natif du langage Javascript, ce qui signifie que dans ce contexte, il est possible de l’utiliser au-delà de ses possibilités standards. Par exemple, en Javascript on s’autorise généralement à écrire les noms des propriétés des hash sans les encadrer par des double quotes (« ), et on peut placer dans ces mêmes hash des données non littérales, comme des fonctions.

var hashJavascript = {
    Property: function (param) { return param; }
};

Mais la notation JSON a aujourd’hui largement dépassé le cadre du seul langage Javascript, au point d’être utilisée couramment dans de nombreux autres langages. Ceux-ci disposent généralement d’un parseur, qui est très pointilleux quand au respect de la syntaxe. En effet, le JSON a été normalisé, et les moteurs de parsing lèvent une exception si la chaîne de caractères qui leur est soumise ne respecte pas cette norme.

Cette norme est toutefois assez simple à respecter. Les valeurs contenues par les tableaux ou par les propriétés des hash, ne peuvent être que des chaînes de caractères, des nombres entiers ou décimaux, des booléens, la valeur spéciale null, ou bien entendu des sous-tableaux ou des sous-objets. Sont prohibés les fonctions et les dates (ce qui pose souvent problème pour faire circuler des informations de type DateTime).

Autre point important : les noms de propriétés dans les hash doivent être encadrés par des double quotes (« ), pas des simples quotes (‘), et ils doivent encore moins être écrits sans formatage. Cette règle s’applique également aux chaînes de caractères : double quotes (« ) obligatoires. Notez qu’à titre personnel, j’ai tendance à trouver la notation des propriétés avec des double quotes nettement plus lisible.

// Cet objet JSON est mal formé
var wrongObject = {
    prop: 'je m\'appelle "toto"',
    today: new Date(2012, 6, 21, 12, 44, 55, 0)
};

// Cet objet JSON respecte la norme
var rightObject = {
    "prop": "je m'appelle \"toto\"",
    // La date est une chaîne de caractères au format ISO-8601, qui est en train de devenir la nouvelle norme
    "today": "2012-06-21T12:44:55"
};

Moralité : si vous devez faire circuler des données au format JSON entre votre serveur et le client, prenez soin de bien respecter le norme, car un parseur comme la méthode jQuery.parseJSON() rejettera votre chaîne de caractères si celle-ci contient des erreurs de formatage.

Optimiser la récupération des données

Il arrive couramment que l’on doive retrouver un objet JSON parmi un tableau de ceux-ci, à partir de la valeur d’une de ses propriétés; typiquement un Id.

var animals = [
    { "Id": "D8777CD6-5E94-4464-8B3D-474DF4EC99FB", "Name": "Warthog" },
    { "Id": "BCDFEEC7-63F5-44C1-850D-246FB40B470A", "Name": "Hedgehog" },
    { "Id": "A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1", "Name": "Fawn" },
    { "Id": "2EBAC3FC-C03E-4263-95D2-1B26E0260C0F", "Name": "Gibbon" }
];

Admettons que nous souhaitons récupérer parmi cette liste d’animaux celui dont l’Id est A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1 (c’est le faon). Une solution consiste à utiliser la méthode utilitaire jQuery.grep().

var idFawn = "A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1";

// Remarquez que jQuery.grep() renvoie un tableau d'éléments respectant le prédicat passé en paramètre
// Comme nous savons qu'il n'y a qu'un seul élément respectant le prédicat, nous prenons directement le premier élément du tableau résultant
var fawn = jQuery.grep(animals, function (item) {
    return item.Id === idFawn;
})[0];

Une autre solution consiste à le faire en Javascript pur. On s’épargne ainsi de boucler sur toutes les valeurs du tableau, puisqu’on peut arrêter la recherche dès lors que le résultat a été trouvé.

var idFawn = "A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1",
    fawn = null;
for (var i = 0; i < animals.length; i++) {
    var animal = animals[i];
    if (animal.Id === idFawn) {
        fawn = animal;
        break;
    }
}

Le problème de ces deux méthodes, c’est qu’elles ne sont en aucun cas optimisées pour les moteurs Javascript. En effet, la solution idéale consiste à utiliser un indexeur nommé sur un hash. Mais pour cela, il faut qu’au départ nos données soient dans un format quelque peu différent.

var animals = {
    "D8777CD6-5E94-4464-8B3D-474DF4EC99FB": { "Name": "Warthog" },
    "BCDFEEC7-63F5-44C1-850D-246FB40B470A": { "Name": "Hedgehog" },
    "A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1": { "Name": "Fawn" },
    "2EBAC3FC-C03E-4263-95D2-1B26E0260C0F": { "Name": "Gibbon" }
},
    idFawn = "A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1",
    fawn = animals[idFawn];

Cette méthode est optimale car elle permet d’aller récupérer le faon directement dans la liste des animaux par son id, sans avoir à effectuer la moindre boucle. Dans des cas de manipulations de très grandes quantités de données, les performances peuvent s’en trouver nettement améliorées.

C’est pourquoi il est important que les données soient présentées dans un format adéquat. Ce format peut être élaboré côté serveur, avec d’injecter les données dans la page ou de les envoyer par le biais d’une requête Ajax. Pour effectuer ce type d’opération, j’ai généralement recours à la très bonne librairie JSON.NET, qui est maintenant fournie en standard dans les projets ASP.NET Web API (qui l’utilise lui-même pour toutes les opérations de manipulation de JSON).

// Exemple de création d'un objet JSON en C# avec JSON.NET
// N'oubliez pas d'ajouter "using Newtonsoft.Json.Linq;"
Animal[] animals = new Animal[]
{
    new Animal { Id = Guid.Parse("D8777CD6-5E94-4464-8B3D-474DF4EC99FB"), Name = "Warthog" },
    new Animal { Id = Guid.Parse("BCDFEEC7-63F5-44C1-850D-246FB40B470A"), Name = "Hedgehog" },
    new Animal { Id = Guid.Parse("A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1"), Name = "Fawn" },
    new Animal { Id = Guid.Parse("2EBAC3FC-C03E-4263-95D2-1B26E0260C0F"), Name = "Gibbon" }
};

JObject objectJson = new JObject();

foreach (Animal animal in animals)
    objectJson.Add(animal.Id.ToString(), JObject.FromObject(animal));

// Résultat sous forme de chaîne de caractères, non-indenté
string json = objectJson.ToString(Formatting.None);

On peut aussi transformer le format des données côté client, opération coûteuse mais qui peut être ensuite contrebalancée par le gain de performances obtenu par l’utilisation des indexeurs nommés.

// Exemple de transformation du tableau d'animaux vers un hash d'animaux
var animals = [
    { "Id": "D8777CD6-5E94-4464-8B3D-474DF4EC99FB", "Name": "Warthog" },
    { "Id": "BCDFEEC7-63F5-44C1-850D-246FB40B470A", "Name": "Hedgehog" },
    { "Id": "A71ED56F-DB42-4105-97E8-5C0BD7C3D3F1", "Name": "Fawn" },
    { "Id": "2EBAC3FC-C03E-4263-95D2-1B26E0260C0F", "Name": "Gibbon" }
], newAnimals = {};
for (var i = 0; i < animals.length; i++) {
    var animal = animals[i];
    newAnimals[animal.Id] = animal;
}