Tutoriel pour apprendre à utiliser le framework Vue.js version 2

Les bases

Maurice Chavelli

Vue.js est une bibliothèque qui permet de créer facilement des interfaces Web interactives. Dans ce cours, nous allons voir dans un premier temps comment l'installer, et dans un deuxième temps les concepts clés ainsi qu'un petit exemple de réalisation pour illustrer.

Commentez

- Titre : Tutoriel pour apprendre à utiliser le framework Vue.js version 2

- Auteur : Maurice Chavelli

- Parution : 6 octobre 2017

- Licence : Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. On se lance !

- I-A. Installation

- I-A-1. La charger et la mettre sur votre serveur

- I-A-2. Utiliser un CDN

- I-B. Le modèle MVVM

- I-B-1. Le modèle

- I-B-2. La vue

- I-B-3. La VueModèle

- I-C. Modèle MVVM dans Vue.js

- I-C-1. VueModèle

- I-C-2. Vue

- I-C-3. Modèle

- I-D. Les directives

- I-E. Interpolations

- I-E-1. Mustache

- I-F. Un exemple

- I-G. En résumé

- II. Action !

- II-A. La directive v-on

- II-B. La directive v-bind

- II-C. Les modificateurs

- II-C-1. Les événements

- II-C-2. Les touches du clavier

- II-D. Le chronomètre

- II-D-1. La Vue

- II-D-2. Le JavaScript

- II-D-3. Le fonctionnement

- II-E. En résumé

- III. On se répète…

- III-A. La directive v-for

- III-B. Un index

- III-C. Itérer un tableau d'objets

- III-D. Et si le tableau est modifié ?

- III-D-1. Les mutateurs

- III-D-2. Les accesseurs

- III-D-3. Modification d'un élément de tableau

- III-E. Itérer un objet

- III-F. Un exemple

- III-G. En résumé

- IV. Double liaison

- IV-A. La directive v-model

- IV-B. Boutons radio et cases à cocher

- IV-B-1. Boutons radio

- IV-B-2. Cases à cocher

- IV-C. Liste de choix

- IV-C-1. Sélection simple

- IV-C-2. Sélections multiples

- IV-C-3. Options dynamiques

- IV-D. Un exemple

- IV-D-1. Ajouter une personne

- IV-D-2. Modifier une personne

- IV-E. En résumé

- V. Accesseurs et mutateurs

- V-A. Accesseurs et mutateurs

- V-A-1. Accesseur

- V-B. Mutateur

- V-C. Un panier

- V-C-1. Fonctionnement

- V-C-2. Le code

- V-D. En résumé

- VI. Les composants (1/2)

- VI-A. Mon premier composant

- VI-B. Passage de données

- VI-B-1. Les props

- VI-B-2. camelCase

- VI-B-3. Attribut dynamique

- VI-C. Gestion des listes

- VI-D. Un tableau

- VI-E. Un composant générique

- VI-F. Un template élégant

- VI-G. Le panier revisité

- VI-H. Vue devtools

- VI-I. En résumé

- VII. Les composants (2/2)

- VII-A. La communication entre composants

- VII-A-1. Compléments sur les props

- VII-B. Les événements

- VII-B-1. Le composant

- VII-B-2. Le parent

- VII-C. Contrôle de saisie et événement

- VII-D. Le panier revisité

- VII-E. En résumé

- VII-F. Remerciements

I - On se lance !

I-A - Installation

Pour installer Vue.js, c'est on ne peut plus simple puisque c'est juste une bibliothèque JavaScript. Vous avez donc deux possibilités…

I-A-1 - La charger et la mettre sur votre serveur

Il suffit de récupérer la bibliothèque en cliquant sur ce bouton sur le site :

Au moment où j'écris ces lignes la version est la 2.0.3, mais il est fort probable que vous trouverez une version plus récente.

Vous n'avez plus ensuite qu'à prévoir une balise script :

<script src="/js/vue.js"></script>

Adaptez évidemment le chemin selon votre contexte.

Comme précisé sur le site pour le développement, il vaut mieux utiliser la version non minifiée (Development Version).

I-A-2 - Utiliser un CDN

Dans ce cas c'est encore plus simple. Vous avez les liens disponibles sur le site au même emplacement que vu ci-dessus. Il suffit ensuite de prévoir une balise script. Voici le lien conseillé :

<script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>

Là aussi il est fort probable que vous aurez une version plus récente.

Il existe d'autres façons d'installer Vue.js, mais qui ne sont à prendre en compte que pour des applications volumineuses qui sortent du cadre de cette série d'articles ( j'y viendrai peut-être dans les derniers). Vous avez tous les détails sur le site.

I-B - Le modèle MVVM

Vue.js utilise le modèle MVVM, autrement dit Modèle-Vue-VueModèle. Voyons ça de plus près. C'est un modèle assez proche du modèle MVC (Modèle-Vue-Contrôleur) qui est plus connu, et du MVP (Modèle-Vue-Présentation) qui est moins connu. Le but est de bien séparer ce qui est présenté à l'écran (l'interface utilisateur) et la gestion.

Ce modèle a originellement été créé en 2005 par Microsoft pour le WPF et Silverlight. Mais il a ensuite été adopté dans certains frameworks JavaScript, comme KnockoutJS.

I-B-1 - Le modèle

Le modèle représente les données nécessaires à l'application. Par exemple, des données sur des livres : titre, année de parution, auteur, éditeur… Le modèle est chargé de gérer ces données : les mémoriser, les conserver, les restituer. Par contre il n'est pas censé effectuer de traitement sur ces données.

I-B-2 - La vue

La vue représente ce qui est accessible à l'utilisateur : l'interface. La principale caractéristique de cette vue c'est qu'elle est interactive. Si l'utilisateur fait une action, la vue doit s'adapter en conséquence. Mais ce n'est pas elle qui contient la logique nécessaire pour le faire, c'est la VueModèle.

I-B-3 - La VueModèle

La VueModèle est l'intermédiaire entre la vue et le modèle. Elle transforme les données en éléments d'affichage dans la vue (data-binding) et elle gère les actions de l'utilisateur. La liaison entre la vue et la VueModèle est donc à double sens :

- un changement dans le modèle doit être actualisé dans la vue ;

- une action dans la vue doit être transmise au modèle.

Voici un petit schéma pour illustrer tout ça :

I-C - Modèle MVVM dans Vue.js

Voyons maintenant comment cela est implémenté dans Vue.js.

I-C-1 - VueModèle

On va voir ici d'emblée le principal objet de Vue.js qui possède le constructeur Vue pour instancier une VueModèle. Donc à la base on va créer un objet :

var vm = new Vue({ /* options */ })

Cette vue sera associée à un élément du DOM. Je trouve un peu dommage la confusion générée avec l'appellation « Vue » étant donné que c'est une VueModèle. Mais on va voir à l'usage que ce n'est pas vraiment un souci.

Nous allons voir, juste après, à quoi correspondent les options transmises.

I-C-2 - Vue

J'ai dit ci-dessus que la VueModèle est associée à un élément du DOM. La vue est justement constituée par cet élément du DOM. Il y a deux façons de déclarer cet élément : on peut le transmettre dans les options lorsqu'on crée la VueModèle :

vm = new Vue({
el: '#element'
});

Ou alors on le déclare ensuite :

var vm = new Vue();
vm.el = "#element";

I-C-3 - Modèle

Le modèle est un objet JavaScript qu'on va associer à la VueModèle. Là encore, on pourra le faire directement dans les options :

1. vm = new Vue({

2. el: '#element',

3. data : {}

4. });

Ou ensuite :

1. var vm = new Vue({

2. el: '#element'

3. });

4. vm.data = {};

Voilà les acteurs en place mais pour le moment on ne peut pas en faire grand-chose, il va nous falloir des outils pour les mettre en relation pour créer de l'interactivité.

I-D - Les directives

Si vous utilisez AngularJS vous êtes déjà habitué aux directives, puisque ici c'est le même principe qui est appliqué, en plus simple.

Une directive est un attribut HTML qui dit de faire une action dans le DOM. Vue.js propose de nombreuses directives, et on peut aussi en créer de nouvelles selon les besoins.

Prenons un exemple. Voici la partie HTML :

<div id="tuto">
<p v-text="texte"></p>
</div>

Et voici la partie JavaScript :

1. var vm = new Vue({

2. el: '#tuto',

3. data: {

4. texte: 'Mon texte'

5. }

6. });

On crée une VueModèle vm, on lui associe l'élément du DOM qui a l'identifiant tuto (la vue) et les données data (le modèle).

Dans le HTML on a la directive v-text qui associe la propriété texte du modèle avec le contenu de la balise p du DOM. On va donc avoir comme contenu de la balise la valeur de la propriété texte. Et donc afficher :

Mon texte

Voici une petite illustration des liaisons :

Là où ça devient intéressant, c'est que si cette valeur change, l'affichage changera également. C'est facile à vérifier en ajoutant cette ligne à la fin du code vu ci-dessus :

setTimeout(function(){ vm.texte = 'Mon autre texte'; }, 3000);

Maintenant au bout de trois secondes l'affichage va changer pour :

Mon autre texte

Remarquez comment j'ai référencé la propriété :

vm.texte = 'Mon autre texte';

On obtient le même résultat que si on avait écrit :

vm.$data.texte = 'Mon autre texte';

Mais c'est bien plus lisible et pratique ainsi !

Une instance de Vue, en plus de sa propriété data qu'on a abordée ci-dessus, expose un certain nombre d'autres propriétés et méthodes. Pour les différencier elles commencent toutes par le caractère $. Par exemple, avec $el on peut connaître l'élément HTML en liaison avec l'instance. Vous pouvez retrouver toutes les propriétés dans la documentation. Vous avez à la suite toutes les méthodes.

I-E - Interpolations

I-E-1 - Mustache

Rien à voir avec nos moustaches même si justement ce nom vient de la ressemblance entre les accolades et les moustaches. Toujours est-il que la syntaxe mustache est devenue une référence pour le templating dans de multiples langages. La base en est d'une grande simplicité. Reprenons l'exemple abordé ci-dessus, mais cette fois en nous passant de la directive :

<div id="tuto">
<p>{{ texte }}</p>
</div>

On conserve le même code JavaScript et on obtient le même résultat. Le texte ainsi transmis est « échappé », c'est-à-dire que si vous prévoyez, par exemple une balise HTML :

data: {
texte: '<span>Mon texte</span>'
}

Le résultat sera rendu en texte :

<span>Mon texte</span>

Ceci pour des raisons de sécurité. Si vous tenez absolument à ne pas protéger les données vous pouvez utiliser cette syntaxe avec la directive v-html :

<p v-html="'<strong>Mon texte</strong>'"></p>

Mais dans ce cas, attention aux attaques XSS !

Voici une petite illustration des liaisons :

I-F - Un exemple

Nous avons à présent suffisamment d'éléments pour construire un premier exemple. Nous allons faire un chronomètre qui se déclenche au chargement de la page et qui affiche les heures, les minutes et les secondes. Donc rien de bien extraordinaire, mais on va voir avec quelle facilité on peut le réaliser avec Vue.js. Voilà le code complet de la page :

1. <!DOCTYPE html>

2. <html lang="fr">

4. <head>

5. <meta charset="utf-8">

6. <meta http-equiv="X-UA-Compatible" content="IE=edge">

7. <meta name="viewport" content="width=device-width, initial-scale=1">

8. <title>Test vue.js</title>

9. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

10. </head>

12. <body>

14. <div class="container">

16. <div id="tuto" class="text-center">

17. <h1>Vous êtes là depuis :</h1>

18. <h1>

19. <span class="label label-primary">{{ heures }}</span> heures

20. <span class="label label-primary">{{ minutes }}</span> minutes

21. <span class="label label-primary">{{ secondes }}</span> secondes

22. </h1>

23. </div>

25. </div>

27. <script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>

28. <script>

30. var vm = new Vue({

31. el: '#tuto',

32. data: {

33. heures: 0,

34. minutes: 0,

35. secondes: 0

36. }

37. });

39. var totalSecondes = 0;

41. setInterval(function() {

42. var minutes = Math.floor(++totalSecondes / 60);

43. vm.secondes = totalSecondes - minutes * 60;

44. vm.heures = Math.floor(minutes / 60);

45. vm.minutes = minutes - vm.heures * 60;

46. }, 1000);

48. </script>

50. </body>

52. </html>

Avec cet aspect :

Voyons un peu comment ça fonctionne…

La partie vue est ici :

1. <div id="tuto" class="text-center">

2. <h1>Vous êtes là depuis :</h1>

3. <h1>

4. <span class="label label-primary">{{ heures }}</span> heures

5. <span class="label label-primary">{{ minutes }}</span> minutes

6. <span class="label label-primary">{{ secondes }}</span> secondes

7. </h1>

8. </div>

On a la DIV englobante avec l'identifiant tuto. On a aussi trois emplacements « mustache » pour :

- heures ;

- minutes ;

- secondes.

Le reste est juste de la mise en forme boostée par Bootstrap.

La partie JavaScript est constituée dans un premier temps par la déclaration de la VueModèle :

1. var vm = new Vue({

2. el: '#tuto',

3. data: {

4. heures: 0,

5. minutes: 0,

6. secondes: 0

7. }

8. });

On trouve l'élément du DOM désigné par la propriété el. On trouve aussi le modèle avec la propriété data. On trouve au niveau des données les trois entités dont nous avons besoin pour gérer le chronomètre. On sait aussi que si on modifie une valeur dans le modèle ça sera répercuté dans la vue. Il suffit donc de mettre à jour régulièrement ces valeurs pour faire fonctionner le chronomètre.

Cette partie est réalisée avec ce code :

1. var totalSecondes = 0;

3. setInterval(function() {

4. var minutes = Math.floor(++totalSecondes / 60);

5. vm.secondes = totalSecondes - minutes * 60;

6. vm.heures = Math.floor(minutes / 60);

7. vm.minutes = minutes - vm.heures * 60;

8. }, 1000);

On initialise une variable totalSecondes qui va cumuler les secondes qui défilent. Ensuite on met en marche un timer avec un pas de 1 seconde, et on actualise à chaque fois les données du modèle.

On n'a pas à se soucier de la manipulation du DOM, ce qui nous incomberait avec juste du JavaScript ou avec JQuery. Ici la liaison est automatiquement créée entre le modèle et la vue par la VueModèle.

I-G - En résumé

- Vue.js implémente le modèle MVVM.

- Vue.js offre des directives pour agir sur le DOM.

- Vue.js offre la syntaxe mustache pour la liaison des données.

II - Action !

Je vous ai dit dans le précédent chapitre que Vue.js est destiné à créer des interfaces interactives. Pour le moment je ne vous l'ai pas encore prouvé ! Dans ce chapitre on va voir comment gérer les actions de l'utilisateur.

II-A - La directive v-on

Pour intercepter les événements survenant au niveau de la vue, Vue.js offre la directive v-on.

Prenons un petit exemple pour introduire cette notion :

<div id="tuto">
<a v-on:click="action">Cliquez ici !</a>
</div>

On a mis la directive v-on dans la balise a. Notez la syntaxe : on fait suivre le nom de la directive de deux points, du nom de l'événement, puis du nom d'une fonction. On comprend vite qu'on va appeler la fonction lorsqu'on clique sur le lien.

Voici le code JavaScript :

1. new Vue({

2. el: '#tuto',

3. methods: {

4. action: function () {

5. alert('On a cliqué !');

6. }

7. }

8. });

Nous découvrons une propriété pas encore rencontrée : methods. C'est ici qu'on va placer les méthodes de la VueModèle. Ici on a besoin d'une méthode action pour répondre à l'événement de clic.

Le résultat au clic est donc l'ouverture de la petite fenêtre d'information :

Voici une illustration de la liaison :

On commence à avoir de l'interactivité !

Il existe une syntaxe plus courte et bien pratique pour la directive v-on, il suffit de la remplacer par le caractère @, on écrira ainsi @click.

On peut aussi, au lieu d'une fonction, utiliser une expression comme argument de la directive v-on. Voici un exemple :

1. <div id="tuto">

2. <h1>

3. <button class="button btn-primary" v-on:click="++n">+</button> {{ n }}

4. </h1>

5. </div>

Cette fois on utilise un bouton et le même événement click. Mais au lieu d'une fonction on se contente d'incrémenter la variable n. D'autre part on affiche la valeur de cette variable avec mustache. Cette approche est à réserver à des traitements sommaires.

Voici le JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: { n: 0 }

4. });

