Développer une application web avec Vue.js et Vue CLI, mise en œuvre des concepts via un exemple (partie 2)

L’objectif de cet article en plusieurs parties est de vous présenter le framework web JavaScript Vue.js en se focalisant sur les principaux concepts au travers d’un exemple unique.

Les différentes parties de cet article sont détaillées ci-dessous :

Lors de l’écriture de l’article, nous avons utilisé la version 2 de Vue.js et la version 3 de Vue CLI.

Cette deuxième partie présente les principaux concepts de Vue.js au travers d’un exemple.

Le plan est le suivant :

  • présentation de l’exemple qui servira de fil rouge ;
  • création du squelette du projet PollDLE avec Vue.js et Vue CLI ;
  • initialisation des modèles et des vues pour les différents composants ;
  • mise en place des bindings entre les modèles et les vues via l’utilisation du templating (interpolation et les directives) ;
  • utilisation des propriétés calculées (computed) et des observateurs (watch) ;
  • détail du fonctionnement d’un composant pour savoir développer, instancier et dialoguer avec un composant ;
  • invocation d’un service web Rest pour modifier ou retourner la valeur d’un modèle ;
  • paramétrage du système de routage pour les changements de valeurs d’URL.

Les codes source pour les exercices sont disponibles sur le dépôt Git suivant : https://github.com/mickaelbaron/vuejs-polldle-tutorial-src (pour récupérer le code, faire : git clone <https://github.com/mickaelbaron/vuejs-polldle-tutorial-src>).

Présentation de l’exemple : PollDLE

L’exemple qui nous servira de fil rouge est appelé PollDLE pour Poll (Sondage) et la dernière partie de Doodle (un outil de planification très simple d’emploi). Il s’agira donc d’une application pour créer un sondage (un titre et des options), voter à un sondage (un choix possible) et afficher les résultats d’un sondage.

La couche client (front-end) sera réalisée avec Vue.js et Bootstrap pour le CSS tandis que la couche serveur (back-end) est écrite en Java. Pour cette dernière partie, nous ne détaillerons pas sa mise en place, elle est déjà codée. Elle s’appuie sur la spécification MicroProfile en utilisant les composants JAX-RS et CDI et en s’appuyant sur l’implémentation fournie par KumuluzEE.

Concernant la partie graphique, il y aura trois écrans pour la création, le vote et la consultation d’un sondage.

Ci-dessous est présenté l’écran pour la création d’un sondage. Un premier champ de texte permet de saisir le titre du PollDLE. Un second champ de texte permet de saisir une nouvelle option d’un PollDLE. La modification d’une option n’est pas autorisée. Pour cela il faudra la supprimer via les boutons situés sur la droite de chaque option d’un PollDLE. Un bouton intitulé « Clear All PollDLE Options » permet de supprimer l’intégralité des options d’un PollDLE. Le titre et le nombre d’options d’un PollDLE sont résumés sur le panneau intitulé « Summary of your PollDLE ». Enfin pour valider la création d’un PollDLE, il suffira de cliquer sur le bouton « Create PollDLE » qui redirigera automatiquement l’utilisateur vers le deuxième écran.

Ecran pour la création d'un PollDLE

Ci-dessous est présenté l’écran pour voter à un sondage. Cet écran reprend le titre du PollDLE et transforme les différentes réponses sous la forme de bouton radio à choix unique. Un bouton « Vote » permet de valider son vote. Veuillez remarquer dans la barre d’adresse, la valeur 1 qui correspond à l’identifiant fonctionnel donné à ce PollDLE. Pour accéder au vote d’un PollDLE, il suffira donc de spécifier dans la barre d’adresse son identifiant.

Ecran pour vôter à un sondage PollDLE

Ci-dessous est présenté l’écran pour l’affichage des résultats d’un sondage. Le résultat est présenté sous deux formes. La première sous forme d’un graphique circulaire et la seconde via une liste avec les différentes options et le nombre de résultats obtenus. Pour accéder à cette page, il suffira de spécifier dans la barre d’adresse l’identifiant du PollDLE suivi de result.

Ecran pour l'affichage des résultats d'un sondage PollDLE

Dans la suite, nous donnerons les codes HTML et JavaScript des différents composants au fur et à mesure. L’intérêt de cet article n’est pas d’apprendre à construire une interface graphique, mais de comprendre comment rendre dynamique une interface avec Vue.js.

Avant chaque concept présenté, nous fournirons un état du code (via des répertoires de la forme polldle-vue-x) afin que vous puissiez directement tester par vous-même.

Création d’un projet Vue.js

Nous allons montrer dans cette section comment créer un projet Vue.js en utilisant Vue CLI et nous examinerons le squelette du projet obtenu après création.

Création d’un projet Vue.js avec l’outil Vue CLI

  • Pour découvrir les possibilités de Vue CLI, ouvrir un terminal et saisir la commande suivante :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ vue -h
Usage: vue <command> [options]

Options:
  -V, --version                              output the version number
  -h, --help                                 output usage information

Commands:
  create [options] <app-name>                create a new project powered by vue-cli-service
  add [options] <plugin> [pluginOptions]     install a plugin and invoke its generator in an already created project
  invoke [options] <plugin> [pluginOptions]  invoke the generator of a plugin in an already created project
  inspect [options] [paths...]               inspect the webpack config in a project with vue-cli-service
  serve [options] [entry]                    serve a .js or .vue file in development mode with zero config
  build [options] [entry]                    build a .js or .vue file in production mode with zero config
  ui [options]                               start and open the vue-cli ui
  init [options] <template> <app-name>       generate a project from a remote template (legacy API, requires @vue/cli-init)
  config [options] [value]                   inspect and modify the config
  upgrade [semverLevel]                      upgrade vue cli service / plugins (default semverLevel: minor)
  info                                       print debugging information about your environment

  Run vue <command> --help for detailed usage of given command.
  • Pour obtenir des informations sur votre environnement de développement et s’assurer que tout est correctement installé (npm, et Vue CLI), saisir la ligne commande suivante :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ vue info

Environment Info:

  System:
    OS: macOS 10.14.3
    CPU: (8) x64 Intel(R) Core(TM) i7-4870HQ CPU @ 2.50GHz
  Binaries:
    Node: 11.13.0 - /usr/local/bin/node
    Yarn: Not Found
    npm: 6.9.0 - /usr/local/bin/npm
  Browsers:
    Chrome: 73.0.3683.86
    Firefox: 67.0
    Safari: 12.0.3
  npmGlobalPackages:
    @vue/cli: 3.8.2
  • Nous allons créer notre premier projet avec Vue CLI en mode console. Depuis la racine du dossier vuejs-polldle-tutorial-src (obtenu en récupérant les codes source depuis le dépôt https://github.com/mickaelbaron/vuejs-polldle-tutorial-src), saisir la ligne de commande suivante : $ vue create polldle-vue-00. Une série de questions vous seront posées.
  • Sélectionner le second élément afin de choisir manuellement le paramétrage.
1
2
3
4
Vue CLI v3.8.2
? Please pick a preset:
  default (babel, eslint)
❯ Manually select features
  • Sélectionner les plugins Babel (un compilateur JavaScript permettant d’utiliser des syntaxes récentes du langage qui seront traduites en JavaScript compréhensible par la plupart des versions des navigateurs) et ESLint (un outil d’analyse statique du code JavaScript permettant de détecter des erreurs avant l’exécution et des problèmes de style).
1
2
3
4
5
6
7
8
9
10
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◯ Vuex
 ◯ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing
  • Pour ESLint, choisir le premier élément afin d’afficher la moindre erreur détectée. C’est assez contraignant au début (moindre espace en trop est une erreur), mais quel plaisir d’avoir un code propre qui respecte les conventions de codage.
1
2
3
4
5
? Pick a linter / formatter config:
❯ ESLint with error prevention only
  ESLint + Airbnb config
  ESLint + Standard config
  ESLint + Prettier
  • Choisir le premier élément pour lancer Lint à chaque sauvegarde d’un fichier JavaScript.
1
2
3
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ Lint on save
 ◯ Lint and fix on commit
  • Choisir le second élément pour stocker les informations spécifiques de Babel et ESLint dans le fichier package.json. À noter que même avec ce choix, un fichier babel.config.js sera quand même créé.
1
2
3
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.?
  In dedicated config files
❯ In package.json
  • Pour savoir si vous souhaitez que vos précédents choix soient considérés par défaut pour les prochaines créations de projets.
1
? Save this as a preset for future projects? (y/N)
  • La création de votre projet va se dérouler.
1
2
3
4
✨  Creating project in /Users/baronm/vuejs-polldle-tutorial-src/polldle-vue-00.
⚙  Installing CLI plugins. This might take a while...

⸨       ░░░░░░░░░░░⸩ ⠇ diffTrees: sill install generateActionsToTake

La création de ce projet aurait pu être effectuée via Vue UI au travers d’une interface web. Les options sont les mêmes, seule l’interface est plus agréable. Nous montrerons par contre comment administrer notre projet avec Vue UI.

Inventaire des fichiers générés

Intéressons-nous à détailler les différents fichiers et répertoires qui ont été générés lors de la précédente section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
polldle-vue-00
├── README.md
├── babel.config.js
├── node_modules/
├── package-lock.json
├── package.json
├── public/
│   ├── favicon.ico
│   └── index.html
└── src/
    ├── App.vue
    ├── assets
    │   └── logo.png
    ├── components
    │   └── HelloWorld.vue
    └── main.js

Pour les développeurs qui utilisaient Vue CLI 2, vous remarquerez la disparition du répertoire config. En effet, depuis Vue CLI 3, il n’y a plus de fichier de configuration. Pas d’inquiétude, vous pourrez toujours ajouter des informations de configuration. Nous en parlerons dans la partie 3 qui traite du déploiement.

Le fichier README.md décrit les différentes commandes à utiliser avec npm. Nous y reviendrons dans la section suivante quand nous expliquerons comment tester le projet.

Le fichier babel.config.js est un fichier de configuration pour l’outil Babel.

Le fichier package.json est donné en exemple ci-dessous. Des métadonnées sont utilisées pour décrire le projet : name et version. Des scripts npm sont définis pour tester, construire la version finale et vérifier la qualité du code. La clé dependencies sert à préciser les bibliothèques utilisées par le projet alors que la clé devDependencies sert à préciser les plugins utilisées pour le développement.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "name": "polldle-vue-00",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    ...
    "npm": "^6.9.0",
    "vue": "^2.6.10"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^3.8.0",
    "@vue/cli-plugin-eslint": "^3.8.0",
    "@vue/cli-service": "^3.8.0",
    "vue-template-compiler": "^2.6.10"
  }
  ... // Informations de configurations des plugins ESLint, PostCSS...
}

Le répertoire node_modules contient l’ensemble des modules nécessaires pour la construction du projet. Ce répertoire est obtenu automatiquement en exécutant le script $ npm install. L’outil npm se base alors sur le fichier package.json pour télécharger les modules directs et transitifs. Par comparaison, c’est très ressemblant à Maven de l’univers Java où pom.xml correspond au fichier package.json.

