Nous avons bien avancé dans l’apprentissage de vue.js. Nous avons comment établir des liaisons, parfois à double sens, entre la vue et le modèle. Jusque là nous n’avons effectué aucun traitement intermédiaire et les données du modèle se retrouvent telles qu’elles dans la vue, et réciproquement. Parfois nous avons besoin d’effectuer quelques modifications, soit avant affichage, soit avant mémorisation des données. Cet article est destiné à présenter cet aspect de vue.js.

Les filtres

Dans vue.js un filtre est une fonction qui transforme une donnée :

img01

On a une donnée à l’entrée, elle est transformée et on la retrouve à la sortie dans un nouvel état.

Un simple filtre

Voici un exemple simple :

<div id="tuto">
  La donnée est "{{ texte }}" mais elle est transformée en "{{ texte | capitalize }}".
</div>

Et le Javascript :

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

Avec comme résultat :

La donnée est « coucou » mais elle est transformée en « Coucou ».

Pour ajouter un filtre on utilise la signe « | » suivi du nom du filtre. Ici capitalize met en majuscule la première lettre.

Voici une illustration du fonctionnement :

img02

Double filtrage

On peut cumuler les filtres :

<div id="tuto">
  La donnée est "{{ texte }}" mais elle est transformée en "{{ texte | lowercase | capitalize }}".
</div>

Avec ce Javascript :

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

On obtient :

La donnée est « COUCOU » mais elle est transformée en « Coucou ».

Pour mettre en œuvre plusieurs filtres il suffit de les ajouter, toujours avec le signe « | ». Ici on commence par tout mettre en minuscules avec le filtre lowercase, puis on met en majuscule le premier caractère avec capitalize.

Voici une illustration du fonctionnement :

img03

Le pluriel

On a souvent l’occasion de jouer avec des pluriels lorsqu’on affiche des données. En général on se retrouve à effectuer un test de quantité pour savoir s’il faut ajouter ou non un « s » à la fin d’un mot. Vue.js propose un filtre spécifique à ce problème :

<ul id="tuto">
  <li v-for="animal in animaux">
    {{ animal.nom }} : {{ animal.nombre }} {{ animal.nombre | pluralize 'présent' }}
  </li>
</ul>

Avec ce Javascript :

new Vue({
  el: '#tuto',
  data: { 
    animaux: [
      { nom: "Chat", nombre: 1 },
      { nom: "Chien", nombre: 3 },
      { nom: "Souris", nombre: 1 }
    ]
  }
});

img04

Le filtre pluralize met le mot qu’on lui passe comme argument, ici « présent », au pluriel en lui ajoutant un « s » selon la valeur filtrée, ici « nombre ». Donc quand « nombre » est plus grand que 1 il ajoute un « s » à « présent ».

Vous allez dire que bien souvent dans la langue française on n’a pas un « s » pour mettre au pluriel. Dans ce cas on peut utiliser plusieurs arguments. Voici un exemple :

<ul id="tuto">
  <li v-for="animal in animaux">
    {{ animal.nom }} : {{ animal.nombre }} {{ animal.nombre | pluralize 'animal' 'animaux' }}
  </li>
</ul>

Avec ce Javascript :

new Vue({
  el: '#tuto',
  data: { 
    animaux: [
      { nom: "Chat", nombre: 1 },
      { nom: "Chien", nombre: 3 },
      { nom: "Souris", nombre: 2 }
    ]
  }
});

Et ce résultat :

img05

Le clavier

On a vu des cas de gestion d’un événement de la souris. On peut de la même manière gérer un événement du clavier. Mais dans ce cas on a pratiquement toujours besoin de savoir quelle touche du clavier a été utilisée. C’est ici qu’intervient le filtre key.

Considérez ce cas :

<div id="tuto">
  <input v-on:keypress="action">
</div>

On a une zone de texte et on écoute l’événement keypress. Voici le Javascript pour traiter cet événement :

new Vue({
  el: '#tuto',
  methods: { 
    action: function() {
      alert('On a actionné une touche !')
    }
  }
});

Lorsque le focus est placé dans la zone de texte, quelle que soit la touche actionnée on obtient l’affichage de la petite fenêtre d’information :

img06

Pour prendre compte seulement une certaine touche on doit utiliser le filtre key :

<input v-on:keypress.left="action">

On change le texte de l’alerte :

new Vue({
  el: '#tuto',
  methods: { 
    action: function() {
      alert('On a actionné la touche "flèche à gauche" !')
    }
  }
});

Maintenant on aura la fenêtre que si on actionne la touche « flèche à gauche » :

img07

Voici une schématisation du fonctionnement :

img20

On obtiendrait le même résultat en transmettant le code de la touche :

<input v-on:keypress.37="action">

Vue.js possède des alias pour les touches les plus utilisées :

  • enter

  • tab

  • delete

  • esc

  • space

  • up

  • down

  • left

  • right

Vous avez donc le choix d’utiliser ces alias ou directement le code.

Filtrer une liste

On a aussi souvent besoin de filtrer des éléments d’une liste. Vue.js possède le filtre filterBy pour le faire. Voici un exemple :

<ul id="tuto">
  <li v-for="personne in personnes | filterBy 'actif'">
    {{ personne.nom }}
  </li>
</ul>

Et le Javascript :

new Vue({
  el: '#tuto',
  data: {
    personnes: [
      { nom: "Claret", statut: "actif" },
      { nom: "Dupont", statut: "actif" },
      { nom: "Durand", statut: "passif" },            
      { nom: "Martin", statut: "actif" },
      { nom: "Torlet", statut: "passif" }            
    ]
  }
});

Avec ce rendu :

img09

Les personnes ne sont prises en compte que si on trouve « actif » parmi les valeurs.

Mais que se passe-t-il si l’une des personnes s’appelle « Actif » :

personnes: [
  { nom: "Claret", statut: "actif" },
  { nom: "Dupont", statut: "actif" },
  { nom: "Actif", statut: "passif" },            
  { nom: "Martin", statut: "actif" },
  { nom: "Torlet", statut: "passif" }            
]

Dans ce cas on va le retrouver dans le rendu alors que son statut est « passif » :

img10

Dans ce cas on peut préciser sur quelle propriété on veut effectuer le filtrage :

<li v-for="personne in personnes | filterBy 'actif' in 'statut'">

Cette fois monsieur Actif ne sera pas sélectionné !

Voici une schématisation :

img21

Évidemment la valeur de filtrage peut être dans une variable et on peut ainsi envisager des choses intéressantes comme nous le verrons dans l’exemple de fin.

Un peu d’ordre

Lorsqu’on crée à l’affichage une liste avec la directive v-for les données sont prises en compte dans l’ordre du modèle. Si on veut modifier cet ordre, par exemple avoir un ordre alphabétique, on peut évidemment réordonner ces données dans le modèle, mais on peut aussi utiliser le filtre orderBy.

Voici un exemple :

<ul id="tuto">
  <li v-for="personne in personnes | orderBy 'nom'">
    {{ personne.nom }}
  </li>
</ul>

Ici on a ajouté le filtre orderBy en précisant qu’il doit agir sur la propriété « nom ».

Avec ce Javascript :

new Vue({
  el: '#tuto',
  data: {
    personnes: [
      { nom: "Martin"},
      { nom: "Claret"},
      { nom: "Durand"},
      { nom: "Dupont"},
      { nom: "Torlet"} 
    ]
  }
});

On voit que les noms ne sont pas dans l’ordre alphabétique au niveau du modèle. Pourtant on obtient grâce au filtre ce résultat :

img12

Voici une schématisation :

img22

On peut obtenir l’ordre inverse en ajoutant un deuxième argument qui doit être négatif :

<li v-for="personne in personnes | orderBy 'nom' -1">

Si on met -1  comme ici alors l’ordre est inversé :

img13

Si on prévoit une valeur positive on aura l’ordre normal, donc autant dans ce cas ne rien mettre !

On peut aussi prévoir de gérer dynamiquement l’ordre de sortie avec une variable.

Un exemple