On initialise n avec la valeur 0 et on sait que sa valeur sera systématiquement répercutée dans la vue. Donc chaque fois qu'on clique sur le bouton la valeur affichée s'incrémente. Voilà l'aspect obtenu :

Voici une schématisation du fonctionnement :

II-B - La directive v-bind

Tous les événements disponibles avec JavaScript le sont évidemment aussi avec Vue.js, ce qui ouvre pas mal de perspectives au niveau de l'interactivité. Prenons un petit exemple pour un changement d'aspect au survol. Voici la vue :

1. <div id="tuto">

2. <h1>

3. <span class="label" v-bind:class="type" v-on:mouseover="changeType">{{type}}</span>

4. </h1>

5. </div>

On voit ici une nouvelle directive : v-bind. Elle permet de lier un attribut à une expression (mustache ne fonctionne pas pour les attributs avec la version 2). Ici on va donc pouvoir changer de façon dynamique  la valeur d'un attribut, ici une classe (ça serait la même chose pour du style).

D'autre part, cette fois l'événement utilisé est mouseover, autrement dit le déclenchement va avoir lieu au survol de l'élément par le curseur de la souris.

Pour avoir le visuel cet exemple nécessite Bootstrap. Donc prévoyez son chargement :

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

Voici le JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: {

4. type: 'label-primary'

5. },

6. methods: {

7. changeType: function() {

8. this.type = (this.type == 'label-primary') ? 'label-success' : 'label-primary';

9. }

10. }

11. });

On a dans les données la propriété type qui au départ a la valeur « label-primary ». À chaque survol de l'étiquette par le curseur de la souris cette valeur va changer, alternant entre « label-primary » et « label-success ». Du coup l'aspect à l'écran va aussi changer en conséquence.

D'autre part, on modifie aussi le texte de l'étiquette qui est relié à la même propriété.

Donc au départ on a :

Et ensuite :

Et ainsi de suite à chaque survol…

Voici une illustration du fonctionnement :

Il existe une syntaxe plus courte pour la directive v-bind, il suffit de la remplacer par le caractère « : », on écrira par exemple « :class ».

II-C - Les modificateurs

II-C-1 - Les événements

Quand on utilise des événements avec JavaScript on a fréquemment besoin d'utiliser aussi event.preventDefault() ou event.stopPropagation(). On peut évidemment ajouter ces méthodes dans le code mais il y a bien plus simple avec Vue.js !

On peut ajouter aux événements des modificateurs, il en existe quatre :

- .stop : on stoppe la propagation de l'événement après le nœud actuel du DOM ;

- .prevent : on empêche l'action implicite de l'événement ;

- .capture : on utilise le mode capture quand on ajoute l'événement ;

- .self : la cible doit être l'élément lui-même.

Au niveau de la syntaxe c'est tout simple, par exemple :

<form @submit.prevent="onSubmit"></form>

En plus on peut chaîner les modificateurs :

<form @submit.stop.prevent="onSubmit"></form>

II-C-2 - Les touches du clavier

Quand on écoute les touches du clavier on aime savoir quelle touche a été activée. Là aussi Vue.js nous simplifie la vie. Il suffit d'ajouter le code de la touche à l'événement :

<input @keydown.13="submit">

Pour nous simplifier encore plus la vie, on dispose d'alias pour les touches les plus courantes :

- enter ;

- tab ;

- delete ;

- esc ;

- space ;

- up ;

- down ;

- left ;

- right.

Par exemple :

<input @keydown.enter="submit">

II-D - Le chronomètre

On va poursuivre l'exemple du chronomètre vu précédemment en lui ajoutant de l'interactivité. On va en profiter au passage pour découvrir la directive v-show très utile, qui permet de masquer des éléments du DOM à l'affichage. Voici la nouvelle version complète :

1. <!DOCTYPE html>

2. <html lang="fr">

4. <head>

5. <meta charset="utf-8">

6. <meta http-equiv="X-UA-Compatible" content="IE=edge">

7. <meta name="viewport" content="width=device-width, initial-scale=1">

8. <title>Test vue.js</title>

9. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

10. </head>

12. <body>

14. <div class="container">

16. <div id="tuto" class="text-center">

17. <h1>Le chronomètre</h1>

18. <hr>

19. <h1>

20. <span class="label label-primary">{{ time.minutes }}</span> minutes

21. <span class="label label-primary">{{ time.secondes }}</span> secondes

22. </h1>

23. <br>

24. <p>

25. <button class="btn btn-danger btn-lg" v-show="etat.backward" @click="backward">Effacer</button>

26. <button class="btn btn-danger btn-lg" v-show="etat.stop" @click="stop">Arrêter</button>

27. <button class="btn btn-primary btn-lg" v-show="etat.play" @click="play">{{ btnPlay }}</button>

28. </p>

29. </div>

31. </div>

33. <script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>

35. <script>

37. var vm = new Vue({

38. el: '#tuto',

39. data: {

40. time: {

41. minutes: 0,

42. secondes: 0

43. },

44. btnPlay: 'Démarrer',

45. etat: {

46. stop: false,

47. backward: false,

48. play: true

49. }

50. },

51. methods: {

52. backward: function() {

53. chronoReset();

54. },

55. play: function() {

56. chronoStart();

57. },

58. stop: function() {

59. chronoStop();

60. }

61. }

62. });

64. var totalSecondes = 0;

65. var timer;

67. chronoStart = function() {

68. timer = setInterval(function() {

69. vm.time.minutes = Math.floor(++totalSecondes / 60);

70. vm.time.secondes = totalSecondes - vm.time.minutes * 60;

71. }, 1000);

72. setEtat(false, true, false);

73. vm.btnPlay = 'Continuer';

74. };

76. chronoStop = function() {

77. clearInterval(timer);

78. setEtat(true, false, true);

79. };

81. chronoReset = function() {

82. totalSecondes = 0;

83. vm.time.minutes = 0;

84. vm.time.secondes = 0;

85. setEtat(true, false, false);

86. vm.btnPlay = 'Démarrer';

87. }

89. setEtat = function(play, stop, backward) {

90. vm.etat.play = play;

91. vm.etat.stop = stop;

92. vm.etat.backward = backward;

93. };

95. </script>

97. </body>

99. </html>

II-D-1 - La Vue

Voici la partie HTML :

1. <div id="tuto" class="text-center">

2. <h1>Le chronomètre</h1>

3. <hr>

4. <h1>

5. <span class="label label-primary">{{ time.minutes }}</span> minutes

6. <span class="label label-primary">{{ time.secondes }}</span> secondes

7. </h1>

8. <br>

9. <p>

10. <button class="btn btn-danger btn-lg" v-show="etat.backward" @click="backward">Effacer</button>

11. <button class="btn btn-danger btn-lg" v-show="etat.stop" @click="stop">Arrêter</button>

12. <button class="btn btn-primary btn-lg" v-show="etat.play" @click="play">{{ btnPlay }}</button>

13. </p>

14. </div>

Pour simplifier je n'ai gardé que les minutes et secondes, ce qui est réaliste pour un chronomètre. On utilise deux étiquettes avec une liaison « mustache ». On trouve également trois boutons de commande avec pour chacun :

1. Une directive v-on (avec la syntaxe courte @) pour détecter l'événement click ;

2. Une directive v-show pour pouvoir changer la visibilité du bouton.

D'autre part, le troisième bouton a son texte avec une liaison « mustache », il pourra donc changer de nom.

La directive v-show fonctionne avec une valeur booléenne. Si celle-ci est vraie l'élément est affiché, sinon il est masqué (concrètement on a une propriété CSS display qui change de valeur, mais l'élément existe dans le DOM). On va s'en servir ici pour faire apparaître ou disparaître les boutons selon l'étape de fonctionnement du chronomètre.

Il existe aussi la directive v-if qui fonctionne visuellement comme v-show, mais il y a deux différences :

- avec v-if l'élément n'existe pas dans le DOM ;

- on peut utiliser la directive v-else pour organiser les conditions.

II-D-2 - Le JavaScript