Le répertoire public est utilisé pour stocker les fichiers statiques HTML. Le fichier index.html est le point d’entrée de votre application (voir ci-dessous). Il sera souvent identique à tous les projets Vue.js. Excepté le titre, vous n’aurez rien à modifier manuellement. Tout le code qui vous allez développer sera injecté dans <div id="app"></div>.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>polldle-vue</title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but polldle-vue doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

Le répertoire src contient le code Vue.js à proprement parler.

Fichier main.js

Le fichier main.js sert à configurer notre projet. Il précise les composants à utiliser (import App from './App.vue'), initialiser des variables globales (Vue.config.productionTip = false) et précise où le rendu doit être effectué ($mount('#app'))).

Ce fichier est en quelque sorte le point central de l’application.

1
2
3
4
5
6
7
8
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

Fichier App.vue

Le fichier App.vue est le premier composant de votre application qui va contenir tous les autres composants. Il est en quelque sorte le composant racine d’une application de type Single-Page application. Tout est localisé à l’intérieur de ce composant. Comme il s’agit d’un composant, nous détaillerons son contenu plus tard.

Répertoire assets

C’est dans ce répertoire que vous déposerez vos ressources (images, vidéos et fichiers).

Répertoire components

Ce répertoire contiendra tous les composants que vous allez développer. Tous les fichiers porteront l’extension *.vue.

Tester le projet généré

  • Pour tester ce nouveau projet, se déplacer à la racine du dossier polldle-vue-00 et exécuter la ligne de commande suivante :

La commande $ npm run serve est un alias défini dans package.json qui lance vue-cli-service serve.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ npm run serve

 ...

 DONE  Compiled successfully in 2977ms


  App running at:
  - Local:   http://localhost:8080/
  - Network: http://WWW.XXX.YYY.ZZZ:8080/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

Ecran pour la création d'un PollDLE

Tout au long de cet article et à chaque fois qu’il vous sera demandé de compléter des fichiers dans un répertoire de la forme polldle-vue-x, pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

Si un problème de ce genre se produit : npm WARN Local package.json exists, but node_modules missing, did you mean to install?, assurez-vous d’avoir fait un $ npm install pour télécharger l’ensemble des modules nécessaires. Les fichiers téléchargés seront déposés dans le répertoire node_modules.

Modèle et vue

Nous vous invitons à vous positionner dans le répertoire polldle-vue-01 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

Nous allons initialiser les différents modèles et vues des composants CreatePolldle et FooterPolldle de l’application PollDLE sans effectuer aucun binding, c’est-à-dire sans relier les vues aux modèles et inversement. En effet, nous voulons montrer qu’un composant est constitué d’une partie vue (HTML classique) et d’une partie modèle (JavaScript) et que l’intérêt de Vue.js est de fournir un ensemble d’outillages (les différentes sections qui vont suivre) pour rendre dynamique l’interface graphique. Par ailleurs, nous ne nous attarderons pas sur les spécificités d’un composant (communication entre des composants ou son cycle de vie) puisque nous y reviendrons plus tard dans une section dédiée.

À cette étape voici le contenu du répertoire src/components.

1
2
3
4
5
6
polldle-vue-01
...
    ├── components
    │   ├── CreatePolldle.vue
    │   └── FooterPolldle.vue
...

Le fichier CreatePolldle.vue concerne le composant décrivant l’écran de création d’un PollDLE et le fichier FooterPolldle.vue désigne le composant pour le bas de page de l’application. Les autres composants seront étudiés quand nous aborderons le concept de composant.

Le fichier CreatePolldle.vue contient le strict minimum et fait apparaitre la partie vue (localisée dans le contenu de la balise <template>) de la partie modèle (localisée dans le contenu de la balise <script>).

1
2
3
4
5
6
7
8
9
<template>
</template>

<script>
</script>

<style>
...
</style>
  • Compléter la partie vue <template>, en ajoutant le code HTML suivant :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<template>
  <div class="container">
    <!-- Titre + description -->
    <h1>PollDLE</h1>
    <h2>Voting done simply in real-time</h2>

    <!-- PollDLE name -->
    <div class="row">
      <div class="col">
        <input type="text" class="large-input mx-auto d-block" placeholder="Add your question here">
      </div>
    </div>

    <h3>Add your PollDLE options</h3>

    <div class="row">
      <div class="col">
        <input type="text" placeholder="Polldle Option" class="large-input mx-auto d-block">
      </div>
    </div>
    <div class="row">
      <div class="col">
        <button type="button" class="clear-button btn-lg btn-danger mx-auto d-block" >Clear all PollDLE Options</button>
      </div>
    </div>

    <!-- PollDLE option -->
    <div class="row justify-content-center"></div>

    <!-- Button Action -->
    <div class="row">
      <div class="col">
        <button type="button" class="validate-button btn-lg btn-primary mx-auto d-block">Create PollDLE</button>
      </div>
    </div>

    <div class="alert alert-primary" role="alert">
      <h4 class="alert-heading">Summary of your PollDLE</h4>
      <hr>
      <p>
        The question is: <strong>TODO</strong>
      </p>
      <p>Number of PollDLE options: TODO</p>
    </div>

    <div class="error-message alert alert-danger" role="alert">TODO</div>
  </div>
</template>

<script>
...
</script>

<style>
...
</style>

Vous remarquerez pour ceux qui utilisent la bibliothèque Bootstrap les styles spécifiques tels row et col. À ce niveau, il s’agit d’une interface graphique développée en HTML des plus classiques.

Pour inclure la bibliothèque Bootstrap au projet (c’est-à-dire déclarer son contenu CSS dans la balise <style>), il est nécessaire d’impacter le fichier main.js.

  • Compléter le fichier main.js en remplaçant le commentaire // Import Bootstrap library par le code ci-dessous.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Vue from 'vue'
import App from './App.vue'

require('./assets/polldle.css')

// Import Bootstrap library
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Vue.config.productionTip = false

new Vue({
  render: h => h(App)
}).$mount('#app')

Cette importation aura comme objectif d’ajouter de manière globale, c’est-à-dire visible dans tout le projet PollDLE, la bibliothèque Bootstrap.

  • Compléter le début de la partie modèle script, en ajoutant le code JavaScript suivant correspondant aux propriétés du modèle.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
  ... // code précédent
</template>
<script>
export default {
  name: "CreatePolldle",
  data() {
    return {
      question: "",
      newPolldleOptionText: "",
      polldleOptions: [],
      errorMessage: "",
      buttonShown: false
    };
  },
  ...
}
</script>
...

La partie modèle est définie dans la fonction data() qui retourne un ensemble de propriétés. Dans le cas présenté ci-dessus, nous retrouvons les propriétés suivantes :

  • question : pour le titre du PollDLE ;
  • newPolldleOptionText : pour saisir la valeur d’une option de PollDLE avec sa création ;
  • polldleOptions : pour l’ensemble des options d’un PollDLE. Il s’agit d’un objet avec une propriété text qui contient la valeur de l’option ;
  • errorMessage : pour le message d’erreur ;
  • buttonShown : pour aider à afficher ou pas le bouton de suppression des options d’un PollDLE.

Des fonctions JavaScript peuvent être utilisées pour regrouper des fonctionnalités ou effectuer des traitements plus complexes.

  • Compléter la fin de la partie modèle script, en ajoutant le code JavaScript correspondant aux méthodes du modèle.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
...
<script>
export default {
  name: "CreatePolldle",
  ...
  methods: {
    removedPolldleOption(polldleOption) {
      let index = this.polldleOptions.indexOf(polldleOption);
      this.polldleOptions.splice(index, 1);
      this.errorMessage = "";
    },

    addPolldleOption() {
      this.polldleOptions.push({
        text: this.newPolldleOptionText
      });
      this.newPolldleOptionText = "";
    },

    clearAllPolldleOptions() {
      this.polldleOptions = [];
      this.errorMessage = "";
    },

    createPolldle() {
      var polldleObject = {
        question: this.question,
        polldleOptions: []
      };

      this.polldleOptions.forEach(element => {
        var newPollOptionElement = { name: element.text };
        if (element.text !== "") {
          polldleObject.polldleOptions.push(newPollOptionElement);
        }
      });

      // Call REST web service with fetch API
    },

    isCreatePolldleDisabled() {
      return (
        this.polldleOptions === null ||
        this.polldleOptions.length < 2 ||
        this.question === ""
      );
    }
  }
};
</script>
...

Nous détaillons ci-dessous l’objectif de ces fonctions JavaScript. Veuillez noter que nous avons utilisé l’écriture Fonctions Fléchées (Arrow Functions en anglais) pour simplifier l’écriture et faciliter l’usage de la portée pour this.

  • removedPolldleOption(polldleOption) : pour supprimer une option au PollDLE, l’élément à supprimer est passé en paramètre ;
  • addPolldleOption() : pour ajouter une nouvelle option au PollDLE ;
  • clearAllPolldleOptions() : pour supprimer toutes les options du PollDLE ;
  • createPolldle() : pour créer le PollDLE et appeler le service web côté serveur ;
  • isCreatePolldleDisabled() : pour savoir si un PollDLE peut être créé (qu’il existe des options de PollDLE, au moins deux et qu’un titre soit présent).

Dans le corps des fonctions fléchées, nous avons utilisé le mot clé this. Celui-ci a une portée globale et fait référence au modèle du composant.

N’hésitez par à consulter les autres fichiers .vue pour découvrir comment les modèles et les vues ont été construits.

Templating avec Vue.js

Nous vous invitons à vous positionner dans le répertoire polldle-vue-02 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

Dans cette section, nous allons apprendre à réaliser le binding entre la vue et le modèle via le templating. Pour cela et comme précisé en première partie de cet article, deux outillages sont disponibles : l’interpolation via la notation moustache et les directives qui sont des attributs enrichissant les balises HTML.

Interpolation

La forme la plus basique pour réaliser un binding est une interpolation de texte en utilisant une moustache qui est une double accolade entourant l’expression à évaluer : {{ ... }}. Le rendu va consister à utiliser la moustache pour injecter la valeur de l’expression.

À titre d’exemple, dans le composant CreatePolldle, la zone située en bas de la page donne un résumé des données saisies (titre, nombre d’options et message d’erreur si existant).

  • Compléter le code du fichier CreatePolldle.vue en remplaçant la balise de commentaire <!-- Mustache with question --> par une notation moustache associée à la propriété question.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
  ...
    <div class="alert alert-primary" role="alert">
      <h4 class="alert-heading">Summary of your PollDLE</h4>
      <hr>
      <p>
        The question is:
        <strong>
          <!-- Mustache with question -->
          {{ question }}
        </strong>
      </p>
      <p>Number of PollDLE options: TODO</p>
    </div>
  ...
</template>
...

À chaque modification de la valeur de la propriété question, la moustache injectera la valeur de la propriété dans le DOM.

Le résultat obtenu après le rendu de cette moustache est donné sur le code HTML suivant où question a pour valeur Aimez-vous les frites ?.

1
2
3
4
5
6
7
8
9
10
11
  ...
    <div class="alert alert-primary" role="alert">
      <h4 class="alert-heading">Summary of your PollDLE</h4>
      <hr>
      <p>
        The question is:
        <strong>Aimez-vous les frites ?</strong>
      </p>
      <p>Number of PollDLE options: TODO</p>
    </div>
  ...

