Vue.js : Créer un filtre

Nous avons vu les filtres dans un précédent article. Il s’agissait alors des filtres prédéfinis dans vue.js. Dans le présent article nous allons voir comment créer un filtre sur mesure selon ses besoins.

Les filtres prédéfinis

Vue.js est déjà bien équipé en filtres prédéfinis :

  • capitalize : met le premier caractère en capitale

  • uppercase : met tous les caractères en capitale

  • lowercase : met tous les caractères en minuscule

  • currency : ajoute en début le symbole de la monnaie qu’on peut passer en argument (défaut: $)

  • pluralize : permet de mettre au pluriel avec la possibilité de jouer avec plusieurs formes

  • json : transforme en JSON

  • limitBy : limite le nombre d’argument dans une liste générée par v-for

  • filterBy : filtre les éléments d’une liste générée par v-for

  • orderBy : trie les éléments d’une liste générée par v-for

Ces filtres sont nombreux et bien pratiques, certains sont équipés d’arguments permettant de moduler leurs effets. Nous les avons presque tous rencontrés au cours de la première partie de cette série d’articles.

Un filtre sur mesure

Si on sort des cas d’utilisation des filtres prédéfinis alors c’est le moment d’en créer un personnalisé. Par exemple il se peut que le filtre currency  ne nous plaise pas trop parce qu’il ajoute le symbole de la monnaie au début alors qu’on préfèrerait le voir à la fin. Une bonne occasion de créer un filtre !

Voici le HTML :

<div id="tuto">
  <p v-text="montant | euros"></p>
</div>

On voit qu’on a associé à la propriété montant  le filtre euros  qui n’existe pas dans la panoplie de base.

Voici le Javascript :

new Vue({
  el: '#tuto',
  data: {
    montant: '23.50'
  },
  filters: {
    euros: function(valeur) {
      return valeur + ' €';
    }
  }
});

La partie nouvelle dans ce code est constituée par la propriété filters qui permet de créer des filtres. Ici on crée le filtre euros et on définit son action avec une fonction.

Un filtre personnalisé comporte automatiquement un paramètre qui transmet la valeur de la propriété associée, ici le contenu de montant. Il suffit alors de retourner ce montant en lui ajoutant le symbole à la fin et on obtient :

23.50 €

Voici une illustration du fonctionnement :

img01

Passage d’un argument

Un filtre sur mesure peut aussi accepter un ou plusieurs arguments. Reprenons le cas du symbole monétaire vu ci-dessus et améliorons-le pour accepter le symbole :

Voici le nouveau HTML :

<p v-text="montant | devise '€'"></p>

Et le Javascript :

filters: {
  devise: function(valeur, symbole) {
    return valeur + ' ' + symbole;
  }
}

On voit qu’on récupère l’argument dans la fonction en deuxième position puisque la première est d’office affectée à la valeur de la propriété. Pour le reste le fonctionnement est exactement le même à la différence que maintenant on peut choisir le symbole monétaire sans changer le filtre.

Voici un schéma de fonctionnement :

img02

Argument optionnel

L’argument d’un filtre peut être optionnel, dans ce cas il prend une valeur par défaut si aucune valeur n’est transmise. C’est le cas de certains filtres prédéfinis. Voyons un cas avec un filtre sur mesure.

Supposons que nous voulons un filtre qui affiche une valeur avec le symbole pourcentage et en limitant le nombre de décimale selon une valeur passée comme argument, avec deux décimales par défaut.

Voici le HTML :

<div id="tuto">
  <p v-text="montant | pourcentage"></p>
</div>

Et voici le Javascript :

new Vue({
  el: '#tuto',
  data: {
    montant: 23.514
  },
  filters: {
    pourcentage: function(valeur, decimales) {
      if(decimales === undefined) {
        decimales = 2;
      }
      return Math.round(valeur * Math.pow(10, decimales)) / Math.pow(10, decimales) + ' %';
    }
  }
});

Le résultat est :

23.51 %

Comme on n’a pas passé de valeur le nombre de décimales s’est fixée par défaut à 2. Si on en passe un :

<p v-text="montant | pourcentage 1"></p>

Alors le résultat change en conséquence :

23.5 %

Argument dynamique

L’argument peut lui-même être une variable est donc posséder une valeur selon le contexte du script. Cela peut être très intéressant mais commençons par voir ce fonctionnement avec un cas simple :

<div id="tuto">
  <form>
    <div class="form-group">
      <input type="text" class="form-control" v-model="saisie">
    </div>
  </form>
  <p>{{ 'Texte saisi : ' | ajoute saisie }}</p>
</div>

On a une zone de texte liée à la propriété saisie. D’autre part on affiche avec mustache un texte avec un filtre (ajoute) et un argument dynamique pour ce filtre qui est justement la propriété saisie .

Voici le Javascript :

