Dans les articles précédents nous avons vu comment stocker des valeurs dans le modèle et comment les lier à la vue. Les filtres permettent d'apporter des modifications avant affichage mais ils ne portent que sur une donnée spécifique. Il est aussi possible d'effectuer un peu de traitement dans la vue mais c'est assez limité (concaténation, simple calcul...)
Dans cet article nous allons voir comment intervenir au passage lorsque nous stockons une donnée dans le modèle et lorsque nous l'extrayons.
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 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 :
Accesseur
Prenons un exemple simple :
new Vue({ el: '#tuto', data: { responsable: {nom: "Claret", prenom: "Marcel"} }, });
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 :
new Vue({ el: '#tuto', data: { responsable: {nom: "Claret", prenom: "Marcel"} }, computed: { nomComplet: function() { return this.responsable.nom + ' ' + this.responsable.prenom; } } });
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.
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 :
<form id="tuto"> <div class="form-group"> <input type="texte" class="form-control" v-model="nomComplet"> </div> <p>Le nom est : {{ responsable.nom }}</p> <p>Le prénom est : {{ responsable.prenom }}</p> </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 :
new Vue({ el: '#tuto', data: { responsable: {nom: "Claret", prenom: "Marcel"} }, computed: { nomComplet: { get: function () { return this.responsable.nom + ' ' + this.responsable.prenom; }, set: function (value) { var nom = value.split(' '); this.responsable.nom = nom[0]; this.responsable.prenom = nom[1]; } } } });
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
.
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 :
<!DOCTYPE html> <html lang="fr"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Test vue.js</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"> </head> <body> <div class="container" id="tuto"> <br> <div class="panel panel-primary"> <div class="panel-heading">Panier</div> <table class="table table-bordered table-striped"> <thead> <tr> <th class="col-sm-4">Article</th> <th class="col-sm-2">Quantité</th> <th class="col-sm-2">Prix</th> <th class="col-sm-2">Total</th> <th class="col-sm-1"></th> <th class="col-sm-1"></th> </tr> </thead> <tbody> <tr v-for="item in panier"> <td>{{ item.article | capitalize }}</td> <td>{{ item.quantite }}</td> <td>{{ item.prix | currency '€' }}</td> <td>{{ item.quantite * item.prix | currency '€' }}</td> <td><button class="btn btn-info btn-block" v-on:click="modifier($index)"><i class="fa fa-edit fa-lg"></i></button></td> <td><button class="btn btn-danger btn-block" v-on:click="supprimer($index)"><i class="fa fa-trash-o fa-lg"></i></button></td> </tr> <tr> <td colspan="3"></td> <td><strong>{{ total | currency '€' }}</strong></td> <td colspan="2"></td> </tr> <tr> <td><input type="text" class="form-control" v-model="input.article" v-el:modif placeholder="Article"></td> <td><input type="text" class="form-control" v-model="input.quantite" placeholder="Quantité"></td> <td><input type="text" class="form-control" v-model="input.prix" placeholder="Prix"></td> <td colspan="3"><button class="btn btn-primary btn-block" v-on:click="ajouter()">Ajouter</button></td> </tr> </tbody> </table> </div> </div> <script src="http://cdn.jsdelivr.net/vue/1.0.10/vue.min.js"></script> <script> new Vue({ el: '#tuto', data: { panier: [ { article: "cahier", quantite: 2, prix: 5.30 }, { article: "crayon", quantite: 4, prix: 1.10 }, { article: "gomme", quantite: 1, prix: 3.25 } ], input: { article: '', quantite: 0, prix: 0 } }, computed: { total: function () { var total = 0; this.panier.forEach(function(el) { total += el.prix * el.quantite; }); return total; } }, methods: { ajouter: function() { this.panier.push(this.input); this.input = { article: '', quantite: 0, prix: 0 }; }, modifier: function(index) { this.input = this.panier[index]; this.panier.splice(index, 1); this.$$.modif.focus(); }, supprimer: function(index) { this.panier.splice(index, 1); }, } }); </script> </body> </html>
Fonctionnement
Au départ nous avons cet aspect :
Sont déjà présents 3 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 le renvoyer 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 :
Le code
Le modèle comporte le tableau panier
pour les articles de la liste et l'objet input
pour le formulaire :
data: { panier: [ { article: "cahier", quantite: 2, prix: 5.30 }, { article: "crayon", quantite: 4, prix: 1.10 }, { article: "gomme", quantite: 1, prix: 3.25 } ], input: { article: '', quantite: 0, prix: 0 } },
La liste est créée avec une boucle v-for
:
<tr v-for="item in panier"> <td>{{ item.article | capitalize }}</td> <td>{{ item.quantite }}</td> <td>{{ item.prix | currency '€' }}</td> <td>{{ item.quantite * item.prix | currency '€' }}</td> <td><button class="btn btn-info btn-block" v-on:click="modifier($index)"><i class="fa fa-edit fa-lg"></button></td> <td><button class="btn btn-danger btn-block" v-on:click="supprimer($index)"><i class="fa fa-trash-o fa-lg"></i></button></td> </tr>
Remarquez que le calcul pour le montant par article selon la quantité est effectué directement avec une expression dans mustache. D'autre part on utilise le filtre currency
pour ajouter le symbole de la monnaie (par défaut c'est $).
Pour le montant total on a ce code :
<tr> <td colspan="3"></td> <td><strong>{{ total | currency '€' }}</strong></td> <td colspan="2"></td> </tr>
On voit qu'on référence la propriété total
. C'est une propriété calculée (un accesseur) qu'on retrouve ici :
computed: { total: function () { var total = 0; this.panier.forEach(function(el) { total += el.prix * el.quantite; }); return total; } },
Le reste du code correspond à des choses que nous avons déjà rencontrées et je vous renvoie aux articles précédents si quelque chose ne vous semble pas clair.
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.
Par bestmomo
Nombre de commentaires : 2