Laravel 5.7 par la pratique – La galerie
Maintenant qu’on a créé l’essentiel de la gestion des catégories et des images on va enfin passer à la réalisation de la galerie elle-même pour visualiser les images sur la page d’accueil. On va aussi donner la possibilité de zoomer les images. On va prévoir une pagination, un affichage par catégorie…
Une light box
Quand on fait une recherche sur Internet pour une light box on a surtout l’embarras du choix. je me suis décidé pour Magnific Popup qui est simple, rapide et esthétique. On l’installe avec npm :
npm i magnific-popup -D
Il faut importer dans resources/sass/app.scss :
@import '~magnific-popup/dist/magnific-popup.css';
Et dans resources/js/app.js :
try { ... require('magnific-popup'); } catch (e) {}
Et relance npm run dev.
Pour l’utilisation on trouve une documentation assez détaillée sur le site.
Une nouvelle directive Blade
On a déjà créé une directive Blade pour filtrer des administrateurs. On va en ajouter une pour filtrer « owner ou admin ». On ajoute ce code dans AppServiceProvider :
public function boot() { ... Blade::if ('adminOrOwner', function ($id) { return auth ()->check () && (auth ()->id () === $id || auth ()->user ()->admin); ... }
On pourra ainsi écrire @adminOrOwner dans les vues ! Ça va nous servir pour autoriser la suppression des images.
Les réglages utilisateur
On va ajouter un champ settings dans la table users pour pouvoir personnaliser le statut adulte et la pagination. Pour prévoir l’avenir on choisit un champ JSON :
$table->json('settings');
On va aussi compléter le seeder pour renseigner ce champ :
class UsersTableSeeder extends Seeder { public function run() { User::create([ 'name' => 'Durand', 'email' => 'durand@chezlui.fr', 'role' => 'admin', 'password' => bcrypt('admin'), 'settings' => '{"pagination": 8, "adult": true}', 'email_verified_at' => Carbon::now(), ]); User::create([ 'name' => 'Dupont', 'email' => 'dupont@chezlui.fr', 'password' => bcrypt('user'), 'settings' => '{"pagination": 8, "adult": true}', 'email_verified_at' => Carbon::now(), ]); User::create([ 'name' => 'Martin', 'email' => 'martin@chezlui.fr', 'password' => bcrypt('user'), 'settings' => '{"pagination": 8, "adult": true}', 'email_verified_at' => Carbon::now(), ]); } }
On relance la migration et la population :
php artisan migrate:fresh --seed
Il faut actualiser le contrôleur RegisterController :
protected function create(array $data) { return User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => Hash::make($data['password']), 'settings' => '{"pagination": 8, "adult": false}', ]); }
Et le modèle User :
protected $fillable = [ 'name', 'email', 'password', 'settings' ];
La vue pour la galerie
Pour l’affichage des vignettes de photos on va utiliser encore le composant card de Bootstrap 4 avec l’intéressante possibilité de les afficher automatiquement en colonnes sans effort !
Voilà le nouveau code de la vue views/home :
@extends('layouts.app') @section('content') <main class="container-fluid"> @isset($category) <h2 class="text-title mb-3">{{ $category->name }}</h2> @endif @isset($user) <h2 class="text-title mb-3">{{ __('Photos de ') . $user->name }}</h2> @endif <div class="d-flex justify-content-center"> {{ $images->links() }} </div> <div class="card-columns"> @foreach($images as $image) <div class="card @if($image->adult) border-danger @endif" id="image{{ $image->id }}"> <a href="{{ url('images/' . $image->name) }}" class="image-link"> <img class="card-img-top" src="{{ url('thumbs/' . $image->name) }}" alt="image"> </a> @isset($image->description) <div class="card-body"> <p class="card-text">{{ $image->description }}</p> </div> @endisset <div class="card-footer text-muted"> <em> <a href="#" data-toggle="tooltip" title="{{ __('Voir les photos de ') . $image->user->name }}">{{ $image->user->name }}</a> </em> <div class="pull-right"> <em> {{ $image->created_at->formatLocalized('%x') }} </em> </div> </div> </div> @endforeach </div> <div class="d-flex justify-content-center"> {{ $images->links() }} </div> </main> @endsection @section('script') <script> $(() => { $('[data-toggle="tooltip"]').tooltip() $('.card-columns').magnificPopup({ delegate: 'a.image-link', type: 'image', tClose: '@lang("Fermer (Esc)")'@if($images->count() > 1), gallery: { enabled: true, tPrev: '@lang("Précédent (Flèche gauche)")', tNext: '@lang("Suivant (Flèche droite)")' }, callbacks: { buildControls: function () { this.contentContainer.append(this.arrowLeft.add(this.arrowRight)) } }@endif }) }) </script> @endsection
La gestion
Le contrôleur HomeController
On va compléter le code du contrôleur HomeController pour envoyer les images dans la vue :
<?php namespace App\Http\Controllers; use App\Repositories\ImageRepository; class HomeController extends Controller { public function index(ImageRepository $repository) { $images = $repository->getAllImages (); return view ('home', compact ('images')); } }
On voit qu’on délègue la gestion des données au repository.
Le repository ImageRepository
On complète ainsi son code :
public function getAllImages() { return Image::latestWithUser()->paginate (config ('app.pagination')); }
La pagination par défaut est réglée dans la configuration (config/app.php) :
'pagination' => 8,
Un scope dans le modèle Image
Dans le repository on fait appel au modèle Image avec un scope :
public function scopeLatestWithUser($query) { $user = auth()->user(); if($user && $user->adult) { return $query->with ('user')->latest (); } return $query->with ('user')->whereAdult(false)->latest (); }
Un accesseur dans le modèle User
Dans le scope ci-dessus on demande l’attribut adult du modèle User, cet attribut n’existe pas, on doit créer un accesseur (et tant qu’à faire un second pour faire propre) :
public function getAdultAttribute() { return $this->settings->adult; } public function getSettingsAttribute($value) { return json_decode ($value); }
Le code est ainsi bien organisé !
Les routes
On avait précédemment ajouté le middleware verified à la route home. On va maintenant l’enlever pour rendre la galerie accessible pour tout le monde et réserver cette vérification pour l’accès aux autres fonctionnalités. :
Route::get('/', 'HomeController@index')->name('home');
La galerie
On peut enfin voir la galerie :
Vérifiez que la lightbox fonctionne en cliquant sur une image :
Le nom de celui qui a envoyé la photo apparaît. Pour le moment le lien n’est pas actif, on codera ça plus tard.
Le colonnes
Il y a quand même un petit souci avec l’organisation des colonnes, on a que deux situations : 3 colonnes pour les écrans larges et une colonne quand ça se réduit vraiment. Il faudrait apporter plus de finesse à ce réglage. Dans resources/sass/app.scss ajoutez ce code :
.card-columns { @include media-breakpoint-only(sm) { column-count: 2; } @include media-breakpoint-only(lg) { column-count: 3; } @include media-breakpoint-only(xl) { column-count: 4; } }
Relancez la compilation (npm run dev).
On a maintenant 4 situations avec 1, 2, 3 ou 4 colonnes, ce qui est bien plus esthétique !
Les transitions
Un autre aspect n’est pas très élégant : comme il faut du temps pour charger les images c’est assez saccadé quand on change de page ou qu’on arrive sur la galerie. Ce qui serait bien c’est de faire apparaître une icône mobile d’attente jusqu’à ce que la page soit totalement chargée.
On va donc recouvrir toute la page avec l’icône mobile au milieux. On ajoute dans la vue home ce code :
@extends('layouts.app') @section('content') <div class="site-wrapper"> <div class="site-wrapper-inner text-white text-center"> <i class="fas fa-spinner fa-pulse fa-4x"></i> </div> </div> ... <script> $(() => { $('.site-wrapper').fadeOut(1000) ... }) </script> @endsection
Et un peu de style pour le recouvrement dans resources/sass/app.scss :
html, body { height: 100%; } .site-wrapper { display: grid; height: 100%; grid-template-columns: 1fr; grid-template-rows: 1fr; grid-template-areas: "center"; } .site-wrapper-inner { grid-area: center; align-self: center; }
J’ai adopté le Grid Layout qui est maintenant bien supporté par les navigateurs.
On relance la compilation et maintenant on a l’icône d’attente au milieu d’un écran noir le temps du chargement :
L’affichage par catégorie
Le contrôleur
Pour l’affichage par catégorie on va commencer par ajouter la route :
Route::name('category')->get('category/{slug}', 'ImageController@category');
Ajouter cette fonction dans le contrôleur ImageController :
... use App\Repositories\ { ImageRepository, CategoryRepository }; class ImageController extends Controller { ... protected $categoryRepository; public function __construct( ImageRepository $imageRepository, CategoryRepository $categoryRepository) { { $this->imageRepository = $imageRepository; $this->categoryRepository = $categoryRepository; } ... public function category($slug) { $category = $this->categoryRepository->getBySlug ($slug); $images = $this->imageRepository->getImagesForCategory ($slug); return view ('home', compact ('category', 'images')); } }
Là on délègue encore le traitement aux repositories.
Les repositories
Dans le repository ImageRepository :
public function getImagesForCategory($slug) { return Image::latestWithUser()->whereHas('category', function ($query) use ($slug) { $query->whereSlug($slug); })->paginate(config('app.pagination')); }
En ce qui concerne le repository CategoryRepository on a déjà tout ce qu’il faut.
La vue
On ajoute un menu déroulant dans la barre de navigation :
<li class="nav-item dropdown"> <a class="nav-link dropdown-toggle @isset($category) {{ currentRoute(route('category', $category->slug)) }} @endisset " href="#" id="navbarDropdownCat" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> @lang('Catégories') </a> <div class="dropdown-menu" aria-labelledby="navbarDropdownCat"> @foreach($categories as $category) <a class="dropdown-item" href="{{ route('category', $category->slug) }}">{{ $category->name }}</a> @endforeach </div> </li> @admin
Et normalement :
On affiche bien les images par catégorie mais le nom de la catégorie n’est pas visible…
Ajoutez ce code dans resources/assets/css/app.css :
.text-title, h1 { color: white; }
Et relancez npm.
Le nom de la catégorie doit être maintenant visible :
On verra dans le prochain chapitre l’affichage par utilisateur, la suppression des images, et bien d’autres choses !
Conclusion
Dans ce chapitre on a :
- ajouté une lighbox pour la visualisation des images
- ajouté une directive Blade
- créé la route et la vue pour la galerie
- ajouté la gestion de la galerie dans le contrôleur et les repositories
- ajouté un écran d’attente de chargement de la page
- ajouté un menu pour les catégories
- écrit le code pour l’affichage par catégorie
Pour vous simplifier la vie vous pouvez charger le projet dans son état à l’issue de ce chapitre.
16 commentaires
Laravel@com
Salut Mr. je rencontre gros probleme que je decide d’afficher mes services et leur directions.voici mon probleme
Illuminate \ Database \ QueryException
SQLSTATE [42S02]: Table de base ou vue introuvable: 1146 La table ‘sigrah1.direction_service’ n’existe pas (SQL: sélectionnez `directions`. *,` Direction_service`.`service_id` as `pivot_service_id`,` direction_service`. `direction_id` as` pivot_direction_id` from `directions` jointure interne` direction_service` on `directions`.`id` =` direction_service`.`direction_id` where `direction_service`.`service_id` in (1, 2, 3, 4, 5, 6))
services contient une clé etrangere id_direct provenant de directions.
Modele sevice:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Service extends Model
{
protected $fillable= [‘name’, ‘abrege’,’id_direct’];
public function direction()
{
return $this->belongsToMany(‘App\Models\Direction’);
}
}
//Modele direction
……..
public function service()
{
return $this->hasMany(‘App\Models\Service’);
}
// controller
$services = Service::with(‘direction’)->get();
return view(‘services.affiche’)->with(‘services’, $services);
ingionoso
Petite question pour afficher tes dates au format français tu fais comment?
Aurais-tu un tuto explicatif expliquant étape par étape?
bestmomo
Bonsoir,
En fait j’utilise les possibilités de Carbon, la documentation est bien faite.
La Terreur
Bonjour Mr.
Un grand merci pour vos cours
J’ai cette erreur avec le repository ImageRepository:
« Call to undefined method App\Models\Image::latestWithUser() ».
la méthode latestWithUser() » ne semble pas être reconnu. Comment faire??
yeroka1394@gmail.com
salut , très intéressant le projet !
mais j’ai des erreurs quand je tape par exemple : php artisan serve
voila l’erreur:
In Connection.php line 664:
SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘album.categories’ doesn’t exist (SQL: select * from `cat
egories`)
In Connection.php line 326:
SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘album.categories’ doesn’t exist
bestmomo
Bonjour,
Les migrations ont bien été faites ? Il semble qu’il manque la table des catégories…
yeroka1394@gmail.com
oui ca marche maintenant mais je ne vois pas les images de la page d’accueil ..
webwatson
Salut! super comme d’habitude mais je n’arrive pas à toujours faire npm run dev et sans cela rien ne marchera.
Depuis j’avance sans cela car j’ai tout essayé mais rien.
J’ai désinstallé node.js et j’ai installer la dernière version LTS mais le problème persiste.
Un lien pour resoudre le problème serai le bienvenue.
bestmomo
Essaie comme ça :
rm -rf node_modules
rm package-lock.json
npm cache clear --force
npm install
litetekno
Par contre, lors de l’inscription
Symfony \ Component \ Debug \ Exception \ FatalThrowableError (E_ERROR)
Class ‘App\User’ not found
albumplus7/app/Http/Controllers/Auth/RegisterController.php
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
‘name’ => ‘required|string|max:255′,
’email’ => ‘required|string|email|max:255|unique:users’,
‘password’ => ‘required|string|min:6|confirmed’,
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\User
*/
protected function create(array $data)
{
return User::create([
‘name’ => $data[‘name’],
’email’ => $data[’email’],
‘password’ => Hash::make($data[‘password’]),
]);
}
}
Arguments
« Class ‘App\User’ not found »
L’erreur est signalée sur la ligne return User::create([
bestmomo
Oui j’avais oublié le contrôleur pour l’enregistrement et le modèle User. j’ai mis le ZIP et l’article à jour. Merci 😉
litetekno
Bonjour,
Merci pour ce cours très intéressant.
Après chargement du code sur centos 7 et composer update, j’ai les erreurs suivantes :
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover
In Connection.php line 664:
SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘albumplus.catego
ries’ doesn’t exist (SQL: select * from `categories`)
In Connection.php line 326:
SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘albumplus.catego
ries’ doesn’t exist
Script @php artisan package:discover handling the post-autoload-dump event returned with error code 1
Je ne rencontre pas cette erreur sur les codes téléchargés précédemment.
Merci d’avance
bestmomo
Bonjour,
Merci d’apprécier cette série !
Dans l’erreur rencontrée il est spécifié que la table categories n’existe pas. Il faudrait voir si la migration se passe bien et ce qu’il y a dans la base.
litetekno
Bonjour,
La base de données est vide. J’ai juste décompressé le projet et lancé « composer install » dans le répertoire du projet.
bestmomo
Alors il faut la remplir :
php artisan migrate –seed
litetekno
Je ne réussis pas à le faire sur le dernier projet téléchargé. Mais ça marche avec le projet de la page précédente.
Autrement dit, pour moi j’ai decompressé albumplpus6.zi et lancé php artisan migrate:fresh –seed.
Apres avoir decompressé albumplus7.zip pas besoin de faire php artisan migrate –seed et ça marche.
Merci encore