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 :
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) :
php artisan migrate:reset
php atisan migrate --seed
Vous devriez avoir ainsi 1000 utilisateurs :
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 :
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 :
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 :
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 :
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 :
Pour compléter tout ça, on va prévoir une liste déroulante pour choisir le nombre d'enregistrements par page :
V-C. Le composant principal▲
Voici le nouveau code du composant principal (App) :
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 :
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 :
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) :
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 :
<
pagination :records
=
"
maxUsers
"
:number-per-page
=
"
paginationNumber
"
:hide
=
"
edition
"
@changepage
=
"
changePage
"
></pagination
>
Et on le déclare dans le JavaScript :
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 :
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 :
Le rafraîchissement de la page se fait avec cette fonction :
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 :
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:
768
px)
{
#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) :
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 :
<div
id
=
"pagination"
v-if
=
"paginationEnabled"
>
La directive v-if permet d'effacer la pagination dans deux cas :
- 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 :
<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 :
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 :
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 :
<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 :
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 :
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 :
- 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 :
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:
768
px)
{
#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.