jul. 2013

CloudAtlas #2 : une application pour tester Meteor.JS [tuto]

Cet article fait suite au premier article de ce blog et est issu d’un tutoriel disponible sur la page github du projet « cloudAtlas ». Il en reprend la seconde partie décrivant le développement d’une application pour tester Meteor.JS.

Créer la structure de mon application

Précisons deux choses avant d’amorcer la structuration de notre application. Tout d’abord, nous avons déjà souligné que le développement d’une application avec Meteor se fait intégralement en Javascript (ce qui n’est pas entièrement vrai d’ailleurs puisque une application Meteor fonctionne comme un site Internet et nécessite donc une base de HTML et de CSS). C’est-à-dire que notre code Javascript sera, par défaut, à la fois exécuté du côté du navigateur et du côté serveur. Il sera donc nécessaire, bien souvent, de séparer ce qui doit être lu par l’un et ce qui doit être lu par l’autre. Pour séparer le code « client » du code « serveur », Meteor propose deux solutions :

  • ce que l’on appelle les « booleans » Meteor.isClient et Meteor.isServer, qui est la solution par défaut ;
  • une séparation des dossiers /client et /serveur.

Puisque nous n’allons pas vraiment toucher au côté serveur, nous garderons ici la solution par défaut pour ne pas trop se compliquer. Le code complet disponible sur github vous montre cependant que la deuxième solution fonctionne évidemment très bien.

Un boolean est comme une condition (d’où la présence du if d’ailleurs), qui renvoie un accord quand elle est remplie, ou un désaccord quand elle ne l’est pas. Ce que signifie donc Meteor.isClient c’est que ce qui suit (entre les premières accolades) ne doit être exécuté que du côté client (la condition), et évidemment Meteor.isServer que du côté serveur. La structure du fichier .js ressemble donc pour l’instant à ceci :

if (Meteor.isClient) {
}
if (Meteor.isServer) {
}

Le code qui n’est présent dans aucune des deux conditions est exécuté des deux côtés. Pour notre part, nous ne mettrons principalement notre code que dans la première accolade, du côté client donc. Mais nous y reviendrons plus tard, car un deuxième point important est à noter.

Pour générer du contenu dynamiquement, Meteor utilise le système de template Handlebars qui permet de désigner les parties variables de notre code par des double accolades ouvrantes et fermantes {{ }} . Rien de très difficile ici, il suffira seulement de coder une page HTML toute simple en ajoutant ces marqueurs pour les parties dynamiques. Chaque variable étant définie par un template lui signifiant ce qu’elle doit afficher, il sera par ailleurs nécessaire de préciser dans le même code ces templates. Ainsi, notre page HTML comprendra trois parties : le head, le body, et les templates.

Nous souhaitons également que notre application se structure en trois parties :

  1. une partie où l’on enregistre nos items, nos « clouds » donc, via un formulaire nous permettant de renseigner les trois champs mentionnés plus haut ;
  2. une partie affichant tous les « clouds » enregistrés, quelque soit les champs, qui nous permettra par ailleurs de vérifier si le cloud que l’on enregistre est effectivement sauvegardé ;
  3. une partie dans laquelle il est possible de renseigner le troisième champ de référence et qui nous affichera en retour tous les « clouds » appartenant à ce champ. Puisque l’objectif de l’application est avant tout de trouver les « clouds » en fonction de champs spécifiques, il est préférable d’un point de vue interface que cette dernière partie apparaisse en première position sur la page.

Voyons à quoi ressemble notre code HTML.

Tout d’abord, il est nécessaire de définir un titre dans les balises head, sans avoir à préciser cependant la mention du <!DOCTYPE>. Puis dans le body, nous faisons apparaître les trois parties de notre application :

<body>
    <div id="app">
        <h1>CloudAtlas</h1>
        <div id="cloud-search">
            {{> cloudSearch}}
        </div>
        <div id="cloud-form">
            {{> cloudForm}}
        </div>
        <div id="cloud-atlas">
            {{> cloudAtlas}}
        </div>
    </div>