Voyons la partie JavaScript. D'abord la déclaration propre à Vue.js :

1. var vm = new Vue({

2. el: '#tuto',

3. data: {

4. time: {

5. minutes: 0,

6. secondes: 0

7. },

8. btnPlay: 'Démarrer',

9. etat: {

10. stop: false,

11. backward: false,

12. play: true

13. }

14. },

15. methods: {

16. backward: function() {

17. chronoReset();

18. },

19. play: function() {

20. chronoStart();

21. },

22. stop: function() {

23. chronoStop();

24. }

25. }

26. });

On trouve :

- la liaison avec l'élément du DOM : #tuto ;

- le modèle (data) avec trois propriétés :

1. time : deux propriétés pour les minutes et secondes du chronomètre,

2. btnPlay : pour le texte du bouton (Démarrer/Continuer),

3. etat : trois propriétés pour la visibilité des trois boutons ;

- les méthodes :

1. backward : pour remettre le chronomètre à 0,

2. play : pour lancer ou relancer le chronomètre,

3. stop : pour arrêter le chronomètre.

On a ensuite les variables globales et les fonctions de gestion :

1. var totalSecondes = 0;

2. var timer;

4. chronoStart = function() {

5. timer = setInterval(function() {

6. vm.time.minutes = Math.floor(++totalSecondes / 60);

7. vm.time.secondes = totalSecondes - vm.time.minutes * 60;

8. }, 1000);

9. setEtat(false, true, false);

10. vm.btnPlay = 'Continuer';

11. };

13. chronoStop = function() {

14. clearInterval(timer);

15. setEtat(true, false, true);

16. };

18. chronoReset = function() {

19. totalSecondes = 0;

20. vm.time.minutes = 0;

21. vm.time.secondes = 0;

22. setEtat(true, false, false);

23. vm.btnPlay = 'Démarrer';

24. }

26. setEtat = function(play, stop, backward) {

27. vm.etat.play = play;

28. vm.etat.stop = stop;

29. vm.etat.backward = backward;

30. };

II-D-3 - Le fonctionnement

Au départ on a cet aspect :

Lorsqu'on clique sur le bouton « Démarrer », le chronomètre se met en marche. Au niveau des boutons, « Démarrer » disparaît et « Arrêter » apparaît :

Lorsqu'on clique sur « Arrêter » le comptage s'interrompt, le bouton « Arrêter » disparaît. Les boutons « Effacer » et « Continuer » apparaissent :

Si on clique sur « Effacer » on revient à la situation de départ, et si on clique sur « Continuer » le comptage reprend et le bouton « Arrêter » réapparaît.

II-E - En résumé

- On gère les événements avec la directive v-on. La syntaxe courte est « @ ».

- On peut ajouter des modificateurs aux événements.

- On modifie des attributs avec la directive v-bind. La syntaxe courte est « : ».

- On masque les éléments du DOM avec la directive v-show.

III - On se répète…

On a vu qu'on pouvait établir une liaison entre une propriété dans le modèle et un élément dans la vue. Mais pour le moment on a utilisé des valeurs simples : nombre ou chaîne de caractères. Dans de multiples cas on aura besoin d'afficher des valeurs contenues dans un tableau ou un objet. Comment procéder dans ce cas ? Vue.js nous propose une directive puissante et simple à utiliser pour réaliser cela.

III-A - La directive v-for

La directive v-for permet de répéter l'affichage d'un élément du DOM en fonction des valeurs contenues dans un tableau du modèle. Un petit exemple vous fera comprendre cela rapidement.

Voilà le HTML :

1. <ul id="tuto">

2. <li v-for="value in items">

3. {{ value }}

4. </li>

5. </ul>

Le but est de créer une liste non ordonnée. On englobe donc avec une balise ul. On prévoit la directive v-for dans la balise li qui doit être répétée, et on précise le nom de la propriété qui contient les objets. On utilise enfin mustache pour les valeurs des items.

Voilà le JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: {

4. items: [

5. "Je suis l'item 1",

6. "Je suis l'item 2",

7. "Je suis l'item 3"

8. ]

9. }

10. });

On a la propriété items qui contient un tableau de valeurs.

Avec ce rendu :

Le tableau est donc parcouru, et pour chaque objet rencontré, une balise li est créée avec la valeur de l'item.

On peut également utiliser for à la place de in pour se rapprocher de la syntaxe des itérateurs.

Voici une illustration du fonctionnement :

III-B - Un index

Il est souvent utile de connaître l'index de l'élément répété, et pour l'obtenir il faut ajouter la variable index. Voici un exemple :

<li v-for="(value, index) in fruits">
{{ index }} => {{ value }}
</li>

Avec ce JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: {

4. fruits: [

5. 'pomme',

6. 'cerise',

7. 'abricot'

8. ]

9. }

10. });

Et ce résultat :

Le principal intérêt de connaître l'index est dans une perspective dynamique. Voici le code HTML modifié pour illustrer cela :

<li v-for="(value, index) in fruits" @click="getFruit(index)">
{{ index }} => {{ value }}
</li>

On a maintenant en place la détection d'un clic. On transmet comme paramètre l'index.

Voici le JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: {

4. fruits: [

5. 'pomme',

6. 'cerise',

7. 'abricot'

8. ]

9. },

10. methods: {

11. getFruit: function(index) {

12. alert('Je suis ' + this.fruits[index]);

13. }

14. }

15. });

On a une méthode getFruit qui attend l'argument index. Selon la valeur de l'index on va extraire le bon fruit du tableau et obtenir ce genre de message :

Sans cet index on ne pourrait pas localiser l'action.

Voici une illustration de ce fonctionnement :

III-C - Itérer un tableau d'objets

On a vu qu'on peut créer une répétition dans le DOM avec un tableau de valeurs. Peut-on aussi le faire à partir d'un tableau d'objets ? La réponse est positive et nous allons voir comment faire avec un premier exemple.

Voici la partie HTML :

1. <table class="table table-bordered">

2. <caption>Personnes</caption>

3. <thead>

4. <tr>

5. <th>Nom</th>

6. <th>Prénom</th>

7. </tr>

8. </thead>

9. <tbody id="tuto">

10. <tr v-for="personne in personnes">

11. <td>{{ personne.nom }}</td>

12. <td>{{ personne.prenom }}</td>

13. </tr>

14. </tbody>

15. </table>

La structure est en place et on va répéter des cellules avec les informations du modèle. On se réfère à la propriété personnes du modèle et aux propriétés nom et prenom.

Voici le JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: {

4. personnes: [

5. {nom: "Durand", prenom: "Jacques"},

6. {nom: "Dupont", prenom: "Albert"},

7. {nom: "Martin", prenom: "Denis"},

8. ]

9. }

10. });

On a le modèle avec toutes les personnes mémorisées (nom et prénom).

Voici le résultat à l'affichage :

Voici une schématisation du fonctionnement :

III-D - Et si le tableau est modifié ?

On a vu que lorsqu'on lie une propriété avec une valeur simple le changement de celle-ci est répercuté dans la vue. Qu'en est-il avec les tableaux ?

III-D-1 - Les mutateurs

Toutes les méthodes qui modifient un tableau (les mutateurs) sont prises en compte. Voici les mutateurs en œuvre.

On conserve le HTML utilisé ci-dessus :

1. <table class="table table-bordered">

2. <caption>Personnes</caption>

3. <thead>

4. <tr>

5. <th>Nom</th>

6. <th>Prénom</th>

7. </tr>

8. </thead>

9. <tbody id="tuto">

10. <tr v-for="personne in personnes">

11. <td>{{ personne.nom }}</td>

12. <td>{{ personne.prenom }}</td>

13. </tr>

14. </tbody>

15. </table>

Avec ce JavaScript :

1. var vm = new Vue({

2. el: '#tuto',

3. data: {

4. personnes: [

5. {nom: "Durand", prenom: "Jacques"},

6. {nom: "Dupont", prenom: "Albert"},

7. {nom: "Martin", prenom: "Denis"},

8. ]

9. }

10. });

12. setTimeout(function(){vm.personnes.push({nom: "Claret", prenom: "Marcel"});}, 2000);

13. setTimeout(function(){vm.personnes.pop();}, 4000);

14. setTimeout(function(){vm.personnes.unshift({nom: "Claret", prenom: "Marcel"});}, 6000);

15. setTimeout(function(){vm.personnes.shift();}, 8000);

16. setTimeout(function(){vm.personnes.splice(1, 1, {nom: "Claret", prenom: "Marcel"});}, 10000);

17. setTimeout(function(){vm.personnes.sort(function (a, b) { return (a.nom > b.nom) });}, 12000);

18. setTimeout(function(){vm.personnes.reverse();}, 14000);

Lorsque vous chargez la page, le tableau apparaît comme présenté ci-dessus, puis toutes les deux secondes il se transforme conformément aux méthodes utilisées. Vous allez constater que le tableau de la vue suit fidèlement les changements du tableau du modèle.

III-D-2 - Les accesseurs

Les accesseurs ne modifient pas un tableau mais en retournent un nouveau. Dans ce cas évidemment les modifications ne sont pas répercutées. Il faut donc remplacer l'ancien tableau par le nouveau. Prenons un nouvel exemple, le HTML est le même que ci-dessus :

Voici la partie JavaScript :

1. var vm = new Vue({

2. el: '#tuto',

3. data: {

4. personnes: [

5. {nom: "Durand", prenom: "Jacques"},

6. {nom: "Dupont", prenom: "Albert"},

7. {nom: "Martin", prenom: "Denis"},

8. ]

9. }

10. });

12. setTimeout(function(){

13. vm.personnes = vm.personnes.concat([

14. {nom: "Claret", prenom: "Marcel"},

15. {nom: "Verlou", prenom: "Gustave"}

16. ]);

17. }, 2000);

18. setTimeout(function(){vm.personnes = vm.personnes.slice(1, 4);}, 4000);

On a encore une modification du tableau toutes les deux secondes. Vous remarquerez que cette fois il a fallu affecter à nouveau le tableau pour prendre en compte les modifications.

III-D-3 - Modification d'un élément de tableau

Regardez maintenant cet exemple :

1. var vm = new Vue({

2. el: '#tuto',

3. data: {

4. personnes: [

5. {nom: "Durand", prenom: "Jacques"},

6. {nom: "Dupont", prenom: "Albert"},

7. {nom: "Martin", prenom: "Denis"},

8. ]

9. }

10. });

12. vm.personnes[1] = {nom: "Claret", prenom: "Marcel"};

Dans ce cas le tableau ne sera pas mis à jour dans la vue.

Pour y parvenir, Vue.js propose la méthode set :

1. var vm = new Vue({

2. el: '#tuto',

3. data: {

4. personnes: [

5. {nom: "Durand", prenom: "Jacques"},

6. {nom: "Dupont", prenom: "Albert"},

7. {nom: "Martin", prenom: "Denis"},

8. ]

9. }

10. });

12. Vue.set(vm.personnes, 1, {nom: "Claret", prenom: "Marcel"});

Cette fois le nouvel objet sera pris en compte !

III-E - Itérer un objet

Maintenant voyons le cas de l'itération d'un objet avec un exemple simple.

Voici le HTML :

1. <ul>

2. <li v-for="value in personne">

3. {{ value }}

4. </li>

5. </ul>

Et le JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: {

4. personne: {

5. nom: "Durand",

6. prenom: "Jacques"

7. }

8. }

9. });

On va ainsi itérer les propriétés de l'objet personne :

Il est aussi possible de récupérer les clés. Voici l'exemple ci-dessus complété :

1. <ul>

2. <li v-for="(value, key) in personne">

3. {{ key }} => {{ value }}

4. </li>

5. </ul>

Le JavaScript est le même et voici le résultat :

III-F - Un exemple

Avec tout ce que nous avons vu jusqu'à présent, nous allons pouvoir élaborer un exemple un peu plus intéressant. Disons que nous avons une liste de personnes dans un tableau. Nous voulons pour chaque personne un bouton pour la mettre à la poubelle. On a donc aussi une liste de personnes en poubelle. Dans cette poubelle nous voulons pouvoir soit rétablir la personne dans la liste des personnes, soit la supprimer complètement. Vous comprendrez sans doute mieux en faisant fonctionner l'exemple.

Voici le code complet :

1. <!DOCTYPE html>

