Laravel-Backpack/MenuCRUD

Menu for front page tutorial

shinokada opened this issue · 14 comments

Hi I am wondering if you have any tutorial how to display menus for front page usage.
Thanks in advance.

HI @shinokada ,

There's no nice way to do it right now - for a few projects I just hacked a 2-level menu this way:

@if ($menu_items->count())
    @foreach ($menu_items as $k => $menu_item)
        @if (($menu_item->page_id && is_object($menu_item->page)) || !$menu_item->page_id)
          @if ($menu_item->children->count())
            <li class="navitem dropdown {{ ($k==0)?' fistitem':'' }}">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ $menu_item->name }} <span class="caret"></span></a>
              <ul class="dropdown-menu">
                @foreach ($menu_item->children as $i => $child)
                  <li class="{{ ($child->url() == Request::url())?'active':'' }}"><a href="{{ $child->url() }}">{{ $child->name }}</a>
                  </li>
                @endforeach
              </ul>
            </li>
          @else
            <li class="navitem {{ ($k==0)?' fistitem':'' }} {{ ($menu_item->url() == Request::url())?' active':'' }}">
                <a href="{{ $menu_item->url() }}">{{ $menu_item->name }}</a>
            </li>
          @endif
        @endif
    @endforeach
@endif

A bootstrap-friendly, infinite level should and will be done, with a clean syntax.

I'll leave this issue open until we do, so we don't forget. You're more than welcome to contribute, if you'll write a solution for that yourself.

Cheers!

Ok thanks. I will see if I can.

Hi Im confused and lost on some point:
1.
I'm getting Undefined variable: menu_items when using the above code on front-end..
2.
In the read-me files, the instructions are either wrong or i am missing something
it says to add service provider class as: 'Backpack\MenuCRUD\MenuCRUDServiceProvider',
isn't it suppose to be like this: Backpack\MenuCRUD\MenuCRUDServiceProvider::class, ?
3.
When from the admin section im trying add a new menu, there is no select options for the pages ive created when i select page link as type.. isnt it suppose to show the pages created in pagemanager crud?
4.
If I'm using composer install method.. there is no entry on route.php as its mentioned in the manual install section? is it not required if package install is used?

Hi @mpixelz ,

  1. To have the $menu_items variable in your view you need to get it in your controller, then pass it to the view. You can do it manually in each of your controller methods, something like this:
$this->data['menu_items'] = MenuItem::getTree();
return view('your_view_name', $this->data);

Or better yet, with a view composer, which makes sure that every time you use a view, you have those variables already loaded. It would be something like:

<?php

namespace App\Http\ViewComposers;

use Illuminate\Contracts\View\View;
use App\Models\MenuItem;
use App\Models\SecondaryMenuItem;

class MainComposer
{
    /**
     * Bind data to the view.
     *
     * @param  View  $view
     * @return void
     */
    public function create(View $view)
    {
        $menu_items = MenuItem::getTree();
        $view->with('menu_items', $menu_items);
    }
}
  1. They're both ok. They'll both work.

  2. That's odd. I've just tried it and it does for me:
    screen shot 2016-09-16 at 10 21 01
    Are you sure you have entries in your "Pages" to show there?

  3. With the composer install you don't need to specify a route manually, the package does it for you.

Cheers, sorry you're having problems.

Thanks for all the help.. issue 2,3 and 4 sorted.. but the menu_items issue is still there..
heres my code:
routes.php
Route::get('/', 'HomeController@index');

HomeController.php

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;

class HomeController extends Controller
{
     public function index()
    {
    $this->data['menu_items'] = MenuItem::getTree();
        return view('welcome', $this->data);
    }
}

App\Providers\ComposerServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class ComposerServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->composeNavigation(); 
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
    private function composeNavigation()
    {
         view()->composer('app', 'App\Http\ViewComposers\MenuComposer');
    }
}

App\Http\ViewComposers\MenuComposer.php

<?php

namespace App\Http\ViewComposers;

use Illuminate\Contracts\View\View;
use App\Models\MenuItem;
use App\Models\SecondaryMenuItem;

class MenuComposer
{
    /**
     * Bind data to the view.
     *
     * @param  View  $view
     * @return void
     */
    public function create(View $view)
    {
        $menu_items = MenuItem::getTree();
        $view->with('menu_items', $menu_items);
    }
}

welcome.blade.php

