- Installation
- Basic usage
- Configuration
- Creating menus
- Adding menu items
- Nesting menu items
- Adding attributes
- Inheriting/Consuming auto-generated item classes
- Changing the default HTML output
- Determining the active item
- Retrieving the active menu item
- Extracting a path of menu items
- Advanced usage
- Examples
-
Use Composer to add the menu plugin to your project:
$ composer require icings/menu
-
Make sure that you are loading the plugin in your bootstrap, either run:
$ bin/cake plugin load Icings/Menu
or add the following call to your
Application
class'bootstrap()
method in thesrc/Application.php
file:$this->addPlugin('Icings/Menu');
-
Load the helper in your
AppView
class'initialize()
method, located in thesrc/View/AppView.php
file:$this->loadHelper('Icings/Menu.Menu');
Build and render the menu via the helpers create()
and render()
methods:
$menu = $this->Menu->create('main');
$menu->addChild('Home', ['uri' => ['controller' => 'Pages', 'action' => 'display', 'home']]);
$menu->addChild('About', ['uri' => ['controller' => 'Pages', 'action' => 'display', 'about']]);
$menu->addChild('Services', ['uri' => '#']);
$menu['Services']->addChild('Research', ['uri' => ['controller' => 'Pages', 'action' => 'display', 'research']]);
$menu['Services']->addChild('Security', ['uri' => ['controller' => 'Pages', 'action' => 'display', 'security']]);
$menu->addChild('Contact', ['uri' => ['controller' => 'Pages', 'action' => 'display', 'contact']]);
echo $this->Menu->render();
In the default setup, this would generate the following HTML:
<ul>
<li>
<a href="/pages/display/home">Home</a>
</li>
<li>
<a href="/pages/display/about">About</a>
</li>
<li class="has-dropdown">
<a href="#">Services</a>
<ul class="dropdown">
<li>
<a href="/pages/display/research">Research</a>
</li>
<li>
<a href="/pages/display/security">Security</a>
</li>
</ul>
</li>
<li>
<a href="/pages/display/contact">Contact</a>
</li>
</ul>
There are basically four levels of configuration:
They are weighted in that exact order, which means that menu item configuration overwrites menu configuration, menu configuration overwrites render configuration, and render configuration overwrites helper configuration.
The menu helper can be configured like any other CakePHP helper, either via the $options
argument of
View::loadHelper()
:
$this->loadHelper('Icings/Menu.Menu', [
'option' => 'value'
]);
or via Helper::setConfig()
:
$this->Menu->setConfig([
'option' => 'value'
]);
The following options also apply to MenuHelper::render()
, and partially to MenuHelper::create()
. Defining them in
the scope of the helper will make them the default values that can be overwritten in MenuHelper::create()
and
MenuHelper::render()
if necessary.
For more advanced configuration that allows to change the internally used matcher, voters or renderer, please refer to the Advanced usage section.
matching
(string
, defaults to\Icings\Menu\View\Helper\MenuHelper::MATCH_URL
)
Defines the mode to use for matching the menu items against the current request in order to determine the active items.
-
templates
(string[]
)
The templates that should be used. Defaults to:[ 'menu' => '<ul{{attrs}}>{{items}}</ul>', 'nest' => '<ul{{attrs}}>{{items}}</ul>', 'item' => '<li{{attrs}}>{{link}}{{nest}}</li>', 'link' => '<a href="{{url}}"{{attrs}}>{{label}}</a>', 'text' => '<span{{attrs}}>{{label}}</span>' ]
-
templateVars
(mixed[]|null
, defaults tonull
)
An array of template variables. -
currentClass
(string
, defaults toactive
)
The class to set for the current/active items. -
ancestorClass
(string|null
, defaults toactive-ancestor
)
The class to set for ancestors of current/active items. -
leafClass
(string|null
, defaults tonull
)
The class to set for menu items that do not have children. -
branchClass
(string|null
, defaults tohas-dropdown
)
The class to set for menu items that do have children. -
nestedMenuClass
(string|null
, defaults todropdown
)
The class to set for the element that holds the children of a menu item. -
menuLevelClass
(string|null
, defaults tonull
)
The class prefix to use for denoting the level of a menu. The appended level is an integer starting at1
. -
firstClass
(string|null
, defaults tonull
)
The class to set for the first child item. -
lastClass
(string|null
, defaults tonull
)
The class to set for the last child item. -
depth
(integer|null
, defaults tonull
)
The depth up to which the menu should be rendered. -
matchingDepth
(integer|null
, defaults tonull
)
The depth up to which items should be matched. -
clearMatcher
(boolean
, defaults totrue
)
Defines whether the matcher cache should be cleared after rendering the menu. -
currentAsLink
(boolean
, defaults totrue
)
Whether the active item should render a link, or a text element. -
inheritItemClasses
(array|boolean|null
, defaults tonull
)
Defines which classes should be inherited by the menu item's link and text elements.true
will cause all classes to be inherited. An array is used to specify specific classes to inherit (valid class names arecurrentClass
,ancestorClass
,leafClass
,branchClass
,firstClass
,lastClass
).null
orfalse
will disable this feature. -
consumeItemClasses
(array|boolean|null
, defaults tonull
)
Defines which classes should be consumed by the menu item's link and text elements.true
will cause all classes to be consumed. An array is used to specify specific classes to consume (see theinheritItemClasses
option for a list of valid class names).null
orfalse
will disable this feature.
Rendering can be configured using the $options
argument of MenuHelper::render()
. It supports the exact same options
as the helper itself, and can be used to overwrite the helpers defaults.
Menus can be configured via the $options
argument of MenuHelper::create()
.
In the default setup, the following options are supported:
menuAttributes
(array
, defaults tonull
) The HTML attributes to apply to the menu element.
Additionally to the options listed above, all rendering related options options of the helper are supported too, and can be used to overwrite the helpers defaults.
Menu items can be configured via the $options
argument of ItemInterface::addChild()
.
In the default setup, the following options are supported:
-
uri
(array|string|null
, defaults tonull
)
Either a CakePHP URL array, or a string URL. This value will be used for generating the link URL of the menu item, as well as for matching against the current request target, in order to determine whether the current item is active.What kind of matching is being used depends on the configured matcher and voters. In the default setup the generated string URL will be used for matching, where the query string will be ignored.
If this options is omitted or
null
, no link will be generated, but a text label. -
routes
(array|null
, defaults tonull
)
An array of CakePHP URL arrays or string URLs, which will be used for matching additionally to the one set for theuri
option. -
addUriToRoutes
(boolean
, defaults totrue
)
Defines whether the URL defined in theuri
option should be added to theroutes
option. If set tofalse
, only the URLs defined in theroutes
option will be used for matching. Whentrue
, the URL defined inuri
will be matched first before the URLs defined in theroutes
option. -
ignoreQueryString
(boolean|null
, defaults tonull
)
Defines whether the query string should be ignored in the matching process. Whennull
, the matching configuration (respectively the voter) will decide the behavior.This option only applies to the
UrlVoter
, respectively theMenuHelper::MATCH_URL
matching mode. -
voters
(Knp\Menu\Matcher\Voter\VoterInterface[]|null
, defaults tonull
)
Defines voters that should be used for matching this menu item. The voters defined here will be used before the default ones. -
templates
(array
, defaults tonull
)
The templates that should be used for the menu item. For a list of available templates, refer to the Configuration > Helper configuration > Menu helper options section. -
defaultTemplates
(array
, defaults tonull
)
The templates that should be used for the menu item and its children. When specifying both,templates
as well asdefaultTemplates
, the latter will still apply to the current item as well, unless already specified via the former. -
templateVars
(array
, defaults tonull
)
The template variables that should be used for the menu item. -
defaultTemplateVars
(array
, defaults tonull
)
The template variables that should be used for the menu item and its children. When specifying both,templateVars
as well asdefaultTemplateVars
, the latter will still apply to the current item as well, unless already specified via the former. -
escape
(boolean
, defaults totrue
)
Defines whether the attributes and the label (link and non-link text) should be escaped (passed throughh()
). -
escapeLabel
(boolean
, defaults totrue
) Defines whether the label (link and non-link text) should be escaped (passed throughh()
). -
label
(string
, defaults tonull
)
Defines the items label. Whennull
, the first argument ofaddChild()
is being used as the label.There might be situations in which you need to specify different values for the items name (identifier) and its label, for example when you want to translate the label, and need to retain the possibility to access the item by its name without having to use translation functions, in such a case you should use this option to specify the label text.
-
current
(boolean
, defaults tonull
)
Defines whether the item should be marked as active. -
display
(boolean
, defaults totrue
)
Defines whether the item should be displayed/rendered. -
displayChildren
(boolean
, defaults totrue
)
Defines whether the items children should be displayed/rendered. -
attributes
(array
, defaults tonull
)
The HTML attributes to apply to the menu item element. -
linkAttributes
(array
, defaults tonull
)
The HTML attributes to apply to the menu item link element. -
textAttributes
(array
, defaults tonull
)
The HTML attributes to apply to the menu item text element (that is, when no link is generated). -
nestAttributes
(array
, defaults tonull
)
The HTML attributes to apply to the element that holds the children of the menu item. -
inheritItemClasses
(array|boolean|null
, defaults tonull
)
Defines which classes should be inherited by the menu item's link and text elements.true
will cause all classes to be inherited. An array is used to specify specific classes to inherit. For a list of valid classes, refer to theinheritItemClasses
renderer option description in the Configuration > Helper configuration > Rendering related options section.false
will disable this feature, andnull
means that the renderer defaults will apply. -
consumeItemClasses
(array|boolean|null
, defaults tonull
)
Defines which classes should be consumed by the menu item's link and text elements.true
will cause all classes to be consumed. An array is used to specify specific classes to consume. For a list of valid classes, refer to theinheritItemClasses
renderer option description in the Configuration > Helper configuration > Rendering related options section.false
will disable this feature, andnull
means that the renderer defaults will apply.
Menus are created via the MenuHelper::create()
method. The method takes two arguments, the first one being the name
of the menu, which serves as an identifier when rendering or obtaining specific menus, and the second one optionally
takes an array of options that should be applied to the menu, ie. to the top level item.
The create()
method returns an instance of KnpMenu\ItemInterface
, so any operations known from KnpMenu that can be
applied via that interface, can be applied here too. It should be noted however that not all methods will have the
same effect as using their option counterparts, for example unlike the uri
option, the setUri()
method will not
work with CakePHP URL arrays!
$menu = $this->Menu->create('main', [
'option' => 'value'
]);
Menu items are added via the addChild()
method of the menu that was created via MenuHelper::create()
. The method
takes two arguments, the first being the label of the menu item, the second being an optional array of options.
In the default setup, items support the uri
option known from KnpMenu. It supports string URLs as well as CakePHP
URL arrays, which are being converted into string URLs, and are being matched against the current request target.
$menu->addChild('Articles', ['uri' => ['controller' => 'Articles', 'action' => 'index']]);
The generated HTML would look like:
<li>
<a href="/articles">Articles</a>
</li>
and it would automatically be marked as active in case the current request URL matches /articles
.
When omitting the uri
option, the item will not render a link, but a text-label element:
$menu->addChild('Label');
The generated HTML would look like:
<li>
<span>Label</span>
</li>
Menu items can be nested by using addChild()
on a menu item itself, either by using a variable reference:
$parent = $menu->addChild('Parent', ['uri' => ['controller' => 'Controller', 'action' => 'action']]);
$parent->addChild('Child', ['uri' => ['controller' => 'Other', 'action' => 'action']]);
by accessing the menu item by its name:
$menu->addChild('Parent', ['uri' => ['controller' => 'Controller', 'action' => 'action']]);
$menu['Parent']->addChild('Child', ['uri' => ['controller' => 'Other', 'action' => 'action']]);
or by using the fluid interface syntax:
$menu
->addChild('Parent', ['uri' => ['controller' => 'Controller', 'action' => 'action']])
->addChild('Child', ['uri' => ['controller' => 'Other', 'action' => 'action']]);
The generated HTML would look like:
<li class="has-dropdown">
<a href="/controller/action">Parent</a>
<ul class="dropdown">
<li>
<a href="/other/action">Child</a>
</li>
</ul>
</li>
Attributes can be added to the menu, the individual menu items, its links, text labels, and nesting elements. The
corresponding options are menuAttributes
, attributes
, linkAttributes
, textAttributes
, and nestAttributes
:
$menu = $this->Menu->create('main', [
'menuAttributes' => [
'data-menu' => 'menu data'
]
]);
$menu->addChild('Parent', [
'uri' => ['controller' => 'Controller', 'action' => 'action'],
'attributes' => [
'data-item' => 'item data'
],
'linkAttributes' => [
'data-link' => 'link data'
],
'nestAttributes' => [
'data-nest' => 'nest data'
]
]);
$menu['Parent']->addChild('Child', [
'textAttributes' => [
'data-text' => 'text data'
]
]);
In the default setup, this would generate the following HTML:
<ul data-menu="menu data">
<li data-item="item data">
<a data-link="link data" href="/controller/action">Parent</a>
<ul data-nest="nest data">
<li>
<span data-text="text data">Child</span>
</li>
</ul>
</li>
</ul>
In certain situations it might be required to have classes like the one for the current/active item to be set on the
link
/text
element, instead of the outer item
element.
For example in the default setup, the current/active item would receive the class, and the following HTML would be generated for the menu item:
<li class="active">
<a href="/controller/action">Label</a>
</li>
In order to have the active
class being set on the link element instead, one can use the inheritItemClasses
or
consumeItemClasses
option. Both options are supported as rendering options (see the
Configuration > Helper configuration > Rendering related options section), or as menu item
options.
Specifying true
will cause all auto-generated item classes to be inherited/consumed, using an array a list of specific
classes to inherit/consume can be specified (valid class names are currentClass
, ancestorClass
, leafClass
,
branchClass
, firstClass
, lastClass
), and false
will disable the feature. On renderer level null
will disable
the feature too, where as on menu item level it means that the renderer defaults should be used.
"Inheriting" means that the classes that are being set on the item, will be set on the item's link/text elements too. The following active menu item would generate HTML where the current class is being set on the link element too:
$menu->addChild('Label', [
'uri' => ['controller' => 'Controller', 'action' => 'action'],
'inheritItemClasses' => [
'currentClass'
]
]);
<li class="active">
<a href="/controller/action" class="active">Label</a>
</li>
"Consuming" means that the classes are being set on the item's link/text elements instead of on the item. The following active menu item would generate HTML where the current class is being set on the link element only:
$menu->addChild('Label', [
'uri' => ['controller' => 'Controller', 'action' => 'action'],
'consumeItemClasses' => [
'currentClass'
]
]);
<li>
<a href="/controller/action" class="active">Label</a>
</li>
In the default setup, the helper uses a string template renderer, which utilizes string templates as known from the
CakePHP core helpers that can be customized via the templates
and templateVars
options.
Changing the default output is fairly simple, just pass the templates
and/or templateVars
options to either
MenuHelper::setConfig()
, in order to change the defaults for all menus:
$this->Menu->setConfig([
'templates' => [
'menu' => '<nav class="menu-wrapper"><ul{{attrs}}>{{items}}</ul></nav>',
'item' => '<li data-item="{{itemVar}}"{{attrs}}>{{link}}{{nest}}</li>'
],
'templateVars' => [
'itemVar' => 'default item data'
]
]);
or pass them to MenuHelper::create()
or ItemInterface::addChild()
, to change the defaults for specific menus/items
only:
$menu = $this->Menu->create('main', [
'templates' => [
'menu' => '<nav class="menu-wrapper"><ul{{attrs}}>{{items}}</ul></nav>',
'item' => '<li data-item="{{itemVar}}"{{attrs}}>{{link}}{{nest}}</li>'
],
'templateVars' => [
'itemVar' => 'default item data'
]
]);
That would render a <nav>
element around the menu, and it defines a data-item
attribute for all items, which can be
fed via the custom template variable itemVar
, and is set to a default value of default item data
.
The generated HTML would look like:
<nav class="menu-wrapper">
<ul>
<li data-item="default item data">...</li>
...
</ul>
</nav>
Configuring the menu items is equally simple:
$menu->addChild('Label', [
'uri' => ['controller' => 'Controller', 'action' => 'action'],
'templates' => [
'link' => '<a href="{{url}}"{{attrs}}><i class="fa fa-check-square"></i> {{label}}</a>'
],
'templateVars' => [
'itemVar' => 'specific item data'
]
]);
This would render the link of this specific menu item with an additional <i>
element, and the data-item
attribute
with a value of specific item data
.
The generated HTML would look like:
<li data-item="specific item data">
<a href="/controller/action"><i class="fa fa-check-square"></i> Label</a>
</li>
Templates and template variables that are defined on a specific menu item, will apply these options to only that
specific item. If you want to change the menu defaults for a whole branch of items (ie child items), you can use the
defaultTemplates
and defaultTemplatVars
options.
The templates and template variables defined via these options will apply to the current item (unless already specified
via templates
and templateVars
), as well as all child items of the current item.
$menu->addChild('Parent', [
'uri' => ['controller' => 'Controller', 'action' => 'action'],
'templates' => [
'link' => '<a href="{{url}}"{{attrs}}><i class="fa fa-check-square"></i> {{label}}</a>'
],
'templateVars' => [
'itemVar' => 'specific item data'
]
]);
$menu['Parent']->addChild('Child', [
'uri' => ['controller' => 'Controller', 'action' => 'action']
]);
$menu['Parent']['Child']->addChild('Grandchild', [
'uri' => ['controller' => 'Controller', 'action' => 'action'],
'defaultTemplates' => [
'item' => '<li data-item-new-defaults="{{itemVar}}"{{attrs}}>{{link}}{{nest}}</li>'
],
'defaultTemplateVars' => [
'itemVar' => 'new item default data'
]
]);
$menu['Parent']['Child']['Grandchild']->addChild('GreatGrandchild', [
'uri' => ['controller' => 'Controller', 'action' => 'action']
]);
The generated HTML would look like:
<li data-item="specific item data">
<a href="/controller/action"><i class="fa fa-check-square"></i> Parent</a>
<ul>
<li data-item="default item data">
<a href="/controller/action">Child</a>
<ul>
<li data-item-new-defaults="new item default data">
<a href="/controller/action">Grandchild</a>
<ul>
<li data-item-new-defaults="new item default data">
<a href="/controller/action">GreatGrandchild</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
In the default setup, the helper will compare the URLs generated for the menu items uri
and routes
options against
the current request URL, while ignoring the query string.
There are three matching modes that can be configured via the matching
option:
Icings\Menu\View\Helper\MenuHelper::MATCH_URL
(default)Icings\Menu\View\Helper\MenuHelper::MATCH_URL_WITH_QUERY_STRING
Icings\Menu\View\Helper\MenuHelper::MATCH_FUZZY_ROUTE
For example, if you want to change the matching to include the query string, set the MATCH_URL_WITH_QUERY_STRING
mode
like this:
use Icings\Menu\View\Helper\MenuHelper;
$this->Menu->setConfig([
'matching' => MenuHelper::MATCH_URL_WITH_QUERY_STRING
]);
If you want to match not only against the URL defined in the menu items uri
option, you can add additional URLs to
match against in the routes
option, which takes an array of URL arrays or string URLs.
This is supported by all matching modes.
In case you want to only match against the URLs provided in the routes
option, and use the URL defined via the uri
option solely for generating the menu items link, you can set the addUriToRoutes
option to false
. Doing so will
exclude the primary URL from matching.
This is supported by all matching modes.
There might be times when you want your menu items to match multiple URLs, but you do not want to define them all one
by one. This is where fuzzy URL matching comes into play. Fuzzy route matching compares the parsed parameters of the
route that matched the current request, against the URL arrays provided via the uri
and routes
options.
Fuzzy route matching can be enabled by setting the matching
option to MATCH_FUZZY_ROUTE
:
use Icings\Menu\View\Helper\MenuHelper;
$this->Menu->setConfig([
'matching' => MenuHelper::MATCH_FUZZY_ROUTE
]);
The matching is fuzzy in the sense that a match is successful when all of the parameters provided in the URL array are present in the parsed route parameters, so if the URL array for example only contains a controller, then it will be matched successfully for all routes that connect to this controller, regardless of the action, the plugin, the prefix, further parameters like IDs, etc.
Consider the following URL array, which is as fuzzy as it gets:
['controller' => 'Articles']
This will successfully match all of the following (and more) routes:
$routes->plugin('PluginName', function(RouteBuilder $routes) {
$routes->fallbacks();
});
$routes->prefix('PrefixName', function(RouteBuilder $routes) {
$routes->fallbacks();
});
$routes->connect('/:controller/:action/:id');
$routes->connect('/:controller/:action/:id');
$routes->connect('/:controller/:action');
$routes->connect('/:controller');
The matching can be made less fuzzy by specifying more (restrictive) parameters.
['controller' => 'Articles', 'action' => 'view']
From the above routes examples, now the following route will not be matched anymore, as it by default connects to the
index
action:
$routes->connect('/:controller');
['controller' => 'Articles', 'action' => 'view', 1]
From the above routes examples, now the following routes will not be matched anymore, as they do not define the additional ID:
$routes->connect('/:controller/:action');
$routes->connect('/:controller');
['plugin' => 'PluginName', 'controller' => 'Articles']
From the above routes examples, now only the plugin routes will be matched:
$routes->plugin('PluginName', function(RouteBuilder $routes) {
$routes->fallbacks();
});
['prefix' => 'PrefixName', 'controller' => 'Articles']
From the above routes examples, now only the prefix routes will be matched:
$routes->prefix('PrefixName', function(RouteBuilder $routes) {
$routes->fallbacks();
});
Please note that this feature requires at least CakePHP 3.5.11!
Named routes that contain elements do match in a similar fuzzy manner as the routes discussed previously, that is, unlike with strict matching, the elements defined in the route do not need to be present.
Consider a route like this:
$routes->connect(
'/articles/view/:id/:slug',
[
'controller' => 'Articles',
'action' => 'view'
],
[
'pass' => ['id', 'slug'],
'_name' => 'routeName'
]
);
In order to match it strictly via its name, the URL array would need to contain the name, and all the elements:
['_name' => 'routeName', 'id' => 123, 'slug' => 'the-article-slug']
With fuzzy route matching, matching will work with providing only the name:
['_name' => 'routeName']
and with providing partial elements:
['_name' => 'routeName', 'id' => 123]
As mentioned initially, in the default setup possible query strings are excluded from matching, which means a URL with query arguments can match regardless of the query string values that might be present in the request URL.
If for example the current request URL is:
/articles?filter=all
and the menu has an item defined like this:
$menu->addChild('Item', ['uri' => ['controller' => 'Articles', 'action' => 'index']]);
then this item will match, and it will be set as the current item, even though it has no query arguments defined. Likewise an item which has query arguments defined would also match, even if the query arguments are different:
$menu->addChild('Item', ['uri' => ['controller' => 'Articles', 'action' => 'index', 'filter' => 'active']]);
In order to change this behavior, you can change the default matching type via the matching
option to
MATCH_URL_WITH_QUERY_STRING
, or use fuzzy route matching, ie MATCH_FUZZY_ROUTE
(refer to the next section for more
information):
use Icings\Menu\View\Helper\MenuHelper;
$this->Menu->setConfig([
'matching' => MenuHelper::MATCH_URL_WITH_QUERY_STRING,
]);
Even though query strings are generally not part of the route matching process in CakePHP, fuzzy route matching does support it (in a fuzzy manner of course).
If the URLs of a menu item define query arguments, then they will be included in the matching process, and all query values defined in the URL arrays must be present in order for a match to succeed. A match will be successful too if the current request has further query arguments, additionally to the ones defined in the URL arrays of the menu item.
Imagine the following URL array for the menu item:
['controller' => 'Articles', 'action' => 'index', 'filter' => 'all']
the following route:
$routes->connect('/:controller/:action');
and the following request URL:
/articles?filter=all
The menu item with the above URL array will match in that situation. It will also match when providing the query values
via the special ?
key:
['controller' => 'Articles', 'action' => 'index', '?' => ['filter' => 'all']]
and also when no query is provided at all:
['controller' => 'Articles', 'action' => 'index']
And when won't it match? It won't match when the keys/values are different. The following URL arrays wouldn't match:
['controller' => 'Articles', 'action' => 'index', 'filter' => 'active']
['controller' => 'Articles', 'action' => 'index', 'other' => 'value']
['controller' => 'Articles', 'action' => 'index', 'filter' => 'all', 'other' => 'value']
You can retrieve the current active item using the menu helper's MenuHelper::getCurrentItem()
method, it takes an
optional menu instance/name (from which to retrieve the active item) as the first argument, and options as the second
argument. The method will return the first active item in the menu, or null
if no active item was found.
The following options are available for retrieving the active item:
-
matching
(string
, defaults to\Icings\Menu\View\Helper\MenuHelper::MATCH_URL
) Defines the mode to use for matching the menu items against the current request in order to determine the active items. This is shorthand for passing a constructed matcher object via thematcher
option. -
matcher
(Icings\Menu\Matcher\MatcherInterface
, defaults toIcings\Menu\Matcher\Matcher
) The matcher object to use. -
voters
(Knp\Menu\Matcher\Voter\VoterInterface[]
, defaults to[Icings\Menu\Matcher\Voter\FuzzyRouteVoter]
) The voter objects to use. -
clearMatcher
(boolean
, defaults totrue
) Defines whether the matcher cache should be cleared after searching through the menu.
It should be noted that unless you are using the same matcher for retrieving the current item and for rendering (with
clearMatcher
disabled), the process for determining/matching the active item will run twice, once when using this
method, and again when rendering the menu!
Also note that similar to the MenuHelper::render()
method, this method will use the helper defaults for the options if
not specified.
Retrieve the active item from the menu with the name main
:
$currentItem = $this->Menu->getCurrentItem('main');
Retrieve the active item from the given menu instance:
$currentItem = $this->Menu->getCurrentItem($menu);
Retrieve the active item from the last created menu:
$currentItem = $this->Menu->getCurrentItem();
There are situations where you need to create new menus from existing menus, for example a breadcrumb navigation based on the main menu, this is where the ability to extract paths from a menu is useful.
Paths can be extracted using the menu helper's MenuHelper::extractPath()
method, it takes a menu item instance (for
which to extract the path) as the first argument, and options as the second argument. The method will return an array of
menu item instances, starting at the root, up to and including the given item.
The extracted items will be clones of the original items, with their respective parent and child items removed, so that
they represent a flat list of menu items. The original item (which will include the references to its parent and child
items) is attached as an extra with the key original
, so it can be retrieved via $item->getExtra('original')
.
The following options are available for extracting paths:
includeRoot
(bool
, defaults tofalse
) Defines whether to include the root element in the returned path. The root element, ie the top most element in a menu is usually the menu itself, not an actual menu item that is being rendered and has a URL assigned for matching, hence it is by default excluded.
Here's an example for extracting a path and creating a breadcrumb navigation from it:
// create main menu
$mainMenu = $this->Menu->create('main');
$mainMenu->addChild('Library', ['uri' => ['controller' => 'Library', 'action' => 'index']]);
$mainMenu['Library']->addChild('Web', ['uri' => ['controller' => 'Web', 'action' => 'index']]);
$mainMenu['Library']['Web']->addChild('Data', ['uri' => ['controller' => 'Data', 'action' => 'index']]);
$mainMenu->addChild('Projects', ['uri' => ['controller' => 'Projects', 'action' => 'index']]);
$mainMenu->addChild('Settings', ['uri' => ['controller' => 'Settings', 'action' => 'index']]);
// extract path based on the current/active item
$crumbs = [];
$currentItem = $this->Menu->getCurrentItem('main');
if ($currentItem) {
$crumbs = $this->Menu->extractPath($currentItem);
}
// create new menu
$crumbsMenu = $this->Menu->create('breadcrumbs', [
'templates' => [
'menu' => '<ol class="breadcrumb"{{attrs}}>{{items}}</ol>',
],
'currentAsLink' => false,
]);
$crumbsMenu->addChild('Home', ['uri' => ['controller' => 'Pages', 'action' => 'display', 'home']]);
// add extracted items to the new menu
foreach ($crumbs as $crumb) {
$crumbsMenu->addChild($crumb);
}
Assuming the Data
item is the current item, the above example would render the following HTML for the main menu and
the breadcrumbs menu:
<ul>
<li class="active-ancestor has-dropdown">
<a href="/library">Library</a>
<ul class="dropdown">
<li class="active-ancestor has-dropdown">
<a href="/web">Web</a>
<ul class="dropdown">
<li class="active">
<a href="/data">Data</a>
</li>
</ul>
</li>
</ul>
</li>
<li>
<a href="/projects">Projects</a>
</li>
<li>
<a href="/settings">Settings</a>
</li>
</ul>
<ol class="breadcrumb">
<li>
<a href="/">Home</a>
</li>
<li>
<a href="/library">Library</a>
</li>
<li>
<a href="/web">Web</a>
</li>
<li class="active">
<span>Data</span>
</li>
</ol>
The menu helper is designed to abstract creating a matcher, voters, and a renderer, but if required it's possible to hook in custom objects.
The following options are available for MenuHelper::setConfig()
and MenuHelper::render()
:
-
matcher
(Icings\Menu\Matcher\MatcherInterface
, defaults toIcings\Menu\Matcher\Matcher
)
The matcher object to use. -
voters
(Knp\Menu\Matcher\Voter\VoterInterface[]
, defaults to[Icings\Menu\Matcher\Voter\UrlVoter]
)
The voter objects to use. -
renderer
(Knp\Menu\Renderer\RendererInterface
, defaults toIcings\Menu\Renderer\StringTemplateRenderer
)
The renderer object to use.
$this->Menu->setConfig([
'matcher' => new CustomMatcher()
]);
$this->Menu->setConfig([
'voters' => [
new CustomVoter(),
new OtherCustomVoter()
]
]);
Additionally to defining multiple voters for the helper/menu, it's also possible to define voters per item via the
voters
option:
use Icings\Menu\Matcher\Voter\FuzzyRouteVoter;
$menu->addChild('Label', [
'uri' => ['controller' => 'Controller', 'action' => 'action'],
'voters' => [
new FuzzyRouteVoter($this->request),
new CustomVoter()
]
]);
The voters defined on an item will be tested before the voters defined in the helper/render configuration.
use Icings\Menu\Matcher\Matcher;
$this->Menu->setConfig([
'renderer' => [
new CustomRenderer($matcher)
]
]);
Aside from using the menu helper and its various configuration possibilities, it's also possible to manually utilize the library provided by this plugin, optionally combining things with the KnpMenu library:
use Icings\Menu\Integration\PerItemVotersExtension;
use Icings\Menu\Integration\RoutingExtension;
use Icings\Menu\Integration\TemplaterExtension;
use Icings\Menu\Matcher\Matcher;
use Icings\Menu\Matcher\Voter\UrlVoter;
use Icings\Menu\MenuFactory;
use Icings\Menu\Renderer\StringTemplateRenderer;
$factory = new MenuFactory();
$factory->addExtension(new RoutingExtension());
$factory->addExtension(new PerItemVotersExtension());
$factory->addExtension(new TemplaterExtension());
$menu = $factory->createItem('main');
$menu->addChild('Home', ['uri' => ['controller' => 'Pages', 'action' => 'display', 'home']]);
$menu->addChild('About', ['uri' => ['controller' => 'Pages', 'action' => 'display', 'about']]);
$menu->addChild('Services', ['uri' => '#']);
$menu['Services']->addChild('Research', ['uri' => ['controller' => 'Pages', 'action' => 'display', 'research']]);
$menu['Services']->addChild('Security', ['uri' => ['controller' => 'Pages', 'action' => 'display', 'security']]);
$menu->addChild('Contact', ['uri' => ['controller' => 'Pages', 'action' => 'display', 'contact']]);
$matcher = new Matcher();
$matcher->addVoter(new UrlVoter($this->request));
$renderer = new StringTemplateRenderer($matcher);
echo $renderer->render($menu);
$menu = $this->Menu->create('main', [
'menuAttributes' => [
'class' => 'side-nav',
],
]);
$menu->addChild('Articles', ['uri' => ['controller' => 'Articles']]);
$menu->addChild('Comments', ['uri' => ['controller' => 'Comments']]);
$menu->addChild('Divider', [
'templates' => ['text' => ''],
'attributes' => ['class' => 'divider'],
]);
$menu->addChild('Tags', ['uri' => ['controller' => 'Tags']]);
$menu->addChild('News', ['uri' => ['controller' => 'News']]);
<ul class="side-nav">
<li>
<a href="/articles">Articles</a>
</li>
<li>
<a href="/comments">Comments</a>
</li>
<li class="divider"></li>
<li>
<a href="/tags">Tags</a>
</li>
<li>
<a href="/news">News</a>
</li>
</ul>
use Icings\Menu\View\Helper\MenuHelper;
$this->Menu->setConfig([
'matching' => MenuHelper::MATCH_URL_WITH_QUERY_STRING,
]);
$menu = $this->Menu->create('main', [
'templates' => [
'menu' => '<dl{{attrs}}><dt>Filter:</dt>{{items}}</dl>',
'item' => '<dd{{attrs}}>{{link}}</dd>',
],
'menuAttributes' => [
'class' => 'sub-nav',
],
]);
$menu->addChild('All', ['uri' => ['controller' => 'Articles', 'action' => 'index', 'filter' => 'all']]);
$menu->addChild('Active', ['uri' => ['controller' => 'Articles', 'action' => 'index', 'filter' => 'active']]);
$menu->addChild('Pending', ['uri' => ['controller' => 'Articles', 'action' => 'index', 'filter' => 'pending']]);
$menu->addChild('Suspended', ['uri' => ['controller' => 'Articles', 'action' => 'index', 'filter' => 'suspended']]);
<dl class="sub-nav">
<dt>Filter:</dt>
<dd>
<a href="/articles?filter=all">All</a>
</dd>
<dd>
<a href="/articles?filter=active">Active</a>
</dd>
<dd>
<a href="/articles?filter=pending">Pending</a>
</dd>
<dd>
<a href="/articles?filter=suspended">Suspended</a>
</dd>
</dl>
$menu = $this->Menu->create('main', [
'menuAttributes' => [
'class' => 'dropdown menu',
'data-dropdown-menu',
],
'nestedMenuClass' => 'menu',
'branchClass' => 'is-dropdown-submenu-parent',
]);
$menu->addChild('Articles', ['uri' => ['controller' => 'Articles']]);
$menu['Articles']->addChild('Comments', ['uri' => ['controller' => 'Comments']]);
$menu['Articles']->addChild('Tags', ['uri' => ['controller' => 'Tags']]);
$menu->addChild('News', ['uri' => ['controller' => 'News']]);
$menu->addChild('Status', ['uri' => ['controller' => 'Pages', 'action' => 'display', 'status']]);
<ul class="dropdown menu" data-dropdown-menu="data-dropdown-menu">
<li class="is-dropdown-submenu-parent">
<a href="/articles">Articles</a>
<ul class="menu">
<li>
<a href="/comments">Comments</a>
</li>
<li>
<a href="/tags">Tags</a>
</li>
</ul>
</li>
<li>
<a href="/news">News</a>
</li>
<li>
<a href="/pages/display/status">Status</a>
</li>
</ul>
$menu = $this->Menu->create('main', [
'templates' => [
'link' => '<a href="{{url}}"{{attrs}}><i class="{{icon}}"></i> <span>{{label}}</span></a>',
],
'menuAttributes' => [
'class' => 'vertical menu',
],
'nestedMenuClass' => 'nested vertical menu',
'branchClass' => null,
]);
$menu->addChild('Articles', [
'uri' => ['controller' => 'Articles'],
'templateVars' => ['icon' => 'fi-book'],
]);
$menu['Articles']->addChild('Comments', [
'uri' => ['controller' => 'Comments'],
'templateVars' => ['icon' => 'fi-comments'],
]);
$menu['Articles']->addChild('Tags', [
'uri' => ['controller' => 'Tags'],
'templateVars' => ['icon' => 'fi-pricetag-multiple'],
]);
$menu->addChild('News', [
'uri' => ['controller' => 'News'],
'templateVars' => ['icon' => 'fi-rss'],
]);
$menu->addChild('Status', [
'uri' => ['controller' => 'Pages', 'action' => 'display', 'status'],
'templateVars' => ['icon' => 'fi-shield'],
]);
<ul class="vertical menu">
<li>
<a href="/articles"><i class="fi-book"></i> <span>Articles</span></a>
<ul class="nested vertical menu">
<li>
<a href="/comments"><i class="fi-comments"></i> <span>Comments</span></a>
</li>
<li>
<a href="/tags"><i class="fi-pricetag-multiple"></i> <span>Tags</span></a>
</li>
</ul>
</li>
<li>
<a href="/news"><i class="fi-rss"></i> <span>News</span></a>
</li>
<li>
<a href="/pages/display/status"><i class="fi-shield"></i> <span>Status</span></a>
</li>
</ul>
$menu = $this->Menu->create('main', [
'menuAttributes' => [
'class' => 'nav navbar-nav',
],
'nestedMenuClass' => 'dropdown-menu',
'branchClass' => 'dropdown',
]);
$menu->addChild('Page A', [
'uri' => ['controller' => 'Pages', 'action' => 'display', 'page-a'],
]);
$menu->addChild('Dropdown', [
'uri' => '#',
'templates' => [
'link' => '<a href="{{url}}"{{attrs}}>{{label}} <span class="caret"></span></a>',
],
'linkAttributes' => [
'class' => 'dropdown-toggle',
'data-toggle' => 'dropdown',
'role' => 'button',
'aria-haspopup' => 'true',
'aria-expanded' => 'false',
]
]);
$menu['Dropdown']->addChild('Page B', [
'uri' => ['controller' => 'Pages', 'action' => 'display', 'page-b'],
]);
$menu['Dropdown']->addChild('Divider', [
'templates' => ['text' => ''],
'attributes' => ['role' => 'separator', 'class' => 'divider'],
]);
$menu['Dropdown']->addChild('Page C', [
'uri' => ['controller' => 'Pages', 'action' => 'display', 'page-c'],
]);
$menu->addChild('Page D', [
'uri' => ['controller' => 'Pages', 'action' => 'display', 'page-d'],
]);
<ul class="nav navbar-nav">
<li>
<a href="/pages/display/page-a">Page A</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
<ul class="dropdown-menu">
<li>
<a href="/pages/display/page-b">Page B</a>
</li>
<li role="separator" class="divider"></li>
<li>
<a href="/pages/display/page-c">Page C</a>
</li>
</ul>
</li>
<li>
<a href="/pages/display/page-d">Page D</a>
</li>
</ul>