
Créer un blog – les commentaires 1/2
Nous avons dans le précédent article codé l’affichage des articles. On a aussi prévu l’affichage des articles par catégorie, par étiquette et par auteur. On a aussi ajouté la possibilité de faire une recherche sur les titres, les contenus et les résumés. Nous allons maintenant voir nous intéresser aux commentaires.
J’ai fait le choix de ne pas les présenter immédiatement au chargement d’un article pour accélérer l’affichage, et puis on n’est pas forcément intéressé par les commentaires dans un premier temps. On va donc prévoir un bouton de chargement de ces commentaires s’il en existe. Il faudra aussi ajouter un formulaire de création. L’auteur d’un commentaire devra aussi pouvoir le supprimer s’il le désire. Pour fluidifier l’affichage on va utiliser Ajax et on va donc avoir pas mal de Javascript. On dispose de JQuery avec notre thème Calvin mais je préfère coder en pur Javascript, JQuery est appelé à décliner dans les prochaines années alors autant s’habituer à s’en passer, d’autant que les API des navigateurs sont désormais bien équipées.
Je vais aborder ce traitement des commentaires de façon progressive pour bien montrer la réflexion sous-jacente.
Vous pouvez télécharger le code final de cet article ici.
Le contrôleur
On a créé un contrôleur pour les commentaires en même temps que la migration et le modèle :
Comme pour le contrôleur des articles on va déplacer celui-ci dans le dossier Front (attention à l’espace de noms) :
De quoi allons-nous avoir besoin ?
- on doit pouvoir créer un commentaire (nouveau ou réponse), donc une fonction store
- on doit pouvoir supprimer un commentaire, donc une fonction destroy
- on doit pouvoir récupérer tous les commentaires d’un article, donc une fonction comments
D’autre part toutes ces opérations devront passer en Ajax.
On va préparer le code du contrôleur :
<?php namespace App\Http\Controllers\Front; use App\Http\Controllers\Controller; class CommentController extends Controller { public function __construct() { if(!app()->runningInConsole() && !request()->ajax()) { abort(403); } } public function store() { } public function destroy() { } public function comments() { } }
J’ai déjà prévu un constructeur pour vérifier qu’on est bien en Ajax, sinon on renvoie une erreur 403. On vérifie aussi qu’on n’est pas en mode console pour éviter d’avoir un souci avec les commandes d’Artisan, en particulier le listing des routes.
Afficher les commentaires
Le contrôleur et la route
On va commencer par coder le chargement des commentaires pour un article. On code la fonction dans le contrôleur :
use App\Models\Post; ... public function comments(Post $post) { $comments = $post->validComments() ->withDepth() ->latest() ->get() ->toTree(); return [ 'html' => view('front/comments', compact('comments'))->render(), ]; }
Pour l’article concerné on va récupérer :
- les commentaires valides (validComments)
- avec la profondeur (withDepth)
- classés par date (latest)
- sous forme de tableau (toTree)
On ajoute la route :
use App\Http\Controllers\Front\{ PostController as FrontPostController, CommentController as FrontCommentController }; ... Route::prefix('posts')->group(function () { ... Route::name('posts.comments')->get('{post}/comments', [FrontCommentController::class, 'comments']); });
Les vues pour les commentaires
Pour les vues on va devoir coder un système itératif parce que pour chaque commentaire il faut vérifier s’il y a des commentaires enfants et ainsi de suite. Ce genre de codage récursif n’est pas toujours très intuitif. On commence par prévoir une vue de base :
Avec un code très simple :
<h3> @lang('Comments') @if(Auth::guest()) <span>@lang('You must be connected to add a comment or reply.')</span> @endif </h3> <!-- Commentlist --> <ol class="commentlist"> <x-front.comments :comments="$comments"/> </ol>
On a un message pour les utilisateurs non connectés pour les avertir qu’ils ne peuvent pas laisser de commentaire.
Ensuite on ouvre une liste non ordonnée et on appelle un composant :
Dans ce composant on va itérer tous les commentaires :
@props(['comments']) @foreach($comments as $comment) <x-front.comments-base :comment="$comment"/> @endforeach
Donc ce composant sera appelé pour chaque commentaire et on ira ensuite explorer tous les enfants et les enfants des enfants…
On crée enfin le composant qui va effectivement créer le commentaire :
Avec ce code :
@props(['comment']) <li class="comment"> <div class="comment__avatar"> <img class="avatar" src="{{ Gravatar::get($comment->user->email) }}"> </div> <div class="comment__content"> <div class="comment__info"> <div class="comment__author">{{ $comment->user->name }}</div> <div class="comment__meta"> <div class="comment__time">{{ formatDate($comment->created_at) }}</div> @if(Auth::check()) <div class="comment__reply"> @if($comment->depth < config('app.commentsNestedLevel')) <a class="comment-reply-link replycomment" href="#" data-name="{{ $comment->user->name }}" data-id="{{ $comment->id }}"> @lang('Reply') </a> @endif @if(Auth::user()->name == $comment->user->name) <a href="#" class="comment-reply-link deletecomment" style="color:red"> @lang('Delete') </a> @endif </div> @endif </div> </div> <div class="comment__text"> <p>{{ $comment->body }}</p> </div> <ul class="children"> <x-front.comments :comments="$comment->children"/> </ul> </div> </li>
Vous remarquez que dans ce composant on appelle le composant précédent à chaque fois, pour l’itération.
On utilise aussi une valeur dans la configuration (commentsNestedLevel) pour savoir s’il faut faire apparaître le bouton de réponse qui ne doit effectivement pas être présent si on atteint la profondeur maximale. On crée cette information dans config.app :
/* |-------------------------------------------------------------------------- | Comments |-------------------------------------------------------------------------- */ 'commentsNestedLevel' => 4,
Pour comprendre comment les commentaires sont explorés voilà une petite illustration :
La vue post
On va maintenant compléter la vue des articles (front.post) pour afficher les commentaires.
On commence par ajouter un bouton pour commander l’affichage des commentaires s’il y en a (on ajoute ce code en bas de la page) :
<!-- comments ================================================== --> <div class="comments-wrap"> <div id="comments" class="row"> <div id="commentsList" class="column large-12"> @if($post->valid_comments_count > 0) <div id="forShow"> <p id="showbutton" class="text-center"> <a id="showcomments" href="{{ route('posts.comments', $post->id) }}" class="btn h-full-width">@lang('Show comments')</a> </p> <p id="showicon" class="h-text-center" hidden> <span class="fa fa-spinner fa-pulse fa-3x fa-fw"></span> </p> </div> @endif </div> </div> </div>
Maintenant on a un bouton si des commentaires existent pour l’article :
On écritt du Javascript pour lancer la requête, récupérer et afficher les commentaires. Comme je l’ai déjà dit bien qu’on dispose de JQuery on va plutôt coder en simple Javascript :
@section('scripts') <script> (() => { // Variables const headers = { 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' } // Prepare show comments const prepareShowComments = e => { e.preventDefault(); document.getElementById('showbutton').toggleAttribute('hidden'); document.getElementById('showicon').toggleAttribute('hidden'); showComments(); } // Show comments const showComments = async () => { // Send request const response = await fetch('{{ route('posts.comments', $post->id) }}', { method: 'GET', headers: headers }); // Wait for response const data = await response.json(); document.getElementById('commentsList').innerHTML = data.html; } // Listener wrapper const wrapper = (selector, type, callback, condition = 'true', capture = false) => { const element = document.querySelector(selector); if(element) { document.querySelector(selector).addEventListener(type, e => { if(eval(condition)) { callback(e); } }, capture); } }; // Set listeners window.addEventListener('DOMContentLoaded', () => { wrapper('#showcomments', 'click', prepareShowComments); }) })() </script> @endsection
Pour la mise en place des écoutes pour les événements j’utilise une fonction universelle wrapper. Il suffit de lui transmettre toutes les informations. Dans notre cas on a un bouton avec un identifiant showcomments. On va détecter le clic sur ce bouton et diriger vers la fonction prepareShowComments.
Comme son nom l’indique cette fonction prépare le chargement des commentaires. Dans un premier temps on empêche l’événement de suivre son cours normal (preventDefault), ensuite on cache le bouton et on fait apparaître une icône animée d’attente. On appelle alors la fonction showComments qui va lancer la requête.
On a les headers dans la variable headers parce qu’ils nous resserviront. Dans la fonction showComments on utilise fetch pour lancer la requête. Au retour on envoie tout le paquet à son emplacement :
document.getElementById('commentsList').innerHTML = data.html;
Si tout se passe bien vous devriez avoir les commentaires qui s’affichent :
Conclusion
On a commencé à voir le traitement des commentaires dans cet article. On a maintenant l’affichage correct avec la hiérarchisation. Pour ne pas trop alourdir je traiterai les deux autres fonctionnalités, ajout et suppression, dans le prochain article. On aura ainsi codé la partie la plus importante du frontend. On complètera ensuite avec l’authentification pour laquelle il nous faut adapter les vues, le formulaire de contact et d’autres petites choses…


43 commentaires
lafia
Salut cher bestmomo.
Vous avez utilisé EVAL au niveau du listerner, pouvez-vouq une altenative comme c’est un risque securitaire ?
bestmomo
Bonjour,
Si on veut se passer du wrapper il suffit d’écrire directement le listener. Dans ce contexte je ne trouve pas EVAL très dangereux.
lafia
Merci pour le retour. que pensez vous de ce bout en evitant eval et mettant la condition à true directement
const wrapper = (selector, type, callback, condition = true, capture = false) => {
const element = document.querySelector(selector);
if(element) {
element.addEventListener(type, e => {
if(condition) {
callback(e);
}
}, capture);
}
};
bestmomo
J’avais créé cette fonction dans le cadre d’un autre article. On pouvait alors avoir à traiter une condition pour déterminer si elle était vraie ou fausse. Dans le cadre du code du blog on n’en a pas besoin et on peut même éviter ce paramètre.
lafia
Ok merci beaucoup
softcode
bonjour best momo, je viens de rencontrer un problèmee au niveau du buton show comments, après avoir cliqué sur ce dernier je recois un message d’accès interdit que voici
403 ERREUR 403, ACCÈS INTERDIT
svp suis vraiment bloqué par là que peut être la cause merci?
bestmomo
Salut,
Regarde si request()->ajax() dans le constructeur du contrôleur fonctionne bien.
softcode
Oui bien-sûr je l’ai bien renseigné dans le constructeur, je ne sais pas comment je peux constater son fonctionnement si ce n’est qu’après un click sur le buttons show comments qui devait me donner la liste de commentaires mais ça ne marche pas deux jours déjà suis bloqué par là
bestmomo
Justement, commente ce code pour voir si ça fonctionne sans lui.
softcode
Bonjour bestmomo,
je viens de le commenter et j’ai essaiyé par exemple de voir les commentaires du post 5 en cliquant sur le button show comments, je suis redirigé vers cet url : http://127.0.0.1:8000/posts/5/comments biensûr et avec comme resultat il m’a affiché du code html que voici :
{« html »: »\r\n Commentaires \r\n\r\n\r\n \r\n\r\n \r\n \r\n \r\n\r\n \r\n\r\n \r\n sosthene\r\n\r\n \r\n 23 juillet 2022\r\n \r\n \r\n R\u00e9pondre \r\n \r\n \r\n \r\n \r\n\r\n \r\n Autem temporibus a veniam. Aliquam eveniet et rem sit ut molestiae. Eligendi velit libero inventore quia soluta. Amet necessitatibus impedit beatae rerum libero dolore. Dolores excepturi at sequi sunt est. Et pariatur repellat voluptatem nobis.\r\n \r\n\r\n \r\n \r\n\r\n \r\n\r\n »}
bestmomo
Manifestement, tu n’es pas en Ajax.
TT1303
Bonjour, quand je clique sur « show comments » il y a juste un message « undefined », d’où cela peut venir ? Merci d’avance
bestmomo
Salut,
Regarde au niveau du javascript si l’action va bien dans la fonction showComments .
TT1303
Je viens de regarder et j’avais #showcomments, j’ai donc enlevé le « # » mais maintenant il me dit accès interdit, sûrement dû au fait que j’ai avancé en laissant les commentaire pour l’instant.
bestmomo
Salut,
Oui il vaut mieux tout faire dans l’odre sinon il peut se passer des choses étranges 🙂
TT1303
Ducoup je viens de tout refaire et malheureusement j’ai toujours la même chose
https://we.tl/t-nSunOvqZ0E
Savez-vous d’où cela peut venir?
MVBJUlien
Salut Momo, je t’écris ce commentaire pour te remercier pour ta série de tutos « créer un blog » parce qu’on y voit beaucoup de trucs utiles et c’est bien expliqué – malgré quelques erreurs minimes mais bon c’est pas grave ça nous oblige à réfléchir. ^^
Bon pour ma part je suis une q en javascript donc je vais essayer de faire un bouton « afficher + de commentaires » histoire d’en afficher plusieurs par défaut au lieu d’en afficher seulement si on click.
Merci encore c’est cool je suis en train de bosser sur un projet !
bestmomo
Merci d’apprécier.
J’ai un peu laissé tomber ce blog depuis quelques mois pour diverses raisons, j’espère m’y remettre…
jnn
Bonjour BestMomo. Encore Merci pour ce tutoriel détaillé.
Le clic sur mon bouton show comment me renvoie « null ». J’ai pourtant des commentaires () et tous mes utilisateurs valides.
En console, j’obtiens une erreur 500 dans le fetch du code js .
Merci de m’éclairer
bestmomo
Bonjour,
Il faudrait voir le détail de l’erreur transmise par le serveur.
abinkanrin
Bonjour. J’ai une erreur 403
Thibaut
Bjr, un grd merci pour tes tutos tjr aussi explicite et complet, je suis entrain de refaire mon blog et j’ai ton approche surtt celui de l’utlisation des repositorys, jusqu’a present je m y interessait pas, mais grace a toi je vois tout son interet.
neanmoins je rencontre un probleme avec l’affichage des commentaires, j’ai suivi à la lettre ton tutos bien qu’utilisant mon propre template que j’ai codé avec tailwindcss.
j’ai bien la function validComments dans mon model post, mais lorsque je fais un de dd($post); dans mon PostController(la function show) je vois que les commentaires ne sont validés.
pour voir le button show comment j’ai du commenté le condition @if($post->valid_comments_count > 0) et une fois commenté j’ai mon button show comments et lorsque je click j’ai le message me demandant d’etre connectet pour poster un commentaire, mais je n’ai pas les commentaires, j’ai inspecté et il n y a pas d’erreur sur la console, et j’ai verifie j’ai un status 200 lors de la requete ajax.
je ne comprends d’où vient le probleme, merci pour ton aide!
bestmomo
Salut,
Il faudrait lancer cette requête pour voir le résultat avec les articles :
Post::with('validComments')->find(1);
Déjà pour vois si les commentaires sont chargés.
Thibaut
j’ai fais un dd(Post::with(‘validComments’)->find(1)) et effectivement les commentaires ne sont pas chargés,
bestmomo
Y-a-t-il des commentaires valides ?
Thibaut
bjr
dans la relation validComments est vide:
#relations: array:1 [▼
« validComments » => Kalnoy\Nestedset\Collection {#1494 ▼
#items: []
}
]
bestmomo
Il faudrait voir la cohérence des données. Mettre tous les users valides et vérifier que les articles sotn bien liés à des commentaires.
Thibaut
ok j’ai mis tt les users valides et en faisant :
$testPost = Post::with(‘validComments’)->find(1);
dd($testPost);
j’ai bien:
#relations: array:1 [▼
« validComments » => Kalnoy\Nestedset\Collection {#1525 ▶}
je recupere bien les commentaires, mais la j’ai un pb d’autorisation qui n »etait pas la lorsque je click sur le bouton show comments, j’ai une page 403
bestmomo
Au niveau de l’affichage des commentaires on n’a prévu aucune autorisation (pas de middleware sur la route et pas de policy)…
Thibaut
ok cool , merci les commentaires fonctionnent tres bien , tout s’affiches et j’ai pu integre avec mon design sans pb , reste plus qu ‘a gerer les alertes vu que j’ai deja un system d’alert avec alpine js, je vais un peu bidouille et voir si je vais y arrivé et sinon je verrais comment le faire avec sweetalert, meme au font j’aimerai utilisé que alpine js comme librairy js. un grd merci pour ta reactivité
bestmomo
Super si ça fonctionne !
Pour l’alerte il y a des exemples dans Alpine Toolbox.
webwatson
Symfony\Component\Routing\Exception\RouteNotFoundException
Route [posts.comments] not defined. (View:
bensa
Salut!
C’est un plaisir de suivre votre tuto 🙂
svp j’ai pas compris cette déclaration dans post.blade.php: @if($post->valid_comments_count > 0)
en fait dans quelle partie on a déclaré ce champ ou bien cette fonction: valid_comments_count
Merci beaucoup.
bestmomo
Salut,
Dans le modèle Post il y a la méthode validComments qui renvoie tous les commentaires validés. Dans les requêtes on prévoit ->withCount(‘validComments’) qui donne donc le nombre de ces commentaires valides. Et par définition on retrouve ce nombre dans la propriété valid_comments_count.
ronald169
je sais pas pour vous mais la méthodes ->withDepth() (aller en profondeur) ne marche pas chez moi et lorsque je check cela sur la doc de laravel j’y voie rien. mais lorque je commente cette methode tous donne bien.
« laravel/framework »: « ^8.12 »,
bestmomo
Salut
La méthode withDepth appartient au package NestedSet, pas à Laravel. Il sert à ajouter pour chaque commentaire l’information de sa profondeur et ainsi éviter d’afficher le bouton de réponse si on est arrivé au maximum.
ronald169
Merci pour la remarque,
ronald169
Salut le best c’est toujour un plasir de te lire et d’apprendre avec toi jusqu’ici tout marche comme sur des roulettes. Mais j’ai une demande a faire si je peux me le permettre, etant donnée que nous somme en Laravel 8, pourquoi encore rentrer dans ce bon vieux Jquery ? ne serait-il pas préférable de gérer les comment avec Livewire ou encore Vuejs ?
ca sera vraiment benefique pour nous nous permettra dans la meme occasion d’en apprendre un peu plus sur le sujet.
Merci
bestmomo
Salut,
C’est une bonne question et il n’est pas facile de répondre rapidement. Tu as pu remarquer que j’ai écrit le Javascript pour les commentaires sans utiliser de librairie, même pas JQuery qui est pourtant disponible avec le thème choisi. Concernant Vue.js j’ai écrit un cours sur le sujet et je l’ai utilisé un certain temps. Pour Livewire j’ai rédigé une introduction et je l’ai bien exploré.
Personnellement je distingue deux cas : soit on a une SPA avec une prépondérance du frontend, soit une application classique avec une prépondérance de backend. Dans le premier cas une librairie Javascript s’impose, quelle qu’elle soit, et pourquoi pas Vue.js, et Laravel se pose juste en pourvoyeur d’API (et autant utiliser Lumen). Mais dans le second cas c’est franchement inutile.
En ce qui concerne Livewire, après l’avoir décortiqué, je trouve que le concept peut sembler séduisant mais au final je trouve que c’est un monstre inutile qui complique plus la vie qu’autre chose. Ce mélange des genres est source de confusion. Mais ce n’est que mon avis.
Enfin je me place dans une perspective didactique et je trouve plus pertinent d’en revenir chaque fois au plus près des technologies de base du web. Quand on maîtrise HTML, CSS, Javascript et PHP on peut utiliser n’importe quelle fantaisie à la mode, ou pas…
Dans la gestion des commentaires j’ai justement épuré la partie Javascript en l’isolant de toute librairie pour en revenir à plus de simplicité et montrer que les API des navigateurs permettent désormais de gérer facilement l’interactivité côté client, et il me semble que c’est bien plus instructif.
Mais encore une fois ce n’est que mon point de vue.
ronald169
Salut et merci pour la reponse sinon pour ce type de projet il est vraiment pas nécessaire d’utiliser ce puissant Livewire juste pour les commentaires.
Josuke
Salut! Merci a toi pour ces tutos! J’ai un soucis quand je clique sur show comments ça ne montre pas les commentaires.
Et en voulant mettre un commentaire j’ai le « Something went wrong » si jamais tu peu m’aider.
Merci a toi!
bestmomo
Bonjour,
Il faut regarder dans les outils développeur du navigateur (F12). Ce qui passe en ligne dans l’onglet réseaux et s’il y a des erreurs Javascript dans la console.
webwatson
Salut comment à pu resourdre ton problème? j’ai le même problème et après avoir inspecté je ne vois pas d’erreur dans le volet network