elmdash / menu
简单的Laravel菜单结构
Requires
- php: >=5.5.9
- laravel/framework: ~5.3
Requires (Dev)
- phpunit/phpunit: ~6.0
README
菜单只是一个简单的树结构。您可以用百万种方式实现它们,但这是一种干净且最小的菜单结构,它处理了在Laravel应用程序中根据用户的授权设置活动状态和切换菜单项的基本任务。
在提供者的boot方法中添加如下内容
$m = new \ElmDash\Menu\Menu('top'); // a login route, only visible to guests $m->add('access')->guests(); // menu items for users with proper permissions $m->add('books.edit', function (Menu $books) { $books->can('edit-books'); // It's optional to edit within callbacks. // However, this will be called once the menu is rendered // (instead of immediately) }); // an adit route, active for any account routes $m->add('account.edit')->match('account.*'); // you can also nest items $c = $m->add('event.create'); // you may add route parameters that are // included when determining active states $c->add('event.create')->params(['type' => 'basic']); $c->add('event.create')->params(['type' => 'special']); // more menu items $m->add('logout'); $this->app->instance('menu-top', $m);
然后在您的视图中
{% set menu = app.make('menu-top') %} <div container> <nav class="top"> <div class="logo"></div> <nav> {% for item in menu.children %} {% set activeClass = item.isActive ? 'active' : '' %} <span> <a href="{{ item.href }}" class="{{ activeClass }}"> {{ item.label }} </a> </span> <nav> {% for subitem in item.children %} {% set activeClass = subitem.isActive ? 'active': '' %} <span> <a href="{{ subitem.href }}" class="{{ activeClass }}"> {{ subitem.label }} </a> </span> {% endfor %} </nav> {% endfor %} </nav> </nav> </div>
标签设置在lang/menu.php
<?php return [ 'top' => [ 'access' => 'Sign up or sign in', 'account-edit' => 'Settings', 'event-create' => 'Create an event', 'logout' => 'Sign out', ], ];
请注意,翻译密钥是菜单项的路由名称,但用短横线代替了点。此外,请注意,菜单的最高级别名称用于分组翻译。
功能
根菜单
顶级(根)菜单对象通常不会被渲染,也不会有一个路由来引用。您可以为了方便起见给这些路由命名。
$menu = new Menu('top-left');
添加项目
菜单树由仅Menu
对象组成,所以树中的每个项目都有与其他每个项目相同的方法。也就是说,没有“MenuRoot”、“MenuGroup”或“MenuItem”的区别。
添加一个项目可以像添加一个路由名称一样简单。(目前,您不能添加未命名的任何路由。)
$menu->add('my.route');
这返回了新创建的Menu
子项。您可以使用此进行链式调用。
$menu->add('my.route')->add('my.child.route');
然而,出于几个原因,使用回调来创建您的树结构可能更容易。首先,它更具有视觉吸引力,其次,函数仅在需要时调用。这对于在初始化其他所有内容之后延迟菜单初始化代码非常有用。
$menu->add('my.route', function (Menu $m) { // runs eventually, but not right away $m->add('my.child.route'); }); // this would trigger all callbacks and load the whole tree (likely called in your view) $children = $menu->children();
还有其他添加项目的方法
// prepending $menu->add('my.second.child'); $child = $menu->prepend('my.first.child'); // using groups (the name is _not_ a route name) // they do not have a route associated with them. So you can't, for example, // attempt to get the href of a group. $child->group('some.group.name', function (Menu $g) { $g->add('my.grand.child'); }); // you can also prepend a group $child->groupPrepend('some.other.group', function (Menu $g) { $g->add('my.step.grand.child'); });
路由
通常在添加菜单项时指定路由名称。
$menu->add('my.first.route'); // or more manually $child = $menu->add(); $child->route('my.route');
如果您的路由接受参数,您应该将它们添加到菜单中。它们将用于渲染正确的href值并确定活动菜单项。
$menu->add('my.route.default', function (Menu $m) { // if the param is optional $m->params(['type' => null]); }); $menu->add('my.route.type', function (Menu $m) { $m->params(['type' => 'fish']); }); $menu->add('my.route.type', function (Menu $m) { $m->params(['type' => 'mammals']); });
用于查找匹配项的参数只是路由上定义的参数。为了获得更精细的控制,您可以自己提取这些参数。结果是缓存的,所以它只会被调用一次。例如,您可能需要从一个不同的路由参数中获取参数值(例如,在编辑路由期间)。
foreach ($contexts as $ctx) { // list by context (i.e. /media/{context}) $m->add('media.index') ->params(['context' => $ctx->handle]) ->activeFor('media.*') // get the context from the media when editing that media // (i.e. /media/item/{media}/edit) ->extractParams(function ($currentParams) { $media = array_get($currentParams, $media); if ($media) { return ['context' => $media->context->handle]; } return []; }); }
在视图中渲染URL的方式如下
<a href="{{ item.href }}" class="{{ item.isActive ? 'active' : '' }}"> {{ item.label }} </a>
或者如果您需要绝对URL
<a href="{{ item.href(true) }}" class="{{ item.isActive ? 'active' : '' }}"> {{ item.label }} </a>
活动菜单项
当子项处于活动状态时,所有父项都将被视为活动状态。要找出哪个菜单项与当前活动URL匹配,我们比较当前路由的名称与每个菜单项的路由名称。例如,当“users.account”路由处于活动状态时,“users.account”菜单项将是活动的。这只是一种严格名称(和可能参数)的比较。
然而,您可能对每个最低级别的菜单项有多个视图。您需要一种方法来指定其他应标记菜单项为活动的命名路由。为此,我们提供了globbing。
// single glob $users->activeFor('users.*'); // multi glob $settings->activeFor('users.settings.*|account|account.edit.*'); // regex $account->activeFor('/^users\.account\..*$/');
再次强调,我们只处理命名路由,所以请确保您的所有路由都已命名。
如上所示,要在视图中检查项目是否处于活动状态,只需调用item.isActive
。
对于子菜单,您可能还需要获取某些根菜单对象当前的活动项。
{% set menu = app.make('nav-top-right').activeChild %}
访问
菜单项可以隐藏没有权限的用户。这仅仅使用了Laravel的授权策略。
$left->add('wiki.index')->can('edit-wiki');
对于当前用户不可见的菜单项,menu.children
函数将不会返回。如果父项不可见,则子项也将不会标记为可见。在子菜单中,你可能需要直接检查可见性(例如,如果你不是直接循环遍历 menu.children
的结果)。
{% set menu = app.make('nav-top-left').activeChild %} {% if not menu %} {% set menu = app.make('nav-top-right').activeChild %} {% endif %} {% if menu and menu.isVisible %} <nav class="nav nav-pills nav-stacked"> {% for group in menu.children %} {% if group.isVisible %} <li class="nav-item nav-header">{{ group.label }}</li> {% for item in group.children %} <li class="nav-item"> <a class="nav-link {{ item.isActive ? 'active' : '' }}" href="{{ item.href }}"> {{ item.label }} </a> </li> {% endfor %} {% endif %} {% endfor %} </nav> {% endif %}
默认情况下,所有菜单项都需要身份验证。要为匿名用户创建菜单项
$menu->add('photos.index')->guests();
标志
你可以向菜单项添加标志,以便以后查询菜单项。
// during initialization $m->flags(['ajax-only', 'bold']);
// later in the view {% if menu.is('ajax-only') %} {# ... do something different for this menu item #} {% endif %}
标签
标签只是从 Laravel 的翻译基础设施中获取。
默认情况下,翻译键只是添加菜单项时提供的路由名称,但点号被转换为短横线。
$menu->add('my.route.name'); // key is "my-route-name"
如果顶级菜单对象有一个名称,则该名称将作为翻译键的前缀:即 'top-left' 和 'my.route.name' => 'top-left.my-route-name'。
$menu = new Menu('top-left'); $menu->add('my.route.name'); // key is "top-left.my-route-name"
组名也将作为前缀
$menu = new Menu('top-left'); $menu->group('branding', function (Menu $g) { $g->add('my.route.name'); // key is "top-left.branding.my-route-name" });
你可以在菜单树中的任何位置指定翻译命名空间。
$menu = new Menu('top-left'); $menu->langNamespace('app'); $menu->add('my.route.name'); // key is "app::top-left.my-route-name"
你可以覆盖要使用的语言键
$menu->add('my.route.name', function (Menu $m) { $m->labelKey('my-full-route-name'); });
或者你可以直接覆盖标签
$menu->add('my.route.name', function (Menu $m) { $m->label('My Route Here!'); });