Cours Laravel 6 – 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. Nous allons partir d’une installation de base de Laravel et ajouter une page modale pour afficher un formulaire de contact qui sera traité en Ajax.

Installation côté serveur

Créez une nouvelle application Laravel 6 :

composer create-project --prefer-dist laravel/laravel laravel6

Et vérifiez que ça fonctionne :

On va se contenter de cette trame de base. On va juste franciser Laravel en copiant le fichier récupéré ici.

Et dans config/app.php :

'locale' => 'fr',

Le contrôleur

On va avoir besoin d’un contrôleur et comme on aura une seule fonction on va créer un contrôleur invokable :

php artisan make:controller MessageController --invokable

C’est un contrôleur qui n’a qu’une fonction.

Au départ on a ce code :

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class MessageController extends Controller
{
    /**
     * Handle the incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function __invoke(Request $request)
    {
        //
    }
}

On va compléter ainsi la fonction :

public function __invoke(Request $request)
{
    if ($request->ajax()) {

        $this->validate($request, [
            'email' => 'bail|required|email',
            'message' => 'bail|required|max:250'
        ]);

        // Gestion des données

        return response ()->json ();
    }

    abort(404);
}

On vérifie que la requête qui arrive est bien en ajax :

if ($request->ajax())

Sinon on renvoie une erreur 404 :

abort(404);

Si on a bien une requête ajax alors on valide les données qui arrivent, on les traite (je ne traite pas cette partie ici), et si tout va bien on revoie une réponse JSON vide.

La route

Il nous faut une route pour la soumission de notre formulaire :

Route::post('message', 'MessageController')->name('message');

Notre code côté serveur est prêt voyons maintenant le côté client…

Côté client

Autant le codage côté serveur est assez circonscrit avec Laravel, autant côté client les possibilités sont vraiment multiples. Il existe un foule de frameworks et librairies pour gérer le CSS et le Javascript. Le présent cours n’a pas pour objectif de développer cet aspect mais de montrer la relation qui existe avec Laravel qui n’impose rien dans ce domaine. Je détaillerai dans un prochain article ce que Laravel propose avec Bootstrap, Vue.js et React. Pour ce chapitre je vais me contenter de tout traiter directement dans les vues en utilisant Pure CSS et en envisageant le traitement Javascript d’une part de façon très classique avec JQuery et d’autre part en utilisant les API modernes avec du vanilla Javascript.

La vue welcome

On va transformer la vue welcome pour avec une simple page de base en ajoutant une page modale entièrement gérée en CSS (code largement inspiré de ce codepen), parce que Pure CSS ne comporte aucun composant de ce genre :

<!doctype html>
<html lang="fr">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>Ajax</title>
    <link rel="stylesheet" href="https://unpkg.com/purecss@1.0.1/build/pure-min.css">
    <style>
        .modal {
            opacity: 0;
            visibility: hidden;
            position: fixed;
            top: 0;
            right: 0;
            bottom: 0;
            left: 0;
            text-align: left;
            background: rgba(0,0,0, .9);
            transition: opacity .25s ease;
        }

        .modal__bg {
            position: absolute;
            top: 0;
            right: 0;
            bottom: 0;
            left: 0;
            cursor: pointer;
        }

        .modal-state {
            display: none;
        }

        .modal-state:checked + .modal {
            opacity: 1;
            visibility: visible;
        }

        .modal-state:checked + .modal .modal__inner {
            top: 0;
        }

        .modal__inner {
            transition: top .25s ease;
            position: absolute;
            top: -20%;
            right: 0;
            bottom: 0;
            left: 0;
            width: 50%;
            margin: auto;
            overflow: auto;
            background: #fff;
            border-radius: 5px;
            padding: 1em 2em;
            height: 40%;
        }

        .modal__close {
            position: absolute;
            right: 1em;
            top: 1em;
            width: 1.1em;
            height: 1.1em;
            cursor: pointer;
        }

        .modal__close:after,
        .modal__close:before {
            content: '';
            position: absolute;
            width: 2px;
            height: 1.5em;
            background: #ccc;
            display: block;
            transform: rotate(45deg);
            left: 50%;
            margin: -3px 0 0 -1px;
            top: 0;
        }

        .modal__close:hover:after,
        .modal__close:hover:before {
            background: #aaa;
        }

        .modal__close:before {
            transform: rotate(-45deg);
        }

        body {
            padding: 1%;
            font: 1/1.5em sans-serif;
            text-align: center;
        }

        .button-success {
            color: white;
            border-radius: 4px;
            text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
            background: rgb(28, 184, 65);
        }

        .button-right {
            float: right;
        }

        input, textarea {
            width: 100% !important;
        }

        .pure-form-message {
            color: red;
        }

        .input-invalid {
            border-color: red !important;
        }

        .alert {
            padding: 20px;
            background-color: green;
            color: white;
            margin-top: 30px;
            margin-bottom: 15px;
        }

    </style>
</head>
<body>
    <h1>Ajax avec Laravel</h1>
    <p><label id="open" class="pure-button button-success" for="modal">Ouvrir la page de contact</label></p>
    <input class="modal-state" id="modal" type="checkbox">
    <div class="modal">
      <label class="modal__bg" for="modal"></label>
      <div class="modal__inner">
        <label class="modal__close" for="modal"></label>

        <div class="alert" style="display: none">
            Votre message nous est bien parvenu, merci !
        </div>

        <form class="pure-form pure-form-stacked" id="maform" method="post" action="{{ route('message') }}">
            <fieldset>
                <legend>Laissez-nous un message</legend>

                <label for="email">Email</label>
                <input id="email" name="email" placeholder="Votre Email">
                <span class="pure-form-message"></span>

                <br>
                <label for="email">Message</label>
                <textarea id="message" name="message" class="pure-input-1-2" placeholder="Votre message"></textarea>
                <span class="pure-form-message"></span>

                <br>
                <button type="submit" class="pure-button button-success button-right">Envoyer</button>
            </fieldset>
        </form>
      </div>
    </div>

</body>
</html>

On obtient cet page :

Et avec un clic sur le bouton la page modale apparaît avec le formulaire :

Tout ça fonctionne très bien mais évidemment on ne peut pas encore soumettre ce formulaire…

JQuery

J’ai utilisé JQuery si longtemps que c’est mon premier réflexe dès que j’ai un traitement Javascript. J’essaie de m’en passer depuis quelques temps et j’avoue que c’est devenu très facile désormais. Mais comme c’est quand même la librairie la plus utilisée je vous montre le code correspondant.

Voici le Javascript à ajouter à la vue :

<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>

<script>

    $(() => {

        $.ajaxSetup({
            headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }
        })

        function reset() {
            $('#email').removeClass('input-invalid');
            $('#message').removeClass('input-invalid');
            $('legend').show();
            $('span').html('');
            $('.alert').hide();
        }

        $('#maform').submit((e) => {
            let that = e.currentTarget;
            e.preventDefault();
            reset();
            $('.pure-form-message').html('');
            $.ajax({
                method: $(that).attr('method'),
                url: $(that).attr('action'),
                data: $(that).serialize()
            })
            .done((data) => {
                $('legend').hide();
                $('.alert').show('slow');
            })
            .fail((data) => {
                if(data.status == 422) {
                    $.each(data.responseJSON.errors, function (i, error) {
                        $('form')
                            .find('[name="' + i + '"]')
                            .addClass('input-invalid')
                            .next()
                            .append(error[0]);
                    });
                }
            });
        });

        $('#open').on('click', (e) => {
            reset();
            $('input').val('');
            $('textarea').val('');
        });

    });

</script>

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 ? Vous avez ce code :

$.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…

Une autre partie intéressante du code est celle qui gère la validation :

if(data.status == 422) {
    $.each(data.responseJSON.errors, function (i, error) {
        $('form')
            .find('[name="' + i + '"]')
            .addClass('input-invalid')
            .next()
            .append(error[0]);
    });
}

Si on regarde les informations retournées en cas de souci de validation on a ce genre de données :

Il faut donc faire une boucle dans errors pour récupérer les inputs qui posent problème et le texte de l’erreur qui correspond. Il est alors facile avec JQuery de rendre tout ça visible :

Et quand tout va bien on affiche une alerte bien visible :

Une grande partie du code est consacrée à faire le ménage dans le formulaire pour les différentes situations.

On voit que JQuery permet de bien gérer cette intenadance.

Vanilla Javascript

Voyons maintenant comment coder avec simplement du Javascript et sans l’aide d’une librairie comme JQuery. J’ai d’ailleurs écrit récemment un article sur le sujet.

On va apporter une petite modification dans le HTML pour l’alerte (il est tout simplement plus facile d’utiliser cet attribut avec du simple Javascript) :

<div class="alert" hidden>

Et voici une possibilité de Javascript à utiliser :

function reset() {
    document.querySelector('#email').classList.remove('input-invalid');
    document.querySelector('#message').classList.remove('input-invalid');
    document.querySelector('legend').hidden = false;
    document.querySelector('.alert').hidden = true;
    let spans = document.querySelectorAll('span');
    spans.forEach(
        currentValue => { currentValue.innerHTML = ''; }
    );
}

document.forms[0].onsubmit = async(e) => {
    e.preventDefault();

    fetch('{{ route('message') }}', {
        headers: {
            "Content-Type": "application/json",
            "X-Requested-With": "XMLHttpRequest",
            "X-CSRF-Token": document.head.querySelector("[name=csrf-token][content]").content
        },
        method: 'post',
        body: JSON.stringify ({
            email: document.querySelector('#email').value,
            message: document.querySelector('#message').value
        })
    }).then(response => {
        if (response.ok) {
            document.querySelector('legend').hidden = true;
            document.querySelector('.alert').hidden = false;
        } else {
            if(response.status == 422) {
                response.json().then(errors => {
                    Object.entries(errors.errors).forEach(entry => {
                        let key = entry[0];
                        let value = entry[1];
                        document.querySelector('[name="' + key + '"]').classList.add('input-invalid');
                        document.querySelector('[name="' + key + '"] + span').innerHTML = value;
                    });
                })
            }
        }
    });
}

document.querySelector('#open').addEventListener('click', () => {
    reset();
    document.querySelector('#email').value= '';
    document.querySelector('#message').value = '';
})

Essentiellement on utilise fetch pour la requête. Je ne rentre pas dans le détail du code. Vous pouvez consulter le niveau de prise en charge de cette fonction dans les navigateurs.

On se rend compte qu’on peut très bien se passer d’une librairie Javascript pour ce type de traitement.

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 avec JQuery.
  • On peut aussi coder en Vanilla Javascript grâce aux API modernes.
Print Friendly, PDF & Email

Laisser un commentaire