2. <html lang="fr">

4. <head>

5. <meta charset="utf-8">

6. <meta http-equiv="X-UA-Compatible" content="IE=edge">

7. <meta name="viewport" content="width=device-width, initial-scale=1">

8. <title>Test vue.js</title>

9. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

10. </head>

12. <body>

14. <div class="container" id="tuto">

15. <br>

17. <div class="panel panel-primary" v-show="personnes.length">

18. <div class="panel-heading">Personnes actives</div>

19. <table class="table table-bordered table-striped">

20. <thead>

21. <tr>

22. <th class="col-sm-5">Nom</th>

23. <th class="col-sm-5">Prénom</th>

24. <th class="col-sm-2"></th>

25. </tr>

26. </thead>

27. <tbody>

28. <tr v-for="(personne, index) in personnes">

29. <td>{{ personne.nom }}</td>

30. <td>{{ personne.prenom }}</td>

31. <td><button class="btn btn-warning btn-block" @click="supprimer(index)">Poubelle</button></td>

32. </tr>

33. </tbody>

34. </table>

35. <div class="panel-footer">

36. &nbsp

37. <button class="button btn btn-xs btn-warning" @click="toutPoubelle">Tout à la poubelle</button>

38. </div>

39. </div>

41. <div class="panel panel-danger" v-show="poubelle.length">

42. <div class="panel-heading">Poubelle</div>

43. <table class="table table-bordered table-striped">

44. <thead>

45. <tr>

46. <th class="col-sm-4">Nom</th>

47. <th class="col-sm-4">Prénom</th>

48. <th class="col-sm-2"></th>

49. <th class="col-sm-2"></th>

50. </tr>

51. </thead>

52. <tbody>

53. <tr v-for="(personne, index) in poubelle">

54. <td>{{ personne.nom }}</td>

55. <td>{{ personne.prenom }}</td>

56. <td><button class="btn btn-success btn-block" @click="retablir(index)">Rétablir</button></td>

57. <td><button class="btn btn-danger btn-block" @click="eliminer(index)">Supprimer</button></td>

58. </tr>

59. </tbody>

60. </table>

61. <div class="panel-footer">

62. &nbsp

63. <div class="btn-group">

64. <button class="button btn btn-xs btn-success" @click="toutRetablir">Tout rétablir</button>

65. <button class="button btn btn-xs btn-danger" @click="toutEliminer">Tout supprimer</button>

66. </div>

67. </div>

68. </div>

70. </div>

72. <script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>

74. <script>

76. var vm = new Vue({

77. el: '#tuto',

78. data: {

79. personnes: [

80. {nom: "Claret", prenom: "Marcel"},

81. {nom: "Dupont", prenom: "Albert"},

82. {nom: "Durand", prenom: "Jacques"},

83. {nom: "Martin", prenom: "Denis"},

84. {nom: "Torlet", prenom: "Arthur"}

85. ],

86. poubelle: []

87. },

88. methods: {

89. supprimer: function(index) {

90. this.poubelle.push(this.personnes[index]);

91. this.personnes.splice(index, 1);

92. this.poubelle.sort(ordonner);

93. },

94. retablir: function(index) {

95. this.personnes.push(this.poubelle[index]);

96. this.poubelle.splice(index, 1);

97. this.personnes.sort(ordonner);

98. },

99. eliminer: function(index) {

100. this.poubelle.splice(index, 1);

101. },

102. toutPoubelle: function() {

103. this.poubelle = this.poubelle.concat(this.personnes);

104. this.poubelle.sort(ordonner);

105. this.personnes = [];

106. },

107. toutRetablir: function() {

108. this.personnes = this.personnes.concat(this.poubelle);

109. this.personnes.sort(ordonner);

110. this.poubelle = [];

111. },

112. toutEliminer: function() {

113. this.poubelle = [];

114. }

115. }

116. });

118. var ordonner = function (a, b) { return (a.nom > b.nom) };

120. </script>

122. </body>

124. </html>

Au départ on a la liste des personnes :

Pour chaque personne on dispose d'un bouton pour la mettre à la poubelle. On a aussi un bouton pour mettre tout le monde à la poubelle. Si je clique, par exemple, sur le bouton de Dupont, j'obtiens :

Le panneau de la poubelle devient visible avec Dupont présent. On dispose d'un bouton pour le rétablir et d'un autre pour le supprimer définitivement. Si on clique sur « Rétablir » on se retrouve dans la situation initiale.

Si on envoie Martin aussi à la poubelle, on se retrouve ainsi :

On remarque que la liste de la poubelle s'ordonne automatiquement.

Si on envoie tout le monde à la poubelle, le premier panneau disparaît et il ne reste que la liste de la poubelle :

On peut rétablir une ou toutes les personnes d'un coup. Vous avez compris le principe, je vous laisse faire des tests et analyser le code, il ne comporte que des choses que nous avons déjà vues, et c'est une bonne occasion pour réviser ce qui a été présenté lors des articles précédents.

III-G - En résumé

- La directive v-for permet de répéter des éléments du DOM à partir de données du modèle.

- La directive v-for génère un index pour identifier les éléments.

- On peut itérer un tableau de valeurs ou d'objets.

- Lorsqu'on utilise un accesseur il faut assigner de nouveau le tableau généré.

- On dispose de la méthode set pour modifier un élément de tableau.

IV - Double liaison

Contrairement à ce que le titre de ce chapitre pourrait suggérer, il ne s'agit pas de mœurs dissolues mais toujours de notre bibliothèque JavaScript. On a vu au cours des chapitres précédents qu'on peut facilement établir un lien entre le modèle et la vue et que, si le modèle change, alors la vue change aussi pour refléter ce changement.

Maintenant nous allons nous intéresser à l'inverse, autrement dit : si on change quelque chose dans la vue, est-ce que ça va modifier le modèle ? Comme Vue.js est censé justement établir une double liaison, ça devrait pouvoir se faire.

IV-A - La directive v-model

La directive v-model crée une double liaison entre le modèle et un contrôle de formulaire : input, select, checkbox, textarea… Donc tout changement dans le modèle est répercuté dans le contrôle et réciproquement. Prenons un petit exemple simple, voici le HTML :

<form id="tuto">
<input type="text" v-model="texte"><p>{{ texte }}</p>
</form>

Et le JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: { texte: 'coucou' }

4. });

Le résultat est un contrôle de texte et un texte associé au-dessous. Lorsqu'on change le texte dans le contrôle, le texte associé change également :

Voici une illustration du fonctionnement :

Pour être sûr que ça marche dans les deux sens vous pouvez ajouter cette ligne à la fin du JavaScript :

setTimeout(function(){ vm.texte = 'Nouveau texte'; }, 2000);

Et vous verrez que deux secondes après le chargement de la page, le nouveau texte figurera aux deux emplacements. C'est bien de la double liaison !

IV-B - Boutons radio et cases à cocher

Les boutons radio et les cases à cocher ont ceci de particulier qu'ils fonctionnent en « tout ou rien ».

IV-B-1 - Boutons radio

Les boutons radio s'utilisent groupés puisqu'un seul peut être coché.

Voici un exemple de mise en œuvre HTML :

1. <form id="tuto">

2. <div class="radio">

3. <label>

4. <input type="radio" name="sexe" value="male" v-model="sexe"> Male

5. </label>

6. </div>

7. <div class="radio">

8. <label>

9. <input type="radio" name="sexe" value="femelle" v-model="sexe"> Femelle

10. </label>

11. </div>

12. <div class="form-control">

13. On a choisi {{ sexe }}

14. </div>

15. </form>

Et le JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: { sexe: 'male' }

4. });

On a donc besoin d'une seule propriété qui a pour valeur celle du bouton radio coché. Au départ on a ceci :

Et quand on coche l'autre bouton, on a :

Voici une illustration du fonctionnement :

La valeur d'une case à cocher est en général une chaîne de caractères statique. Il peut arriver qu'on ait besoin d'un comportement dynamique. Il suffit alors d'établir une liaison avec la directive v-bind :

<input type="radio" name="sexe" v-bind:value="male" v-model="sexe"> Male

IV-B-2 - Cases à cocher

Les cases à cocher peuvent être solitaires, contrairement aux boutons radio. Prenons un exemple…

Voici le HTML :

1. <form id="tuto">

2. <div class="checkbox">

3. <label>

4. <input type="checkbox" name="nom" value="valeur" v-model="checked"> Je suis <strong>{{ checked }}</strong>

5. </label>

6. </div>

7. </form>

Et le JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: { checked: false }

4. });

Au départ on a ceci :

On aurait évidemment la case cochée si on avait affecté true à checked.

Lorsqu'on clique on obtient l'état inverse :

La valeur d'une case à cocher est en général un booléen qui peut être vrai ou faux. Il peut arriver qu'on ait besoin d'autres types de données. Il suffit alors d'établir une liaison avec la directive v-bind :

1. <input type="checkbox"

2. name="nom"

3. v-bind:true-value="vrai"

4. v-bind:false-value="faux"

5. v-model="checked">

IV-C - Liste de choix

Une liste de choix propose une liste d'éléments, le plus souvent sous forme déroulante. C'est un contrôle très utilisé.

IV-C-1 - Sélection simple

Commençons avec un exemple simple, voici le HTML :

1. <form id="tuto">

2. <select class="form-control" v-model="choix">

3. <option>Choix 1</option>

4. <option>Choix 2</option>

5. <option>Choix 3</option>

6. </select>

7. <p>On a choisi "{{ choix }}"</p>

8. </form>

Et le JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: { choix: 'Choix 2' }

4. });

Au départ on a cette situation :

Et selon ce qu'on sélectionne, le texte suit fidèlement :

IV-C-2 - Sélections multiples

On peut désirer pouvoir choisir plusieurs éléments dans une liste de choix en utilisant l'attribut multiple. Voici notre exemple modifié en conséquence :

1. <form id="tuto">

2. <select class="form-control" v-model="choix" multiple>

3. <option>Choix 1</option>

4. <option>Choix 2</option>

5. <option>Choix 3</option>

6. </select>

7. <p>On a choisi "{{ choix }}"</p>

8. </form>

Et le JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: { choix: ['Choix 2', 'Choix 3'] }

4. });

Comme on a plusieurs choix possibles on utilise un tableau de valeurs. Au départ on a :

Et selon les choix suivants, l'affichage s'adapte :

IV-C-3 - Options dynamiques

On a souvent besoin de remplir de façon dynamique une liste de choix, et pour le faire on met à contribution la directive v-for qu'on connaît déjà. Mais il faut également gérer la liaison entre les options et leurs valeurs dans le modèle, on le fait avec une directive v-bind (ici j'ai utilisé la syntaxe courte avec les deux points) :

1. <form id="tuto">

2. <select class="form-control" v-model="choix" multiple>

3. <option v-for="option in options" :value="option.value">

4. {{ option.text }}

5. </option>

6. </select>

7. <p>On a choisi "{{ choix }}"</p>

8. </form>

Et le JavaScript :

1. var vm = new Vue({

2. el: '#tuto',

3. data: {

4. choix: [1, 3],

5. options: [

6. { text: 'Choix 1', value: '1' },

7. { text: 'Choix 2', value: '2' },

8. { text: 'Choix 3', value: '3' }

9. ],

10. }

11. });

13. setTimeout(function(){ Vue.set(vm.options, 1, { text: 'Choix 2a', value: '2a' })}, 2000);

Au bout de deux secondes on change la deuxième option de la liste.

IV-D - Un exemple

Nous allons poursuivre l'exemple du précédent chapitre en ajoutant la possibilité de modifier les données d'une personne et aussi d'en ajouter une. Voici le code complet :

1. <!DOCTYPE html>

2. <html lang="fr">

4. <head>

5. <meta charset="utf-8">

6. <meta http-equiv="X-UA-Compatible" content="IE=edge">

7. <meta name="viewport" content="width=device-width, initial-scale=1">

8. <title>Test vue.js</title>

9. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">

10. </head>

12. <body>

14. <div class="container" id="tuto">

15. <br>

17. <div class="panel panel-primary">

18. <div class="panel-heading">Personnes actives</div>

19. <table class="table table-bordered table-striped">

20. <thead>

21. <tr>

22. <th class="col-sm-4">Nom</th>

23. <th class="col-sm-4">Prénom</th>

