Vue.js : Créer une directive

Nous avons vu les directives dans la première partie de cette série d’articles. Il s’agissait alors des directives prédéfinies dans vue.js. Dans le présent article nous allons voir comment créer une directive sur mesure selon ses besoins pour étendre les possibilités de vue.js.

Les directives prédéfinies

Vue.js est déjà bien équipé en directives prédéfinies :

  • v-text : modifie le contenu texte

  • v-html : modifie le contenu html

  • v-if : fait apparaître ou disparaître un élément selon une valeur booléenne

  • v-show: permet de masquer ou faire apparaître un élément

  • v-else : à utiliser avec v-if et v-show

  • v-on : permet de mettre en place un événement

  • v-bind : permet de créer une liaison

  • v-model : permet de lier la valeur d’un contrôle de formulaire

  • v-for : crée un objet vue.js pour chaque élément se trouvant dans un tableau ou un objet

  • v-transition : applique une transition

  • v-ref : crée une référence d’un composant enfant pour son parent

  • v-el : crée une référence d’un élément du DOM

Ces directives sont nombreuses et bien pratiques. Nous en avons rencontrées plusieurs lors de la première partie de ce cours.

Une directive personnalisée

Si on sort des cas d’utilisation des directives prédéfinies alors c’est le moment d’en créer une sur mesure. Par exemple nous voulons créer une directive pour afficher un titre avec des règles de style spécifiques. Sur le fond l’exemple est un peu stupide parce qu’il suffit de créer une classe CSS pour aboutir au même résultat mais ça va nous permettre de commencer avec la création de directive de façon simple :

<div id="tuto">
  <div v-titre="Mon titre"></div>
</div>

Je définis une directive v-titre et je donne le texte du titre de façon littérale.

Voici le Javascript :

new Vue({
  el: '#tuto',
  directives: {
    titre: {
      bind: function() {
        this.el.style.color = 'Yellow';
        this.el.style.backgroundColor = 'DarkBlue';
        this.el.style.textAlign = 'center';
        this.el.style.fontSize = 'x-large';
        this.el.style.padding = '20px';
        this.el.innerHTML = this.expression;
      }
    }
  }
});

On voit apparaître une nouvelle propriété pour notre objet vue.js : directives. C’est ici que nous définissons les directives propres à cet objet.

On commence par nommer la directive (titre) et ensuite on définit son fonctionnement.

La méthode bind a pour objet de relier la directive à l’élément du DOM, elle est appelée une seule fois au démarrage (de la même manière on peut utiliser unbind qui est appelée une seule fois lorsque la liaison avec le DOM est supprimée). On a deux propriétés de la directive intéressantes :

  • el : pour connaître l’élément du DOM lié à la directive, on peut alors manipuler cet élément à notre guise, ici je fixe des règles de style et le contenu,

  • expression : le contenu de l’expression.

Si on regarde de plus près les propriétés on trouve :

img13

Le résultat visuel est :

img01

Un argument

Une directive peut utiliser un argument. Voici un exemple :

<div id="tuto">
  <div v-titre:1="titre"></div>
</div>

Ici la directive v-titre a l’argument 1 et la valeur titre. Voici le Javascript :

new Vue({
  el: '#tuto',
  data: {
    titre: 'Mon titre'
  },
  directives: {
    titre: function(value) {
      this.el.innerHTML = 
        '<h' + this.arg + '>' + 
        value + 
        '</h' + this.arg + '>';
    }
  }
});

On voit qu’il y a juste une fonction pour la directive qui correspond en fait à update. Comme c’est la seule utilisée on n’a pas besoin de préciser qu’il s’agit d’elle.

Voilà un petit point sur les propriétés :

img10

Le résultat est la valeur de « titre » dans une balise h1 :

img09Si on utilise :

<div v-titre:2="titre"></div>

Alors on aura une balise h2 et ainsi de suite.

On se rend compte qu’une directive possède un certain nombre de propriétés bien pratiques. Faisons un peu le point :

  • el : il s’agit de l’élément du DOM en liaison avec la directive (dans notre cas « div »)

  • expression : l’expression de la liaison en excluant l’argument ou un éventuel filtre (dans notre cas c’est « titre »)

  • arg : l’argument (dans notre cas c’est « 1 »)

On a d’autres propriétés moins utiles :

  • raw : c’est la totalité de ce qui est transmis (dans notre cas « titre » )

  • name : c’est le nom de la directive (dans notre cas « titre »)

Plusieurs valeurs

