How to create nested menu in laravel
Author
Author

Robert Nicjoo [68]

I love Laravel & WordPress, interested to pass little knowledge that I have to others.

Qr-Code

Scan to visit on mobile

TJD STUDIO is Hiring

Photographer & Videographer

Apply now Jabodetabek

How to create nested menu in laravel

Overview

Today I will cover one of the most wanted & useful topics "Nested Menu in Laravel

In this article we will demonstrates usage of Nestable plugin jQuery plugin to provide the user with nice menu ordering experience without a page refresh.

This article is unique in web, at least I couldn’t find any similar article for nestable menus in laravel or even close to this.

 

What we need

Creating the menu controller in app/controllers/Admin/MenuController.php and the menu model is in app/models/Menu.php

 

A note on the data structure for the menu

The important columns of the "menus" table are:

  • id
  • parent_id
  • order

With these 3 fields we can build nested menus as many levels deep as you want. The Nestable plugin helps modify the values of these fields for the appropriate rows of data.

Use of recursion

The hard part that took me a long time to build is a very small function inside of app/models/Menu.php:

 

public function buildMenu($menu, $parentid = 0) 
{ 
  $result = null;
  foreach ($menu as $item) 
    if ($item->parent_id == $parentid) { 
      $result .= "<li class='dd-item nested-list-item' data-order='{$item->order}' data-id='{$item->id}'>
      <div class='dd-handle nested-list-handle'>
        <span class='glyphicon glyphicon-move'></span>
      </div>
      <div class='nested-list-content'>{$item->label}
        <div class='pull-right'>
          <a href='".url("admin/menu/edit/{$item->id}")."'>Edit</a> |
          <a href='#' class='delete_toggle' rel='{$item->id}'>Delete</a>
        </div>
      </div>".$this->buildMenu($menu, $item->id) . "</li>"; 
    } 
  return $result ?  "\n<ol class=\"dd-list\">\n$result</ol>\n" : null; 
} 

This function uses recursion to display the menu to the user even if the menu is many many levels deep. This function alone can save you a bunch of time.

 

Let’s code

 

Step 1

Make Menu model and migration for it. Open your terminal and type command below

Php artisan make:model Menu -m

With this artisan command we tell laravel to make model named Menu in App folder and migration file for our schema in database/migrations folder.

 

Step 2

Make Menu controller. Open your terminal and type command below

Php artisan make:controller MenuController

With this artisan command we tell laravel to make controller named MenuController in App\Http\Controllers folder.

 

Step 3

  1. Make schema

Open your migration file that you created in step 1 and add following codes,

Schema::create('top_menus', function (Blueprint $table) {
  $table->increments('id');
  $table->string('title')->nullable();
  $table->string('slug')->nullable();
  $table->integer('parent_id')->unsigned()->nullable();
  $table->integer('order')->unsigned()->default(0);
  $table->timestamps();
});
Schema::table('top_menus', function (Blueprint $table) {
  $table->foreign('parent_id')->references('id')->on('top_menus')->onUpdate('cascade')->onDelete('cascade');
});

remember columns id , parent_id & order are important to us rest of them are optional.

Now save the file and close it.

 

  1. Prepare model

 

Open your Menu model and add following codes,

protected $fillable = [
  'title', 'slug', 'order', 'parent_id'
];

public function buildMenu($menu, $parentid = 0) 
{ 
  $result = null;
  foreach($menu as $item) 
    if ($item->parent_id == $parentid) { 
	$result .= "<li class='dd-item nested-list-item' data-order='{$item->order}' data-id='{$item->id}'>
	<div class='dd-handle nested-list-handle'>
          <i class='fas fa-arrows-alt'></i>
	</div>
	<div class='nested-list-content'>{$item->title}
	  <div class='float-right'>
	    <a href='/admin/menustop/{$item->id}'>Edit</a> |
	    <a href='#' class='delete_toggle text-danger' rel='{$item->id}'>Delete</a>
	  </div>
	</div>".$this->buildMenu($menu, $item->id) . "</li>"; 
    } 
    return $result ?  "\n<ol class=\"dd-list\">\n$result</ol>\n" : null; 
}
// Getter for the HTML menu builder
public function getHTML($items)
{
  return $this->buildMenu($items);
}

 

Now save and close it.

 

  1. Migrate your schema with following command
php artisan migrate

 

Step 4

Now we have everything ready let’s take care of our MenuController and then make our views.

Open your MenuController which you create in step 2 and add following code.

use Illuminate\Support\Facades\Input;
use App\Menu;


//index page and return menu data by code we defined in our model (getHTML)
public function index()
{		
  //menu
  $menus = TopMenu::orderby('order', 'asc')->get();

  $menu = new Menu;
  $menu = $menu->getHTML($menus);

  return view('admin.menus.index', compact('menus', 'menu'));
}