@if ($menu_items->count())
    @foreach ($menu_items as $k => $menu_item)
        @if (($menu_item->page_id && is_object($menu_item->page)) || !$menu_item->page_id)
          @if ($menu_item->children->count())
            <li class="navitem dropdown {{ ($k==0)?' fistitem':'' }}">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ $menu_item->name }} <span class="caret"></span></a>
              <ul class="dropdown-menu">
                @foreach ($menu_item->children as $i => $child)
                  <li class="{{ ($child->url() == Request::url())?'active':'' }}"><a href="{{ $child->url() }}">{{ $child->name }}</a>
                  </li>
                @endforeach
              </ul>
            </li>
          @else
            <li class="navitem {{ ($k==0)?' fistitem':'' }} {{ ($menu_item->url() == Request::url())?' active':'' }}">
                <a href="{{ $menu_item->url() }}">{{ $menu_item->name }}</a>
            </li>
          @endif
        @endif
    @endforeach
@endif

ERROR:

FatalErrorException in HomeController.php line 13:
Class 'App\Http\Controllers\MenuItem' not found

Ok. I think it's better if we stick with sending the information from the controller to the view for now. No need to complicate things with view composers. So we'll stick with these two files:

  • HomeController.php
  • welcome.blade.php

From what I can see, everything is properly configured. Apart from the fact that your controller should throw an error because MenuItem is not loaded on top (use Backpack\MenuCRUD\app\Models\MenuItem;).

Can you please do a a dump in HomeController before returning the view, and tell me its results?

dd($this->data);

Sorry for formatting. On mobile. Cheers!

Thanks for all the help.. yeah it works now.. here is the dd result:
array:1 [▼ "menu_items" => Collection {#410 ▼ #items: array:2 [▼ 0 => MenuItem {#406 ▼ #table: "menu_items" #fillable: array:5 [▶] #connection: null #primaryKey: "id" #keyType: "int" #perPage: 15 +incrementing: true +timestamps: true #attributes: array:13 [▶] #original: array:12 [▶] #relations: [] #hidden: [] #visible: [] #appends: [] #guarded: array:1 [▶] #dates: [] #dateFormat: null #casts: [] #touches: [] #observables: [] #with: [] #morphClass: null +exists: true +wasRecentlyCreated: false } 1 => MenuItem {#407 ▼ #table: "menu_items" #fillable: array:5 [▶] #connection: null #primaryKey: "id" #keyType: "int" #perPage: 15 +incrementing: true +timestamps: true #attributes: array:13 [▶] #original: array:12 [▶] #relations: [] #hidden: [] #visible: [] #appends: [] #guarded: array:1 [▶] #dates: [] #dateFormat: null #casts: [] #touches: [] #observables: [] #with: [] #morphClass: null +exists: true +wasRecentlyCreated: false } ] } ]

but now i will have to pass this in all the controllers? as its a navigation system and all the pages will require this nav.
i'll try to get it to work using view composer so it stays on all the pages old and new.
thanks a lot!

Here is the ViewComposer Method.. this will allow to have menu on all the pages without having to call menus on each controller.

Create a Service Provider in App/providers/ComposerServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class ComposerServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        view()->composer('*',  'App\Http\ViewComposers\MenuComposer');
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

add service provider in config/app.php

App\Providers\ComposerServiceProvider::class,

App/Http/ViewComposers/MenuComposer.php

<?php

namespace App\Http\ViewComposers;

use Illuminate\View\View;
use Backpack\MenuCRUD\app\Models\MenuItem;

class MenuComposer
{
    public $data = [];
    /**
     * Create a movie composer.
     *
     * @return void
     */
    public function __construct()
    {
        $this->data['menu_items'] = MenuItem::getTree();
    }

    /**
     * Bind data to the view.
     *
     * @param  View  $view
     * @return void
     */
    public function compose(View $view)
    {
        $view->with('menu_items', end($this->data));
    }
}

Then you can use this to display menu wherever you want:

@if ($menu_items->count())
    @foreach ($menu_items as $k => $menu_item)
        @if (($menu_item->page_id && is_object($menu_item->page)) || !$menu_item->page_id)
          @if ($menu_item->children->count())
@foreach ($menu_item->children as $i => $child)
            <li class="navitem dropdown {{ ($k==0)?' fistitem':'' }} {{ ($child->url() == Request::url())?'active':'' }}">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ $menu_item->name }} <span class="caret"></span></a>
              <ul class="dropdown-menu">
                @foreach ($menu_item->children as $i => $child)
                  <li class=""><a href="{{ $child->url() }}">{{ $child->name }}</a>
                  </li>
                @endforeach
              </ul>
            </li>
@endforeach
          @else
            <li class="navitem {{ ($k==0)?' fistitem':'' }} {{ ($menu_item->url() == Request::url())?' active':'' }}">
                <a href="{{ $menu_item->url() }}">{{ $menu_item->name }}</a>
            </li>
          @endif
        @endif
    @endforeach
@endif

I've followed mpixelz tutorial but in my views I solved reading this: https://www.sitepoint.com/laravel-blade-recursive-partials/

