konekt / menu
Menus in Laravel 10 - 11
Installs: 76 827
Dependents: 1
Suggesters: 0
Security: 0
Stars: 26
Watchers: 5
Forks: 150
Open Issues: 1
Requires
- php: ^8.1
- illuminate/support: ^10.0|^11.0
- illuminate/view: ^10.0|^11.0
Requires (Dev)
- ext-sqlite3: *
- mockery/mockery: ^1.0
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0
README
This is a rework of Lavary Menu
A quick and easy way to create menus in Laravel v5.4 - v10
Laravel Compatibility
PHP Compatibility
Documentation
- Installation
- Getting Started
- Sub-items
- Referring to Items
- Referring to Menu Objects
- HTML Attributes
- Manipulating Links
- Active Item
- Inserting a Separator
- Append and Prepend
- Meta Data
- Manipulating The Items
- Sorting the Items
- Rendering
- Authorization
- Configuration
Installation
composer require konekt/menu
Getting Started
You can define the menus in a service provider's boot method, so any request hits your application, the menu objects will be available.
Create A Menu
$sidebar = Menu::create('sidebar'); $sidebar->addItem('Home', '/'); $sidebar->addItem('About', 'about');
You can reference it later as Menu::get('sidebar')
.
Access Menu In Views
If you want the make the menu object available across all application views pass the 'share' option for create()
:
Menu::create('sidebar', null, ['share' => true]); // will be $sidebar in views Menu::create('main', null, ['share' => 'mainMenu']); // will be $mainMenu in views
In a blade view:
{{-- Render with the built in 'ul' renderer --}} {!! $mainMenu->render('ul') !!} {{--Or render items manually--}} <nav> @foreach($mainMenu->items as $item) <div class="nav-link><a href="{{ $item->url }}">{{ $item->title }}</a></div> @endforeach </nav>
Adding Items
$navbar = Menu::create('navbar'); // Simple link; to '/' via the URL helper $navbar->addItem('home', 'Home', '/'); // Named route $navbar->addItem('clients', 'Clients', ['route' => 'client.index']); // Named route with parameter $navbar->addItem('my-profile', 'My Profile', ['route' => ['user.show', 'id' => Auth::user()->id]]); // Refer to an action $navbar->addItem('projects', 'Projects', ['action' => 'ProjectController@index']); // Action with parameter $navbar->addItem('issue7', 'Issue 7', ['action' => ['IssueController@edit', 'id' => 7]]);
The addItem()
method receives 3 parameters:
- the name of the item
- the title of the item
- and options
options can be a simple string representing a URL or an associative array of options and HTML attributes which is described below.
Removing Items
$menu = Menu::create('main'); $menu->addItem('home', 'Home', '/'); $menu->addItem('about', 'About', '/about'); $menu->getItem('about')->addSubItem('about-us', 'About Us', ['url' => '/about/us']); // This will remove both about and about-us $menu->removeItem('about'); // To keep children, set the second parameter `$removeChildren` to false: $menu->removeItem('about', false); // about-us will remain
Render The Menu
This component provides 3 rendering methods out of the box, ul
, ol
and div
.
You can read about the details here.
{!! $myMenu->render('ul') !!}
You can also access the menu object via the Menu
facade:
{!! Menu::get('navbar')->render('ul') !!}
This will render your menu like this:
<ul> <li class="active"><a href="http://yourdomain.com">Home</a></li> <li><a href="http://yourdomain.com/about">About</a></li> <li><a href="http://yourdomain.com/services">Services</a></li> <li><a href="http://yourdomain.com/contact">Contact</a></li> </ul>
Sub-items
Items can have sub-items too:
$menu = Menu::create('uberGigaMenu') $menu->addItem('about', 'About', ['route' => 'page.about']); // these items will go under Item 'About' // refer to about as a property of $menu object then call `addItem()` on it $menu->about->addSubItem('who-we-are', 'Who We are', '/who-we-are'); // or $menu->getItem('about')->addSubItem('what-we-do', 'What We Do', '/what-we-do'); // or $menu->addItem('our-goals', 'Our Goals',[ 'parent' => 'about', 'url' => '/our-goals' ]);
You can also chain the item definitions and go as deep as you wish:
$menu->addItem('about', 'About', '/about') ->addSubItem('level2', 'Level 2', '/about/level2') ->addSubItem('level3', 'Level 3', '/about/level2/level3') ->addSubItem('level4', 'Level 4', '/about/level2/level4');
It is possible to add sub items directly using parent
attribute:
$menu->addItem('about', 'About'); // You can either set the item object directly as parent: $menu->addItem('team', 'The Team', ['url' => '/about-the-team', 'parent' => $menu->about]); // Or just simply the parent item's name: $menu->addItem('board', 'The Board', ['url' => '/about-the-board', 'parent' => 'about']);
Referring to Items
You can access defined items throughout your code using the methods described below.
Get Item By Name
$menu = \Menu::create('menu'); $menu->addItem('contact', 'Contact', '/contact'); // via the getItem method: $menu->getItem('contact'); // or via magic property accessor: $menu->contact;
You can also store the item variable for further reference:
$about = $menu->addItem('about', 'About', '/about'); $about->addSubItem('who-we-are', 'Who We Are', '/about/who-we-are'); $about->addSubItem('what-we-do', 'What We Do', '/about/what-we-do');
Get All Items
Menus have an items
property that is a collection of menu Item
objects.
$menu->items; // ItemCollection // or: \Menu::get('MyNavBar')->items;
ItemCollection
is a slightly extended Laravel Collection.
Get Sub-Items of the Item
Get the item using the methods described above then call children()
on it.
To get children of About
item:
$aboutSubs = $menu->about->children(); // or outside of the builder context $aboutSubs = Menu::get('MyNavBar')->about->children(); // Or $aboutSubs = Menu::get('MyNavBar')->getItem('about')->children();
children()
also returns an ItemCollection
.
To check if an item has any children or not, you can use hasChildren()
if( $menu->about->hasChildren() ) { // Do something } // or outside of the builder context Menu::get('MyNavBar')->about->hasChildren(); // Or Menu::get('MyNavBar')->getItem('about')->hasChildren();
Magic Where Methods
You can also search the items collection by magic where methods.
These methods are consisted of a where
concatenated with a property (object property or even meta data)
For example to get items with a specific meta data:
$menu->addItem('Home', '#')->data('color', 'red'); $menu->addItem('About', '#')->data('color', 'blue'); $menu->addItem('Services', '#')->data('color', 'red'); $menu->addItem('Contact', '#')->data('color', 'green'); // Fetch all the items with color set to red: $reds = $menu->whereColor('red');
This method returns an ItemCollection
.
Referring to Menu Instances
You might encounter situations when you need to refer to menu instances out of the builder context.
To get a specific menu by name:
$menu = Menu::get('MyNavBar');
Or to get all menus instances:
$menus = Menu::all();
It returns a Laravel Collection
HTML Attributes
Since all menu items would be rendered as HTML entities like list items or divs, you can define as many HTML attributes as you need for each item:
$menu = Menu::create('MyNavBar'); // As you see, you need to pass the second parameter as an associative array: $menu->addItem('home', 'Home', ['route' => 'home.page', 'class' => 'navbar navbar-home', 'id' => 'home']); $menu->addItem('about', 'About', ['route' => 'page.about', 'class' => 'navbar navbar-about dropdown']); $menu->addItem('services', 'Services', ['action' => 'ServicesController@index']); $menu->addItem('contact', 'Contact', 'contact');
If you render it with the ul
renderer, the result will be something like this:
<ul> <li class="navbar navbar-home" id="home"><a href="http://yourdomain.com">Home</a></li> <li class="navbar navbar-about dropdown"><a href="http://yourdomain.com/about">About</a></li> <li><a href="http://yourdomain.com/services">Services</a></li> <li><a href="http://yourdomain.com/contact">Contact</a></li> </ul>
It is also possible to set or get HTML attributes after the item has been defined using attr()
method.
- If you call
attr()
with one argument, it will return the attribute value for you. - If you call it with two arguments, It will consider the first and second parameters as a key/value pair and sets the attribute.
- You can also pass an associative array of attributes if you need to add a group of HTML attributes in one step
- Lastly if you call it without any arguments it will return all the attributes as an array.
$menu->addItem('about', 'About', ['url' => 'about', 'class' => 'about-item']); echo $menu->about->attr('class'); // output: about-item $menu->about->attr('class', 'another-class'); echo $menu->about->attr('class'); // output: another-class $menu->about->attr(['class' => 'yet-another', 'id' => 'about']); echo $menu->about->attr('class'); // output: yet-another echo $menu->about->attr('id'); // output: about print_r($menu->about->attr()); /* Output Array ( [class] => yet-another [id] => about ) */
You can use attr
on an ItemCollection, if you need to target a group of items:
$menu->addItem('About', 'about'); $menu->about->addSubItem('whoweare', 'Who we are', 'about/whoweare'); $menu->about->addSubItem('whatwedo', 'What we do', 'about/whatwedo'); // add a class to children of About $menu->about->children()->attr('class', 'about-item');
Manipulating Links
All the HTML attributes will go to the wrapping tags(li, div, etc); You might encounter situations when you need to add some HTML attributes to <a>
tags as well.
Each Item
instance has an attribute which stores an instance of Link
object. This object is provided for you to manipulate <a>
tags.
Just like each item, Link
also has an attr()
method which functions exactly like item's:
$menu = Menu::create('MyNavBar'); $about = $menu->addItem('About', ['route' => 'page.about', 'class' => 'navbar navbar-about dropdown']); $about->link->attr('data-toggle', 'dropdown');
Link's Href Property
If you don't want to use the routing feature or you don't want the builder to prefix your URL with anything (your host address for example), you can explicitly set your link's href property:
$menu->addItem('about', 'About')->link->href('#');
Active Item
You can mark an item as activated using activate()
on that item:
$menu->addItem('home', 'Home', '/')->activate(); /* Output <li class="active"><a href="/">Home</a></li> */
You can also add class active
to the anchor element instead of the wrapping element (div
or li
):
$menu->addItem('home', 'Home', '/')->link->active(); /* Output <li><a class="active" href="/">Home</a></li> */
The Menu component automatically activates the corresponding item based on the current URI the time you register the item.
You can disable auto activation or choose the element to be activated (item or the link):
// To prevent from auto activation Menu::create('nav', [ 'auto_activate' => false ]); // To set the active element: Menu::create('nav', [ 'active_element' => 'link' // item|link - item by default ]);
URL Wildcards
Konekt Menu component makes you able to define a pattern for a certain item, if the automatic activation can't help:
$menu->addItem('articles', 'Articles', '/articles')->activateOnUrls('articles/*');
So articles
, articles/random-news-title
will both activate the Articles
item.
Check for Active Children
You can check if a menu item has an active sub item by calling:
$item->hasActiveChild(); // (bool) false
You can get the active item(s) from an item colletion by applying the actives()
filter on them:
$menu->roots()->actives(); // Konekt\Menu\ItemCollection // or: $item->children()->actives(); // Konekt\Menu\ItemCollection
Append and Prepend
You can append
or prepend
HTML or plain-text to each item's title after it is defined:
<?php $menu = Menu::create('MyNavBar'); $about = $menu->addItem('about', 'About', ['route' => 'page.about', 'class' => 'navbar navbar-about dropdown']); $menu->about->attr(['class' => 'dropdown-toggle', 'data-toggle' => 'dropdown']) ->append(' <b class="caret"></b>') ->prepend('<span class="glyphicon glyphicon-user"></span> ');
The above code will result:
<ul> <li class="navbar navbar-about dropdown"> <a href="about" class="dropdown-toggle" data-toggle="dropdown"> <span class="glyphicon glyphicon-user"></span> About <b class="caret"></b> </a> </li> </ul>
You can call prepend
and append
on item collections as well so that it'll affect all the items.
Meta Data
You might encounter situations when you need to attach some meta data to each item; This data can be anything from item placement order to permissions required for accessing the item; You can do this by using data()
method.
data()
method works exactly like attr()
method:
If you call data()
with one argument, it will return the data value for you.
If you call it with two arguments, It will consider the first and second parameters as a key/value pair and sets the data.
You can also pass an associative array of data if you need to add a group of key/value pairs in one step; Lastly if you call it without any arguments it will return all data as an array.
$menu->addItem('users', 'Users', ['route' => 'admin.users']) ->data('permission', 'manage_users');
You can also access a data as if it's a property:
$menu->addItem('users', 'Users', '/users')->data('placement', 12); echo $menu->users->placement; // Output : 12
Meta data don't do anything to the item and won't be rendered in HTML either. It is the developer who would decide what to do with them.
You can use data
on a collection, if you need to target a group of items:
$menu->addItem('users', 'Users'); $menu->users->addSubItem('create_user', 'New User', ['route' => 'user.create']); $menu->users->addSubItem('list_users', 'Uses', ['route' => 'user.index']); // add a meta data to children of Users $menu->users->children()->data('tag', 'admin');
Manipulating The Items
Menu items collection can be filtered, sorted, etc by any of Illuminate Collection methods
Rendering
Built-in Renderers
Several rendering formats are available out of the box:
- ul
- ol
- div
Render As UL
$menu = Menu::create('menu'); $menu->addItem('home', 'Home', '/'); $menu->addItem('about', 'About', '/about');
In blade template:
{!! $menu->render('ul') !!}
Result:
<ul> <li class="active"><a href="http://acme.io">Home</a></li> <li><a href="http://acme.io/about">About</a></li> </ul>
Render As OL
$menu = Menu::create('menu', ['class' => 'navigation', 'auto_activate' => false]); $menu->addItem('home', 'Home', '/'); $menu->addItem('about', 'About', '/about');
Template:
{!! $menu->render('ol') !!}
Result:
<ol class="navigation"> <li><a href="http://acme.io">Home</a></li> <li><a href="http://acme.io/about">About</a></li> </ol>
Render As Div
$menu = Menu::create('menu'); $menu->addItem('home', 'Home', '/'); $menu->addItem('about', 'About', '/about')->attr('data-woink', 'kaboom');
In Blade:
{!! $menu->render('div') !!}
Result:
<div> <div class="active"><a href="http://acme.io">Home</a></div> <div data-woink="kaboom"><a href="http://acme.io/about">About</a></div> </div>
Custom Renderers
Rendering was designed to be extensible from ground up.
It is possible to define separate renderers for menus and for items.
Custom Renderer Example (Bulma)
See Bulma Menu Component for reference on CSS.
Create A Menu Renderer Class:
use Konekt\Menu\Contracts\MenuRenderer; use Konekt\Menu\Item; use Konekt\Menu\ItemCollection; use Konekt\Menu\Menu; class BulmaMenuRenderer implements MenuRenderer { public function render(Menu $menu) { $result = sprintf("<aside%s class=\"menu\">\n", $menu->attributesAsHtml()); $result .= $this->renderLevel($menu->items->roots(), 1); $result .= "</aside>\n"; return $result; } protected function renderLevel(ItemCollection $items, $level) { $tabs = str_repeat("\t", $level); $class = $level == 1 ? ' class="menu-list"' : ''; $result = "$tabs<ul$class>\n"; foreach ($items as $item) { $result .= $this->renderItem($item, $level); } return $result . "$tabs</ul>\n"; } protected function renderItem(Item $item, $level) { if ($item->hasChildren()) { return $this->renderItemLi($item, $level, $this->renderLevel($item->children(), $level + 1) ); } return $this->renderItemLi($item, $level); } protected function renderItemLi(Item $item, $level, $extraHtml = '') { $tabs = str_repeat("\t", $level + 1); $link = sprintf('<a href="%s"%s>%s</a>', $item->link->url(), $item->link->attributesAsHtml(), $item->title ); if (empty($extraHtml)) { return sprintf("%s<li%s>%s</li>\n", $tabs, $item->attributesAsHtml(), $link); } return sprintf("%s<li%s>\n%s%s\n%s\n%s</li>\n", $tabs, $item->attributesAsHtml(), $tabs, $link, $extraHtml, $tabs ); } }
Register the renderer:
app()->singleton('konekt.menu.renderer.menu.bulma', BulmaMenuRenderer::class);
Create the menu:
$menu = Menu::create('bulma', [ 'active_element' => 'link', 'active_class' => 'is-active', 'share' => 'bulmaMenu' ]); $menu->addItem('dashboard', 'Dashboard', '/dashboard'); $menu->addItem('customers', 'Customers', '/customers'); $menu->addItem('team', 'Team', '#')->activate(); $menu->team->addSubItem('members', 'Members', '/team/members'); $menu->team->addSubItem('plugins', 'Plugins', '/team/plugins'); $menu->team->plugins->addSubItem('addNewPlugin', 'Add New Plugin', '/team/plugins/new');
Render in blade template:
{!! $bulmaMenu->render('bulma') !!}
Result:
<aside class="menu"> <ul class="menu-list"> <li><a href="http://menu.test/dashboard">Dashboard</a></li> <li><a href="http://menu.test/customers">Customers</a></li> <li> <a href="#" class="is-active">Team</a> <ul> <li><a href="http://menu.test/team/members">Members</a></li> <li> <a href="http://menu.test/team/plugins">Plugins</a> <ul> <li><a href="http://menu.test/team/plugins/new">Add New Plugin</a></li> </ul> </li> </ul> </li> </ul> </aside>
Authorization
Items can be authorized for users in two simple ways:
1. Checking Via Actions
Based on Laravel's built-in
authorization, you can pass action names (strings) that will be tested against the current user with
the can()
method:
$menu = Menu::create('nav', []); $menu->addItem('users', __('Users'), ['route' => 'app.user.index']) ->allowIfUserCan('list users'); $menu->addItem('settings', __('Settings'), ['route' => 'app.settings.index']) ->allowIfUserCan('list settings');
2. Checking Via Callbacks
You can also pass callbacks to authorize menu items for users:
$menu = Menu::create('nav', []); $menu->addItem('users', __('Users'), ['route' => 'app.user.index']) ->allowIf(function($user) { return $user->id > 500; // Add your arbitrary condition });
The callback will receive the user as the first parameter.
You can add multiple
allowIf
and/orallowIfUserCan
conditions to an item. The item will be allowed if ALL the conditions will be met.
Checking Authorization
By default, items without any allow* condition are allowed.
To check if an item is allowed:
$menu->users->isAllowed(); // Checks if the item users item is allowed for the current user $menu->settings->isAllowed(\App\User::find(501)); // Check if an item is available for a given user
To get the list of allowed children:
$item->childrenAllowed(); // Returns an ItemCollection of the allowed item for the current user $item->childrenAllowed(\App\User::find(123)); // Returns the allowed items for the given user
Configuration
You can adjust the behavior of the menu builder by passing settings when creating a menu:
- auto_activate Automatically activates menu items based on the current URI (true by default)
- activate_parents Activates the parents of an active item (true by default)
- active_class CSS class name for active items ("active" by default)
- cascade_data If you need descendants of an item to inherit meta data from their parents, make sure this option is enabled (true by default)
- active_element Which HTML element to be set active 'link' (
<a>
) or 'item' (<li>
,<div>
, etc) (item by default)
Menu::create('menu', [ 'auto_activate' => false, 'activate_parents' => true, 'active_class' => 'active', 'active_element' => 'item', // item|link 'restful' => true, 'share' => 'myMenu' // Will be available as `$myMenu` in blade files ]);