Vue.js : Double liaison

Contrairement à ce que le titre de cet article pourrait suggérer il ne s’agit pas de mœurs dissolues mais toujours de notre librairie Javascript. On a vu au cours des articles 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.

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 :

new Vue({
  el: '#tuto',
  data: { texte: 'coucou' }
});

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 :

img01

Voici une illustration du fonctionnement :

img17

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 2 secondes après le chargement de la page le nouveau texte figurera aux deux emplacements. C’est bien de la double liaison !

Boutons radio et cases à cocher

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

Boutons radio

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

Voici un exemple de mise en œuvre HTML :

<form id="tuto">
  <div class="radio">
    <label>
      <input type="radio" name="sexe" value="male" v-model="sexe"> Male
    </label>
  </div>
  <div class="radio">
    <label>
      <input type="radio" name="sexe" value="femelle" v-model="sexe"> Femelle
    </label>
  </div>
  <div class="form-control">
    On a choisi {{ sexe }}
  </div>
</form>

Et le Javascript :

new Vue({
  el: '#tuto',
  data: { sexe: 'male' }
});

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

img02

Et quand on coche l’autre bouton on a :

img03

Voici une illustration du fonctionnement :

img18

Cases à cocher

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

Voici le HTML :

<form id="tuto">
  <div class="checkbox">
    <label>
      <input type="checkbox" name="nom" value="valeur" v-model="checked"> Je suis <strong>{{ checked }}</strong>
    </label>
  </div>
</form>

Et le Javascript :

new Vue({
  el: '#tuto',
  data: { checked: false }
});

Au départ on a ceci :

img06

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

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

img07

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é.

Sélection simple

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

<form id="tuto">
  <select class="form-control" v-model="choix">
    <option>Choix 1</option>
    <option>Choix 2</option>
    <option>Choix 3</option>
  </select>
  <p>On a choisi "{{ choix }}"</p>
</form>

Et le Javascript :

new Vue({
  el: '#tuto',
  data: { choix: 'Choix 2' }
});

Au départ on a cette situation :

img08

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

img09

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 :

<form id="tuto">
  <select class="form-control" v-model="choix" multiple>
    <option>Choix 1</option>
    <option>Choix 2</option>
    <option>Choix 3</option>
  </select>
  <p>On a choisi "{{ choix }}"</p>
</form>

Et le Javascript :

new Vue({
  el: '#tuto',
  data: { choix: ['Choix 2', 'Choix 3'] }
});

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

img10

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

img11

Options dynamiques

On a souvent besoin de remplir de façon dynamique une liste de choix, pour le faire on met à contribution la directive v-for  qu’on connait 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  :

<form id="tuto">
  <select class="form-control" v-model="choix" multiple>
    <option v-for="option in options" v-bind:value="option.value">
      {{ option.text }}
    </option>
  </select>
  <p>On a choisi "{{ choix }}"</p>
</form>

Et le Javascript :

new Vue({
  el: '#tuto',
  data: { 
    choix: [1, 3],
    options: [
      { text: 'Choix 1', value: '1' },
      { text: 'Choix 2', value: '2' },
      { text: 'Choix 3', value: '3' }
    ],
  }
});

Un exemple

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