Here my code:
On sidebar.blade.php:

            <!-- My menuCrud -->
            @if ($menu_items->count())
                @foreach ($menu_items as $menu_item)
                    @include('layouts.inc.partials.menu', $menu_item)
                @endforeach
            @endif
            <!-- /My menuCrud -->

On layouts.inc.partials.menu.blade.php:

@if (($menu_item->page_id && is_object($menu_item->page)) || !$menu_item->page_id)
<li>
    <a {{ ($menu_item->type == 'external_link') ? 'target="_blank" ' : '' }}
        href="{{ ($menu_item['children']->count()) ? '#': $menu_item->url() }}">
        <i class="fa {{ $menu_item->icon }} fa-fw"></i><!-- Added a icon_picker field on MenuItemCrudController --> 
        {{ $menu_item->name }} {!! ($menu_item['children']->count()) ? '<span class="fa arrow"></span>' : '' !!}
    </a>

    @if ($menu_item['children']->count())
        <ul class="nav nav-{{ ($menu_item->depth == 1) ? 'second':'third' }}-level">
        @foreach($menu_item['children'] as $menu_item)
            @include('layouts.inc.partials.menu', $menu_item)
        @endforeach
        </ul>
    @endif
</li>
@endif
ctf0 commented

@tabacitu i have a bit of an issue showing the nested items for more than 2 level deep

demo

demo2

@if ($menu_items->count())
    @foreach ($menu_items as $k => $menu_item)
        @if (($menu_item->page_id && is_object($menu_item->page)) || !$menu_item->page_id)
            @if ($menu_item->children->count())
                <li class="navitem dropdown {{ ($k==0)?' fistitem':'' }}">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ $menu_item->name }} <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        @foreach ($menu_item->children as $i => $child)
                            <li class="{{ ($child->url() == Request::url())?'active':'' }}"><a href="{{ $child->url() }}">{{ $child->name }}</a></li>
                        @endforeach
                    </ul>
                </li>
            @else
                <li class="navitem {{ ($k==0)?' fistitem':'' }} {{ ($menu_item->url() == Request::url())?' active':'' }}">
                    <a href="{{ $menu_item->url() }}">{{ $menu_item->name }}</a>
                </li>
            @endif
        @endif
    @endforeach
@endif

not sure how exactly to format it 😢 so any help is much appreciated


Fixed

  • menu
@if ($menu_items->count())
    @foreach ($menu_items as $menu_item)
        @if ($menu_item->children->count())
            <li class="navitem dropdown">
                <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                    {{ $menu_item->name }} <span class="caret"></span>
                </a>
                <ul class="dropdown-menu">
                    @foreach ($menu_item->children as $child)
                        <li class="{{ ($child->url() == Request::url()) ? 'active' : '' }}">
                            @if ($child->children->count())
                                @include('layouts.partials.nav.pages.sub')
                            @else
                                <a href="{{ $child->url() }}">{{ $child->name }}</a>
                            @endif
                        </li>
                    @endforeach
                </ul>
            </li>
        @else
            <li class="navitem {{ ($menu_item->url() == Request::url()) ? ' active' : '' }}">
                <a href="{{ $menu_item->url() }}">{{ $menu_item->name }}</a>
            </li>
        @endif
    @endforeach
@endif
  • sub
<a href="{{ $child->url() }}" class="dropdown-toggle" data-toggle="dropdown">
    {{ $child->name }} <span class="caret"></span>
</a>
<ul class="dropdown-menu">
    @foreach ($child->children as $child)
        <li class="{{ ($child->url() == Request::url()) ? 'active' : '' }}">
            @if ($child->children->count())
                @include('layouts.partials.nav.pages.sub')
            @else
                <a href="{{ $child->url() }}">{{ $child->name }}</a>
            @endif
        </li>
    @endforeach
</ul>

and make bs menu dropdown on hover instead of click.

Hi @tabacitu was this ever resolved and is there now a nice way to create a Bootstrap friendly menu on the front end? Thanks

@stevecrossuk - no updates on this yet.

We'll probably create a helper for outputting the menu on the front-end, similar to how Laravel does pagination, where it can also be customized, but it will take considerable effort and we're now focused on the core features of Backpack (Base and CRUD). So don't expect this any time soon.

Cheers!

@tabacitu any updates maybe regarding this?
When can we expect a helper for the front-end?

Hi @xlstefan - still no updates, sorry. We do have a plan for A LOT of Bootstrap front-end components, which should be available with Backpack v4, but it will definitely take us 2-3 months to get the first release out. But once we will... I think it will 2-3x the speed we build Laravel apps. It'll be huge.

So sorry, nothing yet. If you do end up creating something for yourself now, please consider copy-pasting your code here for others to use, or create a blog post or something - we'll help you spread the word.

Cheers!