
In this article you will learn how to create a fully Dynamic navigation Menu in Laravel With Livewire. We will see how we can create a complete CRUD (Create, Read, Update, Delete) in Laravel Livewire for Dynamic Navigation. After that, you will be able to build a dynamic navigation menu for your website easily instead of building a static navigation menu in Laravel. Here we will guide you how you can build a Flexible and multi-level menu.
Now we create livewire components to show the list of menus and edit. make sure to follow the steps to create components and update the code.
Php artisan make:livewire Front/Menu/MenuListComponent
After Creating the update code bellowed.
@section('page-title')
Menu List
@endsection
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h4 class="card-title mb-0">Menu</h4>
<button type="button" class="btn btn-primary mb-2" data-bs-toggle="modal" data-bs-target="#addMenu">Add
Menu</button>
</div>
<div class="card-body">
<div class="row mt-3">
@if ($menus->count() > 0)
<div class="table-responsive">
<table id="example3" class="display" style="min-width: 845px">
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Url</th>
<th>Sub</th>
<th>Created At</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@foreach ($menus as $index => $menu)
<tr>
<td>{{ $index + 1 }}</td>
<td>{{ $menu->title }}</td>
<td>{{ $menu->url }}</td>
<td>{{ $menu->parent->title ?? 'N/A' }}</td>
<td>{{ \Carbon\Carbon::parse($menu->created_at)->format('d, M Y') }}
</td>
<td>
<div class="d-flex">
<a href="{{ route('menu.edit', ['id' => $menu->id]) }}"
class="btn btn-primary shadow btn-xs sharp me-1"><i
class="fa-solid fa-eye"></i></a>
<button data-bs-toggle="modal" type="button"
class="btn btn-danger shadow btn-xs sharp"
data-bs-target="#confirmDeleteModal"
onclick="setDeleteUrl('{{ route('menu.delete', [$menu->id]) }}');">
<i class="fa fa-trash"></i>
</button>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<p class="text-center text-info">No record found</p>
@endif
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade bd-example-modal-lg" id="addMenu">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add new Menu</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form wire:submit.prevent="storeMenu">
<div class="row">
<div class="col-md-6 mb-3">
<label for="title" class="form-label">Title <span
class="text-danger">*</span></label>
<input type="text" id="title"
class="form-control @error('title') is-invalid @enderror" wire:model="title"
placeholder="Enter title">
@error('title')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6 mb-3">
<label for="title" class="form-label">Url <span class="text-danger">*</span></label>
<input type="text" id="title"
class="form-control @error('url') is-invalid @enderror" wire:model="url"
placeholder="Enter url">
@error('url')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6 mb-3">
<label for="title" class="form-label">Order <span
class="text-danger">*</span></label>
<input type="number" id="title"
class="form-control @error('url') is-invalid @enderror" wire:model="order"
placeholder="Enter Order">
@error('order')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6 mb-3">
<label for="type" class="form-label">Parent<span
class="text-danger">*</span></label>
<select id="type" class="form-control @error('type') is-invalid @enderror"
wire:model="parent_id">
<option value="">Select Type</option>
@foreach ($menus as $item)
<option value="{{ $item->id }}">{{ $item->title }}</option>
@endforeach
</select>
@error('type')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="d-flex justify-content-end mt-3 position-relative">
<!-- Button (Visible when not loading) -->
<button type="submit" class="btn btn-primary" wire:loading.remove
wire:target="title, type">
Save
</button>
<!-- Loader (Visible when loading) -->
<span wire:loading wire:target="title, type" class="spinner-border text-primary ms-2"
role="status">
<span class="visually-hidden">Loading...</span>
</span>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
Php artisan make:livewire Front/Menu/MenuEditComponent
After Creating update code given bellow.
@section('page-title')
Menu Edit
@endsection
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h4 class="card-title mb-0">Menu</h4>
</div>
<div class="card-body">
<div class="row mt-3">
<form wire:submit.prevent="storeMenu">
<div class="row">
<div class="col-md-6 mb-3">
<label for="title" class="form-label">Title <span
class="text-danger">*</span></label>
<input type="text" id="title"
class="form-control @error('title') is-invalid @enderror" wire:model="title"
placeholder="Enter title">
@error('title')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6 mb-3">
<label for="title" class="form-label">Url <span
class="text-danger">*</span></label>
<input type="text" id="title"
class="form-control @error('url') is-invalid @enderror" wire:model="url"
placeholder="Enter url">
@error('url')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6 mb-3">
<label for="title" class="form-label">Order <span
class="text-danger">*</span></label>
<input type="number" id="title"
class="form-control @error('url') is-invalid @enderror" wire:model="order"
placeholder="Enter Order">
@error('order')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-md-6 mb-3">
<label for="type" class="form-label">Parent<span
class="text-danger">*</span></label>
<select id="type" class="form-control @error('type') is-invalid @enderror"
wire:model="parent_id">
<option value="">Select Type</option>
@foreach ($menus as $item)
<option value="{{ $item->id }}">{{ $item->title }}</option>
@endforeach
</select>
@error('type')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="d-flex justify-content-end mt-3 position-relative">
<!-- Button (Visible when not loading) -->
<button type="submit" class="btn btn-primary" wire:loading.remove
wire:target="title, type">
Save
</button>
<!-- Loader (Visible when loading) -->
<span wire:loading wire:target="title, type"
class="spinner-border text-primary ms-2" role="status">
<span class="visually-hidden">Loading...</span>
</span>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
After creating components we need model and migration for menu. follow the steps for creating model and migration and then update step by step.
We will add column title, url, order and parent id for relationship for multi level submenu.
Php artisan make:model Menu -m
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Menu extends Model
{
protected $fillable = [
'title',
'url',
'parent_id',
'order',
];
// relation to fetch child menu
public function children()
{
return $this->hasMany(Menu::class, 'parent_id')->orderBy('order');
}
// relation to fetch parent menu
public function parent()
{
return $this->belongsTo(Menu::class, 'parent_id');
}
}
Schema::create('menus', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('parent_id')->nullable(); // for submenu
$table->string('title');
$table->string('url');
$table->integer('order')->default(0);
$table->timestamps();
$table->foreign('parent_id')->references('id')->on('menus')->onDelete('cascade');
});
Let's fetch menu records and adjust the table to show the list of menus.
public $title, $url, $parent_id, $order;
public $menus;
public function mount()
{
$this->menus = Menu::orderBy('created_at', 'desc')->with(['children', 'parent'])->get();
}
protected $rules = [
'title' => 'required|string|max:255',
'url' => 'required|string|max:255',
'order' => 'required',
'parent_id' => 'nullable|exists:menus,id',
];
public function storeMenu()
{
$this->validate();
Menu::create([
'title' => $this->title,
'url' => $this->url,
'order' => $this->order,
'parent_id' => $this->parent_id,
]);
// Clear input fields after successful save
$this->reset(['title', 'url', 'parent_id', 'order']);
return redirect()->route('menu.list')->with('message', 'Menu added');
}
Now move on to updating a record of the menu here we will create a route and then adjust the code for updating the menu.
public $title, $url, $parent_id, $order, $menuId;
public $menus;
public function mount($id)
{
$menu = Menu::find($id);
$this->title = $menu->title;
$this->url = $menu->url;
$this->order = $menu->order;
$this->parent_id = $menu->parent_id;
$this->menuId = $menu->id;
$this->menus = Menu::orderBy('created_at', 'desc')->with(['children', 'parent'])->get();
}
protected $rules = [
'title' => 'required|string|max:255',
'url' => 'required|string|max:255',
'order' => 'required',
'parent_id' => 'nullable|exists:menus,id',
];
public function storeMenu()
{
$this->validate();
$menu = Menu::find($this->menuId);
$menu->title = $this->title;
$menu->url = $this->url;
$menu->parent_id = $this->parent_id;
$menu->order = $this->order;
$menu->save();
// Clear input fields after successful save
$this->reset(['title', 'url', 'parent_id', 'order']);
return redirect()->route('menu.list')->with('info', 'Menu Updated');
}
Update your routes for the menu.
use App\Http\Controllers\ActionhandlerController;
use App\Livewire\DashboardComponent;
use App\Livewire\Authentication\AdminLoginComponent;
use App\Livewire\Front\HomePageComponent;
use App\Livewire\Front\Menu\MenuEditComponent;
use App\Livewire\Front\Menu\MenuListComponent;
use Illuminate\Support\Facades\Route;
Route::get('/', HomePageComponent::class)->name('home');
Route::get('/admin/login', AdminLoginComponent::class)->name('login');
Route::get('admin/dashboard', DashboardComponent::class)->name('dashboard');
Route::get('/admin/menu', MenuListComponent::class)->name('menu.list');
Route::get('/admin/menu/{id}/edit', MenuEditComponent::class)->name('menu.edit');
Route::get('/admin/menu/{id}/delete', [ActionhandlerController::class,'deleteMenu'])->name('menu.delete');
Now the last step create a controller and update code given bellow.
Php artisan make:controller ActionHandlerController
class ActionhandlerController extends Controller
{
// function to delete a menu record
public function deleteMenu($id)
{
$menu = Menu::find($id);
if ($menu) {
$menu->delete();
return redirect()->route('menu.list')->with('message', 'Menu record deleted');
} else {
return redirect()->route('menu.list')->with('error', 'Menu not found');
}
}
}
Create livewire component where you want to show menu, in our project we already setup a template code is given below.
Php artisan make:livewire Front/Partials/FrontHeaderComponent.
public $menus;
public function mount()
{
$this->menus = Menu::orderBy('order', 'asc')->with('children')->get();
}
<header id="header" class="header d-flex align-items-center fixed-top">
<div class="container position-relative d-flex align-items-center justify-content-between">
<a href="index.html" class="logo d-flex align-items-center me-auto me-xl-0">
<h1 class="sitename">Hash Hives</h1>
</a>
<nav id="navmenu" class="navmenu">
<ul>
@foreach ($menus as $menu)
@if ($menu->parent_id === null)
<li class="{{ $menu->children->isNotEmpty() ? 'dropdown' : '' }}">
<a href="{{ $menu->url }}"
class="{{ request()->is(ltrim($menu->url, '/')) ? 'active' : '' }}"
@if ($menu->children->isNotEmpty()) data-bs-toggle="dropdown" @endif>
<span>{{ $menu->title }}</span>
@if ($menu->children->isNotEmpty())
<i class="bi bi-chevron-down toggle-dropdown"></i>
@endif
</a>
@if ($menu->children->isNotEmpty())
<ul>
@foreach ($menu->children as $child)
<li class="{{ $child->children->isNotEmpty() ? 'dropdown' : '' }}">
<a href="{{ $child->url }}"
@if ($child->children->isNotEmpty()) data-bs-toggle="dropdown" @endif>
<span>{{ $child->title }}</span>
@if ($child->children->isNotEmpty())
<i class="bi bi-chevron-down toggle-dropdown"></i>
@endif
</a>
@if ($child->children->isNotEmpty())
<ul>
@foreach ($child->children as $grandchild)
<li><a
href="{{ $grandchild->url }}">{{ $grandchild->title }}</a>
</li>
@endforeach
</ul>
@endif
</li>
@endforeach
</ul>
@endif
</li>
@endif
@endforeach
</ul>
<i class="mobile-nav-toggle d-xl-none bi bi-list"></i>
</nav>
<a class="cta-btn" href="#about">Get Started</a>
</div>
</header>
Now Everything is done just start the server and check.
Comments (0)
No comments yet!