<!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">
  </head>

  <body>

    <div class="container" id="tuto">
      <br>

      <div class="panel panel-primary">
        <div class="panel-heading">Personnes actives</div>        
        <table class="table table-bordered table-striped">
          <thead>
            <tr>
             <th class="col-sm-4">Nom</th>
             <th class="col-sm-4">Prénom</th>
             <th class="col-sm-2"></th>
             <th class="col-sm-2"></th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="personne in personnes">
              <td>{{ personne.nom }}</td>
              <td>{{ personne.prenom }}</td> 
              <td><button class="btn btn-info btn-block" v-on:click="modifier($index)">Modifier</button></td>
              <td><button class="btn btn-warning btn-block" v-on:click="supprimer($index)">Poubelle</button></td>
            </tr>  
            <tr>
              <td><input type="text" class="form-control" v-model="inputNom" v-el: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" v-on:click="ajouter()">Ajouter</button></td>
            </tr>
          </tbody>       
        </table>
        <div class="panel-footer">
          &nbsp
          <button class="button btn btn-xs btn-warning" v-on:click="toutPoubelle">Tout à la poubelle</button>
        </div>
      </div> 

      <div class="panel panel-danger" v-show="poubelle.length">
        <div class="panel-heading">Poubelle</div>
        <table class="table table-bordered table-striped">
          <thead>
            <tr>
             <th class="col-sm-4">Nom</th>
             <th class="col-sm-4">Prénom</th>
             <th class="col-sm-2"></th>
             <th class="col-sm-2"></th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="personne in poubelle">
              <td>{{ personne.nom }}</td>
              <td>{{ personne.prenom }}</td> 
              <td><button class="btn btn-success btn-block" v-on:click="retablir($index)">Rétablir</button></td>
              <td><button class="btn btn-danger btn-block" v-on:click="eliminer($index)">Supprimer</button></td>    
            </tr>  
          </tbody>       
        </table>
        <div class="panel-footer">
          &nbsp
          <div class="btn-group">
            <button class="button btn btn-xs btn-success" v-on:click="toutRetablir">Tout rétablir</button>
            <button class="button btn btn-xs btn-danger" v-on:click="toutEliminer">Tout supprimer</button> 
          </div>
        </div>
      </div> 

    </div>

    <script src="http://cdn.jsdelivr.net/vue/1.0.10/vue.min.js"></script>

    <script>

      new Vue({
        el: '#tuto',
        data: {
          personnes: [
            {nom: "Claret", prenom: "Marcel"},
            {nom: "Dupont", prenom: "Albert"},
            {nom: "Durand", prenom: "Jacques"},            
            {nom: "Martin", prenom: "Denis"},
            {nom: "Torlet", prenom: "Arthur"}            
          ],
          poubelle: [],
          inputNom: '',
          inputPrenom: ''
        },
        methods: {
          supprimer: function(index) {
            this.poubelle.push(this.personnes[index]);
            this.personnes.splice(index, 1);
            this.poubelle.sort(ordonner);
          },
          retablir: function(index) {
            this.personnes.push(this.poubelle[index]);
            this.poubelle.splice(index, 1);
            this.personnes.sort(ordonner);
          },
          eliminer: function(index) {
            this.poubelle.splice(index, 1);
          },
          toutPoubelle: function() {
            this.poubelle = this.poubelle.concat(this.personnes);
            this.poubelle.sort(ordonner);
            this.personnes = [];
          },
          toutRetablir: function() {
            this.personnes = this.personnes.concat(this.poubelle);
            this.personnes.sort(ordonner);
            this.poubelle = [];
          },
          toutEliminer: function() {
            this.poubelle = [];
          },
          ajouter: function() {
            this.personnes.push({nom: this.inputNom, prenom: this.inputPrenom});
            this.inputNom = this.inputPrenom = '';
            this.personnes.sort(ordonner);
          },
          modifier: function(index) {
            this.inputNom = this.personnes[index].nom;
            this.inputPrenom = this.personnes[index].prenom;
            this.personnes.splice(index, 1);
            this.$$.modif.focus();
          }
        }
      });

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

    </script>

  </body>

</html>

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

Lorsqu’on lance la page on obtient ceci :

img15

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" v-el: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" v-on: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.

Par contre il y a une nouvelle directive : v-el. Celle-là nous n’avons pas encore eu l’occasion de la rencontrer. Mais c’est sans doute la plus simple de vue.js. 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 :

data: {
  ...
  inputNom: '',
  inputPrenom: ''
},

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

methods: {
  
    ...
    
  ajouter: function() {
    this.personnes.push({nom: this.inputNom, prenom: this.inputPrenom});
    this.inputNom = this.inputPrenom = '';
    this.personnes.sort(ordonner);
  },

    ...
}
  • 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

Modifier une personne

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

<tr v-for="personne in personnes">
  <td>{{ personne.nom }}</td>
  <td>{{ personne.prenom }}</td> 
  <td><button class="btn btn-info btn-block" v-on:click="modifier($index)">Modifier</button></td>
  <td><button class="btn btn-warning btn-block" v-on:click="supprimer($index)">Poubelle</button></td>
</tr>

Et on a la méthode modifier :

methods: {
  
  ...

  modifier: function(index) {
    this.inputNom = this.personnes[index].nom;
    this.inputPrenom = this.personnes[index].prenom;
    this.personnes.splice(index, 1);
    this.$$.modif.focus();
  }
}
  • on place dans les deux zones de texte le nom et le prénom

  • on supprime la personne de la liste

  • on met le focus dans la zone de texte du prénom (on voit là l’intérêt d’avoir mis une directive v-el dans ce contrôle. Remarquez la syntaxe un peu particulière)

Ce qui donne ce genre d’affichage :

img16

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.

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 .

  • La directive v-el permet d’identifier un élément du DOM.

Laisser un commentaire