Directives

Les moustaches ne peuvent pas être utilisées à l’intérieur d’une balise HTML. Il faut donc passer par l’utilisation de directives qui utilisent le préfixe v-. Dès que l’expression adossée à la directive est modifiée, cette directive va effectuer les changements sur le DOM.

Dans cette section, nous allons étudier les directives suivantes : v-text, v-on, v-bind, v-model et v-once. Les directives v-show, v-if, v-else, v-else-if, v-for seront étudiées plus tard.

Directive v-text

La directive v-text sert à mettre à jour le contenu textuel d’un élément. Elle joue le même rôle qu’une interpolation moustache.

  • Compléter le code du fichier CreatePolldle.vue en ajoutant la directive v-text pour afficher la propriété errorMessage (commentaire <!-- Directive v-text with errorMessage -->).
1
2
3
4
5
6
7
<template>
    ...
    <!-- Directive v-text with errorMessage -->
    <div class="error-message alert alert-danger" role="alert" v-text="errorMessage"></div>
    ...
</template>
...

Le résultat obtenu après le rendu de cette directive v-text est donné sur le code HTML suivant où v-text="errorMessage" permet d’injecter dans le corps de la balise <div> la valeur Problem to create a new Polldle.

1
2
3
...
<div class="error-message alert alert-danger" role="alert">Problem to create a new Polldle.</div>
...

Quand utiliser la directive v-text ou l’interpolation moustache ? Si le contenu textuel d’une balise doit être changé dans son intégralité, utiliser la directive v-text, si par contre le contenu textuel d’une balise doit être changé en partie, utiliser une interpolation moustache.

Directive v-bind

La directive v-bind permet de lier un attribut d’une balise à une expression. L’attribut est donné comme argument séparé par un deux-points après la directive v-bind. Les attributs class et style sont considérés naturellement.

  • Compléter le code du fichier CreatePolldle.vue en ajoutant la directive v-bind pour lier l’attribut disabled avec la fonction isCreatePolldleDisabled() (commentaire <!-- Directive v-bind with isCreatePolldleDisabled() -->).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
    ...
    <!-- Button Action -->
    <!-- Directive v-bind with isCreatePolldleDisabled() -->
    <div class="row">
      <div class="col">
        <button
          type="button"
          class="validate-button btn-lg btn-primary mx-auto d-block"
          v-bind:disabled="isCreatePolldleDisabled()"
        >Create PollDLE</button>
      </div>
    </div>
    ...
</template>
...

La directive v-bind a comme paramètre disabled (séparée par un deux-points) et est associée à la fonction isCreatePolldleDisabled(). Dès lors que la fonction isCreatePolldleDisabled() retourne vrai, l’attribut disabled sera évalué à vrai et ainsi l’état du bouton sera désactivé.

Dans le cas où la fonction isCreatePolldleDisabled() retourne faux, le code HTML obtenu après le rendu de cette directive v-bind:disabled sera le suivant :

1
2
3
4
5
6
7
8
9
10
11
...
<div class="row">
  <div class="col">
    <button
      type="button"
      class="validate-button btn-lg btn-primary mx-auto d-block"
      disabled="disabled"
    >Create PollDLE</button>
  </div>
</div>
...

Vue.js fournit une écriture simplifiée de la directive v-bind. Comme cette directive est largement utilisée, elle peut être remplacée par :.

L’exemple précédent pourra être écrit de cette façon.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
    ...
    <!-- Button Action -->
    <div class="row">
      <div class="col">
        <button
          type="button"
          class="validate-button btn-lg btn-primary mx-auto d-block"
          :disabled="isCreatePolldleDisabled()"
        >Create PollDLE</button>
      </div>
    </div>
    ...
</template>
...

Directive v-model

La directive v-model crée une liaison bidirectionnelle entre un composant de saisie (<input>, <select>, <textarea>) est une propriété du modèle.

  • Compléter le code du fichier CreatePolldle.vue en ajoutant la directive v-model pour lier les propriétés question et newPolldleOptionText au composant de saisie <input> (commentaires <!-- Directive v-model with question --> et <!-- Directive v-model with newPolldleOptionText -->).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
    ...
    <!-- PollDLE name -->
    <div class="row">
      <div class="col">
        <!-- Directive v-model with question -->
        <input
          type="text"
          class="large-input mx-auto d-block"
          placeholder="Add your question here"
          v-model="question"
        >
      </div>
    </div>

    <h3>Add your PollDLE options</h3>

    <div class="row">
      <div class="col">
        <!-- Directive v-model with newPolldleOptionText -->
        <input
          type="text"
          placeholder="Polldle Option"
          v-model="newPolldleOptionText"
          class="large-input mx-auto d-block"
        >
      </div>
    </div>
    ...
</template>
...

Le code HTML obtenu après le rendu est le suivant. Nous constatons que la mécanique de la liaison bidirectionnelle n’est pas réalisée côté HTML, mais du côté JavaScript.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
<div class="row">
  <div class="col">
    <input
      type="text"
      placeholder="Add your question here"
      class="large-input mx-auto d-block"
    >
  </div>
</div>
<h3>Add your PollDLE options</h3>
<div class="row">
  <div class="col">
    <input
      type="text"
      placeholder="Polldle Option"
      class="large-input mx-auto d-block">
  </div>
</div>

Directive v-on

La directive v-on permet d’attacher un écouteur d’événements à un élément et de faire appel à une fonction dès lors qu’un événement est émis. Le type d’événement est donné comme argument séparé par un deux-points après la directive v-on.

  • Compléter le code du fichier CreatePolldle.vue en ajoutant la directive v-on pour lier les fonctions addPolldleOption, clearAllPolldleOptions et createPolldle aux écouteurs d’événements liés à la souris et au clavier (commentaires <!-- Directive v-on with addPolldleOption -->, <!-- Directive v-on with clearAllPolldleOptions --> et <!-- Directive v-on with createPolldle -->).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<template>
    ...
    <h3>Add your PollDLE options</h3>

    <div class="row">
      <div class="col">
        <!-- Directive v-on with addPolldleOption -->
        <input
          type="text"
          placeholder="Polldle Option"
          v-model="newPolldleOptionText"
          class="large-input mx-auto d-block"
          v-on:keypress.enter="addPolldleOption"
        >
      </div>
    </div>
    <div class="row" v-show="buttonShown">
      <div class="col">
        <!-- Directive v-on with clearAllPolldleOptions -->
        <button
          type="button"
          class="clear-button btn-lg btn-danger mx-auto d-block"
          v-on:click="clearAllPolldleOptions"
        >Clear all PollDLE Options</button>
      </div>
    </div>
    ...
    <!-- Button Action -->
    <div class="row">
      <div class="col">
        <!-- Directive v-on with createPolldle -->
        <button
          type="button"
          class="validate-button btn-lg btn-primary mx-auto d-block"
          v-on:click="createPolldle"
          v-bind:disabled="isCreatePolldleDisabled()"
        >Create PollDLE</button>
      </div>
    </div>
    ...
</template>
...

Du côté rendu HTML, l’abonnement aux événements n’est pas visible puisque ce traitement est effectué côté JavaScript.

Il est également possible de filtrer le type d’événement en indiquant des touches spécifiques de clavier ou des boutons de la souris. C’est le cas par exemple pour la validation d’une option de PollDLE qui doit être faite en pressant la touche Enter (v-on:keypress.enter="addPolldleOption").

Vue.js fournit également une écriture simplifiée de la directive v-on. Comme cette directive est largement utilisée, elle peut être remplacé par @.

Une des parties de l’exemple précédent pourra être écrite de cette façon en utilisant l’écriture simplifiée.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
    ...
    <h3>Add your PollDLE options</h3>

    <div class="row">
      <div class="col">
        <input
          type="text"
          placeholder="Polldle Option"
          v-model="newPolldleOptionText"
          class="large-input mx-auto d-block"
          @keypress.enter="addPolldleOption"
        >
      </div>
    </div>
    ...
</template>
...

Directive v-once

Il est possible de n’effectuer le rendu d’une balise ou d’un composant qu’une seule fois. Pour cela il faudra utiliser la directive v-once depuis la balise englobante. Un exemple est disponible dans le composant FooterPolldle.

  • Compléter le code du fichier FooterPolldle.vue en ajoutant la directive v-once à la balise <p> (commentaire de type <!-- Directive v-once -->).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
  <div class="container">
    <!-- Directive v-once -->
    <p class="footer" v-once>{{ description }}</p>
  </div>
</template>

<script>
export default {
  name: "FooterPolldle",
  data() {
    return {
      description:
        "PollDLE ~= Poll + (last part of famous DooDLE app). PollDLE is an open source project developped by Mickael BARON and Mahamadou DIABATE - Powered by Vue.js and Java."
    };
  }
};
</script>
...

Dans ce cas, la directive v-once prend tout son sens, car le rendu ne sera réalisé qu’une seule, même si la description change.

Propriétés calculées et observateurs

Nous vous invitons à vous positionner dans le répertoire polldle-vue-03 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

Lors de changement des propriétés du modèle, on peut vouloir calculer de nouvelles propriétés (les propriétés calculées) ou déclencher des opérations coûteuses (les observateurs).

Propriétés calculées (computed)

Il peut arriver que dans une interpolation ou une directive, l’expression transmise soit complexe ce qui peut alourdir la lisibilité de la partie vue. Par ailleurs, si cette expression est répétée, il devient nécessaire de regrouper le code (code dupliqué == bogue répété).

Prenons l’exemple du résumé du nombre d’options de PollDLE du composant CreatePolldle (voir fichier CreatePolldle.vue).

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
...
    <div class="alert alert-primary" role="alert">
      <h4 class="alert-heading">Summary of your PollDLE</h4>
      <hr>
      <p>
        The question is:
        <strong>{{ question }}</strong>
      </p>
      <p>Number of PollDLE options: {{ this.polldleOptions.length }}</p>
    </div>
...
</template>

Pour remplacer le code this.polldleOptions.length, nous allons utiliser une propriété calculée qui sera mise à jour à chaque changement de la propriété polldleOptions.

  • Compléter la partie vue du fichier CreatePolldle.vue en remplaçant la balise de commentaire <!-- Mustache with computed property: listSize --> par une notation moustache associée à la propriété listSize.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
...
    <div class="alert alert-primary" role="alert">
      <h4 class="alert-heading">Summary of your PollDLE</h4>
      <hr>
      <p>
        The question is:
        <strong>{{ question }}</strong>
      </p>
      <!-- Mustache with computed property: listSize -->
      <p>Number of PollDLE options: {{ listSize }}</p>
    </div>
...
</template>
  • Compléter la partie JavaScript du fichier CreatePolldle.vue en remplaçant la balise de commentaire // Computed property listSize when polldleOptions changes par une propriété calculée comme présenté ci-dessous. Toutes les propriétés calculées de ce composant devront être placées comme éléments de l’attribut computed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default {
  ...
  data() {

  },
  // Computed property listSize when polldleOptions changes
  computed: {
    listSize() {
      return this.polldleOptions.length;
    }
  },
  methods: {
    ...
  }
}

La propriété calculée listSize est mise en cache et tant que les propriétés dont elle dépend ne changent pas (ici, il s’agit de polldleOptions), l’expression ne sera pas réévaluée. Ainsi, si un nouveau rendu est effectué côté vue, la valeur de la propriété calculée listSize sera prise depuis le cache.

Le résultat aurait été identique si nous avions utilisé une fonction déclarée dans la zone methods. Toutefois, à chaque rendu de la vue, l’expression de la fonction déclarée dans methods aurait été évaluée. Cette optimisation a son importance quand les expressions des propriétés calculées commencent à devenir complexes.

Observateurs (watch)

Lorsqu’une valeur de propriété du modèle est modifiée, on peut vouloir invoquer une fonction JavaScript (en mode asynchrone) ou modifier la valeur d’une propriété. Vue.js fournit un mécanisme appelé Observateurs qui pour chaque changement de valeur d’une propriété ciblée vous permet d’effectuer un traitement.

Nous présentons dans l’exemple de création d’un PollDLE, la mise en place d’un observateur pour la propriété polldleOptions qui, pour chaque changement, modifiera la valeur de la propriété buttonShown.

  • Compléter la partie JavaScript du fichier CreatePolldle.vue en remplaçant la balise de commentaire // Watcher on polldleOptions par un observateur comme présenté ci-dessous. Tous les observateurs de ce composant devront être placés comme éléments de l’attribut watch.
1
2
3
4
5
6
  // Watcher on polldleOptions
  watch: {
    polldleOptions() {
      this.buttonShown = this.polldleOptions != null && !(this.polldleOptions.length === 0);
    }
  }

Dans cet exemple, le nom de la fonction observateur prend le même nom que la propriété à observer : polldleOptions. Ainsi tout le code contenu à l’intérieur de cette fonction sera appelé à chaque fois que la valeur de polldleOptions change.

Dans ce cas précis, l’utilisation d’un observateur au lieu d’une propriété calculée est discutable. L’utilisation d’une propriété calculée pour obtenir la valeur de buttonShown aurait aussi fonctionné.

Rendu conditionnel

Nous vous invitons à vous positionner dans le répertoire polldle-vue-04 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

Nous allons dans cette section revenir sur l’étude des directives ( Templating avec Vue.js) en détaillant les directives v-show et v-if. Ces directives permettent de créer ou pas le rendu d’un bloc.

Directive v-if

La directive v-if permet d’effectuer le rendu ou pas du bloc si l’expression associée à cette directive est vraie. Par ailleurs comme tout bloc de condition, v-if peut s’utiliser avec v-else-if (où une autre expression peut être évaluée) v-else pour le bloc par défaut si aucune expression n’est satisfaite.

Nous donnons un exemple d’utilisation de v-if et de v-else dans le composant ResultPolldle.

  • Compléter la partie vue du fichier ResultPolldle.vue en remplaçant les balises de commentaire <!-- Directive v-if ... --> et <!-- Directive v-else --> par l’utilisation des directives v-if et v-else.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
  <div class="container">
    <!-- Directive v-if with !isErrorState() -->
    <div v-if="!isErrorState()">
      <h1>{{ question }}</h1>
      <div class="row">
        <div class="col-8">
        </div>
        <div class="col-4">
          <div></div>
        </div>
      </div>

      <!-- Directive v-if with isEmptyState() -->
      <div v-if="isEmptyState()">
        <h2>No vote at this moment, keep in touch. Results update in real-time.</h2>
      </div>
    </div>
    <!-- Directive v-else -->
    <div v-else class="error-message alert alert-danger" role="alert">{{ errorMessage }}</div>
  </div>
</template>
<script>
...
export default {
  ...
  methods: {
    isEmptyState() {
      return this.state === stateResult.EMPTY;
    },

    isErrorState() {
      return this.state === stateResult.ERROR;
    }
  }
}
</script>

Avant-propos, vu que c’est la première fois que nous présentons ce composant : les fonctions isErrorState() et isEmptyState() utilisées comme expression dans les directives conditionnelles v-if et v-else permettent d’accéder à la propriété state. La valeur de cette propriété est modifiée lors de l’appel au service web (voir plus tard). Si state === stateResult.EMPTY le service web a été invoqué, mais aucune donné n’est transmise. Si state === stateResult.ERROR l’invocation au service web a généré une erreur. Si le service web a été invoqué et contient des données alors state === stateResult.RESULT.

Le premier rendu conditionnel est affiché s’il n’y a pas eu de problème lors du chargement des résultats du vote. Le deuxième rendu conditionnel est affiché s’il n’y a pas eu de problème et si le contenu retourné par le service web est vide. Enfin, le troisième rendu conditionnel (v-else) est lié au premier rendu conditionnel et affichera les erreurs causées par l’invocation du service web.

Nous donnons ci-dessous, le rendu HTML dans le cas où l’invocation au service web a provoqué une erreur.

1
2
3
4
5
...
  <div class="container">
    <div role="alert" class="error-message alert alert-danger">Problem to retrieve Polldle result.</div>
  </div>
...

Comme on peut le remarquer, le premier bloc lié à l’affichage des résultats ne sera pas créé. Seul le dernier bloc sera créé.

Directive v-show

La directive v-show a le même effet que la directive v-if (et sœurs) du point de vue visuel. Toutefois au niveau du code, le rendu d’un bloc avec v-show sera réalisé, mais l’affichage sera contrôlé par la propriété CSS display.

  • Compléter la partie vue du fichier CreatePolldle.vue en remplaçant les balises de commentaire <!-- Directive v-show with buttonShown --> et <!-- Directive v-show with errorMessage --> par une directive v-show.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
...
    <!-- Directive v-show with buttonShown -->
    <div class="row" v-show="buttonShown">
      <div class="col">
        <button
          type="button"
          class="clear-button btn-lg btn-danger mx-auto d-block"
          @click="clearAllPolldleOptions"
        >Clear all PollDLE Options</button>
      </div>
    </div>
    ...
    <!-- Directive v-show with errorMessage -->
    <div
      v-show="errorMessage !== ''"
      class="error-message alert alert-danger"
      role="alert"
      v-text="errorMessage"
    ></div>
...
</template>

Le premier bloc correspond au bouton « Clear All PollDLE Options » qui ne sera visible que s’il existe des options de Polldle. Pour rappel, la valeur de la propriété buttonShown est modifiée par l’intermédiaire d’un observateur sur la propriété polldleOptions. Pour le second bloc où la directive v-show est utilisée, nous utilisons une expression JavaScript qui vérifie si la propriété errorMessage est vide ou pas.

Nous donnons ci-dessous le rendu HTML correspondant au premier bloc permettant de rendre visible ou pas le bouton « Clear All PollDLE Options ».

1
2
3
4
5
6
7
...
  <div class="row" style="display: none;">
    <div class="col">
      <button type="button" class="clear-button btn-lg btn-danger mx-auto d-block">Clear all PollDLE Options</button>
    </div>
  </div>
...

Dans cet état de valeur de propriétés où butonShown vaut faux, la directive v-show injectera la valeur de style style="display: none;".

Quand utiliser v-if ou v-show ?

Les directives v-if et v-show permettent d’obtenir le même résultat visuellement, mais le code obtenu est différent. Si vous avez besoin d’effectuer des permutations fréquemment (visibles ou pas visibles) au cours de l’utilisation de votre composant, il est préférable d’utiliser v-show. La directive v-if est à utiliser de préférence lors de l’initialisation de votre composant et quand il y a peu de changements. Un changement de CSS (via la directive v-show) est toujours moins coûteux que de devoir créer de nouveaux blocs (via la directive v-if).

Rendu de liste

Nous vous invitons à vous positionner dans le répertoire polldle-vue-05 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

Nous étudions dans cette section la directive v-for qui permet de réaliser plusieurs fois le rendu d’un élément (où s’applique la directive).

La valeur de la directive v-for doit suivre la syntaxe alias in expression ou expression peut-être issue d’une donnée source de type tableau ou d’objet (via les propriétés de l’objet). alias permettra d’accéder à l’élément courant.

Nous présentons ci-dessous les différentes syntaxes que vous pourrez retrouver en utilisant v-for.

1
2
3
4
<div v-for="item in items">{{ item.text }}</div> --> item est l'élément courant (yes)
<div v-for="(item, index) in items"></div> --> item est l'élément courant (yes) et index l'indice de l'élément (0)
<div v-for="(val, key) in object"></div> --> val est la valeur de la propriété (mickael) et key le nom de la propriété (prenom)
<div v-for="(val, key, index) in object"></div> --> val est la valeur de la propriété (mickael), key le nom de la propriété (prenom) et index l'indice de la propriété (0)
  • Compléter la partie vue du fichier CreatePolldle.vue en remplaçant les balises de commentaire <!-- Directive v-for with polldleOptions --> par l’utilisation de la directive v-for.
1
2
3
4
5
6
7
8
9
10
11
...
    <!-- PollDLE option -->
    <!-- Directive v-for with polldleOptions -->
    <div
      class="row justify-content-center"
      v-for="currentPolldleOption in polldleOptions"
      :key="currentPolldleOption.text"
    >
      {{ currentPolldleOption.text }}
    </div>
...

Dans l’exemple complet, l’élément construit plusieurs fois sera relatif au composant CreatePolldleOption. Comme nous n’avons pas encore présenté la notion de composant de Vue.js, nous nous limiterons à l’affichage du texte de l’option PollDLE.

Nous donnons ci-dessous le rendu HTML lorsque polldleOptions contient deux options Oui et Non.

1
2
3
4
5
6
7
...
  <div class="row justify-content-center">
    Oui
  </div>
  <div class="row justify-content-center">
    Non
  </div>

L’élément répété est celui où la directive v-for est appliquée, dans ce cas <div class="row justify-content-center">.

Composant avec Vue.js

Cette section s’intéresse à la notion de composant. Nous verrons comment développer et instancier un composant. Nous étudierons les aspects liés au cycle de vie d’un composant. Nous terminerons par les différentes façons pour communiquer entre des composants.

Savoir développer un composant

Nous vous invitons à vous positionner dans le répertoire polldle-vue-06 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

Pour développer un composant avec Vue.js, il existe plusieurs façons qui sont parfaitement résumées dans cet article : https://vuejsdevelopers.com/2017/03/24/vue-js-component-templates/. Dans le périmètre de notre article, nous nous limiterons au développement du composant via l’utilisation d’un fichier portant l’extension .vue. Cette manière de développer est appelée composants monofichiers ou composants à fichier unique (Single File Components en anglais). Nous avions déjà évoqué dans la partie introductive de Vue.js la description d’un composant sous cette forme. Pour rappel, ce fichier avec l’extension .vue est décomposé en trois parties qui définissent :

  • un template constitué de balises HTML qui définit la structure du composant (balise <template>) ;
  • un code JavaScript qui détermine le comportement du composant (balise <script>) ;
  • des styles CSS qui définissent l’apparence du composant (balise <style>).
  • Créer un fichier CreatePolldleOption.vue relatif au composant CreatePolldleOption et recopier le code ci-dessous.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template >
  <div class="polldle-option-input row justify-content-center no-gutters">
    <div class="col col-auto">
      <input
        type="text"
        class="form-control"
        readonly
      >
    </div>
    <div class="col col-auto">
      <!-- Directive v-on with removePolldleOption -->
      <button
        class="btn btn-outline-secondary"
        type="button"
        @click="removePolldleOption(polldleOption)"
      >X</button>
    </div>
  </div>
