Vue.js

Vue.js : Accesseurs et mutateurs

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 :

img05

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 :

img02

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 :

img03

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 :

img04

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 :

img06

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 :

img07

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 :

img08

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.

Print Friendly, PDF & Email

2 commentaires

Laisser un commentaire