Laravel 5.7 par la pratique – Les catégories 2/2
Dans le précédant chapitre on a commencé à voir la gestion des catégories pour notre galerie photos. On sait maintenant ajouter une catégorie. Maintenant on va voir comment modifier et supprimer une catégorie. C’est encore réservé aux administrateurs évidemment. On va créer deux vues : une qui liste toutes les catégories avec des boutons pour modifier et supprimer, et une pour le formulaire de modification.
Le menu
On va compléter le menu pour qu’on puisse accéder aux nouvelles vues. Dans dans notre layout (views/layouts/app) dans la partie concernant le menu déroulant pour les administrateurs on va avoir ce code :
@admin <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle{{ currentRoute( route('category.create'), route('category.index'), route('category.edit', request()->category?: 0) )}}" href="#" id="navbarDropdownGestCat" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> @lang('Administration') </a> <div class="dropdown-menu" aria-labelledby="navbarDropdownGestCat"> <a class="dropdown-item" href="{{ route('category.create') }}"> <i class="fas fa-plus fa-lg"></i> @lang('Ajouter une catégorie') </a> <a class="dropdown-item" href="{{ route('category.index') }}"> <i class="fas fa-wrench fa-lg"></i> @lang('Gérer les catégories') </a> </div> </li> @endadmin
Avec ce résultat :
On crée quelques catégories
Pour avoir un peu de matériel on va créer des catégories. On crée un seeder :
php artisan make:seeder CategoriesTableSeeder
Avec ce code :
<?php use Illuminate\Database\Seeder; use App\Models\Category; class CategoriesTableSeeder extends Seeder { public function run() { Category::create([ 'name' => 'Paysages', ]); Category::create([ 'name' => 'Maisons', ]); Category::create([ 'name' => 'Personnages', ]); Category::create([ 'name' => 'Animaux', ]); Category::create([ 'name' => 'Végétation', ]); } }
On complète le DatabaseSeeder :
public function run() { $this->call(UsersTableSeeder::class); $this->call(CategoriesTableSeeder::class); }
Et on rafraîchit la base :
php artisan migrate:fresh --seed
Si tout se passe bien vous devez avoir les 5 catégories :
Si on vous dit qu’une classe n’existe pas vous pouvez lancer un composer dumpautoload.
La liste des catégories
On va créer maintenant la vue pour lister les catégories et afficher des boutons de commande. On crée donc cette vue ici :
Avec ce code :
@extends('layouts.form') @section('card') @component('components.card') @slot('title') @lang('Gestion des catégories') @endslot <table class="table table-dark text-white"> <tbody> @foreach($categories as $category) <tr> <td>{{ $category->name }}</td> <td> <a type="button" href="{{ route('category.destroy', $category->id) }}" class="btn btn-danger btn-sm pull-right invisible" data-toggle="tooltip" title="@lang('Supprimer la catégorie') {{ $category->name }}"><i class="fas fa-trash fa-lg"></i></a> <a type="button" href="{{ route('category.edit', $category->id) }}" class="btn btn-warning btn-sm pull-right mr-2 invisible" data-toggle="tooltip" title="@lang('Modifier la catégorie') {{ $category->name }}"><i class="fas fa-edit fa-lg"></i></a> </td> </tr> @endforeach </tbody> </table> @endcomponent @endsection @section('script') <script> $(() => { $('a').removeClass('invisible') }) </script> @include('partials.script-delete', ['text' => __('Vraiment supprimer cette catégorie ?'), 'return' => 'removeTr']) @endsection
On voit que pour la suppression d’une catégorie on fait appel à une vue partielle parce que le code sera utilisé pour une autre vue :
<script> $(() => { $.ajaxSetup({ headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')} }) $('[data-toggle="tooltip"]').tooltip() $('a.btn-danger').click((e) => { let that = $(e.currentTarget) e.preventDefault() swal({ title: '{{ $text }}', type: 'error', showCancelButton: true, confirmButtonColor: '#DD6B55', confirmButtonText: '@lang('Oui')', cancelButtonText: '@lang('Non')' }).then((result) => { if (result.value) { $.ajax({ url: that.attr('href'), type: 'DELETE' }) .done(() => { @switch($return) @case('removeTr') that.parents('tr').remove() @break @case('reload') location.reload() @break @endswitch }) .fail(() => { swal({ title: '@lang('Il semble y avoir une erreur sur le serveur, veuillez réessayer plus tard...')', type: 'warning' }) }) } }) }) }) </script>
Pour activer ces vues on va utiliser la fonction index du contrôleur CategoryController :
public function index() { return view('categories.index'); }
Comme la vue attend une variable $categories et que là on ne lui donne pas on va avoir un problème !
Comme cette variable sera nécessaire pour toutes les vues on va utiliser ce code dans App\Providers\AppServiceProvider :
use App\Repositories\CategoryRepository; ... public function boot() { ... if (request ()->server ("SCRIPT_NAME") !== 'artisan') { view ()->share ('categories', resolve(CategoryRepository::class)->getAll()); } }
On teste qu’on est pas avec une commande d’Artisan pour éviter certains conflits. La méthode share permet de partager la variable $categories avec toutes les vues. La méthode resolve permet de demander au conteneur de créer une classe CategoryRepository pour pouvoir charger la variable avec toutes les catégories. Remarquez que la méthode getAll est en fait dans BaseRepository.
Normalement en cliquant maintenant dans le menu vous devez obtenir la liste :
Vérifiez que les popups fonctionnent (la documentation est ici) :
C’est d’ailleurs tout ce qui fonctionne pour le moment !
Supprimer une catégorie
Pour la suppression d’une catégorie j’ai prévu une alerte pour éviter une suppression accidentelle. Plutôt que d’utiliser l’horrible fenêtre de base de Javascript on va utiliser Sweet Alert.On va l’installer avec npm :
npm i sweetalert2 -D
Il faut le charger dans resources/sass/app.scss :
// Sweetalert @import '~sweetalert2/src/sweetalert2.scss';
Et pour la partie Javascript dans resources/js/app.js :
window.swal = require('sweetalert2');
Enfin on relance npm run dev.
Maintenant quand on clique sur un bouton de suppression on a l’alerte esthétique :
Si on clique sur Non ça se referme et rien ne se passe.
Si on clique sur Oui on se rend compte que la catégorie est retirée de la liste mais évidemment la base n’est pas modifiée puisqu’on a pas encore écrit le code correspondant. On obtient aucune erreur parce qu’on a une fonction actuellement vide dans le contrôleur. Si on supprime cette fonction par contre on va avoir une erreur signalée :
On complète le code dans le contrôleur :
public function destroy(Category $category) { $category->delete(); return response()->json(); }
Remarquez la liaison implicite avec le modèle au niveau du paramètre. Maintenant une suppression va être effective.
Mais il serait sans doute judicieux de prévoir un filtre pour être sûr que seules des requêtes Ajax effectuent cette action.
Créons un middleware :
php artisan make:middleware Ajax
<?php namespace App\Http\Middleware; use Closure; class Ajax { public function handle($request, Closure $next) { if ($request->ajax()) { return $next($request); } abort(404); } }
On l’ajoute dans le Kernel :
protected $routeMiddleware = [ ... 'ajax' => \App\Http\Middleware\Ajax::class, ];
Il n’y a plus qu’à l’ajouter dans le contrôleur CategoryController :
public function __construct() { $this->middleware('ajax')->only('destroy'); }
Évidemment que pour la méthode destroy.
Modifier une catégorie
On crée le formulaire pour la modification qui est pratiquement identique à celui pour la création :
@extends('layouts.form') @section('card') @component('components.card') @slot('title') @lang('Modifier une catégorie') @endslot <form method="POST" action="{{ route('category.update', $category->id) }}"> {{ csrf_field() }} {{ method_field('PUT') }} @include('partials.form-group', [ 'title' => __('Nom'), 'type' => 'text', 'name' => 'name', 'value' => $category->name, 'required' => true, ]) @component('components.button') @lang('Envoyer') @endcomponent </form> @endcomponent @endsection
On utilise la fonction edit du contrôleur CategoryController :
public function edit(Category $category) { return view('categories.edit', compact('category')); }
Maintenant quand on clique sur un bouton de modification dans la liste on a bien le formulaire :
On utilise maintenant la fonction update dans le contrôleur :
public function update(CategoryRequest $request, Category $category) { $category->update($request->all()); return redirect()->route('home')->with('ok', __('La catégorie a bien été modifiée')); }
On a dans le précédent chapitre mis en place un événement pour le slug qui sera aussi actif dans la modification, on a donc pas à nous en inquiéter ici.
Quand la catégorie a été modifiée on a une alerte (c’est le même code que celui qu’on a vu pour la création au niveau du layout) :
Conclusion
Dans ce chapitre on a :
- modifié le menu de la barre de navigation pour ajouter un item pour l’administration
- créé un seeder pour les catégories
- créé une vue pour lister les catégories avec des boutons pour la modification et la suppression
- ajouté Sweet Alert 2 à l’application
- complété le contrôleur CategoryController pour la gestion des catégories
- créé un middleware pour Ajax
- partagé les données des catégories pour toutes les vues
Pour vous simplifier la vie vous pouvez charger le projet dans son état à l’issue de ce chapitre.
37 commentaires
Velkacem
$ not a function j ai cette erreur et la requete ajax ne fonctionne plus merci BestMOMO
Velkacem
avec cette erreur LAravel The GET method is not supported for this route. Supported methods: PUT, PATCH, DELETE.
j’ai essayer de mettre un form mais rien n’y fais
bestmomo
Salut,
Il faut vérifier si JQuery est bien chargé.
Velkacem
Salut, Oui jquery est bien chargé, lors de la soumession le formulaire renvoi une erreur e.preventDefault() ne marche plus et l’erreur The GET method is not supported for this route. Supported methods: PUT, PATCH, DELETE. s’affiche
bestmomo
Donc Javascript ne fonctionne pas dans le navigateur, il faut trouver pourquoi, un module de protection ?
Cousin
Bonjour à vous bestmomo et aux autres apprenants. Merci pour votre tutoriel plus qu’explicite. J’ai évolué normalement jusqu’à cette partie mais j’ai un soucis avec les boutons « delete », ceux-ci ne réagissent pas et ne donnent pas accès à l’alerte.
sow3b
Bonjour BestMomo!
Encore un grand merci pour ton tuto qui est vraiment très bien expliqué pour les débutantes comme moi!
Une petite question…lorsqu’on supprime les catégories les photos correspondantes se suppriment en cascade dans la base de données…par contre elles ne se suppriment pas des dossiers photos et thumbs (j’ai choisi de ne pas traiter les photos orphelines expliquées plus loin mais de les supprimer automatiquement) . J’ai donc essayé de modifier mon CategoryController (que j’ai appelé EvenementController) comme ceci :
public function destroy(Evenement $evenement, Photo $photo)
{
if($evenement->ID_EVENT == $photo->ID_EVENT){
Storage::delete ([
‘photos/’ . $photo->getAttribute(‘NOM_PHOTO’),
‘thumbs/’ . $photo->getAttribute(‘NOM_PHOTO’),
]);}
$evenement->delete();
return response()->json();
}
Evidemment ça ne fonctionne pas, les photos sont toujours dans les dossiers malgré l’évènement supprimé et je me rends bien compte qu’il manque quelque chose dans mon code mais je n’arrive pas à mettre le doigt dessus…je te remercie de ton aide!
sow3b
Bon désolée encore du dérangement mais j’ai résolu le problème, en fait j’ai adapté le code pour les orphans en le mettant dans mon controller dans ma fonction delete de l’évènement, ça marche nickel! 🙂
ingionoso
Bonsoir,
Je me permet à nouveau de solliciter votre aide car la suppression ne fonctionne pas.
J’ai le message d’erreur suivant
The GET method is not supported for this route. Supported methods: PUT, PATCH, DELETE.
j’ai essayer de mettre un form mais rien n’y fais.
bestmomo
Bonjour,
Est-ce que je Javascript est bien exécuté ?
ingionoso
Bonjour,
Tout d’abord je tiens à vous remercier pour ce formidable tutoriel.
j’ai un message d’erreur lorsque je veux mettre a jour les informations étant dans mon formulaire.
Method Illuminate\Validation\Validator::validateEmail, 1 does not exist.
peux-tu m’aider à ce niveau stp.
bestmomo
Bonjour,
Cette erreur signale que la règle de validation des email n’existe pas, ce qui est étrange…
ingionoso
Pourtant voici le code:
public function rules()
{
$id = $this->stagiaire ? ‘,’ . $this->stagiaire->id : »;
return $rules = [
//
‘nom’ => ‘bail|required|between:2,60’.$id,
‘prenom’ => ‘bail|required|between:3,50′.$id,
’email’ => ‘bail|required|email’.$id,
‘mobile’ => ‘bail|required|max:10’.$id,
‘adresse’ => ‘bail|required’.$id,
‘codepostal’ => ‘bail|required’.$id,
‘ville’ => ‘bail|required’.$id,
‘ecole’ => ‘bail|required’.$id,
‘niveau’ => ‘bail|required’.$id,
‘datedebut1’ => ‘bail|required’.$id,
‘datefin1’ => ‘bail|required’.$id,
‘duree1’ => ‘bail|required’.$id,
‘valeurduree1’ => ‘bail|required’.$id,
‘statut1’ => ‘bail|required’.$id,
‘domaine’ => ‘bail|required’.$id,
‘service’ => ‘bail|required’.$id,
‘mailchef’ => ‘bail|required’.$id
];
}
bestmomo
Oui ça peut pas marcher comme ça, tu ajoutes $id à toutes les règles… Dans quel but ?
ingionoso
merci beaucoup pour ton aide j’avais complétement compris la méthode pour le $id j’ai chercher a comprendre sa fonctionnalité et maintenant je n’est plus d’erreur.
kouyate
$(‘a.form-delete’).click((e) => {
e.preventDefault();
let href = $(e.currentTarget).attr(‘href’)
swal({
title: ‘@lang(‘Vraiment supprimer cette photo ?’)’,
type: ‘error’,
showCancelButton: true,
confirmButtonColor: ‘#DD6B55’,
confirmButtonText: ‘@lang(‘Oui’)’,
cancelButtonText: ‘@lang(‘Non’)’
}).then((result) => {
if (result.value) {
$(« form[action=' » + href + « ‘ »).submit()
}
})
})
Voici le code js que vous avez mis, mon navigateur me dit qu’il une erreur =’)
bestmomo
Bonjour,
Quelle erreur est signalée ? La syntaxe Javascript est en ES6 mais maintenant tous les navigateurs la digèrent normalement.
kouyate
Salut bestmomo, j’ai mon button de suppression qui ne reagis pas, à l’aide stp.
lethal2019
slt super cool ce site j’ai beaucoup appris mais juste un probleme la suppression ne marche pas.
Aidez moi svp
asher
Bonsoir bestmomo,
merci déjà pour ce tuto mais je n’arrive pas à faire fonctionner l’alerte sur le bouton delete. la console me retourne
TypeError: this is undefined[En savoir plus] app.js:41849:7
SweetAlert http://albumplus.test/js/app.js:41849
http://albumplus.test/category:136
dispatch http://albumplus.test/js/app.js:13993
handle http://albumplus.test/js/app.js:13802
asher
C’est bon le problème est résolu, veuillez remplacer swal par swal.fire()
kouyate
Salut, stp, j’ai le même problème, où est ce que je dois mettre exactement le swal.fire().
Merci de me. Repondre, j’ai essayé de l’ecrire qqpart mais ça génère des erreurs.
adja
Bonsoir,
Merci pour le tutoriel. Cependant, j’ai un problème avec les popups. Ils ne s’affichent pas. Je ne vois ni les icônes ni les textes. J’ai lu la documentation et j’ai revu le code. Mais je ne sais pas d’où vient le problème. Pouvez-vous m’aider s’il vous plait? Merci.
bestmomo
Bonsoir,
Il faut voir si les librairies Javascript sont bien chargées. Il faut jQuery, Popper, la libraire de Bootstrap.
adja
Merci. Je vais vérifier.
yeroka1394@gmail.com
bonsoir bestmomo !
mon soucis est toujours le button supprimé !
quand je clique y’a aucun effet , alors quand je change le » btn btn-danger en btn-success(n’importe d’ailleur) ca me redirige quand meme vers une page non trouvable ! je pense que mon probleme est au niveau du button danger !de l’aide svp
Jean-M4rc
Bonjour,
j’ai un petit souci avec l’update,
lorsque je clique sur le bouton envoyer j’ai une erreur SQL :
SQLSTATE[42S22]: Column not found: 1054 Unknown column ‘name;1’ in ‘where clause’ (SQL: select count(*) as aggregate from `projects` where `name;1` = WebAgency)
comme si dans ma ropute, je voulais trouver la colonne « category->name;categroy->id »
j’ai transformé les catégories en projets, mais quand je regarde votre code et le mien je ne trouve pas d’erreur.
ma fonction d’update est comme ceci :
public function update(ProjectRequest $request, Project $project)
{
dd($project);
//$project->update($request->all());
return redirect()->route(‘projects.index’)->with(‘ok’, __(‘Le projet a bien été modifié’));
}
j’ai mis un dd() pour voir d’où venais mon erreur mais elle vient de plus haut, enfin le dd() n’empêche pas l’erreur de se produire.
Alors je me dis que mon paramètre ProjectRequest (CategoryRequest dans votre code) apporte cette erreur mais là encore je ne vois pas d’erreur.
L’action pour mon formulaire d’update est ainsi : action= »{{ route(‘project.update’, $project->id) }} » et la route project.update est bien présente dans route:list.
Je ne comprend pas pourquoi il ne sépare pas le name et l’id du projet lorqu’il cherche dans la bdd. Et ceet écriture se retrouve dans le ProjectRequest comme suit :
public function rules()
{
$id = $this->project ? ‘;’ . $this->project->id : »;
return $rules = [
‘name’ => ‘required|string|max:255|unique:projects,name’ . $id,
];
}
et j’ai l’impression que le problème vient d’ici avec project;project->id
bestmomo
Salut,
Dans tes règles tu as ce code :
$id = $this->project ? ‘;’ . $this->project->id : ”;
Il y a un point-virgule au lieu d’une virgule :
$id = $this->project ? ‘,’ . $this->project->id : ”;
Jean-M4rc
Merci beaucoup 🙂
Yoh
Bonjour !
Un petit message pour vous remercier pour tous ces tutoriels qui me permettent de me mettre à niveau correctement. Néanmoins, et malgré avoir suivi la formation laravel 5.5, j’ai l’impression qu’il y a un gap énorme entre le cours et la pratique disponible ici. Sur le chapitre précédent, plein d’étapes m’ont semblé tomber du ciel et je suis incapable de savoir les mettre en oeuvre pour le moment. Par exemple, les événements ne me semblent pas indispensables pour réussir son app. Après cela est sans doute plus propre, mais je pense qu’on peut simplifier un peu lorsqu’on débute sur cette techno non ?
Merci encore 😉
bestmomo
Salut,
Je n’ai pas détaillé les choses pour cette série sinon elle se serait étendue sur un quarantaine de chapitres et je suis conscient du fait que tout arrive un peu par magie. En fait ma démarche est de montrer une application réaliste codée correctement plutôt qu’un truc simplifié qui n’est jamais en phase avec la réalité.
Par exemple on peut très bien se passer des événements mais la programmation événementielle ajoute une nouvelle dimension lorsqu’on s’est contenté d’un codage linéaire et j’ai jugé opportun d’en intégrer pour des cas pertinents.
L’idée est de suivre cette série sans trop aller dans les détails mais en voyant comment tout se met en place et s’organise. Après il est toujours possible de se pencher sur un point particulier qui intéresse et de décortiquer le code correspondant.
Après réflexion j’aurais peut-être dû utiliser Vue.js pour cette application parce que le code JQuery est un peu lourd, je ne pensais pas en ajouter autant…
Bonne lecture !
bestmomo
Ah oui pour la suppression d’une catégorie j’ai mis la fonction du contrôleur dans l’article mais pas dans le ZIP. Du coup j’ai mis à jour le ZIP (et les suivants !).
webwatson
Merci M. Bestmomo, Je n’arrive pas à executer la commande npm run dev.
Lorsque je le fais ça me génère une erreur dans la console : ‘cross-env’ n’est pas reconnu comme commande interne.
Lorsque je supprime, j’ai ce retour là :
protected function methodNotAllowed(array $others)
{
throw new MethodNotAllowedHttpException($others);
}
bestmomo
Bonjour,
Il faudrait peut-être réinstaller node.js.
webwatson
Bonsoir M.
Vous n’aviez pas repondu pour la suppression de catégorie
bestmomo
Quelle question ?
webwatson
J’ai désinstallé node js et je l’ai réinstallé mais le problème persiste.
et l’autre c’est au niveau de la suppression d’une catégorie.
Lorsque je supprime, j’ai ce retour là :
protected function methodNotAllowed(array $others)
{
throw new MethodNotAllowedHttpException($others);
}
Même problème que dans le TP album passé