//get edit page
public function getEdit($id)
{	
  $item = Menu::findOrFail($id);
  return view('admin.menus.edit', compact('item'));
}

// same as update function when you make resource controller
public function postEdit(TopMenuRequest $request, $id) //done
{
  $item = TopMenu::find($id);
  $item = TopMenu::where('id',$id)->first();
  $item->title = $request->input('title');
  $item->slug = $request->input('slug');
  $item->parent_id = $request->input('parent_id');
  $item->save();
  return redirect()->route('menus', $item->id)->with('success', 'Item, '. $item->title.' updated');
}

// AJAX Reordering function (update menu item orders by ajax)
public function postIndex(TopMenuRequest $request)
{	
  $source = $request->input('source');
  $destination = $request->input('destination');
  $item = TopMenu::find($source);
  $item->parent_id = $destination;  
  $item->save();
        
  $ordering = json_decode(Input::get('order'));
  $rootOrdering = json_decode(Input::get('rootOrder'));
  if($ordering){
    foreach($ordering as $order => $item_id){
      if($itemToOrder = Menu::find($item_id)){
	 $itemToOrder->order = $order;
	 $itemToOrder->save();
      }
    }
  } else {
     foreach($rootOrdering as $order=>$item_id){
       if($itemToOrder = Menu::find($item_id)){
	 $itemToOrder->order = $order;
	 $itemToOrder->save();
       }
     }
  }
  return 'ok ';
}

//store function (create new item)
public function postNew(TopMenuRequest $request)
{      
    $item = new Menu;
    $item->title = $request->input('title');
    $item->slug = $request->input('slug');
    $item->order = TopMenu::max('order')+1;
    $item->save();
    
    return redirect()->back();
}


//destroy function
public function postDelete(Request $request)
{
  $id = $request->input('delete_id');
  // Find all items with the parent_id of this one and reset the parent_id to null
  $items = TopMenu::where('parent_id', $id)->get()->each(function($item)
  {
    $item->parent_id = '';  
    $item->save();  
  });
  // Find and delete the item that the user requested to be deleted
  $item = TopMenu::findOrFail($id);
  $item->delete();
  Session::flash('danger', 'Menu Item successfully deleted.');
  return redirect()->back();
}

 

Step 5

Adding routes

add this routes to your web.php file

//menu
    Route::get('menus','[email protected]')->name('menus'); //index

    Route::post('menustop/reorder','[email protected]'); //re-order

    Route::post('menustop/new','[email protected]')->name('topnew'); //create

    Route::get('menustop/{id}','[email protected]'); //edit page

    Route::put('menustop/{id}','[email protected]')->name('topeditupdate'); //update data (edit)

    Route::delete('topmenudelete','[email protected]'); //delete item

    Route::get('getCategoryDetails/{id}','[email protected]'); //get category title and slug based on selected option

 

Step 6

 

Creating views

In views folder create new folder name it admin and inside that create new folder name it menus, we will make all this views in menus folder.

views->admin->menus-> (our blades here)

 

Create index.blade.php add this codes: (remember change the design as your admin panel style goes)

@extends('layouts.app')

@section('title', 'Menus')

@section('styles')
<link rel="stylesheet" href="{{asset('css/nestable.css')}}">
@endsection