</body>

Les trois parties sont intégrées dans une <div> englobante (« app ») qui facilitera la manipulation par la suite avec le CSS. L’application commence par un titre sobrement appelé « CloudAtlas ». Le code de chaque partie est à préciser : il s’agit tout simplement de trois <div> possédant chacune un id distinctif afin de pouvoir les manipuler plus facilement et désignant la position des parties. Chaque partie ne contient qu’une variable mise entre double accolades et désignant chacune le template à laquelle elle se réfère. Bien évidemment, chacun de ces 3 templates est à définir directement après la fermeture du body.

L’ordre est indifférent. Suivons l’ordre logique du processus et commençons par celui du milieu, c’est-à-dire le formulaire de création de « clouds », dont le template est intitulé ci-dessus « cloudForm ». La balise pour définir un template est tout simplement la balise <template> </template> et celle, plus connue, pour un formulaire est <form> </form>. On obtient ainsi ceci :

<template name="cloudForm">
     <form>
        <p>REGISTER A CLOUD</p>
        <input id="cloudTitle" placeholder="Name" required>
        <input id="cloudSubTitle" placeholder="First Name" required>
        <select id="cloudType" required>
                <option value="0">CHOOSE</option>
                <option value="field1">field1</option>
                <option value="field2">field2</option>
                <option value="field3">field3</option>
                <option value="field4">field4</option>
                <option value="field5">field5</option>
        </select>
        <textarea rows="4" cols="40" id="cloudComment" placeholder="Comment" required></textarea>
        <div>
            <input type="submit" value="Add New Cloud">
      </div>
    </form>
</template>

On retrouve dans ce template uniquement un formulaire HTML très simple, comprenant deux inputs (les deux champs de titre et de sous-titre qui qualifient le « cloud » que l’on enregistre), un bouton select pour classer le cloud dans un des champs de référence (les 5 fields ici), un textarea ajouter un commentaire, et enfin le bouton submit pour demande à l’application d’enregistrer tous ces renseignements. Nous verrons par la suite ce qui se passe derrière tout ca.

Demandons désormais à l’application d’afficher tous les « clouds » que l’on enregistre. Bien sûr, ca ne fonctionnera pas encore puisque nous n’avons pas encore attaquer le Javascript. Cette partie correspond au template intitulé ci-dessus « cloudAtlas ». On cherche pour l’instant à simplement afficher dans une liste <ul> faite de <li> donc les différents « clouds ». Pour nous y retrouver sur l’interface utilisateur, il est utile ici d’ajouter un titre de section :

