konekt/菜单

Laravel 10 - 11 中的菜单

1.11.0 2024-03-12 16:16 UTC

This package is auto-updated.

Last update: 2024-09-12 17:20:02 UTC


README

这是 Lavary Menu 的重写版本 Lavary Menu

Tests Stable packagist version Packagist downloads StyleCI MIT Software License

在 Laravel v5.4 - v10 中快速轻松地创建菜单

Laravel 兼容性

PHP 兼容性

文档

安装

composer require konekt/menu

入门

您可以在服务提供者的 boot 方法中定义菜单,这样任何请求击中您的应用程序时,菜单对象都将可用。

创建菜单

$sidebar = Menu::create('sidebar');
$sidebar->addItem('Home',  '/');
$sidebar->addItem('About', 'about');

您可以在稍后将其引用为 Menu::get('sidebar')

在视图中访问菜单

如果您想使菜单对象在所有应用程序视图中都可用,请将 'share' 选项传递给 create()

Menu::create('sidebar', null, ['share' => true]); // will be $sidebar in views
Menu::create('main', null, ['share' => 'mainMenu']); // will be $mainMenu in views

在 blade 视图中

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

添加项目

$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]]);

addItem() 方法接受 3 个参数

  • 项目的名称
  • 项目的标题
  • 和选项

选项 可以是一个表示 URL 的简单字符串,也可以是一个包含选项和 HTML 属性的关联数组,具体描述如下。

移除项目

$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

渲染菜单

该组件提供了 3 种内置渲染方法,即 uloldiv。您可以在 此处 了解详细信息。

{!! $myMenu->render('ul') !!}

您也可以通过 Menu 门面访问菜单对象

{!! Menu::get('navbar')->render('ul') !!}

这将按照以下方式渲染您的菜单

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

子项目

项目也可以有子项目

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

您还可以链式定义项目并深入到您想要的任何程度

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

您可以直接使用 parent 属性添加子项目

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

引用项目

您可以使用下面描述的方法在您的代码中访问定义的项目。

按名称获取项目

$menu = \Menu::create('menu');
$menu->addItem('contact', 'Contact', '/contact');

// via the getItem method:
$menu->getItem('contact');

// or via magic property accessor:
$menu->contact;

您还可以将项目变量存储起来以供进一步引用

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

获取所有项目

菜单有一个 items 属性,它是一个菜单 Item 对象的集合。

$menu->items; // ItemCollection
// or:
\Menu::get('MyNavBar')->items;

ItemCollection 是一个略微扩展的 Laravel Collection

获取项目子项目

使用上述方法获取项目,然后在上面调用 children()

要获取 About 项目的子项目

$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() 也返回一个 ItemCollection

要检查一个项目是否有子项目,您可以使用 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();

魔法 Where 方法

您还可以通过魔法 where 方法搜索项目集合。这些方法由一个 where 与属性(对象属性甚至元数据)连接组成

例如,获取具有特定元数据的项目

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

此方法返回一个 ItemCollection

引用菜单实例

您可能会遇到需要在不处于构建器上下文中引用菜单实例的情况。

通过名称获取特定菜单

$menu = Menu::get('MyNavBar');

或获取所有菜单实例

$menus = Menu::all();

它返回一个 Laravel Collection

HTML 属性

由于所有菜单项都将作为HTML实体(如列表项或div)渲染,因此您可以定义每个项所需的任何HTML属性

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

如果您使用 ul 渲染器进行渲染,结果将类似于以下内容

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

您也可以使用 attr() 方法在项定义后设置或获取HTML属性。

  • 如果您使用一个参数调用 attr(),它将为您返回属性值。
  • 如果您使用两个参数调用它,它将考虑第一个和第二个参数作为键/值对并设置属性。
  • 如果您需要一次添加一组HTML属性,也可以传递一个属性关联数组
  • 最后,如果您不带任何参数调用它,它将返回所有属性作为数组。
$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
)
*/

如果您需要针对一组项,可以在ItemCollection上使用 attr

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

操作链接

所有HTML属性都将传递到包装标签(li、div等);您可能会遇到需要将一些HTML属性添加到 <a> 标签的情况。

每个 Item 实例都有一个属性,该属性存储一个 Link 对象的实例。此对象为您提供操作 <a> 标签的能力。

就像每个项一样,Link 也有一个 attr() 方法,其功能与项的完全相同

$menu = Menu::create('MyNavBar');

$about = $menu->addItem('About', ['route' => 'page.about', 'class' => 'navbar navbar-about dropdown']);

$about->link->attr('data-toggle', 'dropdown');

链接的 Href 属性

如果您不想使用路由功能或不想构建器在您的URL前添加任何前缀(例如,您的宿主地址),您可以显式设置链接的href属性

$menu->addItem('about', 'About')->link->href('#');

活动项

您可以使用该项上的 activate() 将项标记为激活

$menu->addItem('home', 'Home', '/')->activate();
/* Output
<li class="active"><a href="/">Home</a></li>	
*/	

您也可以将 active 类添加到锚点元素而不是包装元素(divli)上

