Cours Laravel 5.3 – plus loin – 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.
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 :
Pour se connecter en tant qu’administrateur il suffit d’utiliser le login admin@la.fr avec le mot de passe admin (si vous avez effectué la population prévue dans l’application).
Les messages qui n’ont pas encore été vus apparaissent avec un fond jauni, par défaut on en a deux. D’autre part l’administrateur dispose pour chaque message d’une case à cocher pour changer ce statut.
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 et localement de changer la couleur du fond.
Pour améliorer l’ergonomie on a aussi prévu une petite animation à la place de la case à cocher le temps de l’échange de requêtes :
Il faut aussi gérer cette animation avec JavaScript.
La vue des messages
Dans la vue des messages (views/back/messages/index.blade.php) on a cette partie du code chargée de l’affichage du tableau des messages :
@foreach ($messages as $message) <div class="panel {!! $message->seen? 'panel-default' : 'panel-warning' !!}"> <div class="panel-heading"> <table class="table"> <thead> <tr> <th class="col-lg-1">{{ trans('back/messages.name') }}</th> <th class="col-lg-1">{{ trans('back/messages.email') }}</th> <th class="col-lg-1">{{ trans('back/messages.date') }}</th> <th class="col-lg-1">{{ trans('back/messages.seen') }}</th> <th class="col-lg-1"></th> </tr> </thead> <tbody> <tr> <td class="text-primary"><strong>{{ $message->name }}</strong></td> <td>{!! HTML::mailto($message->email, $message->email) !!}</td> <td>{{ $message->created_at }}</td> <td>{!! Form::checkbox('seen', $message->id, $message->seen) !!}</td> <td> {!! Form::open(['method' => 'DELETE', 'route' => ['contact.destroy', $message->id]]) !!} {!! Form::destroyBootstrap(trans('back/messages.destroy'), trans('back/messages.destroy-warning'), 'btn-xs') !!} {!! Form::close() !!} </td> </tr> </tbody> </table> </div> <div class="panel-body"> {{ $message->message }} </div> </div> @endforeach
On a une boucle pour passer en revue tous les messages :
@foreach ($messages as $message) ... @endforeach
On gère la couleur de fond avec deux classes de Bootstrap :
<div class="panel {!! $message->seen? 'panel-default' : 'panel-warning' !!}">
Les cases à cocher sont générées classiquement (laravelcollective/html) :
<td>{!! Form::checkbox('seen', $message->id, $message->seen) !!}</td>
Ce qui donne ce code pour la case cochée :
<td><input checked="checked" name="seen" type="checkbox" value="3"></td>
On distingue les cases avec l’attribut value dans lequel on mémorise l’identifiant du message.
Le JavaScript
On a aussi sur la page le JavaScript pour gérer Ajax :
$(function () { $(':checkbox').change(function () { $(this).parents('.panel').toggleClass('panel-warning').toggleClass('panel-default'); $(this).hide().parent().append('<i class="fa fa-refresh fa-spin"></i>'); $.ajax({ url: 'contact/' + this.value, type: 'PUT', data: 'seen=' + this.checked }) .done(function () { $('.fa-spin').remove(); $('input[type="checkbox"]:hidden').show(); }) .fail(function () { $('.fa-spin').remove(); var chk = $('input[type="checkbox"]:hidden'); chk.parents('.panel').toggleClass('panel-warning').toggleClass('panel-default'); chk.show().prop('checked', chk.is(':checked') ? null : 'checked'); alert('{{ trans('back/messages.fail') }}'); }); }); });
On utilise jQuery qui est déjà chargé pour toutes les pages.
Même si ce n’est pas l’objet de ce cours on va un peu analyser ce code…
On détecte les changements sur toutes les cases à cocher (on n’a pas d’autres cases à cocher que celles des messages sur la page) :
$(':checkbox').change(function () {
On change la couleur de fond :
$(this).parents('.panel').toggleClass('panel-warning').toggleClass('panel-default');
On cache la case à cocher et on fait apparaître l’icône animée :
$(this).hide().parent().append('<i class="fa fa-refresh fa-spin"></i>');
On envoie la requête Ajax :
$.ajax({ url: 'contact/' + this.value, type: 'PUT', data: 'seen=' + this.checked })
On définit ainsi :
- l’url : de la forme contact/id
- la méthode : PUT,
- l’information : seen: true ou false. (une autre syntaxe possible est data: {seen:this.checked})
Si tout fonctionne bien on exécute ce code :
.done(function () { $('.fa-spin').remove(); $('input[type="checkbox"]:hidden').show(); })
On supprime la petite animation et on affiche à nouveau la case à cocher.
S’il y a eu un problème on exécute ce code :
.fail(function () { $('.fa-spin').remove(); var chk = $('input[type="checkbox"]:hidden'); chk.parents('.panel').toggleClass('panel-warning').toggleClass('panel-default'); chk.show().prop('checked', chk.is(':checked') ? null : 'checked'); alert('{{ trans('back/messages.fail') }}'); });
- on supprime aussi la petite animation,
- on change à nouveau la couleur de fond puisqu’on a échoué,
- on fait réapparaître la case à cocher en changeant son état,
- on affiche une alerte pour prévenir du problème.
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 :
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 template (resources/views/back/template.blade.php) vous aller trouver ce code :
... <meta name="csrf-token" content="{{ csrf_token() }}"> ... <script> $(function() { $.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } ... } </script> ...
Le jeton (token) est mémorisé dans les metas et 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…
Le traitement côté serveur
Le contrôleur
Voici la partie concernée du contrôleur de ressource :
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Http\Requests\ContactRequest; use App\Repositories\ContactRepository; class ContactController extends Controller { /** * The ContactRepository instance. * * @var \App\Repositories\ContactRepository */ protected $contactRepository; /** * Create a new ContactController instance. * * @param \App\Repositories\ContactRepository $contactRepository * @return void */ public function __construct(ContactRepository $contactRepository) { $this->contactRepository = $contactRepository; $this->middleware('admin')->except('create', 'store'); $this->middleware('ajax')->only('update'); } ... /** * Update the specified contact in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { $this->contactRepository->update($request->input('seen'), $id); return response()->json(); } ... }
La requête arrive dans la méthode update. On fait appel au repository pour modifier la base :
public function update($seen, $id) { $contact = $this->getById($id); $contact->seen = $seen == 'true'; $contact->save(); }
Et on renvoie une réponse JSON.
Le middleware
On peut remarquer que la méthode concernée (update) est protégée par un middleware ajax.
En effet on veut que notre méthode ne soit accessible que si on reçoit une requête Ajax. Ce middleware n’existe pas par défaut dans Laravel, il faut donc le créer (app/Http/Middlewares/Ajax.php) :
<?php namespace App\Http\Middleware; use Closure; class IsAjax { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if ($request->ajax()) { return $next($request); } abort(404); } }
La méthode ajax de la requête nous permet facilement de savoir s’il s’agit d’une requête de ce type, si ce n’est pas le cas on revoie une erreur 404.
Il faut informer Laravel que notre middleware existe (app/Http/Kernel.php) :
protected $routeMiddleware = [ ... 'ajax' => \App\Http\Middleware\IsAjax::class ];
En résumé
- Ajax est facile à mettre en œuvre avec Laravel.
- On peut faire en sorte que la protection CSRF soit automatiquement mise en oeuvre pour toutes les requêtes.
- On peut prévoir un middleware particulier pour les requêtes Ajax.
12 commentaires
bestmomo
Tu me donnes toujours pas le contenu de l’erreur 500 🙂
thijo
hahah sorry
mais comment on le trouve
bestmomo
Dans les outils développeur il y a des onglets, tu choisis « Network ». Là tu as les requêtes avec encore des onglets disponibles avec les headers, les cookies, les paramètres et aussi « Response » qui contient le détail de ton erreur.
thijo
« message »: « View [ingredient] not found. »,
« exception »: « InvalidArgumentException »,
« file »: « C:\\Users\\users\\Freelance\\pizzaria\\V_11_09\\pizzaria\\vendor\\laravel\\framework\\src\\Illuminate\\View\\FileViewFinder.php »,
« line »: 137,
« trace »: [
{
« file »: « C:\\Users\\users\\Freelance\\pizzaria\\V_11_09\\pizzaria\\vendor\\laravel\\framework\\src\\Illuminate\\View\\FileViewFinder.php »,
« line »: 79,
« function »: « findInPaths »,
« class »: « Illuminate\\View\\FileViewFinder »,
« type »: « -> »
},
je comprend plus rien hahah il m a dit que j’ai un probleme de view qui n’a rien avoir avec ce que je fais
help please
bestmomo
Plutôt louche ton histoire…
Tu as un code quelque part avec l’appel de cette vue ? Tu es sûr de ta route ?
thijo
Résolu
merci quand meme 😉
thijo
voici l’erreur :
GET http://localhost:8000/findZipCode?id=3 500 (Internal Server Error)
il trouve bien l’id de la ville mais il m’affiche pas correctement le code postal
Merci d’avance
Thijo
bestmomo
Salut,
Je pense que response()->json_encode n’existe pas, tu peux juste retourner ainsi :
return $code
Laravel va convertir automatiquement en JSON.
thijo
Bonjour
j’ai fait comme tu m’as dit mais ca change pas , ça m’envoi la même erreur
bestmomo
Salut,
Il faudrait le détail de l’erreur 500, on sait juste que ça se passe côté serveur mais on en sait pas plus, tu peux avoir le détail en utilisant les outils développeur de ton navigateur (en général F12).
thijo
Salut
findZipCodes?id=2 500 xhr jquery.min.js:4 19.9 KB 807 ms
———————–
Networking => Headers
Request URL: http://localhost:8000/findZipCodes?id=3
Request Method: GET
Status Code: 500 Internal Server Error
Remote Address: 127.0.0.1:8000
Referrer Policy: no-referrer-when-downgrade
thijo
Bonjour Momo 🙂
Je suis entrain de realiser un petit projet en Laravel , et la je suis bloqué sur un petit probleme avec Ajax .
Au fait j’ai un formulaire avec des champs comme nom,prénom etc , je voulais qu’en cliquant sur le champs « votre ville qui est une liste deroulante , que le code postal s’affiche automatiquement selon la ville selection dans le champs « Code postal ».
Voici un peu le code :
Dans mon template principale j’ai fait ce qui est dit ici concernant la protection csrf
…
$(function() {
$.ajaxSetup({
headers: {
‘X-CSRF-TOKEN’: $(‘meta[name= »csrf-token »]’).attr(‘content’)
}
…
}
—————-
Vue :
dans un formulaire
Commune
Votre commune
@foreach ($villes as $ville)
id}} »>{{ $ville->name }}
@endforeach
Code Postal
JS:
$(document).ready(function() {
$(document).on(‘change’,’.villename’ ,function(){
var villeId = $(this).val();
var a = $(this).parent();
$.ajax({
type: ‘get’,
url : ‘/findZipCode’,
data : {‘id’ : villeId},
dataType: « json »,
success:function(data) {
console.log(« sucess »);
//a.find(‘.ville_code’)
},
error:function() {
}
});
});
});
————————-
Route :
Route::get(‘/findZipCode’,’VillesController@findZipCode’);
————————-
Controller :
public function findZipCode(Request $request) {
$code = CodePostal::select(‘codepostal’)->where(‘id’, $request->id)->first();
return response()->json_encode($code);
}