24. <th class="col-sm-2"></th>

25. <th class="col-sm-2"></th>

26. </tr>

27. </thead>

28. <tbody>

29. <tr v-for="(personne, index) in personnes">

30. <td>{{ personne.nom }}</td>

31. <td>{{ personne.prenom }}</td>

32. <td><button class="btn btn-info btn-block" @click="modifier(index)">Modifier</button></td>

33. <td><button class="btn btn-warning btn-block" @click="supprimer(index)">Poubelle</button></td>

34. </tr>

35. <tr>

36. <td><input type="text" class="form-control" v-model="inputNom" ref="modif" placeholder="Nom"></td>

37. <td><input type="text" class="form-control" v-model="inputPrenom" placeholder="Prénom"></td>

38. <td colspan="2"><button class="btn btn-primary btn-block" @click="ajouter()">Ajouter</button></td>

39. </tr>

40. </tbody>

41. </table>

42. <div class="panel-footer">

43. &nbsp

44. <button class="button btn btn-xs btn-warning" @click="toutPoubelle">Tout à la poubelle</button>

45. </div>

46. </div>

48. <div class="panel panel-danger" v-show="poubelle.length">

49. <div class="panel-heading">Poubelle</div>

50. <table class="table table-bordered table-striped">

51. <thead>

52. <tr>

53. <th class="col-sm-4">Nom</th>

54. <th class="col-sm-4">Prénom</th>

55. <th class="col-sm-2"></th>

56. <th class="col-sm-2"></th>

57. </tr>

58. </thead>

59. <tbody>

60. <tr v-for="(personne, index) in poubelle">

61. <td>{{ personne.nom }}</td>

62. <td>{{ personne.prenom }}</td>

63. <td><button class="btn btn-success btn-block" v-on:click="retablir(index)">Rétablir</button></td>

64. <td><button class="btn btn-danger btn-block" v-on:click="eliminer(index)">Supprimer</button></td>

65. </tr>

66. </tbody>

67. </table>

68. <div class="panel-footer">

69. &nbsp

70. <div class="btn-group">

71. <button class="button btn btn-xs btn-success" v-on:click="toutRetablir">Tout rétablir</button>

72. <button class="button btn btn-xs btn-danger" v-on:click="toutEliminer">Tout supprimer</button>

73. </div>

74. </div>

75. </div>

77. </div>

79. <script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>

81. <script>

83. new Vue({

84. el: '#tuto',

85. data: {

86. personnes: [

87. {nom: "Claret", prenom: "Marcel"},

88. {nom: "Dupont", prenom: "Albert"},

89. {nom: "Durand", prenom: "Jacques"},

90. {nom: "Martin", prenom: "Denis"},

91. {nom: "Torlet", prenom: "Arthur"}

92. ],

93. poubelle: [],

94. inputNom: '',

95. inputPrenom: ''

96. },

97. methods: {

98. supprimer: function(index) {

99. this.poubelle.push(this.personnes[index]);

100. this.personnes.splice(index, 1);

101. this.poubelle.sort(ordonner);

102. },

103. retablir: function(index) {

104. this.personnes.push(this.poubelle[index]);

105. this.poubelle.splice(index, 1);

106. this.personnes.sort(ordonner);

107. },

108. eliminer: function(index) {

109. this.poubelle.splice(index, 1);

110. },

111. toutPoubelle: function() {

112. this.poubelle = this.poubelle.concat(this.personnes);

113. this.poubelle.sort(ordonner);

114. this.personnes = [];

115. },

116. toutRetablir: function() {

117. this.personnes = this.personnes.concat(this.poubelle);

118. this.personnes.sort(ordonner);

119. this.poubelle = [];

120. },

121. toutEliminer: function() {

122. this.poubelle = [];

123. },

124. ajouter: function() {

125. this.personnes.push({nom: this.inputNom, prenom: this.inputPrenom});

126. this.inputNom = this.inputPrenom = '';

127. this.personnes.sort(ordonner);

128. },

129. modifier: function(index) {

130. this.inputNom = this.personnes[index].nom;

131. this.inputPrenom = this.personnes[index].prenom;

132. this.personnes.splice(index, 1);

133. this.$refs.modif.focus();

134. }

135. }

136. });

138. var ordonner = function (a, b) {

139. return (a.nom.toUpperCase() > b.nom.toUpperCase())

140. };

142. </script>

144. </body>

146. </html>

Nous allons nous intéresser à ce qui a été ajouté.

Lorsqu'on lance la page on obtient ceci :

IV-D-1 - Ajouter une personne

On voit la présence d'un formulaire sur la dernière ligne du tableau. Si on insère un nom, il va naturellement prendre sa place dans la liste et le formulaire se remet en situation de départ. Voyons comment cela est réalisé.

Voici le code du formulaire :

<td><input type="text" class="form-control" v-model="inputNom" ref="modif" placeholder="Nom"></td>
<td><input type="text" class="form-control" v-model="inputPrenom" placeholder="Prénom"></td>
<td colspan="2"><button class="btn btn-primary btn-block" @click="ajouter()">Ajouter</button></td>

Les deux zones de texte sont équipées d'une directive v-model que vous connaissez bien désormais. Le bouton a une directive v-on pour la gestion de l'événement click.

On trouve également un attribut ref. Le but est de pouvoir identifier l'élément du DOM concerné. Cela va nous servir plus loin pour placer le focus.

Pour gérer ces deux contrôles, ont été ajoutées deux propriétés dans le modèle :

1. data: {

2. ...

3. inputNom: '',

4. inputPrenom: ''

5. },

Pour l'ajout de la personne on a prévu la méthode ajouter :

1. methods: {

3. ...

5. ajouter: function() {

6. this.personnes.push({nom: this.inputNom, prenom: this.inputPrenom});

7. this.inputNom = this.inputPrenom = '';

8. this.personnes.sort(ordonner);

9. },

11. ...

12. }

- on ajoute les données saisies aux personnes ;

- on réinitialise les contrôles ;

- enfin on force le classement pour bien placer alphabétiquement la nouvelle personne.

Je ne fais aucun contrôle de saisie pour conserver la simplicité de l'exemple.

IV-D-2 - Modifier une personne

On dispose d'un bouton de modification pour chaque personne de la liste :

1. <tr v-for="(personne, index) in personnes">

2. <td>{{ personne.nom }}</td>

3. <td>{{ personne.prenom }}</td>

4. <td><button class="btn btn-info btn-block" @click="modifier(index)">Modifier</button></td>

5. <td><button class="btn btn-warning btn-block" @click="supprimer(index)">Poubelle</button></td>

6. </tr>

Et on a la méthode modifier :

1. methods: {

3. ...

5. modifier: function(index) {

6. this.inputNom = this.personnes[index].nom;

7. this.inputPrenom = this.personnes[index].prenom;

8. this.personnes.splice(index, 1);

9. this.$refs.modif.focus();

10. }

11. }

- on place dans les deux zones de texte le nom et le prénom ;

- on supprime la personne de la liste ;

- on met l'accent dans la zone de texte du prénom (on voit là l'intérêt d'avoir mis l'attribut ref dans ce contrôle). Remarquez la syntaxe un peu particulière.

Ce qui donne ce genre d'affichage :

Dupont est en saisie et il a disparu de la liste. On peut modifier nom et prénom. Quand on clique sur « Ajouter », les données modifiées vont prendre leur place dans la liste comme on l'a vu ci-dessus dans le cas de l'ajout d'une personne.

IV-E - En résumé

- La directive v-model permet d'établir une double liaison.

- Tous les contrôles de formulaire peuvent utiliser cette directive.

- Pour remplir de façon dynamique une liste de choix on utilise les directives v-for et v-bind.

- L'attribut ref permet d'identifier un élément du DOM.

V - Accesseurs et mutateurs

Dans les chapitres précédents nous avons vu comment stocker des valeurs dans le modèle et comment les lier à la vue. Dans ce chapitre, nous allons voir comment intervenir au passage lorsque nous stockons une donnée dans le modèle et lorsque nous l'extrayons.

V-A - Accesseurs et mutateurs

Ce vocabulaire, accesseurs (getters) et des mutateurs (setters), est issu de la programmation-objet pour laquelle les propriétés sont en général privées ou au moins protégées et donc non directement accessibles. Il faut ainsi prévoir des méthodes pour accéder à ces propriétés et les modifier.

On a vu que les propriétés d'un objet de Vue.js ne sont pas privées ou protégées, et que nous y avons directement accès. Par contre la notion d'accesseur et de mutateur va devenir intéressante si nous voulons faire subir des modifications aux données au passage (calcul, mise en forme…).

Voici une petite illustration des deux processus complémentaires :

Dans la documentation de Vue.js, on ne parle pas d'accesseurs et mutateurs mais de propriétés calculées (computed properties).

V-A-1 - Accesseur

Prenons un exemple simple :

1. new Vue({

2. el: '#tuto',

3. data: {

4. responsable: {nom: "Claret", prenom: "Marcel"}

5. },

6. });

On sait qu'on peut afficher facilement ce responsable :

<div id="tuto">
<p>Le responsable est {{ responsable.nom }} {{ responsable.prenom }}.</p>
</div>

Avec ce résultat :

Le responsable est Claret Marcel.

Au niveau de mustache on peut très bien effectuer une concaténation :

<p>Le responsable est {{ responsable.nom + ' ' + responsable.prenom }}.</p>

Et on obtiendra le même rendu.

On peut aussi utiliser la directive v-text au lieu de mustache :

<p v-text="'Le responsable est ' + responsable.nom + ' ' + responsable.prenom + '.'"></p>

On y perd en lisibilité, mais le résultat est encore identique.

Mais avouez que tout cela n'est pas très élégant. Voyons une autre façon de procéder. Créons une nouvelle propriété :

<div id="tuto">
<p>Le responsable est {{ nomComplet }}.</p>
</div>

On veut que cette propriété nomComplet nous donne le nom complet. On va voir une nouveauté dans la VueModèle :

1. new Vue({

2. el: '#tuto',

3. data: {

4. responsable: {nom: "Claret", prenom: "Marcel"}

5. },

6. computed: {

7. nomComplet: function() {

8. return this.responsable.nom + ' ' + this.responsable.prenom;

9. }

10. }

11. });

La propriété computed est destinée à la création d'accesseurs comme celui que nous venons de créer.

Voici une schématisation du fonctionnement :

On utilise cette nouvelle propriété comme les autres.

V-B - Mutateur

La question qu'on peut se poser maintenant est : est-ce que ça marche dans les deux sens ? Autrement dit, si j'utilise ce HTML avec le JavaScript vu ci-dessus :

1. <form id="tuto">

2. <div class="form-group">

3. <input type="texte" class="form-control" v-model="nomComplet">

4. </div>

5. <p>Le nom est : {{ responsable.nom }}</p>

6. <p>Le prénom est : {{ responsable.prenom }}</p>

7. </form>

Je me retrouve avec une zone de texte et les deux lignes :

La zone de texte a bien été remplie avec notre nouvelle propriété, mais si on change le contenu de cette zone de texte on se rend compte évidemment que le modèle n'est pas changé. Il va falloir compléter un peu côté JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: {

4. responsable: {nom: "Claret", prenom: "Marcel"}

5. },

6. computed: {

7. nomComplet: {

8. get: function () {

9. return this.responsable.nom + ' ' + this.responsable.prenom;

10. },

11. set: function (value) {

12. var nom = value.split(' ');

13. this.responsable.nom = nom[0];

14. this.responsable.prenom = nom[1];

15. }

16. }

17. }

18. });

On a décomposé la propriété en deux fonctions :

- un accesseur (get) pour récupérer les données ;

- un mutateur (set) pour enregistrer les données.

Maintenant ça fonctionne dans les deux sens :

Moralité : si vous avez juste besoin d'un accesseur il suffit de prévoir une fonction, si vous avez besoin aussi d'un mutateur il faut prévoir get et set.

V-C - Un panier

Nous allons prendre un exemple pour illustrer l'utilisation d'un accesseur. Nous voulons réaliser un panier avec des noms de produits, des quantités et des prix. Nous voulons aussi avoir les prix résultants par produit et le montant total. Voici le code complet de la page :

1. <!DOCTYPE html>

2. <html lang="fr">

4. <head>

5. <meta charset="utf-8">

