Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
KNP Menu Bundle with Bootstrap 3 and Font Awesome 4 (+ fixed several bugs)
{% extends '::base.html.twig' %}
{% block title %}My Awesome Page!{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css">
{% endblock %}
{% block body %}
<div class="navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
{# Brand and toggle get grouped for better mobile display #}
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#admin-navbar-collapse-1">
<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="#">Awesome!</a>
</div>
{# Collect the nav links, forms, and other content for toggling #}
<div class="collapse navbar-collapse" id="admin-navbar-collapse-1">
{{ knp_menu_render('AcmeHelloBundle:MenuBuilder:mainMenu', {'currentClass': 'active', 'template': 'AcmeHelloBundle:Menu:knp_menu.html.twig'}) }}
{{ knp_menu_render('AcmeHelloBundle:MenuBuilder:userMenu', {'currentClass': 'active', 'template': 'AcmeHelloBundle:Menu:knp_menu.html.twig'}) }}
</div>
</div>
</div>
<div class="content" style="margin-top: 60px;">
Content goes here!
</div>
{% endblock %}
{% block javascripts %}
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
<script type="text/javascript" src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
{% endblock %}
{% extends '@KnpMenu/menu.html.twig' %}
{% import 'knp_menu.html.twig' as macros %}
{% block item %}
{% if item.displayed %}
{%- set attributes = item.attributes %}
{%- set is_dropdown = item.getExtra('dropdown', false) %}
{%- set icon = item.getExtra('icon') %}
{%- if item.getExtra('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 #}
<li{{ macros.attributes(attributes) }}>
{%- if is_dropdown %}
{{- block('dropdownElement') }}
{%- elseif item.uri is not empty and (not item.current or options.currentAsLink) %}
{{- block('linkElement') }}
{%- else %}
{{- block('spanElement') }}
{%- endif %}
{# render the list of children#}
{{ block('list') }}
</li>
{%- if item.getExtra('divider_append') %}
{{ block('dividerElement') }}
{%- endif %}
{% endif %}
{% endblock %}
{% block dividerElement %}
{% if item.level == 1 %}
<li class="divider-vertical"></li>
{% else %}
<li class="divider"></li>
{% endif %}
{% endblock %}
{% block linkElement %}
<a href="{{ item.uri }}"{{ macros.attributes(item.linkAttributes) }}>
{%- if icon is not empty %}
<i class="{{ icon }}"></i>
{% endif %}
{{- block('label') -}}
</a>
{% endblock %}
{% block spanElement %}
<span{{ macros.attributes(item.labelAttributes) }}>
{%- if icon is not empty %}
<i class="{{ icon }}"></i>
{% endif %}
{{- block('label') -}}
</span>
{% endblock %}
{% block dropdownElement %}
{%- 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="#"{{ macros.attributes(attributes) }}>
{%- if icon is not empty %}
<i class="{{ icon }}"></i>
{% endif %}
{{- block('label') }}
<b class="caret"></b>
</a>
{% endblock %}
<?php
namespace Acme\HelloBundle\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\DependencyInjection\ContainerAware;
class MenuBuilder extends ContainerAware
{
public function mainMenu(FactoryInterface $factory, array $options)
{
$menu = $factory->createItem('root');
$menu->setChildrenAttribute('class', 'nav navbar-nav');
$menu->addChild('Projects', array('route' => 'acme_hello_projects'))
->setExtra('icon', 'fa fa-list');
$menu->addChild('Employees', array('route' => 'acme_hello_employees'))
->setExtra('icon', 'fa fa-group');
return $menu;
}
public function userMenu(FactoryInterface $factory, array $options)
{
$menu = $factory->createItem('root');
$menu->setChildrenAttribute('class', 'nav navbar-nav navbar-right');
/*
You probably want to show user specific information such as the username here. That's possible! Use any of the below methods to do this.
if($this->container->get('security.context')->isGranted(array('ROLE_ADMIN', 'ROLE_USER'))) {} // Check if the visitor has any authenticated roles
$username = $this->container->get('security.context')->getToken()->getUser()->getUsername(); // Get username of the current logged in user
*/
$menu->addChild('User', array('label' => 'Hi visitor'))
->setExtras(array(
'dropdown' => true,
'icon' => 'fa fa-user',
));
$menu['User']->addChild('Edit profile', array('route' => 'acme_hello_profile'))
->setExtra('icon', 'fa fa-edit');
return $menu;
}
}
<?php
namespace Acme\HelloBundle\Menu;
use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\Voter\VoterInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class RequestVoter implements VoterInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function matchItem(ItemInterface $item)
{
if ($item->getUri() === $this->container->get('request')->getRequestUri()) {
// URL's completely match
return true;
} else if($item->getUri() !== $this->container->get('request')->getBaseUrl().'/' && (substr($this->container->get('request')->getRequestUri(), 0, strlen($item->getUri())) === $item->getUri())) {
// URL isn't just "/" and the first part of the URL match
return true;
}
return null;
}
}
services:
acme.hello.menu.voter.request:
class: Acme\HelloBundle\Menu\RequestVoter
arguments:
- @service_container
tags:
- { name: knp_menu.voter }
@ElvenDev

This comment has been minimized.

Copy link

@ElvenDev ElvenDev commented Feb 27, 2017

Hmm, I love it but it doesn't suppert dropdowns?

Edit: Ill leave that here:

$menu->addChild('Dropdown', ['uri' => "#"])
                ->setLinkAttribute('class', "dropdown-toggle")
                ->setLinkAttribute('data-toggle', "dropdown");
            $menu['Dropdown']
                ->setChildrenAttribute("class", "dropdown-menu")
                ->addChild('Item 1, array('route' => 'route'));
@Gyrus-Ivan

This comment has been minimized.

Copy link

@Gyrus-Ivan Gyrus-Ivan commented Apr 25, 2017

To work correct in symfony 3.2 need updates:

RequestVoter

`<?php
namespace Bundles\CoreBundle\Menu;

use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\Voter\VoterInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;

class RequestVoter implements VoterInterface
{
/**
* @var Request
*/
private $request;

public function __construct(Request $request)
{
    $this->request = $request;
}

/**
 * @param ItemInterface $item
 * @return bool|null
 */
public function matchItem(ItemInterface $item)
{
    if ($item->getUri() === $this->request->getRequestUri()) {
        // URL's completely match
        return true;
    } else if($item->getUri() !== $this->request->getBaseUrl().'/' && (substr($this->request->getRequestUri(), 0, strlen($item->getUri())) === $item->getUri())) {
        // URL isn't just "/" and the first part of the URL match
        return true;
    }
    return null;
}

}
`

and service

service.yml

`
services:

bundles.core.menu.voter.request:

    class: Bundles\CoreBundle\Menu\RequestVoter
    autowire: true
    tags:
        - { name: knp_menu.voter }

`
to see correct remove new line in service

@ghost

This comment has been minimized.

Copy link

@ghost ghost commented Jun 3, 2017

In > Symfony3.2 you should use the ContainerAwareTrait because Symfony\Component\DependencyInjection\ContainerAware is not supported anymore. Thanks for sharing.

Replace:
use Symfony\Component\DependencyInjection\ContainerAware;
with:
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
and then add the trait in the class itself:
use ContainerAwareTrait;

@chmielot

This comment has been minimized.

Copy link

@chmielot chmielot commented Feb 6, 2018

Thank you for providing that. Can you please explain why the RequestVoter is needed?

@chmielot

This comment has been minimized.

Copy link

@chmielot chmielot commented Mar 18, 2018

Dropdown is supported by using:

$username = $this->tokenStorage->getToken()->getUsername();

$menu->addChild($username, [
    'extras' => [
        'dropdown' => true,
    ]
]);
$menu[$username]->addChild('Logout', array(
    'route' => 'fos_user_security_logout'
));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment