IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Tutoriel pour apprendre à utiliser le framework Vue.js version 2

Plus loin


précédentsommaire

V. Pagination avancée

Dans le précédent chapitrechapitreVue-resource (2/2), on a vu un exemple de pagination très simplifié. Je vous propose dans le présent chapitre de poursuivre l'exemple en construisant un composant de pagination digne de ce nom qui soit à la fois pratique, complet et esthétique.

On va donc partir de la situation telle qu'on l'a laissée précédemment en transformant la pagination en composant indépendant.

Je vous ai mis un zip contenant tout le code final ici.

V-A. Côté Laravel

Du côté de Laravel on va garder le même code, mais on va ajouter des utilisateurs. Dans le seeder on va en prévoir 1000 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
<?php
 
use Illuminate\Database\Seeder;
 
class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(App\User::class, 1000)->create();;
    }
}

Alors régénérez votre base de données (on l'a prévue en sqlite) :

 
Sélectionnez
php artisan migrate:reset
php atisan migrate --seed

Vous devriez avoir ainsi 1000 utilisateurs :

Image non disponible

V-B. Quelle pagination ?

Il existe de nombreuses façons de créer une pagination, plus ou moins judicieuses. J'aime bien cet article, même s'il est en anglais. En résumé il préconise :

  • des zones cliquables confortables ;
  • éviter les soulignements ;
  • identifier la page active ;
  • bien espacer les liens ;
  • prévoir des liens « précédent » et « suivant » ;
  • prévoir des liens « premier » et ‘ »dernier » séparés.

S'ensuit une galerie de bonnes et mauvaises paginations.

On va essayer dans ce tutoriel de respecter ces préconisations en y ajoutant la possibilité de déterminer le nombre d'enregistrements par page.

On va partir du principe qu'on affiche par défaut 10 enregistrements, comme on en a 1000 ça nous fait 100 pages. Au départ on affiche la page 1 et la pagination va se présenter ainsi :

Image non disponible

On a :

  • la page active (1) mise en évidence ;
  • une troncature ;
  • les deux dernières pages ;
  • un bouton de changement de page vers le haut ;
  • un bouton de saut pour 10 pages vers le haut.

Si maintenant on affiche la page 5 :

Image non disponible

On a :

  • un bouton de changement de page vers le bas ;
  • deux pages avant la troncature ;
  • une troncature ;
  • un bouton de changement de page vers le haut ;
  • un bouton de saut pour 10 pages vers le haut.

Si maintenant on affiche la page 15 :

Image non disponible

Cette fois on a :

  • un bouton de saut pour 10 pages vers le bas ;
  • un bouton de changement de page vers le bas ;
  • les deux premières pages ;
  • une troncature ;
  • la page active encadrée de deux pages vers le bas et vers le haut ;
  • une troncature ;
  • les dernières pages ;
  • un bouton de changement de page vers le haut ;
  • un bouton de saut pour 10 pages vers le haut.

Prenons un dernier exemple avec la page 96 :

Image non disponible

Là on a :

  • un bouton de saut pour 10 pages vers le bas ;
  • un bouton de changement de page vers le bas ;
  • les deux premières pages ;
  • une troncature ;
  • la page active encadrée avec deux pages vers le bas ;
  • les dernières pages ;
  • un bouton de changement de page vers le haut.

Je pense que vous avez compris les principes mis en œuvre. Il me semble que c'est une façon efficace de gérer une pagination.

Un dernier cas concerne la réduction sur petit support, dans ce cas le plus simple est de ne conserver que les boutons de déplacement :

Image non disponible

Pour compléter tout ça, on va prévoir une liste déroulante pour choisir le nombre d'enregistrements par page :

Image non disponible

V-C. Le composant principal

Voici le nouveau code du composant principal (App) :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
<template>
  <div class="ui raised container segment">
    <message  
      type="positive"
      header="Serveur mis à jour avec succès !"
      :show="success"
      @close="closeSuccess">
    </message>
    <message 
      type="negative"
      header="Echec de la communication avec le serveur !"
      :show="danger"
      @close="closeDanger">
    </message>
    <message 
      type="negative"
      header="Il y a des erreurs dans la validation des données saisies :"
      :list="[ validation.name, validation.email ]"
      :show="validation.name != '' || validation.email != ''"
      @close="closeValidation">
    </message>
    <div class="ui three column grid">
      <div class="row">
        <div class="column"></div>
        <div class="column"><h1>Liste des utilisateurs</h1></div>
        <div class="column right aligned" v-show="!edition">  
          <select v-model="paginationSelect" class="ui menu dropdown">
            <option value="5">5 lignes</option>
            <option value="10">10 lignes</option>
            <option value="20">20 lignes</option>
            <option value="50">50 lignes</option>
          </select>
        </div>
      </div>    
      <div class="row" v-if="users.length == 0" style="height: 100px">
        <div class="ui active inverted centered dimmer">
          <div class="ui text loader">Chargement</div>
        </div>
        <p></p>
      </div>
    </div>    
    <table class="ui celled table" v-if="users.length > 0">
      <thead>
        <tr>
         <th>Nom</th>
         <th>Email</th>
         <th></th>
         <th></th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(user, index) in userShowed">
          <td>{{ user.name }}</td>
          <td>{{ user.email }}</td>
          <td>
            <button class="fluid ui orange button" :class="{ disabled: edition }" data-tooltip="Modifier cet utilisateur" data-position="top center" @click="edit(index)">
              <i class="edit icon"></i>
            </button>
          </td>
          <td>
            <button class="fluid ui red button" :class="{ disabled: edition }" data-tooltip="Supprimer cet utilisateur" data-position="top center" @click="del(index)">
              <i class="remove user icon"></i>
            </button>
          </td>  
        </tr>  
        <tr class="ui form">
          <td>
            <div class="ui field" :class="{ error: validation.name }">
              <input type="text" v-model="user.name" placeholder="Nom">
            </div>
          </td>
          <td>
            <div class="ui field" :class="{ error: validation.email }">
              <input type="email" class="form-control" v-model="user.email" placeholder="Email">
            </div>
          </td>
          <td colspan="2" v-if="!edition">
            <button class="fluid ui blue button" data-tooltip="Ajouter un utilisateur" data-position="top center" @click="add()">
              <i class="add user icon"></i>
            </button>
          </td>
          <td v-if="edition">
            <button class="fluid ui blue button" data-tooltip="Mettre à jour cet utilisateur" data-position="top center" @click="update()">
              <i class="add user icon"></i>
            </button>
          </td>
          <td v-if="edition">
            <button class="fluid ui violet button" data-tooltip="Annuler la modification" data-position="top center" @click="undo()">
              <i class="undo icon"></i>
            </button>
          </td>
        </tr>
      </tbody>       
    </table>
    <pagination :records="maxUsers" :number-per-page="paginationNumber" :hide="edition" @changepage="changePage"></pagination>
  </div>  
</template>
 
<script>
import Message from './Message.vue'
import Pagination from './Pagination.vue'
 
export default {
  name: 'application',
  resource: null,
  data() {
    return {
      users: [],
      userShowed: [],
      user: { name: '', email: '' },
      save: { index: 0, user: {} },
      success: false,
      danger: false,
      edition: false,
      validation: { name: '', email: '' },
      paginationIndex: 1,
      paginationNumber: 10,
      paginationSelect: 10
    }
  },
  computed: {
    maxUsers() {
      return this.users.length
    }
  },
  watch: {
    paginationSelect() {
      this.paginationNumber = _.toInteger(this.paginationSelect)
      this.paginationIndex = 1
      this.refreshPage()
    }
  },
  mounted() {
    this.resource = this.$resource('/users{/id}')
    this.resource.get().then((response) => {
      this.users = response.body
      this.userShowed = this.users.slice(0, this.paginationNumber)
    }, (response) => {
      this.danger = true
    })
  },
  methods: {
    add() {
        this.resetMessages()
        this.resource.save(this.user).then((response) => {
          this.success = true
          this.users.push(this.user)
          this.user = { name: '', email: '' }
        }, (response) => {
        this.setValidation(response)
      });
    },
    update() {
        this.resetMessages()
        this.resource.update({id: this.user.id}, this.user).then((response) => {
          this.success = true
          this.userShowed.splice(this.save.index, 0, this.user)
          this.users[this.save.index + this.getStartPagination] = _.clone(this.user)
          this.edition = false
          this.user = { name: '', email: '' }
        }, (response) => {
        this.setValidation(response)
      });
    },
    del(index) {
      let that = this
      index = index + this.getStartPagination()
      this.resetMessages()
      this.$swal({
        title: 'Vous êtes sûr de vous ?',
        text: "Il n'y aura aucun retour en arrière possible !",
        type: 'warning',
        showCancelButton: true,
        confirmButtonColor: '#3085d6',
        cancelButtonColor: '#d33',
        confirmButtonText: 'Oui supprimer !',
        cancelButtonText: 'Non, surtout pas !',
      }).then(function() {
        that.resource.delete({id: that.users[index].id}).then((response) => {
          that.success = true
          that.users.splice(index, 1)
          that.changePage(1)
        }, (response) => {
          that.danger = true 
        });        
      }).done()
    },
    edit(index) {
      this.resetMessages()
      this.save.index = index
      this.user = this.userShowed[index]
      this.save.user = _.clone(this.user)
      this.edition = true
      this.userShowed.splice(index, 1)
    },
    undo() {
      this.userShowed.splice(this.save.index, 0, this.save.user)
      this.user = { name: '', email: '' }
      this.edition = false
    },
    resetMessages() {
      this.success = false
      this.danger = false
      this.closeValidation()
    },
    setValidation(response) {
      this.validation.name = response.body.name ? response.body.name[0] : ''
      this.validation.email = response.body.email ? response.body.email[0] : ''
    },
    closeSuccess() {
      this.success = false
    },
    closeDanger() {
      this.danger = false
    },
    closeValidation() {
      this.validation = { name: '', email: ''}
    },
    changePage(index) {
      this.paginationIndex = index
      this.refreshPage()
    },
    refreshPage() {
      let start = this.getStartPagination()
      this.userShowed = this.users.slice(start, start + this.paginationNumber)
    },
    getStartPagination() {
      return (this.paginationIndex - 1) * this.paginationNumber
    }
  },
  components: {
    Message, 
    Pagination
  }
}
</script>

On retrouve l'essentiel de ce qu'on avait déjà mis en place, mais avec quelques modifications…

V-C-1. La liste déroulante

Dans le template, on a ajouté la liste déroulante pour le nombre d'enregistrements par page :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
<div class="column right aligned" v-show="!edition">  
  <select v-model="paginationSelect" class="ui menu dropdown">
    <option value="5">5 lignes</option>
    <option value="10">10 lignes</option>
    <option value="20">20 lignes</option>
    <option value="50">50 lignes</option>
  </select>
</div>

On cache la liste en mode édition avec la directive v-show.

On voit une liaison de données avec la directive v-model. On va retrouver la valeur dans le data avec une valeur par défaut de 10 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
data() {
  return {
    ...
    paginationSelect: 10
  }
},

On surveille les changements dans la liste avec une propriété dont je ne vous ai pas encore parlé (watch) :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
watch: {
  paginationSelect() {
    this.paginationNumber = _.toInteger(this.paginationSelect)
    this.paginationIndex = 1
    this.refreshPage()
  }
},

Avec Vue.js il y a deux façons de réagir à un changement de valeur : les propriétés calculées (computed properties) et les observateurs (watchers). Selon les cas, il est plus judicieux d'utiliser l'un ou l'autre. Les observateurs sont intéressants quand on veut accomplir une action spécifique comme c'est le cas ici. Quand la valeur change dans la liste déroulante :

  • on transforme la valeur en integer (dans la liste on a des chaînes de caractères) ;
  • on réinitialise la page actuelle à 1 ;
  • on rafraîchit l'affichage.

Remarquez que j'utilise Lodash parce qu'il est déjà par défaut chargé par Elixir, alors autant s'en servir !

V-C-2. L'intégration de la pagination

Dans le template, on va évidemment aussi trouver le composant de la pagination :

 
Sélectionnez
<pagination :records="maxUsers" :number-per-page="paginationNumber" :hide="edition" @changepage="changePage"></pagination>

Et on le déclare dans le JavaScript :

 
Sélectionnez
1.
2.
3.
4.
components: {
  ... 
  Pagination
}

On voit qu'on va transmettre des valeurs pour trois propriétés :

  • records : le nombre total d'enregistrements donné par la propriété calculée maxUsers ;
  • number-per-page : le nombre d'enregistrements par page donné par la liste déroulante ;
  • hide : l'effacement de la pagination qui servira lorsqu'on veut éditer un utilisateur.

D'autre part on écoute l'événement changePage qui va nous indiquer le changement de page. En cas de changement de page, on actualise l'index et on rafraîchit la page :

 
Sélectionnez
1.
2.
3.
4.
changePage(index) {
  this.paginationIndex = index
  this.refreshPage()
},

Cet index nous est nécessaire pour repérer l'utilisateur pour les actions de suppression et édition. On peut en effet déterminer où commence la page actuelle :

 
Sélectionnez
getStartPagination() {
  return (this.paginationIndex - 1) * this.paginationNumber
}

Le rafraîchissement de la page se fait avec cette fonction :

 
Sélectionnez
1.
2.
3.
4.
refreshPage() {
  let start = this.getStartPagination()
  this.userShowed = this.users.slice(start, start + this.paginationNumber)
},

Avec ça on est parés au niveau du composant principal…

V-D. Le composant de pagination

c'est dans le composant de pagination que se trouve toute la logique correspondante ainsi que le code HTML et CSS. Voici le code complet du composant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
<template>
  <div id="pagination" v-if="paginationEnabled">
    <div class="ui pagination menu" v-show="chunkLeft" @click="bigLeft">
      <a class="item"><i class="angle double left icon"></i></a>
    </div>
    <div class="ui pagination menu" v-show="buttonLeft" @click="left">
      <a class="item"><i class="angle left icon"></i></a>
    </div>
    <div id="numbers" class="ui pagination menu">
      <a v-for="(item, index) in pagination" class="item" :class="{ active: item.active, disabled: item.disabled }" @click="changePage(index)">
        {{ item.text }}
      </a>
    </div>
    <div class="ui pagination menu" v-show="buttonRight" @click="right">
      <a class="item"><i class="angle right icon"></i></a>
    </div>
    <div class="ui pagination menu" v-show="chunkRight" @click="bigRight">
      <a class="item"><i class="angle double right icon"></i></a>
    </div>
  </div>
</template>
 
<script>
export default {
  name: 'pagination',
  props: {
    'records': {
      type: Number,
      required: true
    },
    'numberPerPage': {
      type: Number,
      required: false,
      default: 10
    },
    'chunk': {
      type: Number,
      required: false,
      default: 10
    },
    'hide': {
      type: Boolean,
      required: false,
      default: false
    }
  },
  data() {
    return {
      current: 1,
      adjacent: 2,
      pagination: []
    }
  },
  computed: {
    paginationEnabled() {
      return this.records > this.numberPerPage && !this.hide
    },
    buttonLeft() {
      return this.current > 1
    },
    buttonRight() {
      return this.current < this.totalPages()
    },
    chunkLeft() {
      return this.current >= this.chunk
    },
    chunkRight() {
      return this.current <= this.totalPages() - this.chunk
    }
  },
  watch: {
    numberPerPage() {
      this.current = 1
      this.createPagination()
    },
    records() {
      this.current = 1
      this.createPagination()
    },
    current() {
      this.createPagination()
    }
  },
  methods: {
    changePage(index) {
      if(!this.pagination[index].disabled) {
        this.current = this.pagination[index].text
        this.$emit('changepage', this.current)
      }
    },
    createPagination() {
      let total = this.totalPages()
      this.pagination = []
      let encadrement = this.adjacent * 2
      // Sans troncature
      if(total < 7 + encadrement) {
        this.addPages(..._.range(1, total + 1))
      // Avec troncature
      } else {
        // Troncature à droite
        if (this.current < 2 + encadrement) {
          this.addPages(..._.range(1, 4 + encadrement))
          this.addTroncature()
          this.addLastPages(total)       
        }
        // Deux troncatures
        else if ((encadrement + 1 < this.current) && (this.current < total - encadrement)) {
          this.addFirstPages()     
          this.addTroncature()
          this.addPages(..._.range(this.current - this.adjacent, this.current + this.adjacent + 1))
          this.addTroncature()
          this.addLastPages(total)
        }
        // Troncature à gauche
        else {
          this.addFirstPages()
          this.addTroncature()
          this.addPages(..._.range(total - 2 - encadrement, total + 1))    
        }
      }
    },
    addTroncature() {
      this.pagination.push({ text: '...', active: false, disabled: true })
    },
    addFirstPages() {
      this.addPages(1, 2)
    },
    addLastPages(total) {
      this.addPages(total - 1, total)
    },
    addPages(...valeurs) {
      let that = this
      _.forEach(valeurs, function(valeur) {
        that.pagination.push({ text: valeur, active: that.current == valeur, disabled: false })
      });
    },
    totalPages() {
      return _.ceil(this.records / this.numberPerPage)
    },
    left() {
      this.$emit('changepage', --this.current)
    },
    right() {
      this.$emit('changepage', ++this.current)
    },
    bigLeft() {
      this.current -= this.chunk
      this.$emit('changepage', this.current)
    },
    bigRight() {
      this.current += this.chunk
      this.$emit('changepage', this.current)
    }
  }
}
</script>
 
<style>
.ui.pagination.menu .item {
  background-color: rgba(33,133,208,.2);
}
.ui.pagination.menu .active.item {
  background-color: rgba(33,133,208,.5);
}
@media screen and (max-width: 768px) {
  #numbers { display: none; }
}
#pagination {
  display:flex; 
  justify-content:center
}
</style>

