V. Ajax▲
« Ajax » est une technologie JavaScript fort répandue qui permet d'envoyer des requêtes au serveur et de recevoir des réponses sans rechargement de la page. Il est par ce moyen possible de modifier dynamiquement le « DOM », donc une partie de la page.
Dans ce chapitre nous allons voir comment mettre en œuvre Ajax avec Laravel en prenant un cas de l'application d'exemple.
V-A. Les messages dans l'application▲
Pour les utilisateurs autres que rédacteurs et administrateurs, il y a la possibilité de laisser un message avec un formulaire :
Lorsqu'un administrateur se connecte et va dans le panneau d'administration, il a une page pour ces messages :
L'administrateur peut voir les nouveaux messages grâce à la case à cocher « Nouveau ». Il peut changer le statut en décochant la case.
Il est évident qu'il serait dommage de recharger la page pour ça alors qu'il suffit d'informer le serveur avec une requête Ajax.
Pour améliorer l'ergonomie, on a aussi prévu une petite animation le temps de l'échange de requêtes :
Il faut aussi gérer cette animation avec JavaScript.
Mais il faut distinguer deux cas :
- la case à cocher Nouveau est cochée :
Dans ce cas, si on change le statut d'un message, il n'est plus nouveau et donc il doit disparaître. Du coup, ça change la pagination et il faut recharger la page.
- la case à cocher Nouveau est décochée :
Dans ce cas, au changement de statut, le message reste affiché et il n'y a rien à faire de spécial si ce n'est gérer l'animation.
V-B. La vue des messages▲
Dans les vues, on en a deux qui concernent l'affichage des messages dans l'administration :
Dans la vue table.blade.php, on a le code chargé de l'affichage du tableau des messages :
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.
@foreach
($contacts
as
$contact
)
<
div class
=
"
box
"
>
<
div class
=
"
box-body table-responsive
"
>
<
table id
=
"
contacts
"
class
=
"
table table-striped table-bordered
"
>
<
thead>
<
tr>
<
th>
@lang(
'Name'
)
<
/th
>
<
th>
@lang(
'Email'
)
<
/th
>
<
th>
@lang(
'New'
)
<
/th
>
<
th>
@lang(
'Creation'
)
<
/th
>
<
th></th
>
<
/tr
>
<
/thead
>
<
tbody>
<
tr>
<
td>
{{
$contact
->
name }}
<
/td
>
<
td>
{{
$contact
->
email }}
<
/td
>
<
td>
<
input type
=
"
checkbox
"
name
=
"
seen
"
value
=
"
{{
$contact
->
id }}
"
{{
is_null($contact
->
ingoing) ?
'disabled'
:
'checked'
}}
>
<
/td
>
<
td>
{{
$contact
->
created_at->
formatLocalized('%c'
) }}
<
/td
>
<
td><a class
=
"
btn btn-danger btn-xs btn-block
"
href
=
"
{{
route('contacts.destroy'
,
[
$contact
->
id]
) }}
"
role
=
"
button
"
title
=
"
@lang('Destroy')
"
><span class
=
"
fa fa-remove
"
></span
></a
></td
>
<
/tr
>
<
/tbody
>
<
/table
>
<
/div
>
<!-- /.box-body -->
<
div id
=
"
message
"
class
=
"
box-footer
"
>
{{
$contact
->
message }}
<
/div
>
<
/div
>
<!-- /.box -->
@endforeach
On a une boucle pour passer en revue tous les messages :
2.
3.
@foreach
($contacts
as
$contact
)
...
@endforeach
Voici le code pour les cases à cocher :
<
input type
=
"
checkbox
"
name
=
"
seen
"
value
=
"
{{
$contact
->
id }}
"
{{
is_null($contact
->
ingoing) ?
'disabled'
:
'checked'
}}
>
La case est cochée si on trouve un enregistrement en relation dans la table ingoings. On a vu cet aspect de l'application avec cette table qui permet de connaître les nouveaux éléments :
Les cases sont distinguées grâce à l'attribut value qui contient l'identifiant du message.
V-C. Le JavaScript▲
Dans la vue index.blade.php, on trouve le JavaScript pour gérer Ajax :
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.
var contact = (
function (
) {
var url =
'
{{
route("contacts.index"
) }}
'
var swalTitle =
'@lang("Really destroy contact ?")'
var confirmButtonText =
'@lang("Yes")'
var cancelButtonText =
'@lang("No")'
var errorAjax =
'@lang("Looks like there is a server issue...")'
var onReady =
function (
) {
$(
'#pagination'
).on
(
'click'
,
'ul.pagination a'
,
function (
event
) {
back
.pagination
(
event
,
$(
this),
errorAjax)
}
)
$(
'#pannel'
).on
(
'change'
,
':checkbox[name="seen"]'
,
function (
) {
back
.seen
(
url,
$(
this),
errorAjax)
}
)
.on
(
'click'
,
'td a.btn-danger'
,
function (
event
) {
back
.destroy
(
event
,
$(
this),
url,
swalTitle,
confirmButtonText,
cancelButtonText,
errorAjax)
}
)
$(
'.box-header :radio, .box-header :checkbox'
).click
(
function (
) {
back
.filters
(
url,
errorAjax)
}
)
}
return {
onReady
:
onReady
}
}
)(
);
$(
document
).ready
(
contact.
onReady)
Dans l'application, le JavaScript est organisé de façon modulaire. Les variables et les fonctions sont privées et on expose selon les besoins avec une API.
On détecte les changements sur les cases à cocher qui s'appellent seen :
$(
'#pannel'
).on
(
'change'
,
':checkbox[name="seen"]'
,
function (
) {
On appelle alors une méthode :
back
.seen
(
url,
$(
this),
errorAjax)
La valeur de l'URL est donnée par cette ligne de code :
var url = '{{
route('contacts.index'
) }}
'
donc admin/contacts.
Pour l'application, l'essentiel du JavaScript pour l'administration est rassemblé dans le fichier adminlte/js/back.js. On trouve donc son chargement dans la vue :
On trouve dans ce fichier la méthode seen :
On trouve les deux cas évoqués ci-dessus selon l'état de la case à cocher Nouveau.
Si elle est cochée, il est fait appel à la méthode ajax :
On voit qu'ici, au retour, on recharge la page :
load
(
url,
errorAjax)
Si elle est décochée, on fait appel à la méthode ajaxNoLoad :
Ici, au retour :
- on arrête l'animation (unSpin) ;
- on rend la case à cocher inactive (that
.prop
(
'disabled'
,
true)). On ne peut donc pas revenir au statut Nouveau.
V-D. La protection CSRF▲
Je vous ai dit plusieurs fois dans ce cours que Laravel met en place systématiquement la protection CSRF. Or, ici, à aucun moment on ne transmet le jeton (token) pour cela !
Si vous regardez dans les headers lors de l'envoi de la requête, vous allez trouver ceci :
X-CSRF-TOKEN:nW57unWQZBJGWaaQ4TyKaVcQsRWYo1UMNbKhxj7u
Le middleware de Laravel qui assure la protection (VerifyCsrfToken) ne se contente pas de chercher le jeton dans les paramètres de la requête : il va aussi voir dans les headers s'il y a une information X-CSRF-TOKEN, ce qui est notre cas.
Mais comment s'est créée cette information ?
Si vous regardez le fichier adminlte/js/back.js, vous allez trouver ce code :
2.
3.
4.
5.
$.ajaxSetup
({
headers
:
{
'X-CSRF-TOKEN'
:
$(
'meta[name="csrf-token"]'
).attr
(
'content'
)
}
}
)
Le jeton (token) est mémorisé dans les « metas » :
<
meta name
=
"
csrf-token
"
content
=
"
{{
csrf_token() }}
"
>
Avec la méthode ajaxSetup, on demande à jQuery d'ajouter automatiquement l'information dans les headers. On n'a donc plus à s'en préoccuper ensuite…
V-E. Le traitement côté serveur▲
L'URL de la requête est de la forme admin/contacts/seen/n, avec n représentant l'identifiant du message.
Voici la partie concernée du contrôleur Back/ContactController :
La requête arrive dans la méthode updateSeen. La liaison implicite permet d'obtenir directement une instance du modèle dans la variable $contact
.
On peut alors supprimer l'enregistrement en relation dans la table ingoings :
$contact
->
ingoing->
delete ();
Il suffit ensuite de renvoyer une réponse JSON sans information spécifique :
return response ()->
json ();
V-F. Les commentaires▲
On a déjà vu dans un précédent chapitre qu'on limite le nombre de commentaires affichés pour les articles, et que s'il y en a d'autres, on affiche un bouton :
On a déjà les conditions d'affichage de ce bouton et on va maintenant s'intéresser à son action.
Voici le code du bouton :
<
a id
=
"
nextcomments
"
href
=
"
{{
route('posts.comments'
,
[
$post
->
id,
1
]
) }}
"
class
=
"
button
"
>
@lang(
'More comments'
)
<
/a
>
Voici la route concernée :
On a deux paramètres qui sont renseignés avec la méthode route. On se retrouve donc avec une URL dans ce genre :
...posts/2/comments/1
Voici le code JavaScript :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
$(
'#nextcomments'
).click
(
function(
event
) {
event
.preventDefault
(
)
$(
'#morebutton'
).hide
(
)
$(
'#moreicon'
).show
(
)
$.get
(
$(
this).attr
(
'href'
))
.done
(
function(
data) {
$(
'ol.commentlist'
).append
(
data.
html)
if(
data.
href !==
'none'
) {
$(
'#nextcomments'
).attr
(
'href'
,
data.
href)
$(
'#morebutton'
).show
(
)
}
$(
'#moreicon'
).hide
(
)
}
)
}
)
Sans entrer dans tous les détails, on voit qu'on utilise la méthode $.
get de jQuery pour lancer la requête :
$.get
(
$(
this).attr
(
'href'
))
Au retour on ajoute les commentaires dans la page :
$(
'ol.commentlist'
).append
(
data.
html)
S'il faut ajouter un bouton pour les commentaires suivants on le fait :
2.
3.
4.
if(
data.
href !==
'none'
) {
$(
'#nextcomments'
).attr
(
'href'
,
data.
href)
$(
'#morebutton'
).show
(
)
}
Dans le contrôleur Front/CommentController, on a ce code :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
public function comments(Post $post
,
$page
)
{
$comments
=
$this
->
commentRepository->
getNextComments($post
,
$page
);
$count
=
$post
->
parentComments()->
count();
$level
=
0
;
return [
'
html
'
=>
view('
front/comments/comments
'
,
compact('
post
'
,
'
comments
'
,
'
level
'
))->
render(),
'
href
'
=>
$count
<=
config('
app.numberParentComments
'
) *
++
$page
?
'
none
'
:
route('
posts.comments
'
,
[
$post
->
id,
$page
]
),
];
}
On voit qu'on retourne une réponse JSON (c'est automatiquement fait par Laravel) avec deux éléments :
- html : le code HTML des commentaires à ajouter dans la page ;
- href : éventuellement l'URL pour le bouton des commentaires supplémentaires.
V-G. En résumé▲
- Ajax est facile à mettre en œuvre avec Laravel.
- On peut faire en sorte que la protection CSRF soit automatiquement mise en œuvre pour toutes les requêtes.