Des fois on a besoin de plusieurs valeurs. Voyons un exemple de réalisation:

<div id="tuto">
  <div v-titre="{type : 1, texte : titre}"></div>
</div>

On transmet un littéral objet. Voici le Javascript :

new Vue({
  el: '#tuto',
  data: {
    titre: 'Mon titre'
  },
  directives: {
    titre: function(value) {
      this.el.innerHTML = 
        '<h' + value.type + '>' + 
        value.texte + 
        '</h' + value.type + '>';
    }
  }
});

Voici l’état des propriétés :

img11

On obtient à nouveau le texte dans une balise h1 :

img09

Dans ce cas on récupère directement les valeurs à partir de l’expression. On pourrait aussi transmettre les valeurs séparément mais alors la directive serait appelée plusieurs fois.

Une directive élément

Une autre possibilité intéressante est de pouvoir créer une directive pour avoir une nouvelle balise personnalisée (à la manière de « restrict : ‘E’ » de AngularJS). Voici un exemple :

<div id="tuto">
  <titre>Mon titre</titre>
</div>

La balise <titre> n’existe pas dans le HTML, on la crée avec ce code :

new Vue({
  el: '#tuto',
  elementDirectives: {
    titre: {
      bind: function() {
        this.el.innerHTML = '<h1>' + this.el.innerHTML + '</h1>';
        this.el.style.color = 'Blue';
      }
    }
  }
});

La propriété ne s’appelle plus « directives » mais « elementDirectives ». On peut juste utiliser le bind pour définir la balise. Ici je me suis contenté de mettre le texte dans une balise h1 et de changer la couleur :

img07

On dispose en fait de peu de possibilités :

img12En fait on peut seulement accéder à l’élément.

Le panier

Reprenons l’exemple du panier en utilisant une directive personnalisée. Voici le nouveau code 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 | devise '€' }}</td>
              <td>{{ item.quantite * item.prix | devise '€' }}</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> 
            <tr>
              <td colspan="3"></td>
              <td><strong>{{ total | devise '€' }}</strong></td>
              <td colspan="2"></td>
            </tr> 
            <tr>
              <td><input type="text" class="form-control" v-model="input.article | capitalize" v-el:modif placeholder="Article"></td>
              <td><input type="text" class="form-control" v-model="input.quantite | entier 10" placeholder="Quantité"></td>
              <td><input type="text" class="form-control" v-model="input.prix | flottant" placeholder="Prix"></td>
              <td colspan="3"><btn_ajouter></btn_ajouter></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);
          },
        },
        filters: {
          devise: function(valeur, symbole) {
            return valeur + ' ' + symbole;
          },
          entier: {
            read: function(valeur) {
              return valeur;
            },
            write: function(nouvelleValeur, ancienneValeur, max) {
              var valeur = parseInt(nouvelleValeur);
              if(valeur % 1 === 0) {
                return valeur > max ? ancienneValeur : valeur;
              }
              return 0;
            }
          },
          flottant: {
            read: function(valeur) {
              return valeur;
            },
            write: function(nouvelleValeur, ancienneValeur) {
              return isNaN(nouvelleValeur) ? ancienneValeur : nouvelleValeur;
            }
          }       
        },
        elementDirectives: {
          btn_ajouter: {
            bind: function() {
              this.el.innerHTML = '<button class="btn btn-primary btn-block">Ajouter</button>';
              this.el.addEventListener('click', function(e) {
                this.vm.ajouter();
              }.bind(this));
            }
          }
        }
      });

    </script>

  </body>

</html>

Vous pouvez voir dans le HTML une nouvelle balise :

<td colspan="3">
    <btn_ajouter></btn_ajouter>
</td>

C’est une directive « élément » qui est définie dans cette partie du code Javascript :

elementDirectives: {
  btn_ajouter: {
    bind: function() {
      this.el.innerHTML = '<button class="btn btn-primary btn-block">Ajouter</button>';
      this.el.addEventListener('click', function(e) {
        this.vm.ajouter();
      }.bind(this));
    }
  }
}

On définit le contenu HTML et on ajoute l’événement pour le clic sur le bouton en appelant dans ce cas la méthode ajouter.

En résumé

  • Vue.js est équipé de nombreuses directives prédéfinies.

  • On peut créer une directive personnalisée.

  • On peut transmettre un littéral dynamique dans une directive.

  • On peut utiliser un ou plusieurs arguments dans une directive.

  • On peut créer une directive « élément ».

Laisser un commentaire