How to create a database-driven multi-level navigation menu using Laravel

24,795

Solution 1

So after doing much more searching and reading from different sources this is what I came up with and it's working fine:

/app/models/Navigation.php

<?php

class Navigation extends Eloquent {

    /**
     * The database table used by the model.
     *
     * @var string
     */
    protected $table = 'navigation';

    public function parent() {

        return $this->hasOne('navigation', 'id', 'parent_id');

    }

    public function children() {

        return $this->hasMany('navigation', 'parent_id', 'id');

    }  

    public static function tree() {

        return static::with(implode('.', array_fill(0, 4, 'children')))->where('parent_id', '=', NULL)->get();

    }

}

/app/controllers/HomeController.php

<?php

class HomeController extends BaseController {

    protected $layout = "layouts.main";

    public function showWelcome()
    {

        $items = Navigation::tree();

        $this->layout->content = View::make('layouts.home.index')->withItems($items);

    }

}

/app/views/layouts/home/index.blade.php

<ul>
    @foreach($items as $item)
        <li>{{ $item->title }}
            @foreach($item['children'] as $child)
            <li>{{ $child->title }}</li>
            @endforeach
        </li>
    @endforeach
</ul>

Solution 2

Laravel will not create menus for you, but it will help you write a clean code to do it.

Use your model to query your table and create the menu items:

<?php

class HomeController extends Controller {

    public function index()
    {
        $items = Navigation::all();

        return View::make('index')->withItems($items);
    }

}

And in your view you will be able to

<ul>
    @foreach($items as $item)
        <li>{{ $item->title }}</li>  
    @endforeach
</ul>

Another possibility PHP, Composer and even Laravel gives you is to install a specific package to help you do things, this one is to ease the menu generation: https://github.com/anhsaker/laravel-menu.

EDIT

Looks like you need unlimited multi-level menus and you don't want to use packages, right?

Don't forget that Laravel and Laravel Blade are both PHP, so you can create classes in PHP to help you build things in Laravel, like:

class Menu {

   public function build($collection)
   {
      // build your <li> $items

      return $items;   
   }

}

Then your controller would look like:

<?php

class HomeController extends Controller {

    public function index()
    {
        $items = with(new Menu)->build(Navigation::all());

        return View::make('index')->withItems($items);
    }

}

And in blade you'll just have to

<ul>
    {{ $items }}
</ul>

You have to understand that those things you can't get out of the box, because they are out of the scope of a framework, you can perfectly get by using PHP,

Here's some PHP logic to build multi-level menus, but, of course, you'll have to adapt to your needs:http://forrst.com/posts/Flat_Array_Multi_HTML_UL-IKp.

Share:
24,795
Hossein Jabbari
Author by

Hossein Jabbari

I have a passion for Web Design &amp; Development!

Updated on January 31, 2020

Comments

  • Hossein Jabbari
    Hossein Jabbari about 4 years

    I'm new to Laravel 4 and I'm totally confused about it's models. I'm trying to create a database-driven navigation menu for my project and all I know is I have to create a model to interact with the database (based on my knowledge from codeigniter). I have been studying alot and I'm tired of not being able to go forward, this is the code I have come up with till now:

    /app/models/navigation.php

    <?php
    
    class Navigation extends Eloquent {
    
        /**
         * The database table used by the model.
         *
         * @var string
         */
        protected $table = 'navigation';
    
        /**
         * Get the unique identifier for the menu item.
         *
         * @return mixed
         */
        public function getItemIdentifier()
        {
            return $this->getKey();
        }
    
    }
    

    And this is my Navigation database table I will use for this Model:

    enter image description here

  • Hossein Jabbari
    Hossein Jabbari over 10 years
    This is a great start, right now I am getting all the titles as an output, now how could I arrange my main categories and sub categories (sub-menus, the ones that have a parent_id) inside the output, perhaps and multidimensional array or something better than that that laravel supports out of the box?
  • Hossein Jabbari
    Hossein Jabbari over 10 years
    And about using packages, right now I'm trying to understand and write my own code with Laravel and come out of the dark before using packages.
  • Antonio Carlos Ribeiro
    Antonio Carlos Ribeiro over 10 years
    Edited to answer a little more.
  • Michael Schinis
    Michael Schinis over 9 years
    This saved me hours of hours of building, testing, removing, rewriting code. Thanks :)
  • Banago
    Banago over 9 years
    This looks very neat. Question, what does this piece do: implode('.', array_fill(0, 100, 'children')?
  • Hossein Jabbari
    Hossein Jabbari over 9 years
    No you can keep on going for unlimited levels
  • Manan
    Manan over 9 years
    It might work for n-levels but its a very costly affair.. especially when you're gonna retrieve every now and then... Kindly check the Nested model implementation for SQL. Checkout the following link: Nested Set Model for SQL and the following Laravel package... Perfect for any multilevel navigation or tree type structure Laravel LazyChaser Nested Set Implementation
  • Alexandre Martini
    Alexandre Martini over 9 years
    I'm new to Laravel and I'm building kind of the same structure as you did, only with a few more classes to make it possible to control user permissions (User has many Permissions, which has many Routes, which are related to MenuOptions). What I find strange is that the Laravel documentation says that the inverse of a hasOne relationship is a BelongsTo. In this case, a belongsTo does not work. You have to use hasMany to find the children of a menu option. Good to find someone confirming this. ;)
  • lyhong
    lyhong almost 9 years
    can we store route in database without writing any code?
  • Manoj Sharma
    Manoj Sharma over 8 years
    It is not working in L5. It displaying child twice. As a child and as well as separate.
  • Manoj Sharma
    Manoj Sharma over 8 years
    Perfect, I have just combined it with my existing code and work perfectly. Thanks buddy. I am looking for exactly this kind of solution.
  • antoniputra
    antoniputra over 8 years
    Awesome, this is menu based eloquent. array_fill() as nesting level. for performance reason, I recommended to set nesting level 3 instead 100. public static function tree($level = 3) { return static::with(implode('.', array_fill(0, $level, 'children')))->where('parent_id', '=', NULL)->get(); }
  • Hossein Jabbari
    Hossein Jabbari about 7 years
    @antoniputra Changed it to 4 in case someone needed a bit more.
  • antoniputra
    antoniputra about 7 years
    @HosseinJabbari 4 its okay instead of 100
  • Youssef Boudaya
    Youssef Boudaya over 4 years
    this answer helped me a lot the problem is i want to create folders hierarchy like in windows is it possible to get all children in a tree without having to make a foreach loop for every level ?.