Voyons à présent un exemple récapitulatif pour voir les filtres en action dans une situation réaliste. On va reprendre le cas d’un tableau de personnes mais cette fois on va gérer l’ordre alphabétique normal ou inverse par colonne et on va ajouter la possibilité de filtrer les lignes. Voici le code complet de l’exemple :

<!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">Personnes</div>        
        <table class="table table-bordered table-striped">
          <thead>
            <tr>
              <th v-for="head in heads">
                {{ head.titre }}
                <a href="#" v-on:click="action($index)">
                  <span class="fa fa-fw fa-sort{{ head.classe }}"></span>
                </a>
              </th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="
                personne in personnes
                | filterBy search in filterKey
                | orderBy sortKey orderKey">
              <td>{{ personne.nom | uppercase }}</td>
              <td>{{ personne.prenom }}</td> 
              <td>{{ personne.statut }}</td>
            </tr>  
          </tbody>       
        </table>
      </div> 

      <form>
        <div class="form-group">
          <input type="text" class="form-control" placeholder="Recherche ici" v-model="search">
        </div>
        <div class="radio" v-for="head in heads">
          <label>
            <input type="radio" value="{{ head.sort }}" v-model="filterKey">{{ head.titre }}
          </label>
        </div>
      </form>

    </div>

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

    <script>

      var classOrder = ['', '-desc', '-asc'];
      var keyOrder = ['', 1, -1];

      new Vue({
        el: '#tuto',
        data: {
          personnes: [
            { nom: "Claret", prenom: "Marcel", statut: 'Administrateur' },
            { nom: "Durand", prenom: "Jacques", statut: 'Utilisateur' }, 
            { nom: "Dupont", prenom: "Albert", statut: 'Utilisateur' },
            { nom: "Torlet", prenom: "Arthur", statut: 'Rédacteur' },            
            { nom: "Martin", prenom: "Denis", statut: 'Utilisateur' }                       
          ],
          heads: [
            { titre: 'Nom', classe: '', order: 0, sort: 'nom' },
            { titre: 'Prénom', classe: '', order: 0, sort: 'prenom' },
            { titre: 'Statut', classe: '', order: 0, sort: 'statut' }
          ],
          sortKey: '',
          orderKey: '',
          search: '',
          filterKey: 'nom'
        },
        methods: {
          action: function(index) {
            var self = this;
            this.heads.forEach(function(el) {
              if(el.sort != self.heads[index].sort) {
                el.order = 0;
                el.classe = '';
              };
            });
            var info = this.heads[index];     
            if(++info.order == 3) info.order = 1;
            info.classe = classOrder[info.order];
            this.heads.$set(index, info)
            this.orderKey = keyOrder[info.order];
            this.sortKey = info.sort;
          }
        }
      });

    </script>

  </body>

</html>

Fonctionnement

Au départ on a cet aspect :

img15

On a 3 colonnes pour les noms, les prénoms et le statut. Dans la partie inférieure une zone de texte permet de filtrer la liste et on peut choisir entre les colonnes pour ce filtrage. Il est prévu des petites icônes pour figurer le tri. Au départ aucun tri n’étant effectué on a les deux petits triangles. Si on clique dessus on ordonne selon la colonne, par exemple pour les prénoms :

img16

La petite icône change pour figurer le tri décroissant. Si on clique à nouveau on passe en mode croissant et la petite icône change encore :

img17

Si maintenant on clique sur l’icône de la colonne des noms on remet au repos celle des prénoms et le tri se fait alors sur la colonne des noms :

img18

Et ainsi de suite pour chaque colonne.

Pour le filtrage c’est plus simple : on choisit la colonne avec les boutons radio et on tape les caractères désirés :

img19

Remplissage du tableau

Toutes les données pour remplir le tableau se trouvent dans le modèle :