6. <meta http-equiv="X-UA-Compatible" content="IE=edge">

7. <meta name="viewport" content="width=device-width, initial-scale=1">

8. <title>Test vue.js</title>

9. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">

10. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">

11. </head>

13. <body>

15. <div class="container" id="tuto">

16. <br>

18. <div class="panel panel-primary">

19. <div class="panel-heading">Panier</div>

20. <table class="table table-bordered table-striped">

21. <thead>

22. <tr>

23. <th class="col-sm-4">Article</th>

24. <th class="col-sm-2">Quantité</th>

25. <th class="col-sm-2">Prix</th>

26. <th class="col-sm-2">Total</th>

27. <th class="col-sm-1"></th>

28. <th class="col-sm-1"></th>

29. </tr>

30. </thead>

31. <tbody>

32. <tr v-for="(item, index) in panier">

33. <td>{{ item.article }}</td>

34. <td>{{ item.quantite }}</td>

35. <td>{{ item.prix }} €</td>

36. <td>{{ (item.quantite * item.prix).toFixed(2) }} €</td>

37. <td><button class="btn btn-info btn-block" @click="modifier(index)"><i class="fa fa-edit fa-lg"></i></button></td>

38. <td><button class="btn btn-danger btn-block" @click="supprimer(index)"><i class="fa fa-trash-o fa-lg"></i></button></td>

39. </tr>

40. <tr>

41. <td colspan="3"></td>

42. <td><strong>{{ total }} €</strong></td>

43. <td colspan="2"></td>

44. </tr>

45. <tr>

46. <td><input type="text" class="form-control" v-model="input.article" ref="modif" placeholder="Article"></td>

47. <td><input type="text" class="form-control" v-model="input.quantite" placeholder="Quantité"></td>

48. <td><input type="text" class="form-control" v-model="input.prix" placeholder="Prix"></td>

49. <td colspan="3"><button class="btn btn-primary btn-block" v-on:click="ajouter()">Ajouter</button></td>

50. </tr>

51. </tbody>

52. </table>

53. </div>

55. </div>

57. <script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>

59. <script>

61. new Vue({

62. el: '#tuto',

63. data: {

64. panier: [

65. { article: "Cahier", quantite: 2, prix: '5.30' },

66. { article: "Crayon", quantite: 4, prix: '1.10' },

67. { article: "Gomme", quantite: 1, prix: '3.25' }

68. ],

69. input: { article: '', quantite: 0, prix: 0 }

70. },

71. computed: {

72. total: function () {

73. var total = 0;

74. this.panier.forEach(function(el) {

75. total += el.prix * el.quantite;

76. });

77. return total.toFixed(2);

78. }

79. },

80. methods: {

81. ajouter: function() {

82. this.panier.push(this.input);

83. this.input = { article: '', quantite: 0, prix: 0 };

84. },

85. modifier: function(index) {

86. this.input = this.panier[index];

87. this.panier.splice(index, 1);

88. this.$refs.modif.focus();

89. },

90. supprimer: function(index) {

91. this.panier.splice(index, 1);

92. },

94. }

95. });

97. </script>

99. </body>

101. </html>

V-C-1 - Fonctionnement

Au départ nous avons cet aspect :

Sont déjà présents trois articles avec leur nom, leur prix unitaire, le total par produit et le total du panier. Pour chaque article, on dispose d'un bouton de modification et d'un autre pour la suppression. D'autre part on a un formulaire pour ajouter un article.

Lorsqu'on veut modifier un article en cliquant sur le bouton de modification, il disparaît de la liste et se retrouve dans le formulaire :

Le bouton « Ajouter » permet de renvoyer l'article dans la liste une fois modifié. L'ajout d'un article se fait juste en utilisant le formulaire.

Dans tous les cas les montants dans la colonne « Total » s'actualisent.

On peut supprimer un article ou en ajouter un :

V-C-2 - Le code

Le modèle comporte le tableau panier pour les articles de la liste et l'objet input pour le formulaire :

1. data: {

2. panier: [

3. { article: "cahier", quantite: 2, prix: '5.30' },

4. { article: "crayon", quantite: 4, prix: '1.10' },

5. { article: "gomme", quantite: 1, prix: '3.25' }

6. ],

7. input: { article: '', quantite: 0, prix: 0 }

8. },

La liste est créée avec une boucle v-for :

1. <tr v-for="(item, index) in panier">

2. <td>{{ item.article }}</td>

3. <td>{{ item.quantite }}</td>

4. <td>{{ item.prix }} €</td>

5. <td>{{ (item.quantite * item.prix).toFixed(2) }} €</td>

6. <td><button class="btn btn-info btn-block" @click="modifier(index)"><i class="fa fa-edit fa-lg"></i></button></td>

7. <td><button class="btn btn-danger btn-block" @click="supprimer(index)"><i class="fa fa-trash-o fa-lg"></i></button></td>

8. </tr>

Remarquez que le calcul pour le montant par article, selon la quantité, est effectué directement avec une expression dans mustache.

Pour le montant total on a ce code :

1. <td colspan="3"></td>

2. <td><strong>{{ total }} €</strong></td>

3. <td colspan="2"></td>

On voit qu'on référence la propriété total. C'est une propriété calculée (un accesseur) qu'on retrouve ici :

1. computed: {

2. total: function () {

3. var total = 0;

4. this.panier.forEach(function(el) {

5. total += el.prix * el.quantite;

6. });

7. return total.toFixed(2);

8. }

9. },

Le reste du code correspond à des choses que nous avons déjà rencontrées, et je vous renvoie aux chapitres précédents si quelque chose ne vous semble pas clair.

V-D - En résumé

- Un accesseur permet de récupérer une donnée en la modifiant.

- Un mutateur permet de mémoriser une donnée en la modifiant.

- La propriété computed de Vue.js permet de créer des accesseurs et des mutateurs.

VI - Les composants (1/2)

Vous connaissez peut-être les Web Components. C'est un nouveau standard qui permet d'enrichir le HTML de façon modulaire. En gros on peut créer une entité qui utilise des éléments HTML et des fonctionnalités propres, le tout étant facile à intégrer avec une simple balise personnalisée. On peut espérer que ce standard réduira la prolifération des widgets JavaScript et engendrera une certaine homogénéité. Ce qui est certain c'est que le HTML est plutôt limité et ne propose que des éléments simples, on aimerait disposer d'infobulles, de menus… Mais on est encore loin du but, vous pouvez lire cet excellent article qui résume la situation.

On va voir dans ce chapitre que Vue.js propose une approche simple et efficace très fortement inspirée de polymer, avec comme grande différence qu'au niveau de la machinerie il est fait appel exclusivement aux dernières possibilités des Web components, ce qui améliore les performances.

Pouvoir créer des composants est sans doute un des aspects les plus intéressants de Vue.js, surtout lorsqu'on se lance dans une application ambitieuse. Le code est ainsi bien encapsulé et réutilisable. Un composant est ainsi un élément isolé, autonome, qui possède des propriétés et des comportements et qu'on peut utiliser directement dans le HTML…

On va commencer à voir les composants dans ce chapitre. Je développerai le sujet dans un prochain tutoriel.

VI-A - Mon premier composant

Alors on se lance et on crée un petit composant. Au niveau du JavaScript c'est tout simple :

Vue.component('mon-composant', {
template: '<p>Mon premier composant !</p>'
});

La méthode component crée (on dit qu'on l'enregistre) le composant dont on précise le nom (mon-composant) et le template. C'est vraiment le minimum pour un composant ! Il faut aussi initialiser la VueModèle comme d'habitude :

new Vue({
el: '#tuto'
});

Ensuite on utilise la balise personnalisée dans le HTML :

<div id="tuto">
<mon-composant></mon-composant>
</div>

Avec ce résultat :

Mon premier composant !

Bon, pour le moment c'est très simple, et on a vu qu'on pouvait réaliser cela dans le précédent chapitre avec une directive élément. Mais on va voir que les composants vont bien plus loin !

Une petite illustration du fonctionnement :

J'ai déclaré le composant de façon globale, mais on pourrait aussi le créer propre à la VueModèle avec la propriété components :

1. new Vue({

2. el: '#tuto',

3. components: {

4. 'mon-composant': {

5. template: '<p>Mon premier composant !</p>'

6. }

7. }

8. });

La méthode component est un raccourci qui étend la bibliothèque et enregistre le composant. La syntaxe développée est :

1. var MonComposant = Vue.extend({

2. template: '<p>Mon premier composant !</p>'

3. });

5. Vue.component('mon-composant', MonComposant);

VI-B - Passage de données

VI-B-1 - Les props

Créer un composant c'est bien, mais tout ce qui se trouve à l'intérieur est isolé du reste du monde. Autrement dit le composant n'a accès à aucune donnée en dehors des siennes. Il arrive souvent qu'on ait besoin de transmettre des informations pour renseigner le composant. Voyons comment réaliser cela. On va ajouter une propriété au composant :

1. Vue.component('nom', {

2. props: ['nom'],

3. template: '<p>Mon nom est {{nom}}</p>'

4. });

Les props sont des propriétés pour lesquels le composant attend des valeurs. C'est un tableau, ici on a juste prévu la clé nom. Il ne reste plus qu'à renseigner cette clé dans le HTML :

<div id="tuto">
<nom nom="Toto"></nom>
</div>

Ce qui donne finalement :

Mon nom est Toto

Voici une schématisation du fonctionnement :

Lorsqu'on crée un composant il devient un enfant du composant dans lequel on le crée. Ici le parent est l'instance principale.

VI-B-2 - camelCase

Les attributs HTML ne sont pas sensibles à la casse, majuscules ou minuscules, ils digèrent tout ça indifféremment. Cela peut être un piège si vous utilisez la notation camelCase. Par exemple vous créez ce composant :

1. Vue.component('nom', {

2. props: ['monNom'],

3. template: '<p>Mon nom est {{monNom}}</p>'

4. });

Et vous utilisez ce HTML :

<nom monNom='Toto'></nom>

Mais tout ce que vous obtenez est :

Mon nom est

Pour obtenir le bon résultat il faut utiliser l'équivalent avec trait d'union (hyphenated) :

<nom mon-nom="Toto"></nom>

Vous obtenez bien alors :

Mon nom est Toto

VI-B-3 - Attribut dynamique

Un attribut statique c'est bien, mais s'il est dynamique c'est encore mieux. On voudrait par exemple entrer le nom dans une zone de texte, et qu'il s'affiche avec le composant. Essayons avec ce HTML :

1. <div id="tuto">

2. <input v-model="nomSaisi">

3. <br>

4. <nom :nom="nomSaisi"></nom>

5. </div>

On prévoit l'argument nom pour le composant avec la directive v-bind (avec sa syntaxe simplifiée : ), une propriété liée nomSaisi pour la VueModèle.

Avec ce JavaScript :

1. Vue.component('nom', {

2. props: ['nom'],

3. template: '<p>Mon nom est <strong>{{nom}}</strong></p>'

4. });

6. new Vue({

7. el: '#tuto',

8. data: { nomSaisi: '' }

9. });

Avec ce résultat :

On retrouve en fait les possibilités qu'on avait déjà rencontrées, mais adaptées à un composant.

VI-C - Gestion des listes

Une action très fréquente consiste à générer une liste de données, on a déjà eu l'occasion d'utiliser la directive v-for pour la réaliser. Est-ce que ça peut fonctionner avec un composant ? Voici un exemple :

<div id="tuto">
<liste v-for="personne in personnes" :personne="personne"></liste>
</div>

Et le JavaScript :

1. new Vue({

2. el: '#tuto',

3. data: {

4. personnes: [

5. {nom: "Durand", prenom: "Jacques"},

6. {nom: "Dupont", prenom: "Albert"},

7. {nom: "Martin", prenom: "Denis"},

8. ]

9. },

10. components: {

11. 'liste': {

12. props: ['personne'],

13. template: '<li>{{personne.nom}} {{personne.prenom}}</li>'

14. }

15. }

16. });

Avec ce résultat :

Pour chaque élément de la liste une instance du composant est créée, comme on pouvait logiquement s'y attendre. Notez que les données ne sont pas automatiquement envoyées dans le composant qui est parfaitement isolé. Il faut encore déclarer une propriété.

On pourrait évidemment, là aussi, déclarer le composant de façon globale avec le même fonctionnement :

