Un site d’annonces – les annonces
Dans ce nouvel article on va s’intéresser à l’affichage des annonces. Sur la page d’accueil qu’on a créée dans la précédente étape on a une sélection de la région. On va ajouter la sélection optionnelle du département et de la commune. Selon ces choix on va afficher une liste paginée des annonces correspondantes. On pourra ensuite cliquer sur l’une d’elle pour afficher tous ses détails.
Pour vous simplifier la vie vous pouvez télécharger le dossier complet pour le code de cet article.
Avertissement : de nombreuses personnes ont eu un souci dans l’affichage des annonces. Après analyse il apparaît qu’un nom de classe que j’ai choisi rentre en collision avec le module AdBlockPlus qui est très utilisé. Pour ne pas avoir le souci il suffit de désactiver ce module. Vous pouvez aussi changer le nom de la classe en cause (blockAd). Pour ceux qui ont galéré avec ce problème je rappelle que l’utilisation des outils de développement des navigateurs permettent de traquer facilement ce genre de bug. D’autre part ça met en évidence un problème assez fréquent qui incite à utiliser des solutions comme le shadow dom pour mieux isoler le style, et le reste !
Routes, contrôleur et middleware
On va commencer par définir les routes dont on va avoir besoin pour les annonces ainsi que le contrôleur. A priori notre contrôleur va avoir la plupart des méthodes d’un contrôleur de ressource. Alors on le crée :
php artisan make:controller AdController --resource
Pour les routes on va les définir ainsi :
Route::resource('annonces', 'AdController') ->parameters([ 'annonces' => 'ad' ])->except([ 'index', 'show', 'destroy' ]); Route::prefix('annonces')->group(function () { Route::get('voir/{ad}', 'AdController@show')->name('annonces.show'); Route::get('{region?}/{departement?}/{commune?}', 'AdController@index')->name('annonces.index'); Route::post('recherche', 'AdController@search')->name('annonces.search')->middleware('ajax'); });
On commence par définir partiellement les routes de la ressource en excluant (except) index, show et destroy qui seront définies ailleurs.
On redéfinit le paramètre pour avoir dans l’url annonce, plutôt que ad, ce qui est plus explicite.
On a ensuite un groupe de 3 routes :
- pour voir une annonce (show) mais en ayant le mot voir dans l’url,
- l’url de recherche avec 3 paramètres optionnels (region, departement et commune) étant entendu qu’il y a une hiérarchie et que la région par exemple ne peut pas être optionnelle si on choisit un département et ainsi de suite, sinon ça ne marcherait pas,
- enfin une route de recherche qui sera en Ajax (on voit qu’il y a un middleware qu’on va devoir créer).
Vous avez compris que l’actualisation de la recherche va se faire en Ajax.
Voilà un petit point :
Le middleware Ajax
Puisqu’il nous faut un middleware pour Ajax créons le :
php artisan make:middleware Ajax
Avec ce code :
<?php namespace App\Http\Middleware; use Closure; class Ajax { /** * 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); } }
Et il ne faut pas oublier de le déclarer dans app\Http\Kernel :
protected $routeMiddleware = [ ... 'ajax' => \App\Http\Middleware\Ajax::class, ];
La page d’accueil
On avait pas mis les adresses dans les liens de la page d’accueil (home) pour les régions parce qu’on avait pas encore prévu les routes. On va pouvoir maintenant les compléter :
@extends('layouts.app') @section('content') <div class="container"> <div class="row"> <div class="col-12 col-md-3 col-lg-3"> <a class="btn btn-primary" href="" role="button">Déposer une annonce</a> </div> <div class="col-12 col-md-9 col-lg-9"> <svg xmlns:svg="http://www.w3.org/2000/svg" viewBox="0 0 900 900"> <a href="{{ route('annonces.index', 'occitanie') }}"> <path class="base" data-toggle="tooltip" title="Occitanie" d="m 428.3,774.7 68.28824,1.41765 L 494.4,720.6 l 52.5,-38.8 14.8,7 25.1,-26 0.9,-38.1 -37.5,-8.3 -9.7,-29.4 -34.6,-24.3 -15.42353,24.04118 -15.61765,-21.68824 -21.22941,26.62353 -22.55294,-3.91765 -2.51176,-23.22941 -34.12942,-3.54706 L 351.5,638.6 293.45294,646.81765 302.82353,749.64706 356.4,744.2 Z"/> </a> <a href="{{ route('annonces.index', 'pays_de_la_loire') }}"> <path class="base" data-toggle="tooltip" title="Pays de la Loire" d="M 268.2,432.3 229.2353,441.52941 182.9,401.6 162.2,343.4 231.37059,312.44118 247.29412,255.67647 356.5,281.5 l -20.8,46.1 -30.75882,38.22353 -56.11765,14.82353 z""/> </a> <a href="{{ route('annonces.index', 'bretagne') }}"> <path class="base" data-toggle="tooltip" title="Bretagne" d="M 226.05882,309.17647 157.23529,338.29411 41.82353,307.05882 20.647059,288 48.176471,281.64706 16.941177,259.94117 25.8,247 123.35294,218.64706 143.47059,248.29411 207.5,243.2 242,252.5 Z""/> </a> <a href="{{ route('annonces.index', 'centre') }}"> <path class="base" data-toggle="tooltip" title="Centre-Val de Loire" d="m 313.41177,364.7647 c 9,7.41177 71.31764,68.52942 71.31764,68.52942 L 441.04118,427.20588 490.2,398.1 487.95294,290.12941 388.1513,230.74351 362.62353,241.30588 362.1,283.3 342,329.82353 Z"/> </a> <a href="{{ route('annonces.index', 'normandie') }}"> <path class="base" data-toggle="tooltip" title="Normandie" d="m 323.71176,174.21765 -88.65294,2.60588 -9.52941,-29.11765 -36,-1.58824 25.19191,92.05393 141.17868,35.3929 0.7,-36.86447 33.52419,-16.52616 22.53911,-31.1701 2.80632,-42.7111 L 392.6,117.1 312.35294,151.41176 Z"/> </a> <a href="{{ route('annonces.index', 'ile_de_france') }}"> <path class="base" data-toggle="tooltip" title="Île-de-France" d="M 397.58824,227.11764 415,194 l 79,6.6 20.1,26 -1.1,37.7 -29.3,16.7 z"/> </a> <a href="{{ route('annonces.index', 'hauts_de_france') }}"> <path class="base" data-toggle="tooltip" title="Hauts-de-France" d="m 459.5,28.1 -55.3,18.4 -3.4,39.5 -3.53336,26.02312 23.75689,29.84747 -3.0264,46.31423 76.47889,6.53412 20.30295,23.76216 46.87398,-86.33992 L 558.3,99 Z"/> </a> <a href="{{ route('annonces.index', 'grand_est') }}"> <path class="base" data-toggle="tooltip" title="Grand Est" d="M 563.57853,103.93079 635.69174,156.40927 800,212.3 774.52942,246.70588 757.9,336.4 l -19.6,7.6 -12.21765,-32.78235 -64.04787,-12.25792 -36.27715,30.39069 -38.03513,-33.68543 -43.84789,1.1548 -25.04824,-32.27372 0.18819,-41.30026 48.92475,-91.74037 z/""> </a> <a href="{{ route('annonces.index', 'bourgogne') }}"> <path class="base" data-toggle="tooltip" title="Bourgogne-Franche-Comté" d="m 665.06471,305.92941 56.52353,11.18823 10.88823,27.94118 -71.77058,99.11765 L 635.1,442.5 l -4.6043,-15.10589 -25.49168,-3.77814 -15.22208,26.95233 -46.4355,-3.2994 7.80392,-22.04274 -55.53596,-28.13453 -0.1772,-114.99224 20.02314,-11.68867 26.14333,32.71916 47.7414,0.78345 38.12551,35.23961 z"/> </a> <a href="{{ route('annonces.index', 'auvergne') }}"> <path class="base" data-toggle="tooltip" title="Auvergne-Rhône-Alpes" d="M 535.45882,450.42353 593.6,455.2 608.82353,429.88235 625.7,431.6 l 5.35883,17.87059 36,1.05882 36,-21.17647 30.70588,101.11765 L 632.9,608.1 l 12.8,20.3 -13.5,2.3 -45.6,-16.1 -30.18823,-2.6 -11.11765,-29.64706 -39.13122,-29.42816 -16.90577,21.26103 -15.41509,-17.33867 -22.25617,29.48934 -17.33293,-4.9306 1.98236,-19.69999 30.04875,-93.6762 -19.98993,-33.38263 45.80543,-30.84615 49.88761,25.00432 z"/> </a> <a href="{{ route('annonces.index', 'provence') }}"> <path class="base" data-toggle="tooltip" title="Provence-Alpes-Côte d'Azur" d="m 594,625.23529 c 9,4.76471 41.29412,12.70589 41.29412,12.70589 l 18,-3.17648 -12.08824,-25.51764 68.20589,-54.42353 24.35294,26.47059 L 726.4,621.1 769.76471,636.88235 696.8,716.5 676.3,726.7 567,690.35294 592.94118,664.41176 Z"/> </a> <a href="{{ route('annonces.index', 'corse') }}"> <path class="base" data-toggle="tooltip" title="Corse" d="m 773.65294,695.94118 5.7,72.5 -18.2,63.7 -19.2,-6.7 -24.6,-65.20589 12.17648,-29.11764 L 765,715.23529 l 1.05883,-20.64706 z"/> </a> <a href="{{ route('annonces.index', 'aquitaine') }}"> <path class="base" data-toggle="tooltip" title="Nouvelle-Aquitaine" d="m 224.35882,446.83529 c 9,4.76471 51.35295,-11.64705 51.35295,-11.64705 l -15.35294,-49.76472 48.79411,-13.87058 75.36142,69.83442 53.72682,-6.19912 20.62104,34.50899 -27.50339,87.78513 -40.71765,-4.66471 L 349.3,630.54117 285.86471,643.45294 293.62353,745.47059 205.3,694.6 214.82942,628.95294 235.1874,506.12438 Z"/> </a> </svg> </div> </div> </div> @endsection @section('script') <script> $(function(){ $('[data-toggle="tooltip"]').tooltip(); }); </script> @endsection
Maintenant pour chaque région on a un lien de la forme annonces/{slug de la region} et on vise la méthode index de notre contrôleur. On va créer maintenant cette méthode, ou plutôt la compléter parce qu’elle a été créée avec le contrôleur.
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\ { Category, Region }; class AdController extends Controller { /** * Display a listing of the resource. * * @param \Illuminate\Http\Request $request * @param String $regionSlug * @param Integer $departementCode * @param Integer $communeCode * @return \Illuminate\Http\Response */ public function index( Request $request, $regionSlug = null, $departementCode = null, $communeCode = null) { $categories = Category::select('name', 'id')->oldest('name')->get(); $regions = Region::select('id','code','name','slug')->oldest('name')->get(); $region = $regionSlug ? Region::whereSlug($regionSlug)->firstOrFail() : null; $page = $request->query('page', 0); return view('adsvue', compact('categories', 'regions', 'region', 'departementCode', 'communeCode', 'page')); }
On reçoit les 3 paramètres optionnels dans la méthode.
On va avoir besoin de toutes les catégories dans la vue alors on les récupère dans l’ordre alphabétique (oldest) :
$categories = Category::select('name', 'id')->oldest('name')->get();
Il nous faut aussi la liste complète des régions, la aussi dans l’ordre alphabétique :
$regions = Region::select('id','code','name','slug')->oldest('name')->get();
Ensuite si un slug est présent pour la région (ça sera le cas quand on va cliquer sur une région de la carte) on la récupère, sinon on garde le null :
$region = $regionSlug ? Region::whereSlug($regionSlug)->firstOrFail() : null;
Ensuite on regarde s’il y a une pagination et on renvoie le numéro de la page :
$page = $request->query('page', 0);
En effet comme on va fonctionner en Ajax il va falloir tenir à jour l’url avec le numéro de page pour le cas ou l’utilisateur rechargerait cette page (il ont parfois de ces idées !).
Ensuite on envoie tout ça dans une vue :
return view('adsvue', compact('categories', 'regions', 'region', 'departementCode', 'communeCode', 'page'));
Evidemment cette vue n’existant pas encore pour le moment on obtient une jolie erreur :
Un composant
Pour la vue adsvue on va créer un composant Vue.js qu’on va appeler AdComponent. Mais pour commencer on va prévoir l’intendance de Vue.js en créant un fichier Javascript pour l’initialisation :
window.Vue = require('vue'); Vue.component('ad', require('./components/AdComponent.vue').default); const app = new Vue({ el: '#app' });
On charge Vue.js, le composant qu’on va bientôt créer et on crée l’objet principal pour l’application.
On va le déclarer dans webpack.mix.js :
mix.js('resources/js/vue.js', 'public/js')
Si vous compiler maintenant évidemment on va vous dire que le composant n’existe pas !
On crée le fichier du composant :
<template> <div class="container"> <div class="card bg-light"> <h5 class="card-header">Votre recherche</h5> <div class="card-body"> <form id="formAd" method="POST" :action="url"> <div class="form-group"> <label for="category">Catégorie</label> <select class="custom-select" name="category" id="category" @change="onCategoryChange()" v-model="categorySelected"> <option value="0">Toutes</option> <option v-for="category in categories" :key="category.id" :value="category.id"> {{ category.name }} </option> </select> </div> <div class="form-group"> <label for="region">Région</label> <select class="custom-select" name="region" id="region" @change="onRegionChange()" v-model="regionSelected"> <option data-code="0" value="0">Toute la France</option> <option v-for="region in regions" :key="region.id" :value="region.id" :data-code="region.code"> {{ region.name }} </option> </select> </div> <div v-if="regionSelected != 0" class="form-group"> <label for="departement">Département</label> <select class="custom-select" name="departement" id="departement" @change="onDepartementChange" v-model="departementSelected"> <option value="0">Tous</option> <option v-for="departement in departements" :key="departement.code" :value="departement.code"> {{ departement.nom }} </option> </select> </div> <div v-if="departementSelected != 0" class="form-group" > <label for="commune">Commune</label> <select class="custom-select" name="commune" id="commune" @change="onCommuneChange" v-model="communeSelected"> <option value="0">Toutes</option> <option v-for="commune in communes" :key="commune.code" :value="commune.code"> {{ commune.nom }} </option> </select> </div> </form> </div> </div> <br> <span v-html="ads"></span> </div> </template> <script> export default { props: [ 'url', 'categories', 'regions' ], data () { return { categorySelected: 0, regionSelected: 0, regionIndex: 0, regionSlug: '', departements: [], departementSelected: 0, communes: [], communeSelected: 0, ads: '' } }, methods: { onCategoryChange() { this.submit(); }, onRegionChange() { const index = event.target.selectedIndex; if (index) { this.regionSlug = this.regions[index - 1]['slug']; let code = event.target.options[index].attributes['data-code'].value; this.fillDepartements(code); } else { this.regionSelected = 0; } this.submit(); }, fillDepartements(code) { if(code) { let that = this; $.get('https://geo.api.gouv.fr/regions/' + code + '/departements', data => { that.departements = data; }); } this.departementSelected = 0; }, onDepartementChange() { const index = event.target.selectedIndex; if (index) { this.fillCommunes(event.target.value); } else { this.departementSelected = 0; } this.submit(); }, onCommuneChange() { this.communeId = event.target.selectedIndex; this.submit(); }, fillCommunes(code) { if(code) { let that = this; $.get('https://geo.api.gouv.fr/departements/' + code + '/communes', data => { that.communes = data; }); } this.communeSelected = 0; }, submit(e, comp = '') { $.ajax({ method: 'post', url: this.url + comp, data: { 'category': this.categorySelected, 'region': this.regionSelected, 'departement': this.departementSelected, 'commune': this.communeSelected, '_token': $('meta[name="csrf-token"]').attr('content') } }) .done(data => { this.ads = data; let ref = '/annonces'; if(this.regionSelected) { ref += '/' + this.regionSlug } if(this.departementSelected) { ref += '/' + this.departementSelected } if(this.communeSelected) { ref += '/' + this.communeSelected } if(comp) { ref += comp; } history.pushState({}, 'Annonces', ref); }) } }, mounted(e) { this.regionSelected = $('#start').attr('data-id'); if(this.regionSelected != 0) { this.regionSlug = $('#start').attr('data-slug'); this.fillDepartements($('#start').attr('data-code')); const dep = $('#start').attr('data-departement'); if(dep) { this.departementSelected = dep; this.fillCommunes(dep); } const com = $('#start').attr('data-commune'); if(com) { this.communeSelected = com; } } if($('#start').attr('data-page')) { this.submit(e, '?page=' + $('#start').attr('data-page')); } else { this.submit(); } } } $('body').on('click', 'a.page-link', e => { e.preventDefault(); app.__vue__.$refs.adComp.submit(e, '?' + ($(e.currentTarget).attr('href')).split('?')[1]); }); </script>
Analysons un peu ce code…
On a d’abord un template qui a pour but d’afficher 4 liste de sélection pour :
- les catégories
- les régions
- les départements
- les communes
Au niveau des données on voit qu’on reçoit en props :
- une url
- les catégories
- les régions
Il va donc falloir renseigner des données dans la vue.
Au niveau des données internes du composant on a essentiellement :
- departements : un tableau des départements
- communes : un tableau des communes
- ads : la liste des annonces en cours
Quand on a un changement dans l’une des listes il faut agir !
Si c’est un changement de catégorie on se contente d’envoyer la requête de recherche :
onCategoryChange() { this.submit(); },
Si c’est un changement de région c’est plus complexe, en effet il faut alors actualiser la liste des départements :
onRegionChange() { ... this.fillDepartements(code); ... this.submit(); },
Pour remplir la liste des département on fait appel à l’API comme on l’a vue pour les factories :
fillDepartements(code) { if(code) { let that = this; $.get('https://geo.api.gouv.fr/regions/' + code + '/departements', function(data) { that.departements = data; }); } this.departementSelected = 0; },
C’est le même principe pour un changement de département avec la liste des communes.
Dans tous les cas on lance la requête au serveur :
submit(e, comp = '') { $.ajax({ method: 'post', url: this.url + comp, data: { 'category': this.categorySelected, 'region': this.regionSelected, 'departement': this.departementSelected, 'commune': this.communeSelected, '_token': $('meta[name="csrf-token"]').attr('content') } }) .done((data) => { this.ads = data; let ref = '/annonces'; if(this.regionSelected) { ref += '/' + this.regionSlug } if(this.departementSelected) { ref += '/' + this.departementSelected } if(this.communeSelected) { ref += '/' + this.communeSelected } if(comp) { ref += comp; } history.pushState({}, 'Annonces', ref); }) }
C’est une requête POST avec l’url /annonces/recherche. On a prévu dans les routes que ça arrive sur la méthode search du contrôleur qui n’existe pas encore.
On transmet les 4 paramètres :
- catégorie
- région
- département
- commune
Et on ajoute le token pour la sécurité.
Au retour on actualise la variable ads qui affiche ainsi la liste des annonces dans le template :
<span v-html="ads"></span>
Le serveur renvoie aussi la pagination qu’on affiche telle quelle. Mais comment gérer cette pagination ? Il faut déclencher la méthode submit du composant. Quand on utilise Vue.js on a parfois des soucis de communication quand on se situe à l’extérieur du système. Ce souci est géré ici :
$('body').on('click', 'a.page-link', e => { e.preventDefault(); app.__vue__.$refs.adComp.submit(e, '?' + ($(e.currentTarget).attr('href')).split('?')[1]); });
J’ai dû un peu grater dans le code de Vue.js pour écrire ces lignes mais ainsi ça fonctionne. On va voir qu’li faut quand même créer une réfénrence dans la vue.
La vue adsvue
Il est temps maintenant de créer la vue :
Au passage j’ai supprimer la vue welcome qui ne nous sert pas.
Voici le code :
@extends('layouts.app') @section('content') <div id="app"> @if($region) <div id="start" data-id={{ $region->id }} data-code="{{ $region->code }}" data-slug="{{ $region->slug }}" @if($departementCode) data-departement="{{ $departementCode }}" @if($communeCode) data-commune="{{ $communeCode }}" @endif @endif @else <div id="start" data-id="0" @endif @if($page != 0) data-page="{{ $page }}" @endif ></div> <ad url="{{ route('annonces.search') }}" :categories="{{ $categories }}" :regions="{{ $regions }}" ref="adComp" ></ad> </div> @endsection @section('script') <script src="{{ asset('js/vue.js') }}"></script> @endsection
On voit qu’on renseigne les props du composant ainsi que la fameuse référence pour assurer le fonctionnement de la pagination qu’on a vu précédemment.
On prévoie aussi les données de départ pour le composant :
- la région
- la page
Pourquoi ne pas envoyer ça directement en props ? Tout simplement que dans le cycle de construction du composant ça ne fonctionne malheureusment pas. Alors il faut ruser… Ensuite dans la méthode mounted du composant on vient lire ces données :
mounted(e) { this.regionSelected = $('#start').attr('data-id'); if(this.regionSelected != 0) { this.regionSlug = $('#start').attr('data-slug'); this.fillDepartements($('#start').attr('data-code')); const dep = $('#start').attr('data-departement'); if(dep) { this.departementSelected = dep; this.fillCommunes(dep); } const com = $('#start').attr('data-commune'); if(com) { this.communeSelected = com; } } if($('#start').attr('data-page')) { this.submit(e, '?page=' + $('#start').attr('data-page')); } else { this.submit(); } }
C’est un peu accrobatique mais je n’ai pas trouvé mieux…
Un repository
Pour être un peu organisés on va créer un repository pour gérer les annonces :
On commence à le remplir avec notre recherche :
<?php namespace App\Repositories; use App\Models\Ad; use Carbon\Carbon; class AdRepository { /** * Search. * * @param \Illuminate\Http\Request $request */ public function search($request) { $ads = Ad::query(); if($request->region != 0) { $ads = Ad::whereHas('region', function ($query) use ($request) { $query->where('regions.id', $request->region); })->when($request->departement != 0, function ($query) use ($request) { return $query->where('departement', $request->departement); })->when($request->commune != 0, function ($query) use ($request) { return $query->where('commune', $request->commune); }); } if($request->category != 0) { $ads->whereHas('category', function ($query) use ($request) { $query->where('categories.id', $request->category); }); } return $ads->with('category', 'photos') ->whereActive(true) ->latest() ->paginate(3); } }
Selon catégorie, région, département et commune on extrait les annonces avec une pagination de 3 pages.
Le contrôleur et la vue partielle
Le contrôleur
On en revient au contrôleur où il nous faut la méthode search qui reçoit la demande du composant dans la vue et qui doit utiliser le repository pour aller chercher les annonces correspondantes :
use App\Repositories\AdRepository; class AdController extends Controller { /** * Ad repository. * * @var App\Repositories\AdRepository */ protected $adRepository; /** * Create a new controller instance. * * @return void */ public function __construct(AdRepository $adRepository) { $this->adRepository = $adRepository; } /** * Search ads. * * @param \Illuminate\Http\Request $request * @param String $slug * @return \Illuminate\Http\Response */ public function search(Request $request) { setlocale (LC_TIME, 'fr_FR'); $ads = $this->adRepository->search($request); return view('partials.ads', compact('ads')); }
On voit qu’on fait appel à une vue partielle pour constituer la réponse.
La vue partielle
On crée cette vue :
@foreach($ads as $ad) <a href="{{ route('annonces.show', $ad->id) }}" class="blockAd"> <div class="card d-flex flex-row"> <div class="card-header"> @if($ad->photos->isNotEmpty()) <img class="rounded" src="{{ asset('thumbs/' . $ad->photos->first()->filename) }}" alt=""> @else <img src="{{ asset('thumbs/question.jpg') }}" alt=""> @endif </div> <div class="card-body"> <h4 class="card-title">{{ $ad->title }}</h4> <p class="card-text">{{ $ad->category->name }}</p> <p class="card-text"> {{ $ad->commune_name . ' (' . $ad->commune_postal . ')'}}<br> {{ $ad->created_at->calendar() }} </p> </div> </div> </a> <br> @endforeach <div class="d-flex"> <div class="mx-auto"> {{ $ads->links() }} </div> </div>
On constitue ainsi le code HTML pour la liste des annonces qui correspondent à la recherche ainsi que la pagination en partie inférieure.
Le résultat
Maintenant on a tout mis en place il ne nous reste plus qu’à cliquer sur une région et…
Maintenant on peut par exemple sélectionner un département pour n’avoir que les annonces de celui-ci :
Faites des essais en changeant la catégorie, la région… Normalement à chaque fois les annonces s’actualisent et vous avez la pagination qui fonctionne s’il y a plus de 3 annonces.
Remarquez aussi que l’url est actualisée à chaque fois pour qu’un rechargement de la page aboutisse au même résultat. c’est assuré par ce code dans le composant lorsque la requête de recherche renvoie un résultat positif :
.done(data => { this.ads = data; let ref = '/annonces'; if(this.regionSelected) { ref += '/' + this.regionSlug } if(this.departementSelected) { ref += '/' + this.departementSelected } if(this.communeSelected) { ref += '/' + this.communeSelected } if(comp) { ref += comp; } history.pushState({}, 'Annonces', ref); })
Conclusion
On a désormais un affichage d’une liste d’annonce en fonction de critères de recherches : catégorie et localisation. On a aussi la pagination s’il y en a beaucoup. On a vu que Vue.js s’intègre bien dans Laravel même s’il faut parfois un peu jongler. J’ai utilisé JQuery pour les requêtes HTTP puisqu’il est déjà prévu dans le projet.
Dans le prochain article on créera la vue pour afficher une annonce avec tous ses détails. On verra qu’il faudra prévoir quelques précautions pour ne pas afficher une annonce non active. Enfin on va donner la possibilité au visiteur de laisser un message à l’émetteur de l’annonce…
46 commentaires
Drope
Salut Bestmomo,
Le middleware Ajax a pour but d’empecher l’accès direct au url, ca marche nickel 🙂
Par contre, j’ai développé un système de chargement de commentaires dynamique en Ajax également. Ca marche très bien, mais je constate forcément dans les logs des rejets du Googlebot sur ces urls.
Sais-tu s’il existe des bonnes pratiques pour protéger les url d’appels Ajax sans pour autant empecher le référencement des commentaires ? J’ai du mal a trouver de la littérature sur ce sujet.
bestmomo
Salut,
je n’ai jamais été confronté au problème mais apparemment Google donne des indication ici.
jondelweb
Bonjour,
D’abord, bravo pour le site et les tutos en général, ils sont plutôt bien foutu (C’est un compliment hein ! 😉 )
Ensuite, je rencontre un problème que je ne comprends pas trop… Je ne suis pas un noob sur Laravel mais je l’ai essentiellement utilisé en tant que API avec angular en front. Du coup, je suis complètement novice avec vue…
Voilà mon souci:
La page d’accueil fonctionne (sauf le tooltip() mais ça doit venir du fait que je suis sur Laravel 7.0 et que le chargement des fichiers ne se fait pas de la même manière. Je réglerai ce souci plus tard…).
La page de la vue des annonces fonctionne…partiellement… Je m’explique…
J’ai bien le formulaire de recherche sauf les communes… Lorsque je clique sur une région (occitanie pour l’exemple), j’ai bien dans l’url ‘annonces/occitanie’ et j’ai de temps en temps les annonces adéquates (et oui pas tout le temps, je ne l’explique pas, c’est mon premier problème…). Pourtant, la requête s’effectue bien, je le vois dans l’onglet réseau…
Ensuite, le champs ‘région’ s’actualise automatiquement pour revenir à ‘Toutes la france’ (mais pas la requête) et le slug reste pareil cad ‘annonces/occitanie’. Lorsque j’essaie d’effectuer une deuxième recherche, le slug ne s’actualise pas non plus et je n’ai rien qui se passe… Pas de ‘submit’ en vue et rien dans l’onglet réseau, c’est mon deuxième problème…
Je n’ai pas d’erreur dans ma console…
Ce qui est vraiment chelou, c’est que c’est vraiment aléatoire comme erreur… Souvent ça ne fonctionne pas. Cad que je n’ai aucune annonce qui s’affiche…
Pour info, je suis sous linux (kubuntu 18.04 et Mozilla mais j’ai les mêmes problèmes sous chrome…)
(Je précise aussi que j’ai supprimé la classe AdBlock même si je j’utilise pas adBlock…)
Merci beaucoup d’avance…
bestmomo
Bonjour,
Les bugs aléatoires sont les plus difficiles à traquer. Ça vient certainement du contexte mais au vu de la description je ne vois pas trop d’où ça peut venir. Personne d’autre n’ayant signalé ce souci il faut vraiment voir ce qui est spécifique au système utilisé.
amedee226
Oui j’ai réalisé cela. Ma question c’est quel url utiliser pour remplacer sa ($.get(‘https://geo.api.gouv.fr/regions/’ + code + ‘/departements’, data => {
that.departements = data;)???
bestmomo
Tu choisis l’url que tu veux en créant ton API, tu crées une route, un contrôleur et les accès à ta base pour nourrir l’API.
amedee226
Ok je tente pour le coup. Merci
traore
Bonjour
Pour ce qui ont eu le fameux probeleme souligné ci-dessus , testez ce code sur votre terminal : php artisan db:seed –class=DatabaseSeeder
En tout cas, ca resolu mon prrobleme.
Mon analyse sur le probeme : selon moi la base de données n’etait pas charger des données victives de la classe seeder.
du passage, je tiens a remerciement milles fois la personne l’auteur de tuto
je te connais pas mais t es un homme bien
aller au code
Dave Glad
Bonjour s’il vous plaît quelqu’un pourrait m’aider a apporter les modifications en temps réel plus précisément dans les fichiers sasss ?
NB, je suis entrain de suivre le tutoriel sur le développement d’un site d’annonce en Laravel 5.8!
bestmomo
Salut,
Je ne suis pas sûr de bien comprendre la question mais peut-être c’est cette commande : npm run watch
Dave Glad
Merci beaucoup ça marché… j’ai aussi un petit soucis je n’arrive pas a affiché les annonces , aidé moi je vous en supplie !
bestmomo
Ça serait pas encore l’histoire avec AdBlock ?
Dave Glad
Merci beaucoup j’ai pu régler ça, s’était du à mon extension chrome AdBlock … Et merci encore pour votre énorme travail !
Que Dieu vous bénisse !
amedee226
Bonsoir.
j’ai un petit soucis au niveau de l’affichage des annonces. de mon coté, au lieu d’utiliser l’API, j’ai créer les régions et des départements dans ma BDD en local.
Alors comment appeler les régions et départements dans se cas précis???
Merci
{{{{
fillDepartements(code) {
if(code) {
let that = this;
$.get(‘https://geo.api.gouv.fr/regions/’ + code + ‘/departements’, data => {
that.departements = data;
});
}
this.departementSelected = 0;
},
}}}}
bestmomo
Salut,
Pour remplacer l’API officielle il faudrait créer une API dans l’application.
amedee226
Oui j’ai réalisé cela. Ma question c’est quel url utiliser pour remplacer sa ($.get(‘https://geo.api.gouv.fr/regions/’ + code + ‘/departements’, data => {
that.departements = data;)???
senzoi
Je crois avoir trouvé le soucis 😉 Si ca interresse tout le monde
dans ads.blade.php il y a la ligne :
id) }} » class= »blockAd »>
autrement dit pour chaque annonce on ajoute la class blockAd alors qu’il ne faudrait mettre cette class que lorsque l’annonce n’est pas active 😉
Je réfléchi du coup à comment faire ce petit test 😉
Ai-je bon??
senzoi
Je ne sais pas comment afficher du code dans le message, mais en gros j’ai créé un test @if($ad->active===0) il y a la classe blockAd et @else pas de class blockAd
Je ne sais pas si c’est la meilleure solution mais elle fonctionne.
senzoi
Bonjour Tout le monde.
Bestmomo, franchement merci pour ces tutos. Moi qui souhaitait me mettre à Laravel, tu as fais un travail de dingue!! Encore merci à toi.
Cependant, j’arrive à la même conclusion que pas mal de monde. La page de recherche fonctionne bien sauf l’affichage des annonces… 🙁
J’ai repris l’ensemble de tes lecons et tout le code. J’ai aussi repis les zip mis à dispo pour vérifier à quel endroit j’ai « bouletté » 😉 Mais je n’ai rien trouvé…
Je t’avoue que j’ai un soucis pour la compréhension de l’affichage de ads.blade.php(dans partials) Je comprends que cela doit se dérouler au niveau du AdsComponent () mais …. Ce ne se déroule pas comme prévue. As tu une astuce qui me guiderai dans le débug??? (pas la solution please, j’aimerai comprendre).
Par avance merci
et encore bravo pour ce site…
(ps: as tu d’autres petits tutos en préparation???)
bestmomo
Salut,
Comme pas mal de monde a un souci avec cet affichage j’ai creusé l’affaire et j’ai trouvé le loup bien caché ! En fait j’ai nommé la classe pour le style des annonces AdBlock, or il se trouve que la plupart des gens utilisent AdBlock pour supprimer les publicités et manque de chance il s’y trouve la même classe avec un display none. Donc si je ne me trompe pas de loup tu dois avoir AdBlock activé sur ta page. Désactive le pour voir si ça passe. Si c’est le cas il faudra que je change le nom de cette classe.
senzoi
J’y crois pas!!! lol Merci pour ton aide si rapide. Par ce que tu coup je me disais quel interet d’avoir cette classe en regardant le reste du code lol
merci à toi
oksam
Bonjour,
En cliquant sur une région j’obtiens la page concernée mais sans annonces.
Merci bien
PaulinPryze
J’ai le même problème depuis deux jours.
benji
Salut tout le monde, j’ai commencé le projet d’annonce avec Laravel 6 je n’arrive pas a afficher la partie du haut avec les différents champs de recherche(région, département, service, commune) mais, impossible de faire afficher la liste des annonces trouvés. j’ai une erreur 500 qui s’affiche dans la console de mon navigateur(chrome).
Si quelqu’un a une idée, je suis preneur(ça fait 4 jours que j’essaie de résoudre le problème sans succès.
bugtronik
Bonjour,
Comme mentionner plus haut, en cliquant sur une région j’obtiens la page concernée mais pas sans annonces.
Merci bien
bugtronik
sans annonces je veux dire
PaulinPryze
Salut, j’ai exactement le même problème et je ne sais pas quoi faire. le formulaire de recherche s’affiche mais pas les annonces.
oksam
Le lien envoyé est plutôt correct vu que c’est {annonce/la région} sur laquelle j’ai cliqué. Le soucis est qu’il n’affiche pas le formulaire avec les différentes informations de l’annonce.
bestmomo
Une bonne occasion de faire du débogage dans le code du contrôleur (méthode index).
oksam
bonjour,
lorsque je clic sur une région de la carte cela me renvoi vers une autre page vide. je comprends pas ou est le soucis.
bestmomo
Bonjour,
Il faudrait vérifier quel est le lien en place, quelle url est envoyée. En général une page vide correspond à une méthode du contrôleur pas encore codée, la requête aboutit mais on n’obtient pas de réponse.
PaulinPryze
Bonjour, tout fonctionne bien pour moi mais il y a un bug que ne parviens pas à comprendre. J’affiche la carte sur localhost/monprojet/public quand je clique sur un élément de la carte, je suis redirigé vers localhost/monprojet/public/annonces/region puis directement redirigé bers localhost/annonces/region. Je ne comprend pas pourquoi
bestmomo
Salut
Pour que ça fonctionne correctement il faut un local host pour éviter d’insérer public dans l’url.
PaulinPryze
Je ne comprend pas vraiment votre solution
PaulinPryze
Pourriez-vous être un peu plus clair?
bestmomo
Ce que je veux dire c’est que ça va pas bien fonctionner avec des urls du genre localhost/monprojet/public/…
Il faut un host local du genre monhost.oo.
PaulinPryze
Pouvez-vous m’orienter vers une solution me permettant de créer ce type de host local?
bestmomo
Personnellement comme je suis sous Windows j’utilise Laragon qui est parfait au niveau du développement en local et qui génère automatiquement ces hosts.
ibraLaravel
Bonjour bestmomo, merci pour votre travail.
J’ai un petit souci sur la fonction search et notamment la vue qui retourne les annonces ad.blade.php.
Lorsque j’essaie de charger le résultat de la recherche cela retourne :
GET http://mesannonces8.test/thumbs/question.jpg 404 (Not Found)
Merci d’avance
ibraLaravel
Bonjour, je précise que j’obtiens ce retour pour le navigateur Chrome, pour les autres navigateurs, j’obtiens bien les résultats sours forme de card
bestmomo
Bonjour,
J’ai remarqué que dans certains ZIP j’avais pas mis ce fichier. Je l’ai rajouté partout. Par contre il y a effectivement un bug avec Chrome pour Vue.js, les cards ne s’affichent pas alors que la variable ads du composant est bien renseignée…
ibraLaravel
Merci pour le retour et les pépites partagées sur le site
melt_CDK
Bonjour,
Concernant le problème de card qui ne s’affiche pas sur chrome. Cela vient de la class ‘blockAd’ reconnu par les bloqueurs de pub de type adblock.
En espérant avoir aidé.
Merci pour ton travail.
bestmomo
Salut,
Merci pour l’info, il fallait le trouver 😉
itanea
Je plussoie en renommant la classe ‘blockAd’ en ‘adUrl’ cela fonctionne à nouveau 😉
dans ads.blade.php :
[…]
id) }} » class= »adUrl »>
[…]
Bonne journée !
itanea
zut 🙁 le lien exemple a été interprété. Désolé Bestmomo 🙁