</template>

<script>
export default {
  name: "CreatePolldleOption",
  data() {
    return {
      errorMessage: ""
    };
  },

  methods: {
    removePolldleOption(polldleOption) {
    }
  }
};
</script>

<style>
.polldle-option-input {
  margin-bottom: 5px;
}
</style>

La propriété name: "CreatePolldleOption" permet de définir le nom du composant. Ce nom doit être généralement identique au nom donné au fichier. La convention de nommage recommandée peut-être kebab-case ou PascalCase. C’est cette dernière convention que nous utilisons. La convention de nommage PascalCase consiste à mettre en majuscule la première lettre de chaque mot. Dans le cas de cet exemple, le composant est identifié par CreatePolldleOption et le fichier contenant le code sera nommé CreatePolldleOption.vue.

Quand un composant est développé via un monofichier, c’est à la charge de Vue CLI et des outils annexes (Webpack par exemple avec Vue Loader) de transformer le code contenu dans ce fichier unique pour générer un code JavaScript compréhensible par le navigateur. Sans cet outillage, l’utilisation de fichiers portant l’extension .vue et avec une décomposition en trois parties n’aurait pas d’utilité.

Savoir instancier un composant

Nous vous invitons à vous positionner dans le répertoire polldle-vue-07 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

Précédemment, nous avons vu comment développer un composant, nous allons maintenant voir comment l’instancier au sein d’autres composants Vue.js. À ce propos, deux types de composants sont à distinguer : les composants que vous avez développés (c’est le cas du composant CreatePolldleOption) et les composants externes (c’est le cas de la bibliothèque Highcharts via le composant vue-highcharts). Quelle que soit l’origine des composants, la manière de les utiliser au sein d’un composant reste identique.

Pour instancier un composant, trois choses doivent être prises en compte :

  • l’importation du monofichier de description du composant ;
  • la déclaration locale ou globale ;
  • l’instanciation du composant.

Nous prendrons comme exemple le composant CreatePolldle défini dans le fichier CreatePolldle.vue.

Importation du monofichier de description du composant

  • Dans le code donné ci-dessous du fichier CreatePolldle.vue, remplacer le commentaire // Import CreatePolldleOption component en ajoutant la variable CreatePolldleOption qui permet de pointer sur le composant CreatePolldleOption défini dans le fichier CreatePolldleOption.vue.
1
2
3
4
5
6
7
8
9
<template>
...
</template>
<script>
// Import CreatePolldleOption component
import CreatePolldleOption from "@/components/CreatePolldleOption.vue";
...
</script>
...

Le caractère @ est utilisé par les outillages pour désigner le répertoire courant. Webpack remplacera ce caractère par le chemin courant du projet.

Déclaration locale ou globale

La déclaration locale précise qu’un composant importé n’est visible que par le composant qui en fait la demande. Au contraire, la déclaration globale précise qu’un composant importé est visible par tous les composants du projet. Il y a un risque de rendre globale la déclaration d’un composant. Cela alourdit le code produit et donc téléchargé premièrement et cela empêche d’avoir une visibilité des dépendances entre les composants (qui utilise quoi).

  • Dans le code donné ci-dessous du fichier CreatePolldle.vue, remplacer le commentaire // Add dependencies on CreatePolldleOption component en ajoutant la propriété components pour exprimer que le composant CreatePolldleOption doit être déclaré.