<template name="cloudAtlas">
    <h2>//See All Clouds</h2>
    <ul >
        {{#each clouds}}
        <li>
            {{this.cloudTitle}} {{this.cloudSubTitle}}&nbsp;: {{this.cloudType}}
            <br/>Comment&nbsp;: {{this.cloudComment}}
        </li>
        {{/each}}
    </ul>
</template>

Ici le code se corse un tout petit peu. Nous nous sommes notamment aidé d’un template helper, en l’occurrence ici le processus {{#each object}} … {{/each}} qui se propose d’appliquer ce qu’il contient pour chaque (each) « object » différent. Ainsi ici, à chaque « clouds » correspondra un <li> contenant le code inscrit. Jetons d’ailleurs un petit coup d’œil à ce code : il se propose de renvoyer successivement le titre et le sous-titre du cloud, puis, séparé par deux points, le champ de référence du cloud. Il va ensuite à la ligne pour présenter le commentaire éventuellement ajouté au cloud lors de son enregistrement. Ces informations seront ainsi données en liste pour chaque cloud enregistré. Notons ici que les 4 variables entre accolades (cloudTitle, cloudSubTitle, cloudType et cloudComment) correspondent aux 4 ids renseignés plus pour les sections correspondantes du formulaire. Cependant, ce n’est pas aux ids que les 4 variables font référence. Les 4 variables font en effet l’objet d’une définition qui leur est propre dans le code Javascript. C’est dans ce code qu’il faudra renseigner que ces variables se rapportent aux 4 champs correspondants du formulaire. Nous verrons ca très bientôt, attardons- nous avant pour finir sur le dernier template, intitulé « cloudSearch », et permettant la recherche des « clouds » en fonction d’un champ de référence.

Ce dernier template est paradoxalement plus simple et plus compliqué à la fois. En effet, plus simple car il ne fait finalement qu’un mixe entre les deux autres templates : il se compose ainsi d’un formulaire afin de recueillir le champ de recherche, et d’une liste qui affiche les résultats. Mais également plus compliqué car il demande plus d’abstraction dans la mesure où, tel quel, et contrairement aux autres templates, il ne fait pas exactement ce qu’on lui demande. En effet, difficile de demander au template de seulement afficher les résultats correspondant au champ renseigné dans le même template. Il nous faudra donc, par la suite, contourner la difficulté, en passant notamment par jQuery afin de travailler directement sur l’affichage des items de la liste. Gardons à l’esprit qu’il nous suffit pour l’instant de définir dans le template l’affichage d’une section pour renseigner le champ recherché et l’affichage des « clouds » :

<template name="cloudSearch">
    <form>
        <p>SEARCH FOR A CLOUD</p>
        <select id="search-for-a-cloud" required>
            <option value="0">CHOOSE</option>
            <option value="field1">field1</option>
            <option value="field2">field2</option>
            <option value="field3">field3</option>
            <option value="field4">field4</option>
            <option value="field5">field5</option>
      </select>
   </form>
   <ul>
       {{#each clouds}}
   <li>
           {{this.cloudTitle}} {{this.cloudSubTitle}}</span>
       <br/>Comment&nbsp;: {{this.cloudComment}}
       </li>
       {{/each}}
   </ul>
</template>

Ce dernier template est ainsi sensiblement similaire aux deux autres : dans un premier temps un bouton select permet de sélectionner un des champs de référence, dans un second temps une liste affiche les résultats en présentant titre, sous-titre et commentaire (nul besoin du champ évidemment).

Le fichier HTML est pour l’instant complet, voyons ce qui se cache derrière !

Behind the scene

Dans le fichier cloudAtlas.js, nous travaillerons pour l’instant uniquement du côté du serveur, c’est à dire au sein de la structure :

if (Meteor.isClient) {
}

L’unique ligne que nous ajoutons en dehors de cette structure est le nom de notre base de donnée qui recueillera tous nos clouds. Cette ligne est importante car elle crée pour notre application ce que Meteor appelle une collection. Tous nos « clouds » seront sauvegardés par Meteor (avec l’aide non négligeable des bases de données MongoDb) dans cette collection. C’est pourquoi il est important de la déclarer en premier (c’est avec elle que nous allons constamment interagir) et en dehors des conditions, afin que la collection soit définie à la fois côté client et côté serveur. Amorçons donc le fichier cloudAtlas.js par cette ligne :

var Atlas = new Meteor.Collection('clouds');

Elle signifie simplement que l’on crée une nouvelle collection qui s’intitule «Atlas» et qui est constituée de « clouds ».

Meteor sait désormais que tout ce que l’on fait depuis le début, c’est simplement créer des items appelés « clouds ».

Du côté du serveur, quand un cloud est créé, il est automatiquement ajouté à la collection Atlas, et est donc écrit dans une base de donnée mongo, et y reste tant qu’on ne lui a pas dit le contraire. Du côté client, la collection Atlas se connecte en temps réel au serveur et se met à jour automatiquement, créant comme une copie de la collection Atlas existante côté serveur. Le navigateur, en exécutant le code, sait alors quels clouds de cette collection il doit afficher.

Il nous faut désormais préciser le comportement de chaque template dans le fichier Javascript. Tout comme le fichier HTML, le fichier Javascript est composé de 3 parties correspondantes aux trois templates. Reprenons-les un par un en suivant l’ordre logique que nous avons déjà pu suivre.

Le formulaire d’enregistrement des « clouds » comprend un bouton « submit » qui se traduit en Javascript sous la forme d’un événement. Cet événement, l’appui sur le bouton submit, déclenche plusieurs comportements qui doivent permettre à Meteor d’enregistrer les champs renseignés dans le formulaire. Meteor possède sa propre grammaire dont il est évidemment très intéressant et utile de prendre connaissance, disponible sur sa documentation sur son site. Dans Meteor, un événement se code ainsi :

Template.cloudForm.events = {
  'submit'&nbsp;: function (e, tmpl) {
    e.preventDefault();
  }
 };

La première ligne signifie que le template dont le nom est « cloudForm » attend un événement qu’il définit à la deuxième ligne comme provenant d’un bouton ‘submit’. L’appuie sur un tel bouton déclenche alors une fonction anonyme qui prend deux arguments (e, pour événement, et tmpl, pour template). La première action de cette fonction (troisième ligne) est d’empêcher le fonctionnement par défaut de bouton submit, afin qu’il déclenche ce qu’on lui demande de déclencher.

Ce qu’on lui demande de déclencher est justement à préciser juste après, c’est-à-dire avant les deux dernières accolades fermantes (l’avant dernière ferme la fonction déclenchée par le bouton submit, et la dernière l’instruction déclenchée par l’événement).

Et que lui demande-t-on ? Pleins de choses ! Dans l’ordre :

d’abord on demande à l’application de créer un nouveau « cloud » en lui attribuant les renseignements apportés dans chacun des 4 champs ;

  • ensuite on lui demande d’insérer ce « cloud » dans notre collection, afin de ne pas le perdre et qu’il soit bien conservé avec les autres ;
  • on lui demande alors de vider les champs du formulaire pour laisser celui-ci aussi propre après utilisation qu’avant ;
  • enfin, parce que l’utilisateur ne voit pas toute la machinerie, on lui envoie un jolie message pour lui dire que le « cloud » est bien ajouté.

Allé hop, codons tout ca !

L’enregistrement des clouds est un peu plus complexe que le reste, mais rien d’insurmontable. L’idée est de récupérer chacun des champs renseignés dans le formulaire pour les attribuer à un objet qu’il faudra définir afin qu’il soit créer… Javascript nous propose ainsi très facilement de créer un objet qui aura toujours les mêmes propriétés. Il suffit d’abord de déclarer cet objet (pour le créer, un peu comme la parole divine), puis de préciser quelles sont ses propriétés, puis… c’est tout !

var newCloud = {
 cloudTitle: tmpl.find("#cloudTitle").value,
 cloudSubTitle: tmpl.find("#cloudSubTitle").value,
 cloudType: tmpl.find("#cloudType").value,
 cloudComment: tmpl.find("#cloudComment").value
 };

Notre objet s’appelle donc « newCloud » et il comprend 4 propriétés : cloudTitle, cloudSubTitle, cloudType et cloudComment. C’est à ces 4 propriétés que font référence les variables entre accolades dans le fichier HTML. En effet, pour chaque « clouds », nous avons vu que la helper function demande d’afficher tour à tour chacune de ces propriétés. Il est donc nécessaire, comme nous avons pu le souligner, de préciser ici à quoi se rapportent ces propriétés. Ainsi, pour la propriété cloudTitle par exemple, Meteor demande au template tmpl en question (celui définit avec le bouton ‘submit’, donc il s’agit bien du template du formulaire) de trouver (find) la balise ayant l’id (#) « cloudTile » et d’en récupérer la valeur (.value) qui correspond en réalité à ce que l’utilisateur y a inscrit. Pour le cas du bouton select, la valeur n’est autre que ce que l’utilisateur a choisi. Cet objet Javascript nous permet donc bien d’enregistrer tous les champs enregistrés par l’utilisateur et de les lier dans un cloud.

Il ne reste plus qu’à insérer celui-ci dans notre collection :

Atlas.insert(newCloud);

Et le tour est joué !

Pour ce qui suit il est particulièrement pratique d’utiliser jQuery qui simplifiera notre code. Pour cela, il suffit d’ajouter le package jQuery à Meteor depuis le Terminal avec la commande :

meteor add jquery

jQuery nous permet notamment d’aisément et rapidement effacer les valeurs des champs du formulaire. Prenons bien soin d’ajouter ces quelques lignes de jQuery après avoir enregistrer les valeurs renseignées !

$('#cloudTitle').val('');
 $('#cloudSubTitle').val('');
 $('#cloudType').val('');
 $('#cloudComment').val('');

Ces lignes de codes remplacent simplement les valeurs des champs du formulaire par une chaîne de caractères vide.

Enfin, il ne nous reste plus qu’à indiquer à l’utilisateur que son « cloud » a bien été enregistré. Faisons les choses bien en évitant de passer par les fenêtres d’alerte rudimentaires de Javascript. Écrivons plutôt directement dans le HTML un message que l’on cachera par défaut et que l’on affichera à la fin du processus de création du « cloud » afin de conclure celui-ci par une validation envoyée à l’utilisateur.

Dans le HTML, ajoutons ces lignes dans le body :

<div class="added">
    Added&nbsp;!
</div>

Puis dans le Javascript, avant la fermeture de la fonction déclenchée par le submit :

$('.added').fadeIn('slow', function() {}).delay(1000).fadeOut('slow', function() {});

Le message s’affichera ainsi doucement puis s’effacera tout aussi doucement au bout d’une seconde.

Et n’oublions pas enfin, au cas où ce n’est pas déjà fait, de bien fermer la fonction et le template :

  }
 };

Le premier template est ainsi défini, ne reste plus qu’à s’attaquer aux deux autres, nettement plus rapide à définir !

Pour le template intitulé « cloudAtlas », il nous faut quelque chose de très simple : on veut seulement afficher toute notre collection :

Template.cloudAtlas.clouds = Atlas.find({});

Ici encore la grammaire de Meteor est particulière mais très claire. Le template « cloudAtlas » applique aux « clouds » qu’il contient une fonction : trouver tous les « clouds » contenus dans la collection « Atlas » et les afficher (return) tous. Il n’en faudra pas plus !

Enfin, la dernier template demandera une petite astuce supplémentaire. Comme nous le disions, nous demandons à ce template d’afficher également tous les « clouds », mais de masquer tous ceux dont le champ de référence ne nous intéresse pas. Plus précisément, nous ferons exactement l’inverse : par défaut tous les « clouds » seront cachés, et nous allons demander à l’application, grâce à jQuery, d’afficher uniquement ceux qui nous intéressent. Voyons ca de plus près.

D’abord, demandons à Meteor de dire au template de nous renvoyer tous les « clouds » de la collection :

Template.cloudSearch.clouds = Clouds.find({});

Puis, dans le CSS, masquons les par défaut :

.item {
  display: none;
}

Cela fonctionne évidemment uniquement si la classe « item » a bien été attribué à chaque cloud du template « cloudSearch ». Modifions donc cette partie du template de façon à ajouter la classe :

<ul>
    {{#each clouds}}
    <li>
     {{this.cloudTitle}} {{this.cloudSubTitle}}<br/>Comment: {{this.cloudComment}}
    </li>
    {{/each}}
</ul>

Il nous reste désormais à filtrer l’affichage de ces « clouds » en fonction du champ que l’on cherche. C’est ici qu’apparaît une petite subtilité. Chaque cloud est enregistré selon un champ référent unique. Quand on souhaitera rechercher par exemple le champ « field1 », nous aimerions qu’apparaissent uniquement les « clouds » avec ce champ, évidemment. Il nous suffit de considérer ce champ de référence comme une classe à part entière, afin que jQuery puisse agir dessus : ainsi, quand un utilisateur sélectionne un champ « field1 », l’application lui affichera tous les clouds avec la classe « field1 ». Il nous faut alors préciser 3 détails :

  1. Dans le HTML, modifions l’affichage des clouds en ajoutant à chaque item la classe correspondant à son champ de référence, c’est à dire « cloudType » :
<ul>
    {{#each clouds}}
    <li>
    {{this.cloudTitle}} {{this.cloudSubTitle}}</span>
    <br/>Comment&nbsp;: {{this.cloudComment}}
    </li>
    {{/each}}
</ul>
  1. Dans le fichier Javascript, ajoutons les lignes de code jQuery nécessaires au comportement voulu. Celles-ci sont exécutées au moment où le DOM est chargé, ce qui se traduit dans Meteor par le démarrage du côté client :
Meteor.startup(function() {
  $("#search-for-a-cloud").change(function(){
    $('.item').hide();
    $('.'+$(this).val()).show();
  });
});

Ces lignes précisent ainsi qu’au moment où une option du bouton select est choisie (c’est-à-dire au moment où un champ de recherche est sélectionné), jQuery récupère la valeur de ce bouton (correspondant, nous l’avons dit, au choix effectué par l’utilisateur, donc à la variable « cloudType » qui prend les valeurs field1, field2, field3, etc.) en lui ajoutant en entête un point lui permettant d’être considérée comme une classe. Cette classe est ainsi reconnue comme un objet jQuery sur lequel s’applique la fonction .show() : jQuery affichera tous les « clouds » qui ont pour classe le champ sélectionné par l’utilisateur. Au milieu de ce code s’insère par ailleurs une ligne afin de systématiquement tout cacher, afin de n’afficher que les « clouds » recherchés.

  1. Un dernier point important porte sur la façon de nommer ces champs. Puisque ces champs sont transformés en classes, il est important qu’ils ne comprennent pas d’espace. Mieux vaut donc préférer « field1 », « field-1 » ou encore « field_1 » par exemple à « field 1 » qui ne fonctionnera pas.

Finaliser l’application

Nous ne nous attarderons pas là-dessus ici mais il ne faut pas oublier d’habiller un peu son application en travaillant sur le fichier CSS de la même manière que pour un site web.

cloudatlas application

Par ailleurs, dès les premières lignes, la documentation de Meteor nous apprend que par défaut une application Meteor comprend un package « autopublish » et un package « insecure » qui permettent à n’importe quel utilisateur de respectivement lire tout ce qui se trouve sur la base de donnée et écrire ce qu’il veut sur celle-ci. La première n’est pas très dangereuse pour nous, chaque cloud a de toute façon vocation a être publique. Cependant la deuxième peut-être plus dangereuse et nécessite qu’on la retire pour la production, par une simple commande dans le Terminal, une nouvelle fois :

meteor remove insecure

Enfin il ne nous reste plus qu’à lancer l’application pour ses premiers tests en dehors du localhost et afin qu’elle vienne à la rencontre de son public. Là encore Meteor nous simplifie vraiment la vie en proposant pour chaque application un service d’hébergement gratuit avec la simple commande de Terminal :

meteor deploy CeQueJeVeux.meteor.com

Notre application est ainsi désormais disponible en ligne avec le sous-domaine de notre choix.

Il est par ailleurs possible d’héberger l’application avec notre propre domaine :

meteor deploy www.monsite.com

Ou encore de le télécharger sous forme d’archive pour l’insérer sur son propre site :

meteor bundle cloudAtlas.tgz

Dans ce dernier cas il est important de noter que Meteor dépend de Node.js et de MongoDb. L’application fonctionnera donc uniquement sur un serveur supportant ces deux technologies.

Bien sûr il reste tant de choses à dire sur Meteor ! Nous n’avons pu ici qu’effleurer ses potentialités alors que le framework n’en est actuellement qu’à sa version d’essai. Il n’en demeure pas point que Meteor se présente déjà comme un outil indispensable pour les applications de demain.