Skip to content

Instantly share code, notes, and snippets.

@leoetlino
Forked from nielsmouthaan/MenuBuilder.php
Last active December 16, 2015 11:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leoetlino/5425529 to your computer and use it in GitHub Desktop.
Save leoetlino/5425529 to your computer and use it in GitHub Desktop.
Modified to add support for submenus.

For a recent project, I needed to include KnpMenuBundle in a Symfony2 project. This project was styled using the famous Twitter Bootstrap. I’ve found many articles describing how to do this but all of them didn’t exactly resulted in something I wanted: a web application with a navigation bar including icons and dropdown menu’s. In addition, I needed a “active” class indicating which item was visited, including parent/child items.

The files below belong to a tutorial which can be found here: http://nielsmouthaan.nl/symfony2-knpmenubundle-font-awesome-twitter-bootstrap-integration-navigation-bar-including-icons-and-dropdown-menus/

{% extends '::base.html.twig' %}
{% block title %}My Awesome Page!{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.1.0/css/bootstrap.no-icons.min.css">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/2.0/css/font-awesome.css">
{% endblock %}
{% block body %}
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<a class="brand" href="#">Awesome!</a>
{{ 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 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/twitter-bootstrap/2.1.0/js/bootstrap.min.js"></script>
{% endblock %}
{% extends 'knp_menu.html.twig' %}
{% block item %}
{% import "knp_menu.html.twig" as macros %}
{% if item.displayed %}
{%- set attributes = item.attributes %}
{%- set is_dropdown = attributes.dropdown|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 }) %}
{%- 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 %}
{%- if item.level > 1 %}
{%- set classes = classes|merge(['dropdown-submenu']) %}
{%- else %}
{%- set classes = classes|merge(['dropdown']) %}
{%- endif %}
{%- 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 matcher.isCurrent(item) or options.currentAsLink) %}
{{ block('linkElement') }}
{%- else %}
{{ block('spanElement') }}
{%- endif %}
{# render the list of children#}
{{ block('list') }}
</li>
{%- if 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 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) }}>{{ block('label') }}{% if item.level <= 1 %} <b class="caret"></b>{% endif %}</a>
{% endblock %}
{% block label %}{% if item.attribute('icon') is not empty %}
<i class="{{ item.attribute('icon') }}"></i>
{% endif %}{{ item.label|trans }}{% 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');
$menu->addChild('Projects', array('route' => 'acme_hello_projects'))
->setAttribute('icon', 'icon-list');
$menu->addChild('Employees', array('route' => 'acme_hello_employees'))
->setAttribute('icon', 'icon-group');
return $menu;
}
public function userMenu(FactoryInterface $factory, array $options)
{
$menu = $factory->createItem('root');
$menu->setChildrenAttribute('class', 'nav pull-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'))
->setAttribute('dropdown', true)
->setAttribute('icon', 'icon-user');
$menu['User']->addChild('Edit profile', array('route' => 'acme_hello_profile'))
->setAttribute('icon', 'icon-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() !== '/' && (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 }
@gondo
Copy link

gondo commented Apr 28, 2014

in fact, you don't need custom renderer at all. you can achieve this just in menu builder:

$sub = $menu->addChild('Sub', array(
    'uri' => '#',
    'linkAttributes' => array(
        'class' => 'dropdown-toggle',
        'data-toggle' => 'dropdown',
    ),
    'childrenAttributes' => array(
        'class' => 'dropdown-menu',
    ),
    'attributes' => array(
        'class' => 'dropdown',
    ),
));
foreach ($subMenuItems as $item) {
    $sub->addChild($item['name'], array(
        'route' => $item['route'],
    ));
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment