Meteor : la plateforme Web temps réel qui accroît la productivité

Le Web a connu de nombreuses évolutions ces dernières années. HTML5, WebSocket et Node.js sont des termes que l'on rencontre désormais souvent. Les frameworks JavaScript ont plus que jamais le vent en poupe, maintenant que le langage ne se limite plus nécessairement à la partie client. Il n'est cependant pas toujours facile de savoir par où (re)commencer. Meteor est une plateforme JavaScript permettant de construire rapidement des applications Web modernes. Elle a l'avantage d'être facile à apprendre et, en plus, elle est « full-stack », ce qui veut dire que vous n'avez pas à mélanger plusieurs technologies pour obtenir une application entièrement fonctionnelle. Elle se destine aussi bien au client qu'au serveur. Orientée vers la productivité du développeur et le fonctionnement en temps réel, vous allez découvrir dans les lignes qui suivent qu'il s'agit d'une plateforme prometteuse qui possède de vrais atouts.

7 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Meteor est un projet open source qui a pour ambition de transformer notre manière de concevoir des applications Web. Un des principaux objectifs est d'apporter la réactivité au plus grand nombre, la possibilité de développer des applications temps réel rapidement sans avoir à sortir l'artillerie lourde.

La documentation officielle de Meteor décrit sept principes que s'efforcent de suivre ses créateurs :

  • données sur la ligne. On ne transmet pas du HTML mais des données et c'est le client qui décide quoi en faire ;
  • un langage unique. Les parties client et serveur de l'application sont écrites en JavaScript ;
  • base de données omniprésente. L'API pour accéder à la base de données est la même, que l'on soit sur le client ou le serveur ;
  • compensation de latence. Sur le client, on utilise le prérapatriement et la simulation de base de données pour donner l'impression d'une latence zéro ;
  • réactivité de bout en bout. Le temps réel est la norme. Toutes les couches, de la base de données au template, doivent proposer une interface orientée événement ;
  • intégration à l'écosystème. Meteor est open source et intègre les outils et frameworks existants plutôt que de les remplacer ;
  • simplicité = productivité. Le meilleur moyen de donner l'impression de simplicité est de faire quelque chose qui soit réellement simple et cela passe par la création de belles API.

Techniquement, Meteor s'appuie sur Node.js pour l'exécution côté serveur, MongoDB pour la base de données, les WebSockets et un protocole maison nommé DDP (Distributed Data Protocol) pour les communications client/serveur.

II. Installation

L'installation de Meteor est on ne peut plus simple :

 
Sélectionnez
curl https://install.meteor.com | /bin/sh

Cela installe l'exécutable meteor sur le système. La création d'un projet est tout aussi aisée. Puisque nous disposons dorénavant de la commande meteor, il nous suffit de lui fournir l'argument create suivi du nom que nous souhaitons donner à notre projet :

 
Sélectionnez
meteor create hello

Une fois cette commande exécutée, un dossier hello aura été créé, contenant quelques fichiers d'exemples : hello.css, hello.html et hello.js.

Pour démarrer l'application, il suffit de se placer dans le dossier fraîchement créé et de taper la commande meteor sans argument. Une fois initialisée, celle-ci sera accessible à l'URL par défaut : http://localhost:3000/. Pour le moment, notre application ne fait rien d'exceptionnel et affiche un simple texte et un bouton. Nous allons tout de même jeter un œil au code qui a été généré.

III. Premier contact

Nous allons ouvrir les fichiers fraîchement créés afin de voir ce qu'ils contiennent. Le fichier CSS est vide, commençons donc par analyser le fichier HTML :

 
Sélectionnez
<head>
  <title>hello</title>
</head>

<body>
  <h1>Welcome to Meteor!</h1>

  {{> hello}}
</body>

<template name="hello">
  <button>Click Me</button>
  <p>You've pressed the button {{counter}} times.</p>
</template>

Nous pouvons déjà voir qu'il ne s'agit pas seulement de HTML mais que certains éléments semblent appartenir à la syntaxe d'un moteur de template. Et c'est tout à fait juste puisque Meteor utilise son propre moteur de template, nommé Spacebars. Nous pouvons donc voir une expression {{counter}} (notez les accolades doubles {{ et }}) et la définition d'un template à l'aide de la balise <template> comprenant un attribut name devant contenir l'identifiant du template qui sera référencé pour affichage (en l'entourant par {{> et }}).

Intéressons-nous maintenant à l'endroit où l'expression {{counter}} est définie et ouvrons le fichier JavaScript :

 
Sélectionnez
if (Meteor.isClient) {
  // counter starts at 0
  Session.setDefault("counter", 0);

  Template.hello.helpers({
    counter: function () {
      return Session.get("counter");
    }
  });

  Template.hello.events({
    'click button': function () {
      // increment the counter when button is clicked
      Session.set("counter", Session.get("counter") + 1);
    }
  });
}

if (Meteor.isServer) {
  Meteor.startup(function () {
    // code to run on server at startup
  });
}

La première chose qui devrait vous frapper est l'utilisation de Meteor.isClient et Meteor.isServer qui indiquent que ce code est destiné à la fois au serveur et au client. Je vois déjà les plus paranoïaques (ou consciencieux ?) d'entre vous s'étrangler avec leur café à la vue de ce qui semble être une aberration. Ne vous inquiétez pas, il ne s'agit que d'un exemple et en réalité vous ne serez pas obligés d'exposer votre code serveur au public. C'est même plutôt déconseillé. Il reste toutefois possible de partager du code entre le client et le serveur. C'est utile pour certaines bibliothèques ou éléments de configuration. Je reviendrai ultérieurement sur la manière de structurer votre application afin de séparer votre code serveur du code client.

Si on observe le code destiné au client, on voit tout d'abord que l'on utilise un objet Session pour définir la valeur par défaut d'une clef que l'on a appelée counter. Cet objet est une sorte de grand registre permettant de stocker autant de clefs que vous le souhaitez. Venons-en à l'opération suivante. Template est un autre objet mis à disposition par Meteor. Il contient l'ensemble des instances de template dont celui nommé hello qui a été déclaré dans le fichier HTML. En appelant la fonction helpers() sur notre template, nous pouvons lui passer un objet contenant diverses propriétés utilisées pour l'affichage de celui-ci. Le code ci-dessus se contente d'assigner une fonction à la propriété counter qui retournera la valeur courante de notre compteur. Cette propriété, qui est un helper, est appelée dans le code HTML via l'expression {{counter}}. De manière similaire aux helpers, on déclare les événements appartenant à un template en faisant appel à la fonction events(). Celle-ci prend un objet littéral dont les clefs sont des chaînes de caractères définissant l'événement. Ici, un clic sur l'élément button du template hello entraîne l'incrémentation du compteur.

Vous ne vous en êtes peut-être pas rendu compte, mais la valeur affichée par l'expression {{counter}} est mise à jour automatiquement à chaque clic sur le bouton. Vous vous êtes simplement occupés de mettre à jour une donnée et le rafraîchissement de l'interface a été géré de manière transparente. C'est ce qu'on appelle la réactivité, et c'est un des fondements de Meteor.

La dernière partie du fichier fait appel à Meteor.startup() pour exécuter une fonction au démarrage du serveur. Actuellement, elle ne fait rien et n'est là qu'à titre d'exemple.

Nous avons fait le tour du code d'exemple. Nous n'avons fait qu'effleurer certaines notions de Meteor. Avant d'aller plus loin, nous allons aborder quelques points fondamentaux. Vous aurez remarqué jusqu'ici que l'API de Meteor est partagée par le code serveur et le code client. Il faut savoir qu'il existe cependant des différences dans le comportement et la disponibilité de certaines méthodes ou propriétés en fonction de l'endroit où elles sont appelées. La documentation détaille, pour chaque élément de la plateforme, sa disponibilité (client, serveur ou n'importe où) et son fonctionnement dans les deux cas.

IV. Structurer son application

Une application Meteor réunit à la fois du code JavaScript exécuté dans un conteneur Node.js, du code client destiné au navigateur, des fragments de HTML, du style CSS et diverses ressources statiques. Meteor laisse à chacun le choix de l'organisation de son application, mais il y a un certain nombre de choses à savoir pour ne corrompre ni le fonctionnement, ni la sécurité de son application. Meteor s'appuie notamment sur le nom de vos répertoires et de vos fichiers pour déterminer à qui est destiné leur contenu et dans quel ordre ils seront chargés. Il est donc crucial de savoir comment Meteor traite votre arborescence. Globalement, ce sont trois critères qui vont déterminer le nom et l'emplacement que vous donnerez à un fichier au sein d'une application Meteor : le rôle qu'il joue dans votre application, sa visibilité et l'ordre dans lequel il doit être chargé.

IV-A. Visibilité

L'endroit où vous déciderez de placer vos fichiers va dépendre de la visibilité que vous souhaitez leur donner. Il y a donc trois possibilités : visibles par le serveur, visibles par le client ou les deux. La partie la plus sensible de votre code sera probablement accessible uniquement par le serveur, tandis que ce qui concerne l'interface sera plutôt destiné au client. Enfin, vous voudrez peut-être partager certaines bibliothèques à la fois avec le client et le serveur.

Les fichiers contenus dans les répertoires server et private sont invisibles pour le client et sont donc uniquement accessibles au serveur. À l'inverse, ce qui est contenu dans le répertoire client et public ne sera pas pris en compte sur le serveur.

Tous les fichiers se trouvant ailleurs sont visibles dans les deux cas, à l'exception de ce qui est contenu dans le répertoire tests, qui est un répertoire spécial dont le contenu ne sera pas déployé avec l'application.

IV-B. Rôle

Chaque fichier de votre application joue un rôle précis. Nous pouvons identifier les éléments suivants : le code JavaScript, les fragments de HTML, les mises en forme CSS et des ressources statiques (images, fichiers divers, etc.).

IV-B-1. Code JavaScript

Tout code JavaScript est exécuté à condition qu'il ne se trouve pas dans un répertoire private, public ou tests.

IV-B-2. HTML

La manière dont le HTML est traité est assez particulière. Meteor scanne l'ensemble de vos fichiers HTML et en extrait les éléments <head>, <body> et <template>. Les contenus des éléments <head> et <body> sont fusionnés et placés respectivement dans des éléments <head> et <body> uniques qui seront transmis au client au chargement initial de la page. Les éléments <template> sont, quant à eux, convertis en fonctions JavaScript et rendus accessibles via l'objet Template. Cela est valable pour les fichiers ne se trouvant pas dans un répertoire server, private ou public.

IV-B-3. CSS

Tous les fichiers CSS sont fusionnés et envoyés au client comme une seule feuille de style, à l'exception de ceux se trouvant dans les répertoires server, public et private.

IV-B-4. Ressources statiques

Les répertoires public et private sont dédiés aux ressources statiques. Le contenu du répertoire public est rendu accessible au client comme tout répertoire public d'une application Web. Vous pourrez y placer vos images, favicon.ico, robots.txt, etc. Les fichiers du répertoire private sont rendus accessibles au code serveur via l'API Assets qui dispose de méthodes pour en récupérer le contenu.

IV-C. Priorité

Meteor encourage autant que possible les développeurs à écrire leur code de manière à ce qu'il soit le moins possible sensible à l'ordre de chargement des fichiers. Il est pourtant rare de ne pas avoir à s'en soucier. Voici donc les règles à connaître pour éviter quelques maux de tête :

  • les fichiers se trouvant dans des sous-répertoires sont chargés avant ceux qui se trouvent dans des répertoires parents ;
  • dans un même répertoire, les fichiers sont chargés par ordre alphabétique ;
  • une fois ces deux premières règles appliquées, les fichiers se trouvant dans des répertoires nommés lib sont placés avant tout le reste ;
  • les fichiers correspondant au pattern main.* sont placés à la fin.

Ces règles de chargement peuvent ne pas vous convenir. Il est donc à noter que si vous tenez particulièrement à définir l'ordre de chargement exact de vos fichiers, il vous faudra constituer un package. En effet, le fichier de configuration d'un package permet non seulement de définir les dépendances avec d'autres packages, mais aussi l'ordre dans lequel doivent être chargés les fichiers qu'il contient. Pour en apprendre plus sur la création d'un package, je vous invite à consulter la documentation de Meteor.

V. Définir son modèle de données

Les données sont au centre de toute application. Ce qui différencie toutefois une application Meteor d'une application plus classique est la manière dont est abordé le modèle de données. Ce que j'entends par modèle de données dans ce cas précis est, non seulement la définition et la représentation des données, mais aussi la manière dont l'application va les traiter et les acheminer au client.

Dans une application classique, vous aurez probablement tendance à vous concentrer en premier sur la définition de vos données et sur la manière de les stocker. Une fois que votre schéma sera suffisamment mûr, vous vous lancerez dans le squelette de votre application en suivant l'habituel pattern MVC (Modèle - Vue - Contrôleur). À chaque fois que vous implémenterez une nouvelle fonctionnalité, vous vous demanderez quelle partie de vos données est concernée, qui pourra y avoir accès et dans quelles circonstances. Ces questions, qui vous viennent souvent dans un second temps, Meteor vous amène à y réfléchir dès le départ.

V-A. Une base de données omniprésente

Contrairement à ce qui se fait couramment dans nos applications Web, le serveur Meteor ne se contente pas de transmettre du HTML au client. Au lieu de ça, Meteor envoie des données « brutes » et c'est au client de les traiter. Nous allons éclaircir ce point.

La plateforme Meteor s'efforce de mettre à disposition une API uniforme côté client et serveur. C'est tout particulièrement vrai en ce qui concerne l'accès à la base de données. Ce qui veut dire qu'il est tout autant possible d'accéder à la base de données depuis le client que depuis le serveur. Je ne doute pas que cette révélation amène un certain nombre de questions. Comment les requêtes sont-elles transmises à la base de données et quid de la sécurité ?

La première chose qu'il faut savoir est que Meteor utilise une bibliothèque nommée minimongo pour simuler une base de données MongoDB côté client. Concrètement, c'est une copie de la véritable base de données qui est envoyée à chaque client. Cette copie n'est généralement pas la version intégrale de la base de données d'origine, mais contient les données que l'on souhaite rendre accessibles au client. Celui-ci peut donc effectuer des requêtes sur sa propre copie de la base de données en utilisant la même API que sur le serveur.

Pour ceux qui ne seraient pas familiarisés avec MongoDB, il s'agit d'une base de données orientée documents. Un document équivaut à un enregistrement composé de divers champs. La principale différence avec une base de données relationnelle est que ces champs ne sont pas figés, c'est pourquoi on parle de base de données schema-less (sans schéma donc). Un ensemble de documents est appelé « collection » et c'est l'équivalent d'une table dans une base de données relationnelle.

V-B. Les collections Meteor et l'API partagée

La déclaration d'une collection intitulée messages se fait de la manière suivante :

 
Sélectionnez
Messages = new Meteor.Collection("messages");

En général, on placera la déclaration d'une collection dans un fichier interprété sur le serveur et le client, car c'est un élément commun. Il est ensuite possible d'effectuer des requêtes sur la collection via des méthodes comme find(), findOne(), insert(), update() ou encore remove(). L'API des collections est partagée entre le client et le serveur, il est donc possible d'effectuer des requêtes de manière identique des deux côtés.

La méthode find() permet de récupérer les documents d'une collection qui correspondent au sélecteur MongoDB qui, lui, est passé en paramètre. Pour rechercher des documents selon un champ userId, on procéderait de la manière suivante :

 
Sélectionnez
var myMessages = Messages.find({userId: Session.get('myUserId')}).fetch();

Vous remarquerez l'usage de la méthode fetch() sur le résultat. Celle-ci permet de récupérer les résultats d'un curseur sous la forme d'un tableau, car la méthode find() renvoie en fait un curseur. De la même manière, vous pouvez utiliser la méthode count() sur un curseur pour connaître le nombre de résultats ou la méthode forEach() pour itérer sur les documents retournés :

 
Sélectionnez
var myMessages = Messages.find({userId: Session.get('myUserId')});
console.log(myMessages.count() + " messages found !");
myMessages.forEach(function (message) {
   console.log("Subject of message : " + message.subject);
});

En plus d'un sélecteur, la méthode find() peut prendre un certain nombre d'options dont les plus courantes sont le tri, l'offset, la limite et les champs à retourner. L'exemple suivant montre comment on récupérerait une liste de messages triés par date de création descendante, appartenant à un utilisateur connu, en limitant le nombre de résultats à 10.

 
Sélectionnez
var myMessages = Messages.find({userId: Session.get('myUserId')}, {sort: {crea
ted_at: -1}, limit: 10});

De manière similaire à la méthode find(), la méthode findOne() permet de récupérer un seul résultat, le premier qui correspond au sélecteur. La principale différence est qu'ici, ce n'est pas un curseur qui est renvoyé, mais directement le document qui a été trouvé :

 
Sélectionnez
var myMessage = Messages.findOne({userId: Session.get('myUserId')});

Pour insérer un document dans une collection, vous utilisez la méthode insert(), qui prend en paramètre l'objet à insérer. Cette fonction renvoie l'identifiant du document qui a été inséré.

 
Sélectionnez
var messageId = Messages.insert({subject: "un titre", content: "du texte"});

Un des avantages d'une API unique est probablement que vous n'interrompez pas la progression de votre travail quand vous développez la partie client, puisque vous n'aurez pas nécessairement à faire appel à des procédures distantes (ce qui reste toutefois possible). Vous restez concentrés sur la logique que vous implémentez et non sur les moyens d'y parvenir. Vos requêtes étant effectuées sur une copie locale de la base de données, vous aurez de plus l'impression d'une latence zéro, car les changements sont visibles immédiatement et propagés « en arrière-plan » sur le serveur. Sachez toutefois que le serveur peut tout à fait rejeter une modification et remettre votre copie locale à son état initial. Cela peut provoquer de drôles d'effets visuels si vous n'y êtes pas préparés. Toutefois, vous vous en doutez, il y a quelques précautions à prendre en matière de sécurité et de volume de données à transmettre à chaque client.

V-C. Publications et souscriptions

Il n'est pas souhaitable que l'intégralité de la base de données soit copiée chez chaque client. C'est la raison d'être des publications. Une publication est déclarée côté serveur et permet d'indiquer à Meteor quel sous-ensemble de vos données vous souhaitez envoyer à chaque client. Pour cela on utilise la méthode Meteor.publish() dont le premier argument est un nom arbitraire à donner à votre publication (qui sera utilisé par les souscriptions côté client) et le second une fonction qui renvoie un curseur (ou un tableau de curseurs) pour les documents pouvant être publiés.

 
Sélectionnez
Meteor.publish("myMessages", function () {
   return Messages.find({userId: this.userId});
});

L'expression this.userId contient l'identifiant de l'utilisateur connecté (ou null) quand utilisé au sein d'une publication. Une fonction de publication est évaluée à chaque fois qu'un client souscrit à cette publication. Cette souscription se déclare ainsi, côté client :

 
Sélectionnez
Meteor.subscribe("myMessages");

Le nom indiqué à la souscription doit correspondre à la publication souhaitée. Il est tout à fait possible de déclarer plusieurs publications portant sur la même collection. Un client pourra alors souscrire à l'une ou l'autre, ou même plusieurs en même temps (dans ce dernier cas, les documents seront fusionnés).

Afin que vos publications soient prises en compte, il faut prendre soin de supprimer le package autopublish. Ce package est installé par défaut. Son rôle est de publier automatiquement toutes les données à tous les clients. Cela peut être utile dans certains cas, durant la phase de développement. Il faut cependant s'assurer qu'il soit supprimé avant la mise en production de votre application (et bien avant si vous voulez tester vos publications) en utilisant la commande meteor remove :

 
Sélectionnez
meteor remove autopublish

V-D. Restrictions d'écriture

Par défaut, un client peut mettre à jour la base de données sans restriction. C'est ce que rend possible le package insecure. Tout comme le package autopublish, il ne sert qu'à faciliter certaines opérations de débogage et de test pendant le développement, mais est à bannir en production.

 
Sélectionnez
meteor remove insecure

Une fois ce package supprimé, plus aucun client ne pourra écrire dans la base de données. Il faudra donner les droits d'insertion, de mise à jour et de suppression explicitement. Pour ce faire, Meteor met à disposition les méthodes allow() et deny() sur les objets de collection.

Le code ci-dessous montre comment autoriser un utilisateur à insérer, modifier et supprimer ses propres documents :

 
Sélectionnez
Messages.allow({
   insert: function (userId, doc) {
      return (userId && doc.userId === userId);
   },
   update: function (userId, doc, fields, modifier) {
      return doc.userId === userId;
   },
   remove: function (userId, doc) {
      return doc.userId === userId;
   }
});

Comme vous pouvez le voir, la méthode allow() accepte des callbacks pour chaque type d'opération d'écriture dans la base de données. Ceux-ci renvoient un booléen indiquant si oui ou non l'opération est autorisée. Les callbacks d'insertion et de suppression reçoivent en paramètre l'identifiant de l'utilisateur qui souhaite écrire dans la base de données et le document concerné. Le callback de mise à jour, lui, reçoit quatre paramètres. L'identifiant de l'utilisateur passe en premier. En second, il s'agit du document à l'état avant modification. En troisième se trouvera un tableau contenant le nom des champs que le client souhaite modifier. Enfin, le dernier paramètre est le modificateur MongoDb qui a été utilisé pour effectuer la mise à jour, par exemple {$set: {subject: « Nouveau sujet »}}.

La méthode deny() fonctionne exactement de la même manière sauf que les règles définies avec deny() ont plus de poids que celles définies avec allow(). Plus exactement, en utilisant deny() vous pourrez interdire une opération même si elle a été autorisée avec allow().

VI. Introduction à la réactivité

Ces dernières années, beaucoup de frameworks JavaScript ont vu le jour, chacun apportant son lot de fonctionnalités et sa vision sur la manière de structurer une application JavaScript. Cependant, l'apport qui intéresse probablement la plupart des développeurs que nous sommes, est le rafraîchissement automatique des interfaces utilisateur. Fini le temps où il fallait manuellement répercuter chaque changement d'état sur tous les éléments de votre interface. Le JavaScript moderne est capable de traquer les changements qui opèrent dans vos données et de mettre à jour automatiquement les parties de l'interface concernées. AngularJS, Knockout et React sont un exemple de bibliothèques connues pour avoir implémenté une telle fonctionnalité, que l'on trouve sous différentes appellations. C'est également le cas de Meteor qui a appelé ce concept la « réactivité ».

En réalité, dans Meteor, la notion de réactivité n'est pas restreinte à l'interface, mais j'ai choisi cette image, car elle est facile à comprendre. La réactivité est la capacité d'un système à mettre à jour automatiquement ses objets quand les données dont il dépend changent. Meteor se veut être réactive de bout en bout. Cela ne veut pas pour autant dire que tout fonctionne comme par magie. Deux éléments fondamentaux entrent dans la composition d'un code réactif : le traitement réactif et la source de données réactive.

VI-A. Traitement réactif

Pour qu'un code soit exécuté à chaque fois que ses dépendances changent, vous devrez le placer dans un traitement réactif. Un traitement réactif est un contexte qui contient votre code et traque ses dépendances. Lorsqu'il est notifié d'un changement, il exécute à nouveau le code dont il a la charge.

Meteor exécute le code présent dans les templates avec un traitement réactif. C'est pourquoi il est aisé de créer une interface réactive. Mais il est possible d'exécuter du code arbitraire de manière réactive côté client, à l'aide de la fonction Tracker.autorun() :

 
Sélectionnez
Tracker.autorun(function () {
   console.log("Currently viewing message #" + Session.get("currentMessageId"));
});

Le code ci-dessus enregistre une fonction dans un traitement réactif. Cette fonction sera exécutée à chaque fois que la valeur de Session.get(« currentMessageId ») change. Cela fonctionne, car l'objet Session est une source de données réactive.

VI-B. Source de données réactive

Un traitement réactif n'est pas grand-chose sans source de données réactive. C'est cette dernière qui est responsable d'informer le traitement réactif d'un quelconque changement. Nous n'allons pas entrer dans le détail de la création d'une source de données réactive, mais sachez toutefois que le package Tracker, et plus particulièrement l'objet Tracker.Dependency, peut vous y aider. Meteor possède déjà un certain nombre de sources de données réactives comme les variables Session, les requêtes sur des collections, Meteor.user(), Meteor.userId(), etc.

À propos de l'objet Session, je souhaite y apporter une petite précision. Contrairement à ce que laisse croire son nom, il ne s'agit pas réellement d'un objet de session comme vous pourriez le penser. Voyez cet objet comme un moyen de stocker de manière globale des ensembles de clefs-valeurs. Le principal intérêt de cet objet est qu'il est réactif et il peut vous être utile dans vos templates notamment. En appelant Session.get('something') depuis un template, le template sera rafraîchi à chaque fois que vous appellerez Session.set('something', x).

VII. Préparer ses templates

Les templates sont les éléments qui vont vous permettre de composer l'interface de votre application. Un template contient classiquement du code HTML. Pour chaque vue ou élément récurrent dans votre interface, vous voudrez probablement créer un template. Pour créer un template, il suffit de créer un fichier HTML et d'y placer une balise <template> avec un attribut name :

 
Sélectionnez
<template name="hello">
   <div class="greeting">Hello {{name}}!</div>
</template>

Le contenu des fichiers HTML est automatiquement analysé et les templates rencontrés sont placés dans l'objet global Template sous le nom avec lequel ils ont été déclarés. Le template ci-dessus est ainsi accessible en appelant Template.hello. Ce composant possède des méthodes events() et helpers() pour y ajouter des événements ou des helpers. Il donne également accès aux callbacks rendered(), created() et destroyed() appelés aux différentes étapes du cycle de vie d'une instance de template.

Intéressons-nous maintenant à la manière d'utiliser des templates directement dans notre code HTML, en profitant par ailleurs de la réactivité. Pour cela, nous aurons besoin d'un fichier HTML qui contienne une balise <body> afin d'y appeler notre template :

 
Sélectionnez
<body>
   {{> hello}}
</body>

Cela affiche notre template hello au chargement de l'application. Pour cela nous avons utilisé la syntaxe {{> monTemplate}} qui permet d'afficher le contenu d'un template. Cette syntaxe est un élément du moteur de template utilisé par Meteor, Spacebars, qui est un dérivé de Handlebars. Le moteur de template met à disposition un certain nombre d'expressions qui vous permettent de rendre vos templates dynamiques. Ces expressions sont facilement identifiables, car elles sont entourées de doubles accolades {{ et }}. La plupart de ces expressions sont des helpers.

VII-A. Helpers

Nous avons vu que certains templates contenaient des expressions de la forme {{hello}}. Il peut s'agir de deux choses. Soit le template est exécuté dans un contexte où la variable hello existe et à ce moment-là elle est affichée. Cela peut être le cas si vous étiez en train d'itérer sur une collection de documents et que le document actuel (qui sera this) contient une propriété hello. Soit il existe un helper de ce nom. Sachez d'ailleurs que les helpers ont priorité si une propriété de même nom existe dans le contexte dans lequel vous vous trouvez.

La déclaration d'un helper se fait dans un fichier JavaScript au niveau du client :

 
Sélectionnez
Template.hello.helpers({
    name: function () {
       return "Will";
    }
)};

Nous venons de déclarer un helper sur le template hello qui porte le nom name et qui retourne du texte. Ainsi, le template hello affichera le contenu suivant :

 
Sélectionnez
<div class="greeting">Hello Will!</div>

Apportons maintenant de la réactivité à tout ça. Admettons que vous stockiez le nom de l'utilisateur actuel dans l'objet Session. C'est une source de données réactive, rappelez-vous. Nous pouvons donc faire en sorte que le template hello se rafraîchisse à chaque fois que cette donnée change :

 
Sélectionnez
Template.hello.helpers({
    name: function () {
       return Session.get("username");
    }
)};

Puisqu'il est peu courant de changer de nom, nous allons étudier un autre cas plus probable qui est l'affichage d'une liste de messages.

 
Sélectionnez

Template.hello.helpers({
    messages: function () {
       return Messages.find({userId: Meteor.userId()});
    }
)};

Nous profitons ici de la réactivité apportée par le curseur retourné par la méthode find(). Ce curseur étant aussi une source de données réactive, nous sommes sûrs que notre template sera réévalué à chaque fois que les résultats de cette requête changent. Modifions maintenant notre template pour utiliser notre nouveau helper :

 
Sélectionnez
<template name="hello">
   <ul>
   {{#each messages}}
      <li{{subject}}</li>
   {{/each}}
   </ul>
</template>

Vous remarquerez l'utilisation d'un élément nouveau, #each qui est un helper de bloc. C'est un helper particulier qui vous permet d'itérer sur une liste d'éléments. Il en existe d'autres comme #if, et il est possible d'implémenter les vôtres. Ici, nous faisons usage de #each pour parcourir les éléments renvoyés par le helper messages. Dans notre cas ce sont des documents qui possèdent une propriété subject. Vous pouvez donc constater qu'il est simple de créer des interfaces réactives en un temps limité. Il nous reste cependant à voir comment les rendre interactives.

VII-B. Événements

Il est facile d'ajouter des gestionnaires d'événement à un template. Pour cela, il suffit de passer la définition de vos événements à la fonction events() que vous appellerez sur votre template de la manière suivante :

 
Sélectionnez
Template.hello.events({
   'click': function (event) {
      console.log("J'ai cliqué quelque part");
   },
   'click .send': function (event) {
      console.log("J'ai cliqué sur un élément ayant la classe 'send'");
   },
   'keydown, click button': function (event) {
      console.log("Déclenché au clavier ou au clic sur un bouton");
   }
});

Le format d'un événement est simple. Vous devez spécifier le type d'événement (click, change, keydown, etc.), éventuellement associé à un sélecteur CSS pour restreindre l'événement à un élément précis. Vous pouvez séparer plusieurs événements par une virgule pour leur affecter le même gestionnaire d'événement. Le gestionnaire d'événement peut prendre en argument un objet contenant un certain nombre d'informations à propos de l'événement et des méthodes permettant d'agir sur sa propagation. Lorsque vous vous trouvez dans un gestionnaire d'événement, this contient les informations du contexte de l'élément qui l'a déclenché. Celui-ci est le contexte de données dans lequel l'élément apparaît dans le template. Cela veut dire, par exemple, que si vous itérez sur une collection de documents avec #each, et que pour chaque document vous affichez un lien, le gestionnaire d'événement prenant en charge le clic sur le lien, aura dans this le document concerné.

VIII. Procédures distantes

Bien qu'il soit possible d'effectuer des requêtes côté client, il arrive un moment où vous aurez besoin d'effectuer des opérations plus sensibles côté serveur. C'est notamment le cas si l'action d'un utilisateur nécessite l'accès à un ensemble de données qui n'est pas directement accessible à ce client. Pour cette raison, et beaucoup d'autres, vous voudrez créer des méthodes Meteor qui ne sont rien d'autre que des procédures distantes.

Pour déclarer des procédures distantes vous devez, côté serveur, faire appel à la fonction Meteor.methods() qui prend en paramètre un objet dont chaque clef est le nom de la méthode et la valeur son implémentation :

 
Sélectionnez
Meteor.methods({
   add: function (a, b) {
      return a + b;
   },
   substract: function (a, b) {
      return a - b;
   }
});

Pour invoquer une méthode côté client vous pouvez utiliser la fonction Meteor.call() ou Meteor.apply(). Elles ont la même finalité, la seule différence est que Meteor.apply() prend les paramètres à transmettre à la méthode dans un tableau et dispose de quelques options pour contrôler la manière dont la méthode est invoquée. Voici comment invoquer la méthode add qui a été définie côté serveur :

 
Sélectionnez
var result = Meteor.call('add', 3, 5);

Il vous est aussi possible de retourner une erreur dans une méthode. Pour cela, Meteor met à disposition l'objet Meteor.Error qui représente une exception qui peut être transmise au client :

 
Sélectionnez
Meteor.methods({
   testError: function () {
      throw new Meteor.Error(500, "Une erreur est survenue");
   }
});

Dans une méthode Meteor, la propriété this.userId contient l'identifiant de l'utilisateur courant ou null s'il n'est pas identifié. Si vous avez implémenté vous-même une gestion de comptes utilisateur, vous pouvez définir la valeur de cette propriété dans une méthode avec this.setUserId(). Cela peut cependant être géré automatiquement avec le système de gestion de comptes mis à disposition par Meteor.

IX. Utiliser les packages

Toutes les fonctionnalités de Meteor sont regroupées dans des packages. Certains font partie du cœur de Meteor et d'autres sont optionnels. Pour rechercher un package, vous pouvez tout simplement utiliser la commande meteor search. Pour lister uniquement les packages installés dans votre application, utilisez la commande meteor list.

 
Sélectionnez
#Recherche d'un package contenant la chaîne "router" dans son nom
meteor search router

L'ajout et la suppression d'un package se font avec les commandes meteor add <package> et meteor remove <package>. En plus des packages standards livrés avec Meteor et de ceux de la communauté, vous pouvez placer vos propres packages dans le répertoire packages à la racine de votre application. Cela vous permet notamment d'écrire votre application de manière modulaire.

Les packages vous permettent de disposer de fonctionnalités supplémentaires avec un minimum d'efforts. Cela vous permet de vous concentrer sur ce qui est réellement important. Le meilleur exemple qui pourrait montrer l'intérêt de l'usage des packages est l'intégration du système de gestion de comptes utilisateur dans votre application.

IX-A. Gestion de comptes utilisateur

Meteor dispose d'un système de gestion de comptes utilisateur mis à disposition à travers ses packages standards. Ces packages ne sont pas intégrés par défaut, mais vous pouvez les ajouter à tout moment. Meteor a fait en sorte de séparer chaque aspect de la gestion de comptes dans un package différent afin que vous puissiez sélectionner ce dont vous avez réellement besoin. Le cœur du système se trouve donc dans le package accounts-base. Celui-ci est ajouté automatiquement quand vous intégrez un des gestionnaires d'authentification mis à disposition. Parmi ces gestionnaires d'authentification on trouve accounts-password qui est le système vous permettant de gérer l'authentification par mot de passe. Il en existe d'autres qui apportent l'authentification via des services tiers comme accounts-facebook, accounts-google et accounts-twitter.

Pour l'exemple, nous allons ajouter la gestion de comptes par mot de passe dans une application existante :

 
Sélectionnez
meteor add accounts-password

En faisant cela, le package accounts-base est ajouté implicitement puisqu'il s'agit d'une dépendance du package accounts-password. Le package accounts-base, qui est le cœur du système, ajoute la gestion de documents utilisateur dans la base de données. Il apporte également l'intégration de la propriété userId dans les publications et les méthodes Meteor que nous avons vues précédemment. Enfin, il met à disposition une API pour accéder aux documents utilisateur et gérer l'authentification.

Le package accounts-password apporte, lui, une API pour gérer très simplement l'authentification et la création d'utilisateurs avec mot de passe. Vous aurez donc accès à des fonctions comme Accounts.createUser(), Accounts.forgotPassword(), Accounts.verifyEmail() et d'autres pour implémenter l'enregistrement et la connexion d'utilisateurs.

Meteor va même jusqu'à proposer un package accounts-ui qui va mettre à disposition une interface par défaut pour les différents gestionnaires d'authentification présents dans votre application. Vous aurez alors accès à un template loginButtons que vous placerez dans votre code HTML à l'endroit où vous souhaitez voir apparaître le widget apportant tous les contrôles pour la connexion ou l'enregistrement d'utilisateurs.

Pour profiter d'une interface clef en main pour la gestion de comptes dans votre application, commencez par ajouter le package :

 
Sélectionnez
meteor add accounts-ui

Enfin, éditez le code HTML là où vous souhaitez voir apparaître les contrôles de login :

 
Sélectionnez
<body>
   <div class="myNavbar">
      <div class="title">Mon Application</div>
      <div class="login">
         {{> loginButtons}}
      </div>
   </div>
</body>

Et voilà, c'est aussi simple que ça. Vos utilisateurs peuvent ainsi créer un compte dans votre application et s'y connecter. Le comportement de ce widget est contrôlable via la fonction Accounts.ui.config(). Elle prend en paramètre un objet avec différentes clefs. Celle qui vous intéressera probablement le plus est passwordSignupFields qui vous permet de contrôler les éléments facultatifs et obligatoires lors de l'enregistrement d'un utilisateur. Pour rendre le nom d'utilisateur et l'email obligatoires vous pourrez procéder ainsi, côté client :

 
Sélectionnez
Accounts.ui.config({
   passwordSignupFields: 'USERNAME_AND_EMAIL'
});

Les autres valeurs de configuration possibles sont USERNAME_AND_OPTIONAL_EMAIL (nom d'utilisateur obligatoire, email facultatif), USERNAME_ONLY (uniquement un nom d'utilisateur) et EMAIL_ONLY (uniquement un email).

X. Aller plus loin

Nous avons vu les bases de Meteor et j'espère que vous commencez à entrevoir le potentiel de cette plateforme. Une fois que vous en maîtrisez les concepts fondamentaux, vous êtes capables de produire une application en un temps record. Pour cela, vous pouvez également vous appuyer sur les créations de la communauté qui a déjà publié un certain nombre de packages tiers, destinés à vous simplifier la vie.

La dernière étape dans la production de votre application sera son déploiement. Là encore, Meteor peut vous aider à obtenir un résultat très rapidement, si vous consentez à utiliser l'infrastructure de l'équipe Meteor et un sous-domaine associé.

X-A. Déployer son application

Une fois que votre application sera prête, vous voudrez certainement la rendre publique. Il y a plusieurs moyens de procéder, mais je ne vais parler que de l'option la plus rapide et la plus simple. L'équipe de Meteor met à disposition son infrastructure pour déployer votre application sur un sous-domaine de meteor.com. Pour cela il y a la commande meteor deploy :

 
Sélectionnez
meteor deploy mon-application.meteor.com

Il faut évidemment que le sous-domaine ne soit pas déjà utilisé. La première fois que vous déployez une application, on vous demandera d'entrer une adresse email pour vous créer un compte développeur. On vous enverra alors un email pour activer votre compte, choisir un identifiant et un mot de passe. Une fois ceci fait, vous pourrez utiliser la commande meteor login pour vous connecter avec votre compte développeur. Lorsque vous êtes connecté, vous pouvez déployer votre application avec meteor deploy.

Il vous est possible d'autoriser d'autres développeurs à déployer votre application et accéder à ses données avec la commande meteor authorized. La commande suivante liste les développeurs autorisés pour votre application :

 
Sélectionnez
meteor authorized mon-application.meteor.com

Vous pouvez ajouter un nouvel utilisateur de la manière suivante :

 
Sélectionnez
meteor authorized mon-application.meteor.com --add pierre

De la même manière vous pouvez utiliser meteor authorized <application> --remove <username> pour supprimer l'autorisation d'un utilisateur. Sachez que le fait d'autoriser un utilisateur lui confère les mêmes droits que vous. Il pourra ainsi ajouter et supprimer d'autres utilisateurs.

Pour finir, la commande meteor list-sites vous permet de lister toutes les applications déployées sur les infrastructures Meteor avec votre compte.

XI. Conclusion

Meteor est une plateforme qui a pour objectif de simplifier au maximum le développement d'une application Web. Son orientation temps réel fait qu'elle est parfaitement adaptée au développement d'une application Web monopage. Vous n'avez pas à vous soucier des moyens techniques à mettre en œuvre pour faire communiquer votre serveur et vos clients. Meteor le fait pour vous. Vous pouvez vous concentrer sur l'essentiel et ça, c'est important. Si vous ne souhaitez pas l'adopter pour une application de production, elle peut toujours vous servir pour du prototypage. Tester un concept n'aura jamais été aussi rapide. Ce qui est sûr, c'est que Meteor a des arguments solides pour se faire une place dans le monde des frameworks Node.js.

XII. Ressources utiles

Voici un certain nombre de liens qui m'ont été utiles pour la rédaction de ce tutoriel et qui peuvent vous aider dans l'apprentissage de Meteor :

XIII. Remerciements

Je souhaite remercier Xavier Lecomte pour l'aide apportée à la publication de cet article, ainsi que Philippe Duval et FRANOUCH pour la relecture orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 Sören Ohnmeiss. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.