elmdash/menu

简单的Laravel菜单结构

1.0.7 2019-03-05 18:40 UTC

This package is auto-updated.

Last update: 2024-09-07 03:36:59 UTC


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