@section('content')
<div class="container">
    {{-- menu --}}
    <div class="row justify-content-center">
        <div class="col-md-12 mt-5">
            <div class="card">
                <div class="card-body">
                    <div class="header-title">
                        Top Menu
                        <span class="float-right">
                            <a href="#newModal" class="btn btn-default pull-right" data-toggle="modal">
                                <i class="fas fa-plus"></i> Create menu item
                            </a>
                        </span>
                    </div>

                    {{-- new --}}
                    <div class="row mt-4 mb-4">
                        <div class="col-md-8">  
                            <div class="dd" id="nestable">
                                {!! $menu !!}
                            </div>
                    
                            <p id="success-indicator" style="display:none; margin-right: 10px;">
                                <i class="fas fa-check-circle"></i> Menu order has been saved
                            </p>
                        </div>
                        <div class="col-md-4">
                            <div class="card">
                                <div class="card-body">
                                    <p>Drag items to move them in a different order <br> <span class="text-info">Supports (2) level deep</span></p>
                                </div>
                            </div>
                        </div>
                    </div>
                        
                    <!-- Create new item Modal -->
                    <div class="modal fade" id="newModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
                        <div class="modal-dialog" role="document">
                            <div class="modal-content">

                                <div class="modal-header">
                                    <h5 class="modal-title">Provide details of new menu item</h5>
                                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                                        <span aria-hidden="true">&times;</span>
                                    </button>
                                </div>

                                {{ Form::open(array('route'=>'topnew','class'=>'form-horizontal'))}}
                                    <div class="modal-body">
                                        <div class="form-group row">
                                            <label for="title" class="col-md-3 control-label">Title</label>
                                            <div class="col-md-9">
                                            {{ Form::text('title',null,array('class'=>'form-control'))}}
                                            </div>
                                        </div>
                                        <div class="form-group row">
                                            <label for="slug" class="col-md-3 control-label">Slug</label>
                                            <div class="col-md-9">
                                            {{ Form::text('slug',null,array('class'=>'form-control'))}}
                                            </div>
                                        </div>
                                    </div>
                                    <div class="modal-footer">
                                    <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
                                    <button type="submit" class="btn btn-primary">Create</button>
                                    </div>
                                {{ Form::close()}}
                            </div><!-- /.modal-content -->
                        </div><!-- /.modal-dialog -->
                    </div><!-- /.modal -->
                          
                    <!-- Delete item Modal -->
                    <div class="modal border-danger fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
                        <div class="modal-dialog">
                            <div class="modal-content">

                                <div class="modal-header bg-danger text-white">
                                    <h5 class="modal-title">Delete Item</h5>
                                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                                </div>

                                {{ Form::open(array('url'=>'/admin/topmenudelete', 'method' => 'DELETE')) }}  
                                    <div class="modal-body">
                                        <p>Are you sure you want to delete this menu item?</p>
                                    </div>
                                    <div class="modal-footer">
                                        <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
                                        <input type="hidden" name="delete_id" id="postvalue" value="" />
                                        <input type="submit" class="btn btn-danger" value="Delete Item" />
                                    </div>
                                {{ Form::close() }}
                            </div><!-- /.modal-content -->
                        </div><!-- /.modal-dialog -->
                    </div><!-- /.modal -->
                    {{-- new --}}
                </div>
            </div>
        </div>
    </div>

</div>
@endsection

@section('scripts')
<script src="{{asset('js/jquery.nestable.js')}}"></script>

{{-- topmenu --}}
<script type="text/javascript">
    $(document).ready(function() {
        $(function() {
            $('.dd').nestable({ 
                dropCallback: function(details) {
                
                var order = new Array();
                $("li[data-id='"+details.destId +"']").find('ol:first').children().each(function(index,elem) {
                    order[index] = $(elem).attr('data-id');
                });
                if (order.length === 0){
                    var rootOrder = new Array();
                    $("#nestable > ol > li").each(function(index,elem) {
                    rootOrder[index] = $(elem).attr('data-id');
                    });
                }
                var token = $('form').find( 'input[name=_token]' ).val();
                $.post('{{url("admin/menustop/reorder/")}}', 
                    {
                        source : details.sourceId, 
                        destination: details.destId, 
                        order:JSON.stringify(order),
                        rootOrder:JSON.stringify(rootOrder),
                        _token: token 
                    },
                    function(data) {
                    // console.log('data '+data); 
                    })
                .done(function() { 
                    $( "#success-indicator" ).fadeIn(100).delay(1000).fadeOut();
                })
                .fail(function() {  })
                .always(function() {  });
                }

            });
            //delete item
            $('.delete_toggle').each(function(index,elem) {
                $(elem).click(function(e){
                e.preventDefault();
                $('#postvalue').attr('value',$(elem).attr('rel'));
                $('#deleteModal').modal('toggle');
                });
            });
        });
    });
</script>
@endsection

 

Create edit.blade.php and add following code.

@extends('layouts.app')

@section('title', 'Edit Menu Item')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-12 mt-5">
            <div class="card">
                <div class="card-header">
                    <h2>
                        Edit Menu Item
                        <span class="float-right">
                            <a class="btn btn-outline-danger" href="{{route('menus')}}">Back</a>
                        </span>
                    </h2>
                </div>

                <div class="card-body">
                    {{ Form::model($item, array('route' => array('footeditupdate', $item->id), 'method' => 'PUT')) }}
                    <div class="row">
                        <div class="col-md-12 mt-3">
                            {{ Form::label('title', 'Title') }}
                            {{ Form::text('title', null, array('class' => 'form-control')) }}
                        </div>
                        <div class="col-md-12 mt-3">
                            {{ Form::label('slug', 'Slug') }}
                            {{ Form::text('slug', null, array('class' => 'form-control')) }}
                        </div>
                        <div class="col-md-12 mt-3 text-center">
                            {{ Form::submit('Update', array('class' => 'btn btn-primary')) }}
                        </div>
                    </div>
                    {{Form::close()}}
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

 

That's all you need to make nestable menu in your laravel application, hope you find this article useful to you. If so, don't forget to hit love emoji below this post.

 

How do you like this article?

From Store

JobGard
JobGard
$ 2,000
TJD One
TJD One
$ 27
Back to top