$menu->addItem('home', 'Home', '/')->link->active();
	
/* Output
<li><a class="active" href="/">Home</a></li>	
*/

菜单组件会根据注册项时的当前 URI 自动激活相应的项。

您可以禁用自动激活或选择要激活的元素(项或链接)

// 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 通配符

Konekt菜单组件使您能够为特定项定义一个模式,如果自动激活不起作用

$menu->addItem('articles', 'Articles', '/articles')->activateOnUrls('articles/*');

因此,articlesarticles/random-news-title 都会激活 Articles 项。

检查活动子项

您可以通过调用以下内容来检查菜单项是否有激活的子项

$item->hasActiveChild();
// (bool) false

您可以通过在它们上应用 actives() 过滤器来从项目集合中获取活动项

$menu->roots()->actives();
// Konekt\Menu\ItemCollection

// or:

$item->children()->actives();
// Konekt\Menu\ItemCollection

追加和前置

您可以在定义后向每个项的标题添加HTML或纯文本

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

上述代码将产生以下结果

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

您也可以在项目集合上调用 prependappend,以便它影响所有项。

元数据

您可能会遇到需要将某些元数据附加到每个项的情况;这些数据可以是任何东西,从项放置顺序到访问项所需的权限;您可以通过使用 data() 方法来完成此操作。

data() 方法与 attr() 方法功能完全相同

如果您使用一个参数调用 data(),它将为您返回数据值。如果您使用两个参数调用它,它将考虑第一个和第二个参数作为键/值对并设置数据。如果您需要一次添加一组键/值对,也可以传递一个数据关联数组;最后,如果您不带任何参数调用它,它将返回所有数据作为数组。

$menu->addItem('users', 'Users', ['route'  => 'admin.users'])
    ->data('permission', 'manage_users');

您也可以将数据作为属性访问

$menu->addItem('users', 'Users', '/users')->data('placement', 12);
echo $menu->users->placement;    // Output : 12

元数据不会对项目产生影响,也不会在HTML中渲染。开发者将决定如何使用它们。

如果您需要针对一组项目,可以在集合上使用data

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

操作项目

菜单项目集合可以通过任何Illuminate Collection方法进行筛选、排序等操作。

渲染

内置渲染器

提供了多种默认渲染格式。

  1. ul
  2. ol
  3. div

以 UL 渲染

$menu = Menu::create('menu');
$menu->addItem('home', 'Home', '/');
$menu->addItem('about', 'About', '/about');

在blade模板中

{!! $menu->render('ul') !!}

结果

<ul>
  <li class="active"><a href="http://acme.io">Home</a></li>
  <li><a href="http://acme.io/about">About</a></li>
</ul>

以 OL 渲染

$menu = Menu::create('menu', ['class' => 'navigation', 'auto_activate' => false]);
$menu->addItem('home', 'Home', '/');
$menu->addItem('about', 'About', '/about');

模板

{!! $menu->render('ol') !!}

结果

<ol class="navigation">
  <li><a href="http://acme.io">Home</a></li>
  <li><a href="http://acme.io/about">About</a></li>
</ol>

以 Div 渲染

$menu = Menu::create('menu');
$menu->addItem('home', 'Home', '/');
$menu->addItem('about', 'About', '/about')->attr('data-woink', 'kaboom');

在Blade中

{!! $menu->render('div') !!}

结果

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

自定义渲染器

渲染设计从一开始就是可扩展的。

可以为菜单和项目定义独立的渲染器。

自定义渲染器示例(Bulma)

有关CSS的参考,请参阅Bulma菜单组件

创建一个菜单渲染器类

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

注册渲染器

app()->singleton('konekt.menu.renderer.menu.bulma', BulmaMenuRenderer::class);

创建菜单

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

在blade模板中渲染

{!! $bulmaMenu->render('bulma') !!}

结果

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

授权

项目可以通过两种简单的方式授权给用户

1. 通过动作检查

基于Laravel内置的授权,您可以通过can()方法传递将要对当前用户进行测试的动作名称(字符串)。

$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. 通过回调检查

您还可以传递回调来授权菜单项目给用户

$menu = Menu::create('nav', []);
$menu->addItem('users', __('Users'), ['route' => 'app.user.index'])
    ->allowIf(function($user) {
        return $user->id > 500; // Add your arbitrary condition
    });

回调将接收用户作为第一个参数。

您可以在项目中添加多个allowIf和/或allowIfUserCan条件。如果所有条件都满足,项目将被允许。

检查授权

默认情况下,没有任何allow*条件的项是允许的。

要检查项是否允许

$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

要获取允许子项的列表

$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

配置

您可以通过创建菜单时传递设置来调整菜单构建器的行为

  • auto_activate 根据当前URI自动激活菜单项(默认为true
  • activate_parents 激活活动项的父项(默认为true
  • active_class 活动项的CSS类名(默认为"active"
  • cascade_data 如果您需要项目的后代从其父项继承元数据,请确保此选项已启用(默认为true
  • active_element 要设置为活动的HTML元素 'link'<a>)或 'item'<li><div>等)(默认为item
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
]);