1. Vue.component('liste', {

2. props: ['personne'],

3. template: '<li>{{personne.nom}} {{personne.prenom}}</li>'

4. });

6. new Vue({

7. el: '#tuto',

8. data: {

9. personnes: [

10. {nom: "Durand", prenom: "Jacques"},

11. {nom: "Dupont", prenom: "Albert"},

12. {nom: "Martin", prenom: "Denis"},

13. ]

14. }

15. });

VI-D - Un tableau

Allons un peu plus loin et créons un composant pour générer un tableau. On veut ce HTML :

<div id="tuto">
<tableau :personnes="personnes"></tableau>
</div>

Donc on veut un composant tableau auquel on transmet les données à afficher. On va conserver les données vues précédemment. Voici le JavaScript :

1. var vm = new Vue({

2. el: '#tuto',

3. data: {

4. personnes: [

5. {nom: "Durand", prenom: "Jacques"},

6. {nom: "Dupont", prenom: "Albert"},

7. {nom: "Martin", prenom: "Denis"},

8. ]

9. },

10. components: {

11. tableau: {

12. props: ['personnes'],

13. template: '<table class="table table-bordered">\n' +

14. '<tr v-for="personne in personnes">\n' +

15. '<td v-text="personne.nom"></td>\n' +

16. '<td v-text="personne.prenom"></td>\n' +

17. '</tr>\n' +

18. '</table>\n'

19. }

20. }

21. });

Avec création du tableau :

On peut répercuter tout changement dans les données au niveau du tableau. Par exemple, si vous ajoutez ce code :

setTimeout(function() {
Vue.set(vm.personnes, 1, {nom: "Claret", prenom: "Marcel"});
}, 2000);

Au bout de deux secondes vous allez voir le tableau changer pour cette ligne.

VI-E - Un composant générique

Le tableau réalisé ci-dessus est totalement adapté aux données concernées. On pourrait aborder cela d'une façon plus générale et créer un composant réutilisable qui accepterait des noms de colonnes et des données sans nécessairement en connaître le nombre :

1. <div id="tuto">

2. <tableau

3. :colonnes="colonnes"

4. :lignes="personnes">

5. </tableau>

6. </div>

Ici on utilise un composant tableau en lui transmettant les colonnes et les lignes de données.

Voici le JavaScript :

1. Vue.component('tableau', {

2. props: ['colonnes', 'lignes'],

3. template: '<table class="table table-bordered">\n' +

4. '<thead>\n' +

5. '<tr>\n' +

6. '<th v-for="value in colonnes">{{ value }}</th>\n' +

7. '</tr>\n' +

8. '</thead>\n' +

9. '<tr v-for="ligne in lignes">\n' +

10. '<td v-for="value in ligne">{{ value }}</td>\n' +

11. '</tr>\n' +

12. '</table>\n'

13. });

15. new Vue({

16. el: '#tuto',

17. data: {

18. colonnes: ['Nom', 'Prénom'],

19. personnes: [

20. ["Durand", "Jacques"],

21. ["Dupont", "Albert"],

22. ["Martin", "Denis"],

23. ]

24. }

25. });

Ce qui donne ce résultat :

On constate que maintenant le composant peut resservir dans un autre contexte parce qu'il est codé de façon générique. Remarquez que j'ai également transformé les données des personnes pour les rendre également génériques et ainsi simplifier le codage.

VI-F - Un template élégant

Le code ci-dessus est élégant, mis à part la partie template qui fait un peu désordre. Il serait préférable de pouvoir définir ce template avec une mise en page du code harmonieuse.

On peut inclure le template dans le HTML avec la nouvelle balise template qui est maintenant bien prise en charge par les navigateurs. Voici le nouveau HTML intégrant le template :

1. <template id="tableau-template">

2. <table class="table table-bordered">

3. <thead>

4. <tr>

5. <th v-for="value in colonnes">

6. {{ value }}

7. </th>

8. </tr>

9. </thead>

10. <tbody>

11. <tr v-for="ligne in lignes">

12. <td v-for="value in ligne">

13. {{ value }}

14. </td>

15. </tr>

16. </tbody>

17. </table>

18. </template>

20. <div id="tuto">

21. <tableau

22. :colonnes="colonnes"

23. :lignes="personnes">

24. </tableau>

25. </div>

Remarquez qu'on a un identifiant (tableau-template) qui va permettre de référencer ce template dans le composant :

1. Vue.component('tableau', {

2. props: ['colonnes', 'lignes'],

3. template: '#tableau-template'

4. });

6. new Vue({

7. el: '#tuto',

8. data: {

9. colonnes: ['Nom', 'Prénom'],

10. personnes: [

11. ["Durand", "Jacques"],

12. ["Dupont", "Albert"],

13. ["Martin", "Denis"],

14. ]

15. }

16. });

Avec évidemment le même résultat que ci-dessus :

Cette fois le code est vraiment propre.

VI-G - Le panier revisité

Comme exemple du précédent chapitre sur les directives personnalisées, on a amélioré le panier. Je vous propose de reprendre cet exemple, mais cette fois de créer ce panier sous forme de composant. On va au passage se débarrasser de la directive personnalisée qui n'est plus pertinente, par contre on va voir qu'un composant peut disposer de toutes les propriétés que nous avons rencontrées dans ce tutoriel.

Voici le code complet de la page :

1. <!DOCTYPE html>

2. <html lang="fr">

4. <head>

5. <meta charset="utf-8">

6. <meta http-equiv="X-UA-Compatible" content="IE=edge">

7. <meta name="viewport" content="width=device-width, initial-scale=1">

8. <title>Test vue.js</title>

9. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">

10. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">

11. </head>

13. <body>

15. <div class="container">

16. <br>

18. <template id="panier-template">

19. <div class="panel panel-primary">

20. <div class="panel-heading">Panier</div>

21. <table class="table table-bordered table-striped">

22. <thead>

23. <tr>

24. <th class="col-sm-4">Article</th>

25. <th class="col-sm-2">Quantité</th>

26. <th class="col-sm-2">Prix</th>

27. <th class="col-sm-2">Total</th>

28. <th class="col-sm-1"></th>

29. <th class="col-sm-1"></th>

30. </tr>

31. </thead>

32. <tbody>

33. <tr v-for="(item, index) in panier">

34. <td>{{ item.article }}</td>

35. <td>{{ item.quantite }}</td>

36. <td>{{ item.prix }} €</td>

37. <td>{{ (item.quantite * item.prix).toFixed(2) }} €</td>

38. <td><button class="btn btn-info btn-block" @click="modifier(index)"><i class="fa fa-edit fa-lg"></i></button></td>

39. <td><button class="btn btn-danger btn-block" @click="supprimer(index)"><i class="fa fa-trash-o fa-lg"></i></button></td>

40. </tr>

41. <tr>

42. <td colspan="3"></td>

43. <td><strong>{{ total }} €</strong></td>

44. <td colspan="2"></td>

45. </tr>

46. <tr>

47. <td><input type="text" class="form-control" v-model="input.article" ref="modif" placeholder="Article"></td>

48. <td><input type="text" class="form-control" v-model="input.quantite" placeholder="Quantité"></td>

49. <td><input type="text" class="form-control" v-model="input.prix" placeholder="Prix"></td>

50. <td colspan="3"><button class="btn btn-primary btn-block" v-on:click="ajouter()">Ajouter</button></td>

51. </tr>

52. </tbody>

53. </table>

54. </div>

55. </template>

57. <div id="tuto">

58. <panier :panier="panier"></panier>

59. </div>

61. </div>

63. <script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>

65. <script>

67. Vue.component('panier', {

68. props: ['panier'],

69. template: '#panier-template',

70. data: function () {

71. return {

72. input: { article: '', quantite: 0, prix: 0 }

73. }

74. },

75. computed: {

76. total: function () {

77. var total = 0;

78. this.panier.forEach(function(el) {

79. total += el.prix * el.quantite;

80. });

81. return total.toFixed(2);

82. }

83. },

84. methods: {

85. ajouter: function() {

86. this.panier.push(this.input);

87. this.input = { article: '', quantite: 0, prix: 0 };

88. },

89. modifier: function(index) {

90. this.input = this.panier[index];

91. this.panier.splice(index, 1);

92. this.$refs.modif.focus();

93. },

94. supprimer: function(index) {

95. this.panier.splice(index, 1);

96. },

97. }

98. });

100. new Vue({

101. el: '#tuto',

102. data: {

103. panier: [

104. { article: "Cahier", quantite: 2, prix: '5.30' },

105. { article: "Crayon", quantite: 4, prix: '1.10' },

106. { article: "Gomme", quantite: 1, prix: '3.25' }

107. ],

108. }

109. });

111. </script>

113. </body>

115. </html>

Cela donne évidemment les mêmes rendu et fonctionnement que lors du chapitre précédent :

Vous voyez ainsi qu'il est relativement facile de créer des composants réutilisables !

Vous avez sans doute remarqué que la propriété data du composant comporte une fonction. En effet chaque instance du composant aura ses propres données, il est donc nécessaire de les isoler.

Si vous voulez suivre l'évolution des données lors des manipulations du panier, il suffit d'ajouter cette ligne au HTML :

1. <div id="tuto">

2. <panier :panier="panier"></panier>

3. <pre>{{ $data }}</pre>

4. </div>

Ça permet d'afficher correctement les données :

Ce tutoriel est loin d'épuiser les possibilités des composants, mais vous avez à présent de très bonnes bases. Je vous invite à consulter la documentation et cet excellent exemple pour compléter vos connaissances.

VI-H - Vue devtools

Pour déboguer les applications Vue.js, il existe un plugin pour Chrome. Lorsque vous l'avez installé vous disposez d'un nouveau bouton dans la fenêtre des outils de développement :

Vous pouvez ainsi inspecter vos composants, voilà ce que ça donne pour le panier :

C'est un outil qui devient très intéressant lorsque vous avez de nombreux composants !

VI-I - En résumé

- Vue.js permet la création de composants pour enrichir le HTML.

- Un composant peut utiliser toutes les options existantes (mis à part el).

- Il est possible de passer des données à un composant.

- Un composant peut hériter des données de son parent.

- On peut manipuler des listes avec un composant.

- On peut ajouter un outil de débogage pour Vue.js à Chrome.

VII - Les composants (2/2)

J'ai commencé à présenter les composants dans le précédent chapitre. On a vu qu'ils constituent un aspect important de Vue.js et qu'il sont faciles à créer. On a également vu qu'ils sont organisés hiérarchiquement et qu'on peut passer des informations (props) du parent à l'enfant.

On va continuer notre exploration des composants dans le présent chapitre en voyant la communication inverse : de l'enfant vers le parent. On va en profiter pour évoquer aussi quelques autres éléments.

VII-A - La communication entre composants

Les composants étant isolés, il faut prévoir une procédure pour qu'ils échangent des informations. On a vu les props dans le précédent article, c'est la communication descendante, du parent vers l'enfant. De façon symétrique on aura des événements pour la communication ascendante : de l'enfant vers le parent :

VII-A-1 - Compléments sur les props

Pour les props il faut tenir compte d'une contrainte : si la valeur change au niveau du parent ce changement est répercuté à l'enfant, mais l'inverse n'est pas vrai ! En effet on pourrait avoir des comportements douteux si un enfant pouvait modifier des valeurs chez un parent…

Donc il ne faut pas modifier la valeur d'un prop dans un composant !

Si on a besoin de changer la valeur d'un prop dans un enfant il faut donc d'abord créer une propriété locale ou calculée. On verra ça dans l'exemple en fin d'article.

Il est aussi possible d'effectuer une validation des props pour être sûr de recevoir des données correctes. On peut obliger une valeur à être présente, prévoir une valeur par défaut, un traitement personnalisé… Cette possibilité n'est judicieuse que si vous avez l'intention de créer un composant public. On a ces types disponibles :

- String ;

- Number ;

- Boolean ;

- Function ;

- Object ;

- Array.

Voici un résumé qui s'inspire de l'exemple de la documentation :

