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

Cours pour apprendre à utiliser le framework Laravel 5.5

Plus loin avec Laravel


précédentsommairesuivant

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 :

Image non disponible

Lorsqu'un administrateur se connecte et va dans le panneau d'administration, il a une page pour ces messages :

Image non disponible

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 :

Image non disponible

Il faut aussi gérer cette animation avec JavaScript.

Mais il faut distinguer deux cas :

  • la case à cocher Nouveau est cochée :
Image non disponible

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 :

Image non disponible

Dans la vue table.blade.php, on a le code chargé de l'affichage du tableau des messages :

 
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.
@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 :

 
Sélectionnez
1.
2.
3.
@foreach($contacts as $contact)
    ...
@endforeach

Voici le code pour les cases à cocher :

 
Sélectionnez
<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 :

Image non disponible

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 :

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

 
Sélectionnez
$('#pannel').on('change', ':checkbox[name="seen"]', function () {

On appelle alors une méthode :

 
Sélectionnez
back.seen(url, $(this), errorAjax)

La valeur de l'URL est donnée par cette ligne de code :

 
Sélectionnez
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 :

 
Sélectionnez
<script src="/adminlte/js/back.js"></script>

On trouve dans ce fichier la méthode seen :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
var seen = function (url, that, errorAjax) {
    var urlSeen = url + '/seen/' + that.val()
    // If "new" is checked we must reload the page
    if(getCheckboxValueByName('new')) {
        ajax(urlSeen, 'PUT', url, errorAjax)
    } else {
        ajaxNoLoad(urlSeen, 'PUT', errorAjax, that)
    }
}

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 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
var ajax = function (target, verb, url, errorAjax) {
    spin()
    $.ajax({
        url: target,
        type: verb
    })
        .done(function () {
            load(url, errorAjax)
        })
        .fail(function () {
            fail(errorAjax)
        }
    )
}

On voit qu'ici, au retour, on recharge la page :

 
Sélectionnez
load(url, errorAjax)

Si elle est décochée, on fait appel à la méthode ajaxNoLoad :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
var ajaxNoLoad = function (target, verb, errorAjax, that) {
    spin()
    $.ajax({
        url: target,
        type: verb
    })
        .done(function () {
            unSpin()
            that.prop('disabled', true)
        })
        .fail(function () {
            fail(errorAjax)
        })
}

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 :

 
Sélectionnez
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 :

 
Sélectionnez
1.
2.
3.
4.
5.
$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
})

Le jeton (token) est mémorisé dans les « metas » :

 
Sélectionnez
<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 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
public function updateSeen(Contact $contact)
{
    $contact->ingoing->delete ();

    return response ()->json ();
}

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 :

 
Sélectionnez
$contact->ingoing->delete ();

Il suffit ensuite de renvoyer une réponse JSON sans information spécifique :

 
Sélectionnez
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 :

Image non disponible

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 :

 
Sélectionnez
<a id="nextcomments" href="{{ route('posts.comments', [$post->id, 1]) }}" class="button">@lang('More comments')</a>

Voici la route concernée :

Image non disponible

On a deux paramètres qui sont renseignés avec la méthode route. On se retrouve donc avec une URL dans ce genre :

 
Sélectionnez
...posts/2/comments/1

Voici le code JavaScript :

 
Sélectionnez
1.
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 :

 
Sélectionnez
$.get($(this).attr('href'))

Au retour on ajoute les commentaires dans la page :

 
Sélectionnez
$('ol.commentlist').append(data.html)

S'il faut ajouter un bouton pour les commentaires suivants on le fait :

 
Sélectionnez
1.
2.
3.
4.
if(data.href !== 'none') {
    $('#nextcomments').attr('href', data.href)
    $('#morebutton').show()
}

Dans le contrôleur Front/CommentController, on a ce code :

 
Sélectionnez
1.
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.

précédentsommairesuivant

Copyright © 2018 Maurice Chavelli. 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.