1
2
3
4
5
6
7
8
9
10
11
...
<script>
import CreatePolldleOption from "@/components/CreatePolldleOption.vue";
...
export default {
  name: "CreatePolldle",
  // Add dependencies on CreatePolldleOption component
  components: { CreatePolldleOption },
  ...
</script>
...

Pour déclarer globalement un composant, il est préférable de le faire depuis le fichier main.js. Malgré le fait que nous n’utilisons pas la déclaration globale dans notre projet, nous montrons à titre d’exemple la manière de faire. Dans le code ci-dessous, il faut d’une part importer le monofichier CreatePolldleOption.vue et d’autre part l’ajout globalement à Vue Vue.component('CreatePolldleOption', CreatePolldleOption).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Vue from 'vue'
import App from './App.vue'

require('./assets/polldle.css')

import CreatePolldleOption from "@/components/CreatePolldleOption.vue";

Vue.config.productionTip = false
Vue.component('CreatePolldleOption', CreatePolldleOption)

window.bus = new Vue()

new Vue({
  render: h => h(App)
}).$mount('#app')

Instanciation du composant

La dernière étape consiste à utiliser le composant dans la partie template du monofichier. Le composant est vu comme une nouvelle balise dont le nom est identique à la variable utilisée lors de l’importation.

Il est à noter qu’un composant peut être instancié autant de fois que souhaité. Il n’y a pas de limite, excepté la mémoire utilisée par votre navigateur.

  • Compléter la partie template du fichier CreatePolldle.vue en remplaçant la balise de commentaire <!-- Instance CreatePolldleOption component --> par notre nouvelle balise <CreatePolldleOption>.
1
2
3
4
5
6
7
8
9
10
<template>
  ...
  <div
      class="row justify-content-center"
      v-for="currentPolldleOption in polldleOptions"
      :key="currentPolldleOption.text"
    ><!-- Instance CreatePolldleOption component -->
    <CreatePolldleOption/></div>
</template>
...

Le composant CreatePolldleOption sera créé autant de fois que précisé dans la boucle.

Ci-dessous est présenté le rendu HTML du composant lorsque deux instances du composant CreatePolldleOption ont été créées.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div class="row justify-content-center">
  <!-- Début du composant CreatePolldleOption -->
  <div class="polldle-option-input row justify-content-center no-gutters">
    <div class="col col-auto">
      <input type="text" readonly="readonly" class="form-control">
    </div>
    <div class="col col-auto">
      <button type="button" class="btn btn-outline-secondary">X</button>
    </div>
  </div>
  <!-- Fin du composant CreatePolldleOption -->
</div>
<div class="row justify-content-center">
  <!-- Début du composant CreatePolldleOption -->
  <div class="polldle-option-input row justify-content-center no-gutters">
    <div class="col col-auto">
      <input type="text" readonly="readonly" class="form-control">
    </div>
    <div class="col col-auto">
      <button type="button" class="btn btn-outline-secondary">X</button>
    </div>
  </div>
  <!-- Fin du composant CreatePolldleOption -->
</div>

Le contenu généré est conforme au composant CreatePolldleOption. Ce code n’est pas parfait puisque les champs input ne sont pas renseignés. Nous aborderons cet aspect dans la section suivante dédiée à la communication entre des composants.

Composant externe ou plugin

Nous vous invitons à vous positionner dans le répertoire polldle-vue-08 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

Au sens de composant externe, nous considérons une bibliothèque développée par un tiers et que l’on souhaite intégrer à notre projet. Au niveau de Vue.js, ce type de composant est aussi appelé plugin. C’est le cas pour deux bibliothèques : la bibliothèque JavaScript Highcharts et de sa version packagée vue-highcharts pour le rendu des résultats d’un Polldle et la bibliothèque CSS Bootstrap et de sa version packagée bootstrap-vue.

Pour transformer un composant en un plugin ou composant externe, il faut exposer une méthode install. Cela n’étant pas l’objectif de cet article, une indication est donnée dans la documentation officielle de Vue.js.

Quand vous souhaitez ajouter une bibliothèque dans votre projet, vous devez généralement ajouter des dépendances dans le fichier package.json puis instancier le composant souhaité dans votre code. Examinons ensemble l’ajout de la bibliothèque Highcharts et de sa version packagée vue-highcharts.

  • Saisir la ligne commande suivante permettant d’ajouter la bibliothèque JavaScript Highcharts et de sa version packagée vue-highcharts avec l’outil de dépendances npm.
1
2
3
4
5
6
$ npm install highcharts vue-highcharts
...
+ vue-highcharts@0.1.0
+ highcharts@7.1.1
updated 2 packages and audited 35784 packages in 8.137s
...

Pour trouver précisément le nom des bibliothèques à ajouter, il faut généralement se rendre sur le site web de la bibliothèque qui expliquera comment installer. En effet, il n’existe qu’une bibliothèque JavaScript correspondant à Highcharts, mais plusieurs variantes packagées développées pour Vue.js.

Cette commande va ajouter dans le fichier package.json deux entrées puis télécharger les bibliothèques dans le répertoire node_modules. Ci-dessous est présenté une partie du contenu du fichier package.json.

1
2
3
4
5
6
7
8
9
10
11
{
  ...
  "dependencies": {
    "bootstrap-vue": "^2.0.0-rc.16",
    "highcharts": "^7.1.1",
    "npm": "^6.9.0",
    "vue": "^2.5.17",
    "vue-highcharts": "^0.1.0"
  },
  ...
}

Il faut ensuite ajouter le plugin dans le composant ResultPolldle décrit par le fichier ResultPolldle.vue.

  • Éditer le fichier ResultPolldle.vue en remplaçant les commentaires // Import the VueHighcharts plugin et // Use the VueHighcharts plugin par le code présenté ci-dessous.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
</template>
<script>
// Import the VueHighcharts plugin
import VueHighcharts from "vue-highcharts";

import Vue from "vue";
// Use the VueHighcharts plugin
Vue.use(VueHighcharts);
...
</script>
...
<style>
</style>

Le code Vue.use(VueHighcharts) permet d’utiliser le plugin de manière globale. Dans ce cas, tous les composants décrits par la bibliothèque Highcharts : Highcharts, Highstock, Highmaps et HighchartsGantt sont directement instanciables.

  • Éditer le fichier ResultPolldle.vue en remplaçant la balise de commentaire <!-- Instance of highcharts component --> par la balise <highcharts>.
1
2
3
4
5
6
7
8
9
10
<template>
...
      <div class="row">
        <div class="col-8">
          <!-- Instance of highcharts component -->
          <highcharts/>
        </div>
      </div>
...
</template>

Le code ajouté n’est pas parfait, car il implique une communication entre le composant ResultPolldle et highcharts. Tout comme la communication entre les composants CreatePolldle et CreatePolldleOption, nous aborderons cet aspect dans la section suivante dédiée à la communication entre des composants.

Cycle de vie

Nous vous invitons à vous positionner dans le répertoire polldle-vue-09 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

Un cycle de vie est utilisé comportant un ensemble d’étapes. Chaque étape est associée à un hook permettant d’exécuter un code particulier.

Nous présentons ci-dessous un diagramme du cycle de vie qui permet de lister les différentes étapes (identifiées par un rectangle rouge).

Diagramme du cycle de vie de Vue.js

Huit hooks sont disponibles : beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, beforeDestroy et destroyed. Nous ne les étudierons pas tous exceptés le hook created et mounted, les plus utilisés. Ce lien donne une explication détaillée de leur utilisation Understanding Vue.js Lifecycle Hooks.

Hook created

Le hook created sera exécuté à la création du composant et toutes les propriétés de ce composant (celles qui sont dans data()) sont initialisées et associées au système réactif. Il en est de même pour les événements que nous aborderons dans la section suivante. Le code défini dans ce hook pourra donc accéder aux propriétés du composant. Toutefois, à cette étape, le rendu du template et le DOM virtuel ne sont pas encore effectués. Vous ne devrez donc pas effectuer de modification sur le rendu du composant lors d’un hook created.

Nous utiliserons le hook created dans trois composants :

  • VotePolldle : initialisation des données via l’appel à un service web (utilisation de la bibliothèque JavaScript Axios) ;
  • ResultPolldle : initialisation du Server-Sent Event pour faire du push serveur et récupérer le flux des mises à jour des votes.

Comme nous n’avons pas vu les appels à un service web (via la bibliothèque JavaScript Axios), nous allons focaliser notre présentation du hook created sur le code du composant ResultPolldle.

  • Éditer le fichier ResultPolldle.vue en remplaçant le commentaire // Use created hook to initialize EventSource object par le code présenté dans le hook created.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<template>
...
</template>
<script>
...
export default {
  name: "ResultPolldle",
  data: () => ({
    total: 0,
    state: null,
    question: "",
    errorMessage: "",
    options: options,
    data: []
  }),
  // Use created hook to initialize EventSource object
  created() {
    var source = new EventSource(
      "http://127.0.0.1:9991" +
      "/polldles/" +
      this.$route.params.pathurl +
      "/votes/sse"
    );

    source.addEventListener(
      "update-polldleresult",
      e => {
        var result = JSON.parse(e.data);
        this.options.title.text = "  ";
        this.question = capitalizeFirstLetter(result.question);

        this.data = result.results.map(val => ({
          name: val.name,
          y: val.counter
        }));

        this.total = result.results
          .map(val => val.counter)
          .reduce((partial_sum, a) => partial_sum + a);

        if (this.total > 0) {
          this.state = stateResult.RESULT;
        } else {
          this.state = stateResult.EMPTY;
        }

        this.options.series[0].data = this.data;
      },
      false
    );

    source.onerror = () => {
      this.state = stateResult.ERROR;
      this.errorMessage = "Problem to retrieve Polldle result.";
    };
  },
</script>

Le code présent dans le hook created permet d’initialiser un objet EventSource utilisé pour faire du Server-Sent Event. La première partie initialise l’objet EventSource. La deuxième partie traite les nouvelles données envoyées par le serveur et transforme les données pour les proposer au modèle du composant Highcharts. La troisième partie est une fonction qui s’occupera de traiter les erreurs. On s’aperçoit dans ce code que seules les propriétés du composant sont impactées ce qui est cohérent à l’utilité du scope du hook created.

Si vous désirez des informations supplémentaires sur Server-Sent Event, une technique pour faire du push serveur via une communication unidirectionnelle, nous vous recommandons deux supports de cours : Streaming HTTP : savoir communiquer via des flux et Streaming HTTP : mise en œuvre avec le langage Java.

Hook mounted

Le hook mounted est celui qui vient juste après le premier rendu du template. Au niveau de cette étape, le DOM virtuel est construit et des modifications peuvent être réalisées. Pour accéder au DOM HTML, il est possible d’utiliser une propriété d’instance du composant via l’instruction suivante this.$em.

  • Éditer le fichier CreatePolldle.vue en remplaçant le commentaire // Use mounted hook to log the text content of the DOM par le code présenté dans le hook mounted.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
...
</template>
<script>
...
export default {
  name: "CreatePolldle",
  ...
  // Use mounted hook to log the text content of the DOM
  mounted() {
    console.log(this.$el.textContent);
  },
  ...
};
</script>

Lors de l’exécution du hook mounted, le résultat suivant sera affiché dans la console du développeur.

1
2
3
PollDLEVoting done simply in real-timeAdd your PollDLE optionsClear all PollDLE OptionsCreate PollDLESummary of your PollDLE
      The question is:
      Number of PollDLE options: 0

Savoir communiquer avec un composant

Précédemment, nous avons vu comment créer une instance d’un composant. Toutefois, nous ne nous’étions pas intéressés à expliquer comment transmettre des informations vers le composant créé ou comment ce composant pouvait également communiquer avec d’autres composants. Trois techniques de communication avec un composant seront étudiées :

  • en communication directe via l’utilisation de la référence d’un composant ;
  • en transmettant des propriétés à un composant lors de son instanciation ;
  • en utilisant des événements.

Via la référence d’un composant

Nous vous invitons à vous positionner dans le répertoire polldle-vue-10 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

La communication directe via la référence d’un composant est unidirectionnelle (composant parent vers le composant enfant). Elle permet à un composant parent d’accéder à un composant enfant via sa référence. L’inverse ne sera pas possible. Cette solution amène à un couplage fort entre les composants. En effet, cela suppose d’avoir accès à la référence du composant et de s’assurer que lors de l’utilisation de ce composant celui-ci est toujours existant. Si ce n’est plus le cas, il faudra s’assurer de mettre à jour la référence.

Bien que cette solution ne soit pas la plus avantageuse, elle permet d’accéder à des éléments du composant non disponibles par le système réactif. Dans le cas de notre exemple, la communication par référence est utilisée dans le composant ResultPolldle pour accéder directement à la bibliothèque JavaScript Highcharts via le plugin vue-highcharts. En effet, depuis le composant de la bibliothèque JavaScript certaines caractéristiques ne sont pas accessibles.

  • Éditer le code du composant ResultPolldle via le fichier ResultPolldle.vue en ajoutant l’attribut ref à la balise <highcharts/> (voir commentaire <!-- Declaring Ref attribute -->).
1
2
3
4
5
6
7
8
9
10
<template>
...
      <div class="row">
        <div class="col-8">
          <!-- Declaring Ref attribute -->
          <highcharts ref="highcharts"/>
        </div>
      </div>
...
</template>

Il sera donc possible d’accéder à l’instance de ce composant via la variable highcharts.

  • Éditer le code du composant ResultPolldle via le fichier ResultPolldle.vue en remplaçant le commentaire // Use reference on hightcharts component par le code présenté ci-dessous.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
<template>
...
</template>
<script>
...
  watch: {
    data() {
      // Use reference on hightcharts component
      var chart = this.$refs.highcharts.chart;
      chart.series[0].update(
        {
          data: this.data
        },
        true
      );
    }
  },
...
</script>
...
</template>

La propriété observée data est modifiée à chaque fois qu’un vote est effectué (utilisation de Server-Sent Event depuis le hook created). L’accès au composant se fait de cette manière. L’accès à la propriété d’instance se fait avec this.$refs puis l’accès à la référence du composant est obtenu par this.$refs.highcharts. Le code présenté ensuite permet d’accéder à l’instance chart et de lui impacter des modifications (mise à jour de la partie modèle).

Via les propriétés

Nous vous invitons à vous positionner dans le répertoire polldle-vue-11 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

La communication par propriétés consiste à transmettre des données d’un composant parent à un composant enfant. Ce type de communication est unidirectionnelle (composant parent vers le composant enfant). La communication par propriétés impose d’une part que du côté du composant enfant soit déclarées les propriétés à recevoir et d’autre part que les valeurs des propriétés soient transmises lors de l’instanciation du composant.

Dans notre exemple, nous allons construire des instances du composant CreatePolldleOption utilisées pour afficher les différentes options de notre Polldle. La valeur de chaque option de notre Polldle, éditée depuis un champ de texte, sera transmise depuis le composant CreatePolldle lors de la création des instances CreatePolldleOption.

Côté composant enfant
  • Éditer le composant CreatePolldleOption au niveau de la partie JavaScript en remplaçant le commentaire // Add properties definition on polldleOption object par le code présenté dans props
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
  ...
</template>
<script>
export default {
  ...
  // Add properties definition on polldleOption object
  props: {
    polldleOption: {
      type: Object,
      required: true
    }
  }
  ...
}
</scrip>

Cela permet de déclarer que le composant CreatePolldleOption doit (attribut required) accepter une propriété de type Object qui s’appelle polldleOption.

Une version simplifiée est fournie à titre d’exemple ci-dessous.

1
2
3
4
5
6
7
8
9
10
<template>
  ...
</template>
<script>
export default {
  ...
  props: ["polldleOption"]
  ...
}
</script>
  • Éditer de nouveau le composant CreatePolldleOption au niveau de la partie HTML en remplaçant le commentaire <!-- Bind both value and title attributes with polldleOption property --> par le code présenté.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template >
  <div class="polldle-option-input row justify-content-center no-gutters">
    <div class="col col-auto">
      <!-- Bind both value and title attributes with polldleOption property -->
      <input
        type="text"
        class="form-control"
        readonly
        v-bind:value="polldleOption.text"
        v-bind:title="polldleOption.text"
      >
    </div>
  </div>
</template>

Ici la propriété polldleOption est utilisée pour mapper la valeur textuelle avec les attributs value et title de la balise <input>. Tout comme les propriétés définies dans data(), la propriété polldleOption est déclarée dans le système réactif de Vue.js. Ainsi, tout changement de valeur dans la propriété polldleOption impactera les valeurs dans les attributs value et title.

Côté composant parent

Du côté du composant parent, lors de l’instanciation d’un composant, les valeurs transmises pour les propriétés déclarées de ce composant enfant doivent être renseignées via des attributs portant le même nom que lesdites propriétés. Ces attributs doivent également utiliser la même convention de nommage.

  • Éditer le fichier CreatePolldle.vue en ajoutant l’attribut v-bind:polldleOption="polldleOption" (voir commentaire <!-- Send object value for polldleOption property -->).
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
...
    <div
      class="row justify-content-center"
      v-for="currentPolldleOption in polldleOptions"
      :key="currentPolldleOption.text"
    >
      <!-- Send object value for polldleOption property -->
      <CreatePolldleOption v-bind:polldleOption="currentPolldleOption"/>
    </div>
...
</template>
...

Dans le code montré ci-dessus, pour chaque instance nouvellement créée du composant CreatePolldleOption, un objet de type PolldleOption (contenant une seule propriété String) est transmis comme propriété à ce composant enfant. Ce mode de transmission est appelé dynamique puisque la valeur transmise se fait via la directive v-bind. Ainsi, le système réactif sera également disponible dans le composant CreatePolldleOption pour chaque objet transmis.

  • À titre d’exemple, voici le même code en utilisant la version simplifiée de la directive v-bind.
1
2
3
4
5
6
7
8
9
10
<template>
...
  <div
      class="row justify-content-center"
      v-for="currentPolldleOption in polldleOptions"
      :key="currentPolldleOption.text"
    ><CreatePolldleOption :polldleOption="currentPolldleOption"/></div>
...
</template>
...

Si nous avions souhaité utiliser le mode de transmission statique (en gros une valeur excepté un tableau et un objet), il faudrait avant tout impacter le composant CreatePolldleOption afin de déclarer la propriété pour qu’elle soit une chaîne de caractères et non un objet. Le code correspondant à la transmission de cette chaîne de caractères aurait ressemblé à cela.

1
2
3
4
5
6
7
8
9
10
<template>
...
  <div
      class="row justify-content-center"
      v-for="currentPolldleOption in polldleOptions"
      :key="currentPolldleOption.text"
    ><CreatePolldleOption polldleOption="currentPolldleOption.text"/></div>
...
</template>
...

Depuis le composant ResultPolldle, il y a aussi une transmission de propriétés vers le composant highcharts en utilisant le code suivant : <highcharts :options="options" ref="highcharts"/>. Via :options="options", l’objet options qui contient la configuration du graphique et les données est transmis via des propriétés.

Via le bus d’événements

Nous vous invitons à vous positionner dans le répertoire polldle-vue-12 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

Une communication par bus d’événements amène à un faible couplage entre un composant parent et un composant enfant. Ce type de communication est à choisir dans le cas où vous souhaitez que votre composant enfant puisse communiquer avec le composant parent. Bien entendu, la communication du composant parent vers le composant enfant pourrait également être effectuée via ce type de communication.

La mise en place de ce type de communication est assez classique. Il y a d’abord la phase de création d’événements avec sa transmission vers le bus d’événements. Il y a ensuite la phase d’écouteur qui consiste à réagir (appeler un code particulier) suivant des événements reçus via le bus d’événements. Un événement est constitué d’un identifiant (une chaîne de caractères) et de paramètres (cardinalité zéro ou plusieurs).

Dans notre exemple, le composant CreatePolldleOption va envoyer un événement removedPolldleOption par le bus d’événements lorsque l’utilisateur souhaite supprimer une option (icône de la poubelle). L’abonnement à l’événement removedPolldleOption est réalisé dans le composant parent CreatePolldle. Le traitement à la réception de cet événement consistera à retirer depuis le tableau polldleOptions l’élément correspondant à la bonne option. Pour rappel, c’est dans le composant CreatePolldle que sont stockés les objets relatifs aux options d’un Polldle.

Création d’événements et transmission dans le bus d’événements

Comme précisé dans la section parente, un événement est composé d’une chaîne de caractères et de paramètres. Les paramètres peuvent être de types différents. La transmission de l’événement dans le bus d’événements se fera via la propriété d’instance this.$emit.

Il est à noter que chaque composant à une instance spécifique de bus d’événements. Si vous souhaitez utiliser un bus d’événements commun à tous les composants vous pouvez le déclarer comme cela window.bus.$emit. Toutefois, il serait préférable de passer par un système global comme Vuex.

  • Éditer le code du composant CreatePolldleOption en remplaçant le commentaire // Trigger an event on the current instance par le code suivant.
1
2
3
4
5
6
7
8
9
10
11
12
13
...
<script>
export default {
  ...
  methods: {
    removePolldleOption(polldleOption) {
      // Trigger an event on the current instance
      this.$emit("removed-polldle-option", polldleOption);
    }
  },
  ...
}
</script>

Le code ci-dessus déclenche l’événement removed-polldle-option sur l’instance actuelle en transmettant l’objet PolldleOption. Pour rappel cet objet avait été transmis lors de la création de l’instance du composant PolldleOption (via les propriétés de transmission).

Du fait que le nom de l’événement sera utilisé dans le DOM et que les majuscules seront transformées en minuscules, il est d’usage d’utiliser la convention de nommage kebab-case pour l’écriture des événements.

Abonnement à un événement depuis le bus d’événements

Puisque le déclenchement de l’événement se fait sur l’instance du composant CreatePolldleOption (via l’instance du bus), il faut que l’abonnement s’effectue sur cette même instance. Nous allons donc utiliser la directive v-on dont le nom de l’événement est celui que nous avons déclenché.

  • Éditer le code du composant CreatePolldle en remplaçant le commentaire <!-- Listening the removed-polldle-option event --> par le code suivant.
1
2
3
4
5
6
7
8
9
10
11
<template>
    ...
    <div
      class="row justify-content-center"
      v-for="currentPolldleOption in polldleOptions"
      :key="currentPolldleOption.text"
    >
      <!-- Listening the removed-polldle-option event -->
      <CreatePolldleOption :polldleOption="currentPolldleOption" v-on:removed-polldle-option="removedPolldleOption($event)"/>
    </div>
</template>

Ce code a pour effet d’appeler la fonction removedPolldleOption($event)$event contiendra l’objet transmis lors du déclenchement de l’événement.

  • À titre d’exemple, voici le même code en utilisant la version simplifiée de la directive v-on.
1
2
3
4
5
6
7
8
9
10
<template>
    ...
    <div
      class="row justify-content-center"
      v-for="currentPolldleOption in polldleOptions"
      :key="currentPolldleOption.text"
    >
      <CreatePolldleOption :polldleOption="currentPolldleOption" @removed-polldle-option="removedPolldleOption($event)"/>
    </div>
</template>

Invocation de service REST

Nous vous invitons à vous positionner dans le répertoire polldle-vue-13 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

Nous traitons dans cette section de la communication entre la couche web développée avec Vue.js et la couche serveur développée avec le langage Java. Nous avons déjà évoqué cela en montrant l’utilisation de l’objet EventSource pour faire du push serveur et récupérer le flux des mises à jour des votes. Nous avions alors montré que le code produit ne concernait pas des concepts Vue.js, mais JavaScript. Il en est de même pour l’invocation de service web REST. Même si l’écosystème de Vue.js a fourni le plugin Vue Resource pour faciliter le développement d’appels de service web REST, celui-ci n’est plus recommandé. En effet, les développeurs de Vue.js ont décidé que fournir une bibliothèque spécifique à ce type de tâches était redondant face à la richesse de ce que peut proposer l’écosystème JavaScript.

Dans la suite, nous allons montrer deux façons pour invoquer un service web REST. La première est d’utiliser la nouvelle API JavaScript fetch introduite dernièrement qui se veut remplaçante à XMLHttpRequest. La seconde est d’utiliser la bibliothèque AXIOS.

Dans notre exemple, l’API fetch sera utilisée pour créer un nouveau PollDLE depuis le composant CreatePolldle, tandis que la bibliothèque AXIOS sera utilisée dans le composant VotePolldle. L’objectif est de vous montrer comment intégrer cette API et cette bibliothèque dans un code Vue.js.

Documentation de l’API REST

Nous présentons dans cette section, un détail de l’API REST de notre exemple afin de nous familiariser avec les différents services que nous allons appeler.

Deux ressources sont identifiées : un PollDLE et un Vote.

Le format des objets pour l’envoi et la réception sera du JSON pour toutes les méthodes (excepté celle qui s’occupera de l’initialisation du Server-Sent Event).

PollDLE

  • Création d’un PollDLE
  • POST /polldles
  • Entrée : Polldle
  • Sortie : Polldle (avec l’identifiant renseigné)
  • Retrouver un PollDLE par son identifiant
  • GET /polldles
  • Entrée : pathURL (query) (identifiant du PollDLE)
  • Sortie : Polldle

Vote

La ressource Vote est une sous-ressource de PollDLE.

  • Création d’un vote
  • POST /polldles/{PATH_URL}/votes (identifiant du PollDLE)
  • Entrée : PolldleVote
  • Lister tous les votes d’un PollDLE
  • GET /polldles/{PATH_URL}/votes
  • Sortie : PolldleResult
  • Initialisation du flux (Server-Sent Event) pour la mise à jour des votes
  • GET /polldles/{PATH_URL}/votes/sse
  • Format : text/event-stream

Compiler et exécuter le code serveur

L’objectif de cette section est de montrer comment compiler et exécuter le code serveur développé avec Java afin de pouvoir tester les appels aux services web REST depuis l’application web. Les prérequis logiciels pour continuer sont Java et Maven.

  • Ouvrir un nouveau terminal, se positionner à la racine du répertoire polldle-backend et exécuter la ligne de commande suivante pour compiler la couche serveur à partir de Maven.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ mvn clean package
...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] polldle-parent 0.3-SNAPSHOT ........................ SUCCESS [  0.124 s]
[INFO] poddle-api ......................................... SUCCESS [  1.244 s]
[INFO] polldle-server 0.3-SNAPSHOT ........................ SUCCESS [  6.273 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.811 s
[INFO] Finished at: 2019-06-14T16:24:20+02:00
[INFO] ------------------------------------------------------------------------

Maven compilera le code source, exécutera les tests unitaires et préparera les binaires finals. Ces derniers, disponibles dans le répertoire polldle-server/target sont composés des dépendances (ensemble de fichiers au format .jar) et de fichiers au format .class.

  • Initialiser la variable d’environnement KUMULUZEE_SERVER_HTTP_PORT pour modifier le port d’écoute du serveur.
1
export KUMULUZEE_SERVER_HTTP_PORT=9991
  • Saisir la ligne de commande suivante pour exécuter le serveur qui diffusera les services web REST.
1
2
3
4
$ java -cp "polldle-server/target/dependency/*:polldle-server/target/classes" com.kumuluz.ee.EeApplication
2019-06-14 16:36:12.766 INFO -- org.eclipse.jetty.server.AbstractConnector -- Started ServerConnector@6f5e16cf{HTTP/1.1,[http/1.1]}{0.0.0.0:9991}
2019-06-14 16:36:12.766 INFO -- org.eclipse.jetty.server.Server -- Started @2221ms
2019-06-14 16:36:12.766 INFO -- com.kumuluz.ee.EeApplication -- KumuluzEE started successfully

Les services web REST sont désormais disponibles à cette adresse http://0.0.0.0:9991.

API Fetch

  • Éditer le fichier CreatePolldle.vue en complétant la méthode createPolldle() par le code présenté ci-dessous (au niveau du commentaire // Call REST web service with fetch API).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<script>
...
export default {
  methods: {
    createPolldle() {
      var polldleObject = {
        question: this.question,
        polldleOptions: []
      };

      this.polldleOptions.forEach(element => {
        var newPollOptionElement = { name: element.text };
        if (element.text !== "") {
          polldleObject.polldleOptions.push(newPollOptionElement);
        }
      });

      var request = new Request("http://127.0.0.1:9991" + "/polldles", {
        method: "POST",
        body: JSON.stringify(polldleObject),
        headers: {
          'Content-Type': 'application/json'
        }
      })

      // Call REST web service with fetch API
      fetch(request).then(response => {
        if (response.ok) {
          return response.json();
        } else {
          this.errorMessage = "Problem to create a new Polldle.";
        }
      }).then(data => {
        console.log(data.pathUrl);
      }).catch((error) => {
        this.errorMessage = "Problem to create a new Polldle.";
        console.error(error);
      });
    },
    ...
  },
  ...
}
</script>
...

Lors de la création d’un objet Request nous précisons, l’URL du serveur (qui sera remplacée par une variable d’environnement dans la partie 3), que la méthode HTTP utilisée sera du POST, que le corps est l’objet polldleObject et que le contenu sera au format JSON. Une première promesse retourne l’objet de la réponse si la requête envoyée au serveur s’est correctement déroulée. Une seconde promesse effectue le traitement de l’objet réponse, pour l’instant l’affichage de l’identifiant du Polldle. Dans la section routage, nous modifierons le traitement de la réponse pour rendre visible le composant VotePolldle.

Une documentation exhaustive sur l’API Fetch est disponible à cette adresse : https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

AXIOS

AXIOS est une bibliothèque JavaScript non disponible par défaut dans un projet Vue.js. Il faut donc l’intégrer dans le projet (package.json) et télécharger les dépendances.

  • Saisir la ligne de commande suivante $ npm install axios pour ajouter la bibliothèque AXIOS et compléter automatiquement le fichier package.json.

  • Éditer ensuite le fichier VotePolldle.vue pour ajouter la dépendance de la bibliothèque JavaScript AXIOS au composant VotePolldle (remplacer le commentaire // Add dependency to AXIO JavaScript library par le code présenté ci-dessous).

1
2
3
4
5
6
7
<template>
...
</template>
<script>
// Add dependency to AXIO JavaScript library
import axios from 'axios';
...
  • Compléter le fichier VotePolldle.vue au niveau du hook created en remplaçant le commentaire // To retrieve PollDLE information from REST web service par le code présenté ci-dessous.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
...
<script>
import axios from 'axios';
...
export default {
  name: "VotePolldle",
  data() {
    return {
      ...
    };
  },
  created() {
    // To retrieve PollDLE information from REST web service
    axios.get("http://127.0.0.1:9991" + "/polldles", {
      params : {
        pathURL: this.$route.params.pathurl
      }
    }).then(response => {
      if (response.status === 200) {
        this.question = response.data.question;
        this.polldleOptions = response.data.polldleOptions;
        this.state = stateResult.WAITING_VOTE;
      } else {
        this.errorMessage = "Polldle can not be loaded.";
        this.state = stateResult.ERROR;
      }  
    }).catch(error => {
      this.errorMessage = "Polldle can not be loaded.";
      this.state = stateResult.ERROR;
      console.error(error);
    });
  },
...

Ce code fait donc un appel au service web REST dédié à la récupération des informations d’un PollDLE.

  • Enfin, compléter le fichier VotePolldle.vue au niveau de la méthode vote() en remplaçant le commentaire // To vote for a PollDLE from REST web service par le code présenté ci-dessous.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<script>
import axios from 'axios';
...
export default {
  ...
  methods: {
    vote() {
      // Prepare the data
      var polldleVote = {
        pathUrl: this.$route.params.pathurl,
        polldleOptionResponses: [this.polldleOptionResponses]
      };

      // To vote for a PollDLE from REST web service
      axios({
        method: 'post',
        baseURL: "http://127.0.0.1:9991" + "/polldles/" + this.$route.params.pathurl + "/votes",
        data: JSON.stringify(polldleVote),
        headers: { 
          'Content-Type': 'application/json'
        }
      }).then(response => {
        if (response.status === 200) {
          console.log(this.$route.params.pathurl);
        } else if (response.status === 204) {
          this.state = stateResult.VOTE_ERROR;
          this.errorMessage = "Already voted !!!";
        }
      }).catch(() => {
        this.state = stateResult.VOTE_ERROR;
        this.errorMessage = "Problem to vote for this Polldle.";
      });
    },
  }
};
</script>

Le code ajouté permet d’invoquer le service web REST dédié au vote (création d’une ressource Vote).

Routage avec Vue.js

Nous vous invitons à vous positionner dans le répertoire polldle-vue-14 pour profiter des codes qui vont illustrer cette section. Pensez à faire $ npm install pour installer les modules et $ npm run serve pour démarrer l’exécution en mode développement.

Cette dernière section s’intéresse au routage de notre application Single-Page application. En fonction de l’état de l’URL, nous allons pouvoir choisir quel sera le composant à afficher.

  • / : la création d’un PollDLE (afficher le composant CreatePolldle) ;
  • /{id} : le vote d’un PollDLE ou {id} désigne l’identifiant du PollDLE (afficher le composant VotePolldle);
  • /{id}/result : le résultat des votes d’un PollDLE ou {id} désigne l’identifiant du PollDLE (afficher le composant ResultPolldle).

Vue.js propose un module appelé Vue-Router qui offre un mécanisme de gestion du routage. Vue-Router s’appuie sur un fichier de routage pour établir les différentes règles qui permettent de passer d’un composant à un autre en fonction de la valeur de l’URL.

Initialisation et activation du routage

  • Saisir la ligne de commande suivante $ npm install vue-router pour ajouter le module Vue-Router et compléter automatiquement le fichier package.json.

  • Créer un dossier router à la racine du dossier src puis ajouter un fichier index.js en recopiant le code ci-dessous.

1
2
3
4
5
6
7
8
9
10
11
12
import Vue from 'vue'
import Router from 'vue-router'

import CreatePolldle from '@/components/CreatePolldle'
import VotePolldle from '@/components/VotePolldle'
import ResultPolldle from '@/components/ResultPolldle'

Vue.use(Router)

export default new Router({
  // Règles de routage seront complétées dans la section suivante
})

Ce fichier index.js contrôle le routage de l’application. Les composants développés précédemment sont importés pour être utilisés dans les règles de routage (voir dans la section suivante). Le code Vue.use(Router) permet d’utiliser le plugin Router de manière globale.

  • Éditer le fichier App.vue afin de déléguer au routage le choix du composant en remplaçant le commentaire <!-- Add router-view component --> par le code présenté ci-dessous.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
  <div>
    <!-- Add router-view component -->
    <router-view/>
    <footerPolldle/>
  </div>
</template>

<script>

import footerPolldle from "@/components/FooterPolldle.vue";

export default {
  name: "app",
  components : { footerPolldle }
};
</script>

Contrairement à la version précédente du fichier App.vue, le composant CreatePolldle ne sera pas explicitement utilisé. Cela sera le rôle du composant de routage disponible via la balise <router-view/>.

  • Éditer le fichier main.js en remplaçant les commentaires // Import routing configuration et // Enable routing par le code présenté ci-dessous.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Vue from 'vue'
import App from './App.vue'
// Import routing configuration
import router from './router'

require('./assets/polldle.css')

import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Vue.config.productionTip = false

new Vue({
  // Enable routing
  router,
  render: h => h(App)
}).$mount('#app')

Le code ajouté permettra d’utiliser le système de routage dans l’application complète.

Création de la table de routage

Le composant routage est désormais configuré et activé. Nous allons détailler comment définir des règles de routage.

  • Éditer le fichier router/index.js en complétant par le code présenté dans l’objet Router.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import Vue from 'vue'
import Router from 'vue-router'

import CreatePolldle from '@/components/CreatePolldle'
import VotePolldle from '@/components/VotePolldle'
import ResultPolldle from '@/components/ResultPolldle'

Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'CreatePolldle',
      component: CreatePolldle
    },
    {
      path: '/:pathurl',
      name: 'VotePolldle',
      component: VotePolldle
    },
    {
      path: '/:pathurl/result',
      name: 'ResultPolldle',
      component: ResultPolldle
    }
  ]
})

Trois règles de routage ont été définies dans routes correspondant aux besoins exprimés en début de section. Pour chaque élément d’une route, trois propriétés sont utilisées : path, name et component. La valeur proposée dans la propriété path correspond à un pattern qui doit être satisfait pour activer une route. Si la route est activée alors c’est le composant donné par la propriété component qui sera retourné. À titre d’exemple, pour la règle de routage définie par path: '/:pathurl/result, si elle est active (/123/result), c’est le rendu du composant ResultPolldle qui sera intégré à la place de la balise <router-view> du fichier App.vue.

L’option mode: 'history' permet d’utiliser l’API history.pushState et les URL ressembleront à cela http://localhost:8080/3/result. Dans le cas contraire, le mode par défaut de vue-router est le mode hash qui utilise la partie hash de l’URL pour simuler une URL complète. Les URL ressembleraient à cela http://localhost:8080/#/4/result. L’utilisation du mode history aura un impact pour le déploiement de l’application puisque, quelle que soit l’URL utilisée, un code d’erreur 404 sera retourné. Nous reviendrons sur cet aspect dans la dernière partie de cet article.

Forcer le changement de route

À cet instant si vous testez l’application, vous ne pourrez changer l’affichage de l’application que par l’intermédiaire de la barre d’adresse. Toutefois, nous aimerions pouvoir activer une route quand une opération se termine. Par exemple, à la fin de la création d’un PollDLE, nous aimerions pouvoir voter (composant VotePolldle) ou, à la fin d’un vote, nous aimerions pouvoir visualiser les résultats des votes (ResultPolldle). Nous allons utiliser programmatiquement le mécanisme de navigation via la propriété d’instance this.$router disponible dans une instance de composant Vue.js.

  • Éditer le fichier CreatePolldle.vue en remplaçant le commentaire // Programmatic navigation to display VotePolldle component par le code présenté ci-dessous.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
<script>
...
export default {
  ...
  methods: {
    createPolldle() {
      ...
      fetch(request).then(response => {
        ...
      }).then(data => {
        console.log(data.pathUrl);
        // Programmatic navigation to display VotePolldle component
        this.$router.push({
          name: "VotePolldle",
          params: { pathurl: data.pathUrl }
        });
      }).catch((error) => {
        ...
      });
    }
  }
}
</script>

Lors de la réception de la réponse du service web REST, une nouvelle entrée est ajoutée dans la pile de l’historique this.$router.push. Le nom de la règle de routage est transmis name: "VotePolldle" ainsi que l’identifiant du PollDLE nouvellement créé pathurl: data.pathUrl (par exemple 3). vue-router va donc rechercher une règle portant ce nom et l’activer. Dans ce cas, c’est la règle permettant de retourner le composant VotePolldle qui sera déclenchée.

  • Éditer le fichier VotePolldle.vue en remplaçant le commentaire // Programmatic navigation to display ResultPolldle component par le code ci-dessous.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
<script>
...
export default {
  ...
  methods: {
    vote() {
      ...
     axios({
       ...
      }).then(response => {
        if (response.status === 200) {
          console.log(this.$route.params.pathurl);
          // Programmatic navigation to display ResultPolldle component
          this.$router.push({
            name: "ResultPolldle",
            params: { pathurl: this.$route.params.pathurl }
          });
        } else if (response.status === 204) {
          ...
        }
      }).catch(() => {
        ...
      });
    }
  }
}
</script>

Le même code est obtenu que pour le composant CreatePolldle.vue. À noter que le paramètre transmis params: { pathurl: this.$route.params.pathurl } n’est pas issu de la réponse, mais de la valeur de la route courante.

Conclusion et remerciements

Cette deuxième partie a présenté les principaux concepts de Vue.js au travers d’un exemple complet PollDLE.

Dans la partie suivante, nous nous intéresserons à la problématique de déploiement d’une application Vue.js via l’utilisation de Docker.

Nous tenons à remercier Claude Leloup pour sa relecture orthographique.

Ressources

Cet article est open source. Vous avez noté un problème de typographie ?
Ou quelque chose d'obscur ? Améliorer cet article à partir du dépôt GitHub.
Commentaire

Vous pouvez laisser un commentaire en répondant à ce Tweet.

Je suis Mickaël BARON Ingénieur de Recherche en Informatique 💻 au @LIAS_LAB le jour 🌞
Responsable de zones #Java sur @javaDeveloppez la nuit 🌚
❤️ #Java #Docker #VueJS #Eclipse #Services #WebSemantic