new Vue({
  el: '#tuto',
  data: {
    saisie: ''
  },
  filters: {
    ajoute: function(valeur, saisie) {
      return valeur + saisie;
    }
  }
});

Le filtre se contente d’ajouter ce qui est dans saisie au texte :

img03

Voici une illustration des liaisons :

img04

Bon c’est un peu embrouillé mais ça donne une idée du fonctionnement.

L’exemple ci-dessus n’est pas très réaliste, mais il a le mérite de montrer comment ça fonctionne. Voyons donc à présent un cas un peu plus intéressant. On va créer un filtre pour limiter le nombre de mots affichés dans un texte :

<div id="tuto">
  <p>{{ texte | limite nbrMots }}</p>
  <form>
    <div class="form-group">
      <select class="form-control" v-model="nbrMots">
        <option>1</option>
        <option>2</option>
        <option>3</option>
        <option>4</option>
        <option>5</option>
        <option>6</option>
      </select>
    </div>
  </form>
</div>

On a donc un paragraphe pour l’affichage et une liste de choix avec des valeurs de 1 à 6.

Voici le Javascript :

new Vue({
  el: '#tuto',
  data: {
    texte: 'Juste un petit texte pour tester ce filtre.',
    nbrMots: 3
  },
  filters: {
    limite: function(valeur, nbrMots) {
      return valeur.split(' ').slice(0, nbrMots).join(' ');
    }
  }
});

On crée le filtre limite qui attend comme second argument le nombre de mots à afficher.

On obtient :

img05

On affiche bien 3 mots. Si on change la valeur :

img06On affiche cette fois 5 mots.

Filtre à double sens

Jusque là on a vu uniquement comment on peut modifier à l’affichage une donnée récupérée dans le modèle. Et si on faisait aussi l’inverse ?

Prenons un exemple simple : on va entrer du texte en interdisant de raccourcir la longueur du texte déjà saisi :

<div id="tuto">
  <p>{{ message }}</p>
  <form>
    <div class="form-group">
      <input type="text" class="form-control" v-model="message | limite">
    </div>
  </form>
</div>

Donc une simple zone de texte et une directive v-model dans laquelle on a prévu un filtre personnalisé.

Voilà le Javascript :

new Vue({
  el: '#tuto',
  data: {
    message: 'Bonjour à toi'
  },
  filters: {
    limite: {
      read: function(valeur){
        return valeur;
      },
      write: function(nouvelleValeur, ancienneValeur){
        return nouvelleValeur.length < ancienneValeur.length ? ancienneValeur : nouvelleValeur;
      }            
    }
  }
});

On divise le code du filtre en deux fonctions, une pour la lecture (read) et l’autre pour l’écriture (write). Au départ on a :

img07

Si on ajoute du texte pas de souci :

img08Mais on ne peut pas le raccourcir…

Notez que pour la partie lecture on se contente de renvoyer la valeur. En effet, ce genre de filtre ne peut se concevoir que pour un contrôle de formulaire avec une directive v-model. Or dans ce cas il y a un double lien et la partie lecture est donc inutile.

Le panier amélioré

Maintenant que nous savons créer des filtres, même dans les deux sens, nous allons en profiter pour améliorer l’exemple du panier que nous avons vu à la fin de la première partie de cette série d’articles. Nous allons :

  • utiliser le filtre devise que nous avons créé dans cet article

  • créer un filtre entier pour filtrer uniquement les nombre entiers en prévoyant une valeur maximale à passer en arguments

  • créer un filtre flottant pour filtrer les nombres flottants

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"><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);
          },
        },
        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;
            }
          }       
        }
      });

    </script>

  </body>

</html>

On voit qu’on utilise maintenant le filtre devise à la place du filtre prédéfini currency :

<td>{{ item.prix | devise '€' }}</td>
<td>{{ item.quantite * item.prix | devise '€' }}</td>
,..
<td><strong>{{ total | devise '€' }}</strong></td>

On voit également l’ajout des filtres entier et flottant pour la quantité et le prix :

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

Les filtres sont définis dans la partie Javascript :

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;
    }
  }       
}

On voit qu’on peut ajouter un argument (ou même plusieurs) à la partie « write » d’un filtre bidirectionnel.

Le fonctionnement est le même que celui que nous avons vu précédemment pour ce panier. La différence introduite par le filtre devise est que maintenant le symbole monétaire se situe après la valeur :

img09

D’autre part dans la formulaire on ne peut entrer que des valeurs entières inférieures à 10 dans la zone « quantité » avec le filtre entier, et que des nombres (entiers ou flottants) dans la zone « prix » à l’aide du filtre flottant.

En résumé

  • Vue.js est équipé de nombreux filtres prédéfinis.

  • Vue.js permet de créer des filtres personnalisé.

  • On peut ajouter des arguments à un filtre personnalisé.

  • Un argument de filtre peut être dynamique.

  • Un filtre peut être bidirectionnel.

Laisser un commentaire