V-D-1. Les propriétés

Pour faire les choses correctement, j'ai prévu une validation au niveau des propriétés (props) :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
props: {
  'records': {
    type: Number,
    required: true
  },
  'numberPerPage': {
    type: Number,
    required: false,
    default: 10
  },
  'chunk': {
    type: Number,
    required: false,
    default: 10
  },
  'hide': {
    type: Boolean,
    required: false,
    default: false
  }
},

On contrôle le type de donnée, le fait que la valeur soit obligatoire ou pas et on prévoit une valeur par défaut le cas échéant.

J'ai prévu une propriété optionnelle chunk qui correspond au déplacement de plusieurs pages en avant ou en arrière avec une valeur par défaut de 10.

V-D-2. Le template et les boutons

Au niveau du template, on a déjà une condition globale :

 
Sélectionnez
<div id="pagination" v-if="paginationEnabled">

La directive v-if permet d'effacer la pagination dans deux cas :

 
Sélectionnez
1.
2.
3.
4.
computed: {
  paginationEnabled() {
    return this.records > this.numberPerPage && !this.hide
  },
  • si le nombre d'enregistrements est inférieur ou égal au nombre d'enregistrements par page ;
  • si on est en mode édition.

Pour les boutons de déplacement, on a une logique simple. Si on prend par exemple celui d'une page à gauche :

 
Sélectionnez
<div class="ui pagination menu" v-show="buttonLeft" @click="left">
  <a class="item"><i class="angle left icon"></i></a>
</div>

Une directive v-show permet de le faire apparaître uniquement quand c'est nécessaire :

 
Sélectionnez
buttonLeft() {
  return this.current > 1
},

Très logiquement c'est quand on en est au moins à la page 2. D'autre part si on clique sur le bouton on a un événement left :

 
Sélectionnez
left() {
  this.$emit('changepage', --this.current)
},

On émet l'événement changePage pour le composant parent en lui transmettant le nouvel index.

Pour les boutons des pages on a une simple directive v-for :

 
Sélectionnez
<a v-for="(item, index) in pagination" class="item" :class="{ active: item.active, disabled: item.disabled }" @click="changePage(index)">
  {{ item.text }}
</a>

V-D-3. La logique

On a ces données :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
data() {
  return {
    current: 1,
    adjacent: 2,
    pagination: []
  }
},
  • la page courante current ;
  • le nombre de pages adjacentes à afficher adjacent fixée à 2 ;
  • un tableau contenant les informations pour les boutons des pages pagination.

Le tableau pagination est rempli avec la méthode createPagination aidée par quelques autres méthodes secondaires :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
createPagination() {
  let total = this.totalPages()
  this.pagination = []
  let encadrement = this.adjacent * 2
  // Sans troncature
  if(total < 7 + encadrement) {
    this.addPages(..._.range(1, total + 1))
  // Avec troncature
  } else {
    // Troncature à droite
    if (this.current < 2 + encadrement) {
      this.addPages(..._.range(1, 4 + encadrement))
      this.addTroncature()
      this.addLastPages(total)       
    }
    // Deux troncatures
    else if ((encadrement + 1 < this.current) && (this.current < total - encadrement)) {
      this.addFirstPages()     
      this.addTroncature()
      this.addPages(..._.range(this.current - this.adjacent, this.current + this.adjacent + 1))
      this.addTroncature()
      this.addLastPages(total)
    }
    // Troncature à gauche
    else {
      this.addFirstPages()
      this.addTroncature()
      this.addPages(..._.range(total - 2 - encadrement, total + 1))    
    }
  }
},
addTroncature() {
  this.pagination.push({ text: '...', active: false, disabled: true })
},
addFirstPages() {
  this.addPages(1, 2)
},
addLastPages(total) {
  this.addPages(total - 1, total)
},
addPages(...valeurs) {
  let that = this
  _.forEach(valeurs, function(valeur) {
    that.pagination.push({ text: valeur, active: that.current == valeur, disabled: false })
  });
},
totalPages() {
  return _.ceil(this.records / this.numberPerPage)
},

C'est la principale méthode du composant. Je ne vais pas entrer dans le détail du fonctionnement, j'ai ajouté quelques commentaires pour s'y retrouver.

J'ai profité de la compilation en ES6 pour utiliser le paramètre du reste et l'opérateur de décomposition qui rendent la syntaxe concise, surtout associée à Lodash.

V-D-4. Un peu d'observation

J'ai aussi prévu d'observer (watch) tous les changements qui justifient un recalcul de la pagination :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
watch: {
  numberPerPage() {
    this.current = 1
    this.createPagination()
  },
  records() {
    this.current = 1
    this.createPagination()
  },
  current() {
    this.createPagination()
  }
},
  • si le nombre d'enregistrements par page (numberPerPage) change ;
  • si le nombre total d'enregistrements (records) change ;
  • si la page courante (current) change.

V-D-5. Le style

J'ai aussi prévu un peu de style :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
<style>
.ui.pagination.menu .item {
  background-color: rgba(33,133,208,.2);
}
.ui.pagination.menu .active.item {
  background-color: rgba(33,133,208,.5);
}
@media screen and (max-width: 768px) {
  #numbers { display: none; }
}
#pagination {
  display:flex; 
  justify-content:center
}
</style>

On a ainsi un peu de couleur, un petit effet responsive et un centrage.

V-E. Conclusion

On a ainsi une application bien organisée avec deux composants réutilisables : un pour les messages et l'autre pour la pagination.


précédentsommaire

Copyright © 2017 Vue.js. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.