Updated:
- Twig 2
- Symfony 3
- Nav headers
- corrections in spaceless shorttags
- some extra logged in/out functionality
Thanks to Nate Evans's original extension of Niels Mouthaan's work. https://gist.github.com/nateevans/9958390
Instantly share code, notes, and snippets.
Updated:
Thanks to Nate Evans's original extension of Niels Mouthaan's work. https://gist.github.com/nateevans/9958390
# add this to your main config file to globally use the template located in app/Resources/views/menu/menu.html.twig | |
knp_menu: | |
twig: | |
template: :menu:menu_template.html.twig |
{% block nav %} | |
<div class="navbar navbar-default navbar-fixed-top"> | |
<div class="container-fluid"> | |
<div class="row"> | |
<!-- Brand and toggle get grouped for better mobile display --> | |
<div class="navbar-header"> | |
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse-1" aria-expanded="false"> | |
<span class="sr-only">Toggle navigation</span> | |
<span class="icon-bar"></span> | |
<span class="icon-bar"></span> | |
<span class="icon-bar"></span> | |
</button> | |
<a class="navbar-brand" href="#">Brand</a> | |
</div> | |
<div class="collapse navbar-collapse" id="navbar-collapse-1"> | |
{# only load main menu if logged in #} | |
{% if is_granted('IS_FULLY_AUTHENTICATED') %} | |
{{ knp_menu_render('AppBundle:MenuBuilder:mainMenu', {currentClass: 'active'}) }} | |
{% endif %} | |
{{ knp_menu_render('AppBundle:MenuBuilder:userMenu', {currentClass: 'active'}) }} | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
{%- extends 'knp_menu.html.twig' -%} | |
{%- macro attributes(attributes) -%} | |
{%- for name, value in attributes -%} | |
{%- if value is not none and value is not same as(false) and name != 'icon' -%} | |
{{- ' %s="%s"'|format(name, value is same as(true) ? name|e : value|e)|raw -}} | |
{%- endif -%} | |
{%- endfor -%} | |
{%- endmacro -%} | |
{%- block compressed_root -%} | |
{%- spaceless -%} | |
{{ block('root') }} | |
{%- endspaceless -%} | |
{%- endblock -%} | |
{%- block root -%} | |
{%- set listAttributes = item.childrenAttributes -%} | |
{{ block('list') -}} | |
{%- endblock -%} | |
{%- block list -%} | |
{%- if item.hasChildren and options.depth is not same as(0) and item.displayChildren -%} | |
{%- import _self as knp_menu -%} | |
<ul{{ knp_menu.attributes(listAttributes) }}> | |
{{ block('children') }} | |
</ul> | |
{%- endif -%} | |
{%- endblock -%} | |
{%- block children -%} | |
{# save current variables #} | |
{%- set currentOptions = options -%} | |
{%- set currentItem = item -%} | |
{# update the depth for children #} | |
{%- if options.depth is not none -%} | |
{%- set options = options|merge({'depth': currentOptions.depth - 1}) -%} | |
{%- endif -%} | |
{# update the matchingDepth for children #} | |
{%- if options.matchingDepth is not none and options.matchingDepth > 0 -%} | |
{%- set options = options|merge({'matchingDepth': currentOptions.matchingDepth - 1}) -%} | |
{%- endif -%} | |
{%- for item in currentItem.children -%} | |
{{ block('item') }} | |
{%- endfor -%} | |
{# restore current variables #} | |
{%- set item = currentItem -%} | |
{%- set options = currentOptions -%} | |
{%- endblock -%} | |
{%- block item -%} | |
{%- import _self as knp_menu -%} | |
{%- if item.displayed -%} | |
{%- set attributes = item.attributes -%} | |
{%- set is_dropdown = attributes.dropdown|default(false) -%} | |
{%- set is_header = attributes.header|default(false) -%} | |
{%- set divider_prepend = attributes.divider_prepend|default(false) -%} | |
{%- set divider_append = attributes.divider_append|default(false) -%} | |
{# unset bootstrap specific attributes #} | |
{%- set attributes = attributes|merge({'dropdown': null, 'divider_prepend': null, 'divider_append': null, 'header': null }) -%} | |
{%- if divider_prepend -%} | |
{{ block('dividerElement') }} | |
{%- endif -%} | |
{# building the class of the item #} | |
{%- set classes = item.attribute('class') is not empty ? [item.attribute('class')] : [] -%} | |
{%- if matcher.isCurrent(item) -%} | |
{%- set classes = classes|merge([options.currentClass]) -%} | |
{%- elseif matcher.isAncestor(item, options.depth) -%} | |
{%- set classes = classes|merge([options.ancestorClass]) -%} | |
{%- endif -%} | |
{%- if item.actsLikeFirst -%} | |
{%- set classes = classes|merge([options.firstClass]) -%} | |
{%- endif -%} | |
{%- if item.actsLikeLast -%} | |
{%- set classes = classes|merge([options.lastClass]) -%} | |
{%- endif -%} | |
{# building the class of the children #} | |
{%- set childrenClasses = item.childrenAttribute('class') is not empty ? [item.childrenAttribute('class')] : [] -%} | |
{%- set childrenClasses = childrenClasses|merge(['menu_level_' ~ item.level]) -%} | |
{# adding classes for dropdown #} | |
{%- if is_dropdown -%} | |
{%- set classes = classes|merge(['dropdown']) -%} | |
{%- set childrenClasses = childrenClasses|merge(['dropdown-menu']) -%} | |
{%- endif -%} | |
{# putting classes together #} | |
{%- if classes is not empty -%} | |
{%- set attributes = attributes|merge({'class': classes|join(' ')}) -%} | |
{%- endif -%} | |
{%- set listAttributes = item.childrenAttributes|merge({'class': childrenClasses|join(' ') }) -%} | |
{# displaying the item #} | |
{% if not divider_prepend and not divider_append %} | |
<li{{ knp_menu.attributes(attributes) }}> | |
{%- if is_dropdown -%} | |
{{ block('dropdownElement') }} | |
{%- elseif item.uri is not empty and (not item.current or options.currentAsLink) -%} | |
{{ block('linkElement') }} | |
{%- elseif is_header -%} | |
{{ block('headerElement') }} | |
{%- else -%} | |
{{ block('spanElement') }} | |
{%- endif -%} | |
{# render the list of children#} | |
{{ block('list') }} | |
</li> | |
{% endif %} | |
{%- if divider_append -%} | |
{{ block('dividerElement') }} | |
{%- endif -%} | |
{%- endif -%} | |
{%- endblock -%} | |
{# elements #} | |
{%- block linkElement -%} | |
{%- import _self as knp_menu -%} | |
<a href="{{ item.uri }}"{{ knp_menu.attributes(item.linkAttributes) }}> | |
{%- if item.attribute('icon') is not empty -%} | |
<i class="{{ item.attribute('icon') }}" aria-hidden="true"></i> | |
{%- endif -%} | |
{{ block('label') }} | |
</a> | |
{%- endblock -%} | |
{%- block dividerElement -%} | |
{%- if item.level == 1 -%} | |
<li class="divider-vertical"></li> | |
{%- else -%} | |
<li class="divider"></li> | |
{%- endif -%} | |
{%- endblock -%} | |
{%- block headerElement -%} | |
<li class="dropdown-header">{{ block('label') }}</li> | |
{%- endblock -%} | |
{%- block spanElement -%} | |
{%- import _self as knp_menu -%} | |
<span>{{ knp_menu.attributes(item.labelAttributes) }}> | |
{%- if item.attribute('icon') is not empty -%} | |
<i class="{{ item.attribute('icon') }}" aria-hidden="true"></i> | |
{%- endif -%} | |
{{ block('label') }} | |
</span> | |
{%- endblock -%} | |
{%- block dropdownElement -%} | |
{%- import _self as knp_menu -%} | |
{%- set classes = item.linkAttribute('class') is not empty ? [item.linkAttribute('class')] : [] -%} | |
{%- set classes = classes|merge(['dropdown-toggle']) -%} | |
{%- set attributes = item.linkAttributes -%} | |
{%- set attributes = attributes|merge({'class': classes|join(' ')}) -%} | |
{%- set attributes = attributes|merge({'data-toggle': 'dropdown'}) -%} | |
<a href="#"{{ knp_menu.attributes(attributes) }}> | |
{%- if item.attribute('icon') is not empty -%} | |
<i class="{{ item.attribute('icon') }}" aria-hidden="true"></i> | |
{%- endif -%} | |
{{ block('label') }} | |
<b class="caret"></b> | |
</a> | |
{%- endblock -%} |
<?php | |
namespace AppBundle\Menu; | |
use Knp\Menu\FactoryInterface; | |
use Symfony\Component\DependencyInjection\Container; | |
use Symfony\Component\DependencyInjection\ContainerAwareInterface; | |
use Symfony\Component\DependencyInjection\ContainerAwareTrait; | |
/** | |
* Class MenuBuilder | |
* | |
* @Author Matt Holbrook-Bull <matt@ampisoft.com> | |
* @package AppBundle\Menu | |
*/ | |
class MenuBuilder implements ContainerAwareInterface | |
{ | |
use ContainerAwareTrait; | |
/** @var Container */ | |
protected $container; | |
/** | |
* just to allow easy setup, all the routes are 'homepage', change them as you like | |
*/ | |
/** | |
* @param FactoryInterface $factory | |
* @param array $options | |
* @return \Knp\Menu\ItemInterface | |
*/ | |
public function mainMenu( FactoryInterface $factory, array $options ) | |
{ | |
$menu = $factory->createItem( 'root' ); | |
$menu->setChildrenAttribute( 'class', 'nav navbar-nav' ); | |
$menu->addChild( 'Bananas', [ 'route' => 'homepage' ] ) | |
->setAttribute( 'icon', 'fa fa-list' ); | |
$menu->addChild( 'Strawberries', [ 'route' => 'homepage' ] ) | |
->setAttribute( 'icon', 'fa fa-group' ); | |
return $menu; | |
} | |
/** | |
* @param FactoryInterface $factory | |
* @param array $options | |
* @return \Knp\Menu\ItemInterface | |
* @throws \InvalidArgumentException | |
*/ | |
public function userMenu( FactoryInterface $factory, array $options ) | |
{ | |
$token = $this->container->get( 'security.token_storage' ) | |
->getToken(); | |
$auth = $this->container->get( 'security.authorization_checker' ); | |
$menu = $factory->createItem( 'root' ); | |
$menu->setChildrenAttribute( 'class', 'nav navbar-nav navbar-right' ); | |
if ( $auth->isGranted( 'IS_AUTHENTICATED_FULLY' ) ) { | |
$username = $token->getUser() | |
->getUsername(); // Get username of the current logged in user | |
} else { | |
$username = 'guest'; | |
} | |
$menu->addChild( 'User', [ | |
'label' => $username, | |
] ) | |
->setAttribute( 'dropdown', true ); | |
if ( $auth->isGranted( 'IS_AUTHENTICATED_FULLY' ) ) { | |
$menu[ 'User' ]->addChild( 'Edit profile', [ | |
'label' => 'Edit profile', | |
'route' => 'homepage', | |
] ) | |
->setAttribute( 'icon', 'fa fa-edit' ); | |
$menu[ 'User' ]->addChild( 'Messages', [ | |
'label' => 'Messages', | |
'route' => 'homepage', | |
] ) | |
->setAttribute( 'icon', 'fa fa-envelope-o' ); | |
$menu->addChild( 'Logout', [ | |
'label' => 'Logout', | |
'route' => 'homepage', | |
] ) | |
->setAttribute( 'icon', 'fa fa-sign-out' ); | |
} else { | |
$menu['User']->addChild('header', ['label' => 'section1']) | |
->setAttribute('header', true); | |
$menu['User']->addChild('Register', [ | |
'label' => 'Register', | |
'route' => 'homepage' | |
]); | |
} | |
return $menu; | |
} | |
} |