Laravel

Un framework qui rend heureux

Voir cette catégorie
Vers le bas
Voir cette série
Mon CMS - Les menus (partie 2)
Dimanche 6 octobre 2024 11:51

Dans le précédent article, nous avons créé la liste des menus accompagnés de leurs sous-menus éventuels. On a aussi ajouté les formulaires pour créer un menu ou un sous-menu. Nous pouvons aussi supprimer l'un d'eux à partir de la liste. Enfin, et c'est ce qui a nécessité un codage un peu plus précis, nous pouvons réorganiser l'ordre des menus et sous-menus pour obtenir l'affichage souhaité sur le site. Maintenant, nous allons créer les formulaires qui vont permettre de modifier un menu ou un sous-menu existant.

Pour rappel, la table des matières est ici.

Un composant pour modifier un menu

On a à nouveau besoin d'un composant Volt pour gérer la modification d'un menu :

php artisan make:volt admin/menus/edit --class

On va ajouter la route pour l'atteindre (réservée aux administrateurs) :

Route::middleware('auth')->group(function () {
	...
	Route::middleware(IsAdminOrRedac::class)->prefix('admin')->group(function () {
		...
		Route::middleware(IsAdmin::class)->group(function () {
			...
			Volt::route('/menus/{menu}/edit', 'admin.menus.edit')->name('menus.edit');

On doit prévoir d'ajouter le lien dans la liste (admin.index) :

@if ($menu->order < $menus->count())
    <x-popover>
        ...
    </x-popover>
@endif
<x-popover>
    <x-slot:trigger>
        <x-button icon="c-arrow-path-rounded-square" link="{{ route('menus.edit', $menu->id) }}"

Et voici le code du composant :

<?php

use App\Models\Menu;
use Illuminate\Validation\Rule;
use Livewire\Attributes\{Layout, Title};
use Livewire\Volt\Component;
use Mary\Traits\Toast;

new #[Title('Edit menu'), Layout('components.layouts.admin')] 
class extends Component {
	use Toast;

	public Menu $menu;
	public string $label = '';
	public ?string $link = null;

	public function mount(Menu $menu): void
	{
		$this->menu = $menu;
		$this->fill($this->menu);
	}

	public function save(): void
	{
		$data = $this->validate([
			'label' => ['required', 'string', 'max:255', Rule::unique('menus')->ignore($this->menu->id)],
			'link'  => 'nullable|regex:/\/([a-z0-9_-]\/*)*[a-z0-9_-]*/',
		]);

		$this->menu->update($data);

		$this->success(__('Menu updated with success.'), redirectTo: '/admin/menus/index');
	}
}; ?>

<div>
    <x-header title="{{ __('Edit a menu') }}" separator progress-indicator>
        <x-slot:actions class="lg:hidden">
            <x-button icon="s-building-office-2" label="{{ __('Dashboard') }}" class="btn-outline"
                link="{{ route('admin') }}" />
        </x-slot:actions>
    </x-header>
    <x-card>
        <x-form wire:submit="save">
            <x-input label="{{ __('Title') }}" wire:model="label" />
            <x-input type="text" wire:model="link" label="{{ __('Link') }}" />
            <x-slot:actions>
                <x-button label="{{ __('Save') }}" icon="o-paper-airplane" spinner="save" type="submit"
                    class="btn-primary" />
            </x-slot:actions>
        </x-form>
    </x-card>
</div>

Rien de bien nouveau dans ce code. Voici l'apparence du formulaire :

Vérifiez la validation et la sauvegarde effective.

Un composant pour modifier un sous-menu

On a besoin d'un autre composant Volt pour gérer la modification d'un sous-menu :

php artisan make:volt admin/menus/editsub --class

On va aussi ajouter la route pour l'atteindre (réservée aux administrateurs) :

Volt::route('/submenus/{submenu}/edit', 'admin.menus.editsub')->name('submenus.edit');

On doit prévoir aussi d'ajouter le lien dans la liste (admin.index) :

@if ($submenu->order < $menu->submenus->count())
    <x-popover>
        ...
    </x-popover>
@endif
<x-popover>
    <x-slot:trigger>
        <x-button icon="c-arrow-path-rounded-square" link="{{ route('submenus.edit', $submenu->id) }}"

Et voici le code du composant (on va évidemment réutiliser le composant qu'on a créé précédemment pour le formulaire ainsi que le trait) :

<?php

use App\Models\Submenu;
use Livewire\Attributes\{Layout, Title};
use Livewire\Volt\Component;
use Mary\Traits\Toast;
use App\Traits\ManageMenus;

new #[Title('Edit Submenu'), Layout('components.layouts.admin')] 
class extends Component {
	use Toast, ManageMenus;

	public Submenu $submenu;
	public string $sublabel = '';
	public string $sublink  = '';
	public int $subPost     = 0;
	public int $subPage     = 0;
	public int $subCategory = 0;
	public int $subOption   = 1;

	public function mount(Submenu $submenu): void
	{
		$this->submenu = $submenu;
		$this->sublabel = $submenu->label;
		$this->sublink  = $submenu->link;
		$this->search();
	}

	public function saveSubmenu($menu = null): void
	{
		$data = $this->validate([
			'sublabel' => ['required', 'string', 'max:255'],
			'sublink'  => 'required|regex:/\/([a-z0-9_-]\/*)*[a-z0-9_-]*/',
		]);

		$this->submenu->update([
			'label' => $data['sublabel'],
			'link'  => $data['sublink'],
		]);

		$this->success(__('Menu updated with success.'), redirectTo: '/admin/menus/index');
	}

}; ?>

<div>
    <x-header title="{{ __('Edit a submenu') }}" separator progress-indicator>
        <x-slot:actions class="lg:hidden">
            <x-button icon="s-building-office-2" label="{{ __('Dashboard') }}" class="btn-outline"
                link="{{ route('admin') }}" />
        </x-slot:actions>
    </x-header>
    <x-card>
		@include('livewire.admin.menus.submenu-form')
    </x-card>
</div>

Vous devriez obtenir ce menu :

Vérifiez que tout fonctionne correctement.

Le menu du pied de page

On a aussi un menu au bas de la page, essentiellement destiné à mener à des pages statiques du CSM. À ce niveau, nous n'avons pas besoin de sous-menus, ce qui va simplifier le codage. D'autre part, on va réutiliser du code déjà vu précédemment.

Un composant pour la liste

On a à nouveau besoin d'un composant Volt pour gérer la liste des menus de bas de page et le code PHP qui va faire tout le traitement :

php artisan make:volt admin/menus/footers --class

On va ajouter la route pour l'atteindre (encore réservée aux administrateurs) :

Volt::route('/footers/index', 'admin.menus.footers')->name('menus.footers');

On ajoute un item dans la barre latérale (admin.sidebar) :

@if (Auth::user()->isAdmin())
    <x-menu-sub title="{{ __('Menus') }}" icon="m-list-bullet">
        ...
        <x-menu-item title="{{ __('Footer') }}" link="{{ route('menus.footers') }}" />

Une traduction :

"Footer": "Pieds de page",

Et voici le code complet du composant :

<?php

use App\Models\{Footer};
use Illuminate\Support\Collection;
use Livewire\Attributes\{Layout, Validate, Title};
use Livewire\Volt\Component;
use Mary\Traits\Toast;

new #[Title('Footer Menu'), Layout('components.layouts.admin')] 
class extends Component {
	use Toast;

	public Collection $footers;

	#[Validate('required|max:255|unique:footers,label')]
	public string $label = '';

	#[Validate('nullable|regex:/\/([a-z0-9_-]\/*)*[a-z0-9_-]*/')]
	public string $link = '';

	public function mount(): void
	{
		$this->getFooters();
	}

	public function getFooters(): void
	{
		$this->footers = Footer::orderBy('order')->get();
	}

	public function up(Footer $footer): void
	{
		$previousFooter = Footer::where('order', '<', $footer->order)
			->orderBy('order', 'desc')
			->first();

		$this->swap($footer, $previousFooter);
	}

	public function down(Footer $footer): void
	{
		$previousFooter = Footer::where('order', '>', $footer->order)
			->orderBy('order', 'asc')
			->first();

		$this->swap($footer, $previousFooter);
	}

	public function deleteFooter(Footer $footer): void
	{
		$footer->delete();
		$this->reorderFooters();
		$this->getFooters();
		$this->success(__('Footer deleted with success.'));
	}

	public function saveFooter(): void
	{
		$data          = $this->validate();
		$data['order'] = $this->footers->count() + 1;
		$newFooter     = Footer::create($data);
		$this->footers->push($newFooter);
		$this->success(__('Footer created with success.'));
	}

	private function swap(Footer $footer, Footer $previousFooter): void
	{
		$tempOrder             = $footer->order;
		$footer->order         = $previousFooter->order;
		$previousFooter->order = $tempOrder;

		$footer->save();
		$previousFooter->save();
		$this->getFooters();
	}

	private function reorderFooters(): void
	{
		$footers = Footer::orderBy('order')->get();
		foreach ($footers as $index => $footer) {
			$footer->order = $index + 1;
			$footer->save();
		}
	}
}; ?>

<div>
    <x-header title="{{ __('Footer') }}" separator progress-indicator>
        <x-slot:actions class="lg:hidden">
            <x-button icon="s-building-office-2" label="{{ __('Dashboard') }}" class="btn-outline"
                link="{{ route('admin') }}" />
        </x-slot:actions>
    </x-header>
    <x-card>

        @foreach ($footers as $footer)
            <x-list-item :item="$footer" no-separator no-hover>
                <x-slot:value>
                    {{ $footer->label }}
                </x-slot:value>
                <x-slot:sub-value>
                    {{ $footer->link }}
                </x-slot:sub-value>
                <x-slot:actions>
                    @if ($footer->order > 1)
                        <x-popover>
                            <x-slot:trigger>
                                <x-button icon="s-chevron-up" wire:click="up({{ $footer->id }})" spinner />
                            </x-slot:trigger>
                            <x-slot:content class="pop-small">
                                @lang('Up')
                            </x-slot:content>
                        </x-popover>
                    @endif
                    @if ($footer->order < $footers->count())
                        <x-popover>
                            <x-slot:trigger>
                                <x-button icon="s-chevron-down" wire:click="down({{ $footer->id }})" spinner />
                            </x-slot:trigger>
                            <x-slot:content class="pop-small">
                                @lang('Down')
                            </x-slot:content>
                        </x-popover>
                    @endif
                    <x-popover>
                        <x-slot:trigger>
                            <x-button icon="c-arrow-path-rounded-square" link="{{ route('footers.edit', $footer->id) }}"
                                class="text-blue-500 btn-ghost btn-sm" spinner />
                        </x-slot:trigger>
                        <x-slot:content class="pop-small">
                            @lang('Edit')
                        </x-slot:content>
                    </x-popover>
                    <x-popover>
                        <x-slot:trigger>
                            <x-button icon="o-trash" wire:click="deleteFooter({{ $footer->id }})"
                                wire:confirm="{{ __('Are you sure to delete this footer?') }}" spinner
                                class="text-red-500 btn-ghost btn-sm" />
                        </x-slot:trigger>
                        <x-slot:content class="pop-small">
                            @lang('Delete')
                        </x-slot:content>
                    </x-popover>
                </x-slot:actions>
            </x-list-item>
        @endforeach

    </x-card>

    <br>

    <x-card class="" title="{{ __('Create a new footer') }}">

        <x-form wire:submit="saveFooter">
            <x-input label="{{ __('Title') }}" wire:model="label" />
            <x-input type="text" wire:model="link"
                label="{{ __('Link') }} ({{ __('I.e.') }}: /{{ __('my-page') }}, /pages/slug {{ __('or') }} /pages/{{ strtolower(__('Folder')) }}/{{ __('my_page') }}-1)" />
            <x-slot:actions>
                <x-button label="{{ __('Save') }}" icon="o-paper-airplane" spinner="save" type="submit"
                    class="btn-primary" />
            </x-slot:actions>
        </x-form>
    </x-card>
</div>

Quelques traductions :

"Edit a footer": "Modifier le pied de page",
"Footer deleted with success.": "Pied de page supprimé avec succès.",
"Create a new footer": "Créer un nouveau pied de page",
"Are you sure to delete this footer?": "Etes-vous sûr de vouloir supprimer ce menu de pied de page ?",

Je ne vais pas détailler le code parce qu'il est très proche de celui qu'on a mis en œuvre pour les autres menus.

Un composant pour modifier un menu de bas de page

On a à nouveau besoin d'un composant Volt pour la modification d'un menu de base de page :

php artisan make:volt admin/menus/editfooter --class

On va ajouter la route pour l'atteindre (réservée aux administrateurs) :

Volt::route('/footers/{footer}/edit', 'admin.menus.editfooter')->name('footers.edit');

On ajoute le lien dans la liste des menus (menus.footers) :

<x-button icon="c-arrow-path-rounded-square" link="{{ route('footers.edit', $footer->id) }}"
                                class="text-blue-500 btn-ghost btn-sm" spinner />

Voici le code :

<?php

use App\Models\Footer;
use Illuminate\Validation\Rule;
use Livewire\Attributes\{Layout, Title};
use Livewire\Volt\Component;
use Mary\Traits\Toast;

new #[Title('Edit Footer'), Layout('components.layouts.admin')] 
class extends Component {
	use Toast;

	public Footer $footer;
	public string $label = '';
	public string $link  = '';

	public function mount(Footer $footer): void
	{
		$this->footer = $footer;
		$this->fill($this->footer);
	}

	public function save(): void
	{
		$data = $this->validate([
			'label' => ['required', 'string', 'max:255', Rule::unique('footers')->ignore($this->footer->id)],
			'link'  => 'regex:/\/([a-z0-9_-]\/*)*[a-z0-9_-]*/',
		]);

		$this->footer->update($data);

		$this->success(__('Footer updated with success.'), redirectTo: '/admin/footers/index');
	}
}; ?>

<div>
    <x-header title="{{ __('Edit a footer') }}" separator progress-indicator>
        <x-slot:actions class="lg:hidden">
            <x-button icon="s-building-office-2" label="{{ __('Dashboard') }}" class="btn-outline"
                link="{{ route('admin') }}" />
        </x-slot:actions>
    </x-header>
    <x-card>
        <x-form wire:submit="save">
            <x-input label="{{ __('Title') }}" wire:model="label" />
            <x-input type="text" wire:model="link" label="{{ __('Link') }}" />
            <x-slot:actions>
                <x-button label="{{ __('Save') }}" icon="o-paper-airplane" spinner="save" type="submit"
                    class="btn-primary" />
            </x-slot:actions>
        </x-form>
    </x-card>
</div>

Une traduction :

"Footer updated with success.": "Pied de page mis à jour avec succès",

Et ça devrait fonctionner :

Conclusion

Nous en avons terminé avec les menus, mais nous avons encore du travail, rendez-vous au prochain article !

Pour vous simplifier la vie, vous pouvez charger le projet dans son état à l’issue de ce chapitre.



Par bestmomo

Aucun commentaire

Article précédent : Mon CMS - Les menus (partie 1)
Article suivant : Mon CMS - Les médias (partie 1)