data: {
  personnes: [
    { nom: "Claret", prenom: "Marcel", statut: 'Administrateur' },
    { nom: "Durand", prenom: "Jacques", statut: 'Utilisateur' }, 
    { nom: "Dupont", prenom: "Albert", statut: 'Utilisateur' },
    { nom: "Torlet", prenom: "Arthur", statut: 'Rédacteur' },            
    { nom: "Martin", prenom: "Denis", statut: 'Utilisateur' }                       
  ],
  heads: [
    { titre: 'Nom', classe: '', order: 0, sort: 'nom' },
    { titre: 'Prénom', classe: '', order: 0, sort: 'prenom' },
    { titre: 'Statut', classe: '', order: 0, sort: 'statut' }
  ],

Un tableau d’objets pour les lignes et un autre pour les titres des colonnes et d’autres informations que nous allons voir plus loin.

L’entête du tableau est remplie avec ce code :

<th v-for="head in heads">
  {{ head.titre }}
  <a href="#" v-on:click="action($index)">
    <span class="fa fa-fw fa-sort{{ head.classe }}"></span>
  </a>
</th>

On utilise la directive v-for sur la propriété heads. On utilise mustache pour insérer le titre. on prévoit ensuite un lien pour l’icône de tri avec une directive v-on pour gérer l’événement en appelant la méthode action avec l’index comme paramètre. On gère le changement de l’aspect de l’icône en jouant sur la classe avec la propriété classe.

Le corps du tableau est rempli avec ce code :

<tr v-for="
    personne in personnes
    | filterBy search in filterKey
    | orderBy sortKey orderKey">
  <td>{{ personne.nom | uppercase }}</td>
  <td>{{ personne.prenom }}</td> 
  <td>{{ personne.statut }}</td>
</tr>

On utilise aussi la directive v-for sur la propriété personnes. Nous verrons les filtres plus loin. Les données des lignes suivent dans les balises td. On remarque l’utilisation du filtre uppercase pour avoir les noms en capitales.

Le tri

Le tri est assuré par le filtre orderBy :

orderBy sortKey orderKey

On retrouve les deux arguments dans le modèle :

data: {
  ...
  sortKey: '',
  orderKey: '',
  ...
},

Il suffit donc de bien renseigner ces propriétés pour assurer le tri. On a vu plus haut qu’on appelle la méthode action avec comme paramètre l’index de la colonne lorsqu’on clique sur l’icône de tri. Voici cette méthode :

action: function(index) {
  var self = this;
  this.heads.forEach(function(el) {
    if(el.sort != self.heads[index].sort) {
      el.order = 0;
      el.classe = '';
    };
  });
  var info = this.heads[index];     
  if(++info.order == 3) info.order = 1;
  info.classe = classOrder[info.order];
  this.heads.$set(index, info)
  this.orderKey = keyOrder[info.order];
  this.sortKey = info.sort;
}

Cette méthode accomplit un certain nombre d’actions :

  • réinitialise les propriétés order et classe de toutes les autres colonnes pour le cas où on a déjà un tri actif

  • incrémente la propriété order de la colonne et la ramène dans l’intervalle 1-2

  • actualise les données du modèle

  • assigne la nouvelle valeur des propriétés orderKey et sortKey pour l’affichage

Filtrage

Le filtrage est plus simple, on s’en sort élégamment seulement avec les liaisons.

Dans le modèle on a :

data: {
  ...
  search: '',
  filterKey: 'nom'
},

La propriété search est liée à la zone de texte :

<input type="text" class="form-control" placeholder="Recherche ici" v-model="search">

Le filtrage est assuré par le filtre filterBy :

filterBy search in filterKey

On change la colonne de filtrage avec les boutons radio :

<div class="radio" v-for="head in heads">
  <label>
    <input type="radio" value="{{ head.sort }}" v-model="filterKey">{{ head.titre }}
  </label>
</div>

On crée ces boutons radio avec encore la directive v-for qui aide à rendre le HTML plus lisible.

Cet exemple aurait pu être codé de façon différente mais en l’état il constitue un bon récapitulatif de ce que nous avons vu jusque là.

En résumé

  • Les filtres permettent de modifier les données avant leur affichage.

  • On peut utiliser plusieurs filtres.

  • Il existe un filtre spécifique pour les événements du clavier.

  • Le filtre filterBy permet de filtrer une liste.

  • Le filtre orderBy permet d’ordonner une liste.

Laisser un commentaire