1. Vue.component('mon_composant', {

2. props: {

3. prop1: Number,  // type unique

4. prop2: [Object, Array],  // types multiples

5. prop3: {

6. type: Boolean,

7. required: true  // valeur booléenne requise

8. },

9. prop4: {

10. type: String,

11. default: 'chat' //  une valeur par défaut

12. },

13. prop5: {

14. type: Object,

15. default: function () {

16. return { animal: 'chat' }  // une fonction pour construire la valeur par défaut

17. }

18. },

19. prop6: {

20. validator: function (value) {

21. return value < 4  // une validation personnalisée

22. }

23. }

24. }

25. })

VII-B - Les événements

Un composant peut :

- émettre un événement : $emit(nom de l'événement) ;

- écouter un événement : $on(nom de l'événement) ou v-on dans le template pour un parent.

On va considérer un petit exemple pour illustrer ça : on a un nombre auquel on peut ajouter ou retirer des chiffres. On va pour cela utiliser deux boutons : un pour ajouter un chiffre, et un autre pour enlever un chiffre. On va créer un composant bouton pour réaliser ça. Voici le code complet de l'exemple :

1. <!DOCTYPE html>

2. <html lang="fr">

4. <head>

5. <meta charset="utf-8">

6. <meta http-equiv="X-UA-Compatible" content="IE=edge">

7. <meta name="viewport" content="width=device-width, initial-scale=1">

8. <title>Test vue.js</title>

9. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

10. </head>

12. <body>

14. <div class="container">

15. <br>

16. <div id="tuto">

17. <bouton titre="Ajoute chiffre" type='ajoute' @ajoute="ajoute"></bouton>

18. <bouton titre="Enlève chiffre" type='enleve' @enleve="enleve"></bouton>

19. <h1>{{ nombre }}</h1>

20. </div>

21. </div>

23. <script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>

25. <script>

27. Vue.component('bouton', {

28. props: ['titre', 'type'],

29. template: '<a class="btn btn-info" href="#" @click="action">{{ titre }}</a>',

30. methods: {

31. action: function() {

32. this.$emit(this.type);

33. }

34. }

35. })

37. new Vue({

38. el: '#tuto',

39. data: {

40. nombre: '0'

41. },

42. methods: {

43. ajoute: function() {

44. this.nombre += Math.floor(Math.random() * 10);

45. },

46. enleve: function() {

47. this.nombre = this.nombre.slice(0, -1);

48. }

49. }

50. });

52. </script>

54. </body>

56. </html>

Au départ on a cet aspect :

On ajoute ou retire un chiffre en cliquant sur le bouton correspondant :

Bon ! mon exemple ne sert à rien, je l'avoue, sauf à illustrer le sujet de ce tutoriel !

VII-B-1 - Le composant

Voici le code du composant bouton :

1. Vue.component('bouton', {

2. props: ['titre', 'type'],

3. template: '<a class="btn btn-info" href="#" @click="action">{{ titre }}</a>',

4. methods: {

5. action: function() {

6. this.$emit(this.type);

7. }

8. }

9. })

On a deux props :

- titre : le titre du bouton ;

- type : le type du bouton : ajoute ou enleve.

On a également une détection d'un clic sur le bouton avec une méthode action. On voit que lors d'un clic on émet l'événement qui a comme nom le type du bouton (this.type).

On peut résumer les échanges :

VII-B-2 - Le parent

Dans le template du parent on va utiliser deux boutons :

<bouton titre="Ajoute chiffre" type='ajoute' @ajoute="ajoute"></bouton>
<bouton titre="Enlève chiffre" type='enleve' @enleve="enleve"></bouton>

On transmet à ce niveau deux props : titre et type. D'autre part on écoute l'événement ajoute ou enleve selon le type du bouton. On procède alors au traitement correspondant dans les méthodes appelées :

1. methods: {

2. ajoute: function() {

3. this.nombre += Math.floor(Math.random() * 10);

4. },

5. enleve: function() {

6. this.nombre = this.nombre.slice(0, -1);

7. }

8. }

Vous voyez qu'il est simple de faire communiquer ainsi des composants hiérarchiques.

VII-C - Contrôle de saisie et événement

On peut aussi imaginer avoir un composant avec un contrôle de saisie, par exemple une zone de texte en liaison avec un composant parent. Par exemple, on peut compléter l'exemple ci-dessus en ajoutant un composant avec une zone de texte reliée à la valeur du nombre. Voilà l'exemple modifié en conséquence :

1. <!DOCTYPE html>

2. <html lang="fr">

4. <head>

5. <meta charset="utf-8">

6. <meta http-equiv="X-UA-Compatible" content="IE=edge">

7. <meta name="viewport" content="width=device-width, initial-scale=1">

8. <title>Test vue.js</title>

9. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

10. </head>

12. <body>

14. <div class="container">

15. <br>

16. <div id="tuto">

17. <bouton titre="Ajoute chiffre" type='ajoute' @ajoute="ajoute"></bouton>

18. <bouton titre="Enlève chiffre" type='enleve' @enleve="enleve"></bouton>

19. <saisie v-model="nombre"></saisie>

20. <h1>{{ nombre }}</h1>

21. </div>

22. </div>

24. <script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>

26. <script>

28. Vue.component('bouton', {

29. props: ['titre', 'type'],

30. template: '<a class="btn btn-info" href="#" @click="action">{{ titre }}</a>',

31. methods: {

32. action: function() {

33. this.$emit(this.type);

34. }

35. }

36. })

38. Vue.component('saisie', {

39. template: '<input @input="onInput">',

40. methods: {

41. onInput: function (event) {

42. this.$emit('input', event.target.value);

43. }

44. }

45. })

47. new Vue({

48. el: '#tuto',

49. data: {

50. nombre: '0'

51. },

52. methods: {

53. ajoute: function() {

54. this.nombre += Math.floor(Math.random() * 10);

55. },

56. enleve: function() {

57. this.nombre = this.nombre.slice(0, -1);

58. }

59. }

60. });

62. </script>

64. </body>

66. </html>

Voici le composant pour la zone de saisie :

1. Vue.component('saisie', {

2. template: '<input @input="onInput">',

3. methods: {

4. onInput: function (event) {

5. this.$emit('input', event.target.value);

6. }

7. }

8. })

On intercepte l'événement input, et avec la méthode onInput on émet l'événement input à destination du parent avec la valeur actuelle.

Il suffit d'insérer le composant en prévoyant la liaison de données :

<saisie v-model="nombre"></saisie>

Maintenant la zone de saisie reflète la valeur du nombre et permet de le modifier :

On peut se demander quel est l'intérêt de procéder ainsi ; dans la documentation est évoquée la possibilité des types personnalisés de zones de saisie…

Il y aurait encore des choses à dire sur les composants, mais vous avez déjà acquis l'essentiel. Pour le reste je vous renvoie à la documentation.

VII-D - Le panier revisité

Pour terminer ce tutoriel on va reprendre l'exemple du panier, en ajoutant un composant pour l'édition et l'ajout des articles. Voilà le nouveau code :

1. <!DOCTYPE html>

2. <html lang="fr">

4. <head>

5. <meta charset="utf-8">

6. <meta http-equiv="X-UA-Compatible" content="IE=edge">

7. <meta name="viewport" content="width=device-width, initial-scale=1">

8. <title>Test vue.js</title>

9. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

10. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css">

11. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/united/bootstrap.min.css">

12. </head>

14. <body>

16. <div class="container">

17. <br>

19. <script type="text/x-template" id="panier-template">

20. <div class="panel panel-primary">

21. <div class="panel-heading">Panier</div>

22. <table class="table table-bordered table-striped">

23. <thead>

24. <tr>

25. <th class="col-sm-4">Article</th>

26. <th class="col-sm-2">Quantité</th>

27. <th class="col-sm-2">Prix</th>

28. <th class="col-sm-2">Total</th>

29. <th class="col-sm-1"></th>

30. <th class="col-sm-1"></th>

31. </tr>

32. </thead>

33. <tbody>

34. <tr v-for="(item, index) in panier">

35. <td>{{ item.article }}</td>

36. <td>{{ item.quantite }}</td>

37. <td>{{ item.prix }} €</td>

38. <td>{{ (item.quantite * item.prix).toFixed(2) }} €</td>

39. <td><button class="btn btn-info btn-block" @click="modifier(index)"><i class="fa fa-edit fa-lg"></i></button></td>

40. <td><button class="btn btn-danger btn-block" @click="supprimer(index)"><i class="fa fa-trash-o fa-lg"></i></button></td>

41. </tr>

42. <tr>

43. <td colspan="3"></td>

44. <td><strong>{{ total }} €</strong></td>

45. <td colspan="2"></td>

46. </tr>

47. <editeur :article="article" @add="ajouter"></editeur>

48. </tbody>

49. </table>

50. </div>

51. </script>

53. <script type="text/x-template" id="editeur-template">

54. <tr>

55. <td><input type="text" class="form-control" v-model="input.article" ref="modif" placeholder="Article"></td>

56. <td><input type="text" class="form-control" v-model="input.quantite" placeholder="Quantité"></td>

57. <td><input type="text" class="form-control" v-model="input.prix" placeholder="Prix"></td>

58. <td colspan="3"><button class="btn btn-primary btn-block" @click="ajouter()">Ajouter</button></td>

59. </tr>

60. </script>

62. <div id="tuto">

63. <panier :panier="panier"></panier>

64. </div>

66. </div>

68. <script src="https://unpkg.com/vue@2.0.3/dist/vue.js"></script>

70. <script>

72. Vue.component('panier', {

73. props: ['panier'],

74. template: '#panier-template',

75. data: function () {

76. return {

77. article: { article: '', quantite: 0, prix: 0 }

78. }

79. },

80. computed: {

81. total: function () {

82. var total = 0;

83. this.panier.forEach(function(el) {

84. total += el.prix * el.quantite;

85. });

86. return total.toFixed(2);

87. }

88. },

89. methods: {

90. modifier: function(index) {

91. this.article = this.panier[index];

92. this.panier.splice(index, 1);

93. },

94. supprimer: function(index) {

95. this.panier.splice(index, 1);

96. },

97. ajouter: function(input) {

98. this.panier.push(input);

99. this.article = { article: '', quantite: 0, prix: 0 };

100. }

101. },

102. components: {

103. 'editeur': {

104. props: ['article'],

105. template: '#editeur-template',

106. computed: {

107. input: function() {

108. return this.article;

109. }

110. },

111. methods: {

112. ajouter: function() {

113. this.$emit('add', this.input);

114. }

115. }

116. }

117. }

118. });

120. new Vue({

121. el: '#tuto',

122. data: {

123. panier: [

124. { article: "Cahier", quantite: 2, prix: '5.30' },

125. { article: "Crayon", quantite: 4, prix: '1.10' },

126. { article: "Gomme", quantite: 1, prix: '3.25' }

127. ],

128. }

129. });

131. </script>

133. </body>

135. </html>

L'aspect est le même, j'ai juste changé le thème pour varier un peu l'apparence :

On a toujours le composant panier :

Et dans celui-ci, on a le composant editeur :

Le composant editeur est déclaré dans le composant panier :

1. components: {

2. 'editeur': {

3. props: ['article'],

4. template: '#editeur-template',

5. computed: {

6. input: function() {

7. return this.article;

8. }

9. },

10. methods: {

11. ajouter: function() {

12. this.$emit('add', this.input);

13. }

14. }

15. }

16. }

En entrée (props) on reçoit l'article à éditer article. Comme on sait qu'on n'a pas le droit de le modifier directement, on utilise une propriété calculée input qu'on initialise avec la valeur d'article.

En sortie on émet l'événement add avec comme argument input.

Le composant editeur est ainsi intégré dans le template du panier :

<editeur :article="article" @add="ajouter"></editeur>

Pour le reste on retrouve ce qu'on a précédemment mis en œuvre.

Pour la déclaration des templates, j'ai changé de système parce que le fait d'avoir deux balises template sur la même page ne semble pas fonctionner correctement. Je suis donc passé par une balise script de type text/x-template qui ne pose aucun souci.

VII-E - En résumé

- Un composant enfant ne doit pas modifier la valeur d'un prop.

- On peut valider le type d'un prop.

- Un composant enfant envoie des informations à son parent par des événements.

VII-F - Remerciements

Nous tenons à remercier Maurice Chavelli qui nous a donné l'autorisation de publier ce tutoriel.

Nous remercions également Winjerome pour la mise au gabarit, et Maxy35 pour la correction orthographique.