How to Create a Dynamic Navigation Menu in Laravel with Livewire

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.

Responsive Image Example

 

Topics we can cover in this article:

  1. Database setup - Creating and managing migration and model.
  2. Livewire Component - Developing components for displaying, creating, updating, and deleting menu items.
  3. Complete Crud Operation – Building Complete Crud for Menus.
  4. Multi-Level Dropdowns - Creating nested menus for a clean user experience.
  5. View Composer - Pass menus to all views.

Step 1 - Creating Livewire Components

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>

 

Step 2 – Creating Model and Migration

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

Model:

 

<?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');
    }
}

Migration:

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');
        });

 

Step 3 - Fetching Menu and showing into the list

Let's fetch menu records and adjust the table to show the list of menus.

Livewire class code: MenuListComponent

 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');

    }

 

Step 4 - Updating record

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.

Livewire class code: MenuEditComponent

  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');

    }

 

Step 5 – Routes

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');

 

 

Step 6 – Controller

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');
        }
    }
}

 

 

Step 7 – Menu Livewire Code

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.

Livewire Class Code:

 

 public $menus;
    public function mount()
    {
        $this->menus = Menu::orderBy('order', 'asc')->with('children')->get();
    }

View File code:

<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.

Download Complete Source Code

 

Comments (0)

No comments yet!