I have tried to set an icon next to the link of the menu generated by knpMenuBundle but no way to get it.
I found some solutions like ->setAttribute or ->setExtra, but I can't make it work.
Do I have to add specific lines to the base template of knp ?
Do something special in the twig render ?
Here is what I have tried so far:
//Builder.php
$public function mainMenu(FactoryInterface $factory, array $options)
{
$menu = $factory->createItem('root');
$menu->setChildrenAttribute('class', 'nav navbar-nav ');
$menu->addChild('Mes Informations',['route' => 'espace_client_mesInformations'])
->setAttribute('icon','fa fa-home');
$menu->addChild('Mes Factures', array('route' => 'espace_client_mesFactures'));
$menu->addChild('Mon Extrait de Compte', array('route' => 'espace_client_mesReglements'));
$menu->addChild('Mes Services', array('route' => 'espace_client_mesServices'));
$menu->addChild('Mes Consommations', array('route' => 'espace_client_mesConsommations'));
$menu['Mes Consommations']->addChild('Télephone', array('route' => 'espace_client_mesConsoTelephone'));
$menu['Mes Consommations']->addChild('Internet', array('route' => 'espace_client_mesConsoInternet'));
$menu->addChild('Mes Liens Directs', array('route' => 'espace_client_mesLiens'));
return $menu;
}
//menu.html.twig
<div class="panel panel-default">
<div class="panel-heading">
<center><h4> Menu </h4></center>
</div>
<div class="panel-body">
{{ knp_menu_render('EspaceClientBundle:Builder:mainMenu',{'currentAsLink':false, 'template': 'EspaceClientBundle:Menu:knp_menu.html.twig'}) }}
</div>
If anyone have an answer ?
Thanks.
Edit:
I'm a little bit confused by the different files of the bundle, I must have a problem in my php code so there is the content of them:
//knp_menu.html.twig:
{% extends 'knp_menu_base.html.twig' %}
{% macro attributes(attributes) %}
{% for name, value in attributes %}
{%- if value is not none and value is not same as(false) -%}
{{- ' %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 %}
{% if item.displayed %}
{# 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.matchingDepth) %}
{%- 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 %}
{# Mark item as "leaf" (no children) or as "branch" (has children that are displayed) #}
{% if item.hasChildren and options.depth is not same as(0) %}
{% if options.branch_class is not empty and item.displayChildren %}
{%- set classes = classes|merge([options.branch_class]) %}
{% endif %}
{% elseif options.leaf_class is not empty %}
{%- set classes = classes|merge([options.leaf_class]) %}
{%- endif %}
{%- set attributes = item.attributes %}
{%- if classes is not empty %}
{%- set attributes = attributes|merge({'class': classes|join(' ')}) %}
{%- endif %}
{# displaying the item #}
{% import _self as knp_menu %}
<li{{ knp_menu.attributes(attributes) }}>
{%- if item.uri is not empty and (not matcher.isCurrent(item) or options.currentAsLink) %}
{{ block('linkElement') }}
{%- else %}
{{ block('spanElement') }}
{%- endif %}
{# render the list of children#}
{%- set childrenClasses = item.childrenAttribute('class') is not empty ? [item.childrenAttribute('class')] : [] %}
{%- set childrenClasses = childrenClasses|merge(['menu_level_' ~ item.level]) %}
{%- set listAttributes = item.childrenAttributes|merge({'class': childrenClasses|join(' ') }) %}
{{ block('list') }}
</li>
{% endif %}
{% endblock %}
{% block linkElement %}
{% import "knp_menu.html.twig" as macros %}
<a href="{{ item.uri }}"{{ macros.attributes(item.linkAttributes) }}>
<span class="icon">{{ item.getExtra('icon') }}</span>
<span class="entry">{{ block('label') }}</span>
</a>
{% endblock %}
{% block spanElement %}{% import _self as knp_menu %}<span{{ knp_menu.attributes(item.labelAttributes) }}>{{ block('label') }}</span>{% endblock %}
You should edit your menu template EspaceClientBundle:Menu:knp_menu.html.twig like:
{% block label %}
{% if options.allow_safe_labels and item.getExtra('safe_label', false) %}
{{ item.label|raw|trans }}
{% else %}
{{ item.label }}
{% endif %}
{% if item.extras.icon is defined %}
<i class="{{ item.extras.icon }}"></i>
{% endif %}
{% endblock %}
I recommend you, to delete the whitespaces between twig tags.
Then, in menu builder:
$menu->addChild('Mes Informations', [
'route' => 'espace_client_mesInformations',
'extras' => ['icon' => 'fa fa-home']
]);
Related
I am currently making a menu with the Symfony bundle: KnpMenuBundle. I am using Bootstrap 4 as stylesheet.
Bootstrap 4 requires each list item in the navbar to have the class 'nav-item':
<li class="nav-item active"> <-- this
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
I can't seem to figure out how to add the class 'nav-item' to the list item with KnpMenuBundle. Currently I see this when I load the page:
navbar result
This is my Builder class in src/AppBundle/Menu:
namespace AppBundle\Menu;
use Knp\Menu\MenuFactory;
class Builder
{
public function mainMenu(MenuFactory $factory, array $optioins)
{
$menu = $factory->createItem('root');
$menu->setChildrenAttribute('class', 'navbar-nav mr-auto');
$menu->addChild('Home', ['route' => 'homepage']);
$menu->setChildrenAttributes(['class' => 'nav-item']);
return $menu;
}
}
My code in base.html.twig to generate menu:
{{ knp_menu_render('AppBundle:Builder:mainMenu', {'currentClass': 'active'}) }}
What do I do to make it working?
I did this to get top level correct. Haven't worked out dropdowns yet though.
$menu->setChildrenAttribute('class', 'navbar-nav');
// menu items
foreach ($menu as $child) {
$child->setLinkAttribute('class', 'nav-link')
->setAttribute('class', 'nav-item');
}
You can add a class to each child items when you add it like that:
$menu->addChild('Home', ['route' => 'homepage'])
->setAttributes(array(
'class' => 'nav-item'
));
You should create new template file, for example "extended_knp_menu.html.twig" (also filename should not be named knp_menu.html.twig), with contents (src):
{% extends 'knp_menu.html.twig' %}
{% macro setCssClassAttribute(item, type, add) %}
{% set getter = 'get' ~ type %}
{% set setter = 'set' ~ type %}
{% set value = attribute(item, getter, ['class']) %}
{% if value is iterable %}
{% set value = value|join(' ') %}
{% endif %}
{% do attribute(item, setter, ['class', value ~ ' ' ~ add]) %}
{% endmacro %}
{% block item %}
{% import "knp_menu.html.twig" as macros %}
{#
As multiple level is not currently supported by bootstrap 4
This requires you to install
https://github.com/bootstrapthemesco/bootstrap-4-multi-dropdown-navbar
And set the the use_multilevel = true
#}
{% set use_multilevel = false %}
{% 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'), 'nav-item'] : ['nav-item'] %}
{%- 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(' ') }) %}
<li{{ macros.attributes(attributes) }}>
{# displaying the item #}
{%- if is_dropdown %}
{{ block('dropdownElement') }}
{%- elseif item.uri is not empty and (not item.current or options.currentAsLink) %}
{{ block('linkElement') }}
{%- else %}
{{ block('spanElement') }}
{%- endif %}
{%- if divider_append %}
{{ block('dividerElement') }}
{%- endif %}
{% if item.hasChildren and options.depth is not same as(0) and item.displayChildren %}
{{ block('dropdownlinks') }}
{% endif %}
</li>
{% endif %}
{% endblock %}
{% block dropdownlinks %}
{% if use_multilevel %}
<ul class="dropdown-menu">
{% else %}
<div class="dropdown-menu">
{% endif %}
{% for item in item.children %}
{{ block('renderDropdownlink') }}
{% if use_multilevel and item.hasChildren and options.depth is not same as(0) and item.displayChildren %}
{{ block('dropdownlinks') }}
{% endif %}
{% endfor %}
{% if not use_multilevel %}
</div>
{% else %}
</ul>
{% endif %}
{% endblock %}
{% block renderDropdownlink %}
{% import _self as ownmacro %}
{%- set divider_prepend = item.attributes.divider_prepend|default(false) %}
{%- set divider_append = item.attributes.divider_append|default(false) %}
{%- set attributes = item.attributes|merge({'dropdown': null, 'divider_prepend': null, 'divider_append': null }) %}
{% if use_multilevel %}
<li>
{% endif %}
{%- if divider_prepend %}
{{ block('dividerElementDropdown') }}
{%- endif %}
{%- if item.uri is not empty and (not item.current or options.currentAsLink) %}
{{ ownmacro.setCssClassAttribute(item, 'LinkAttribute', 'dropdown-item') }}
{{ block('linkElement') }}
{%- else %}
{{ block('spanElementDropdown') }}
{%- endif %}
{%- if divider_append %}
{{ block('dividerElementDropdown') }}
{%- endif %}
{% if use_multilevel %}
</li>
{% endif %}
{% endblock %}
{% block spanElementDropdown %}
{% import "knp_menu.html.twig" as macros %}
{% import _self as ownmacro %}
{{ ownmacro.setCssClassAttribute(item, 'LabelAttribute', 'dropdown-header') }}
<div {{ macros.attributes(item.labelAttributes) }}>
{% if item.attribute('icon') is not empty %}
<i class="{{ item.attribute('icon') }}"></i>
{% endif %}
{{ block('label') }}
</div>
{% endblock %}
{% block dividerElementDropdown %}
<div class="dropdown-divider"></div>
{% endblock %}
{% block dividerElement %}
{% if item.level == 1 %}
<li class="divider-vertical"></li>
{% else %}
<li class="divider"></li>
{% endif %}
{% endblock %}
{% block linkElement %}
{% import "knp_menu.html.twig" as macros %}
{% import _self as ownmacro %}
{{ ownmacro.setCssClassAttribute(item, 'LinkAttribute', 'nav-link') }}
<a href="{{ item.uri }}"{{ macros.attributes(item.linkAttributes) }}>
{% if item.attribute('icon') is not empty %}
<i class="{{ item.attribute('icon') }}"></i>
{% endif %}
{{ block('label') }}
</a>
{% endblock %}
{% block spanElement %}
{% import "knp_menu.html.twig" as macros %}
{% import _self as ownmacro %}
{{ ownmacro.setCssClassAttribute(item, 'LabelAttribute', 'navbar-text') }}
<span {{ macros.attributes(item.labelAttributes) }}>
{% if item.attribute('icon') is not empty %}
<i class="{{ item.attribute('icon') }}"></i>
{% endif %}
{{ block('label') }}
</span>
{% endblock %}
{% block dropdownElement %}
{% import "knp_menu.html.twig" as macros %}
{%- set classes = item.linkAttribute('class') is not empty ? [item.linkAttribute('class')] : [] %}
{%- set classes = classes|merge(['dropdown-toggle', 'nav-link']) %}
{%- set attributes = item.linkAttributes %}
{%- set attributes = attributes|merge({'class': classes|join(' ')}) %}
{%- set attributes = attributes|merge({'data-toggle': 'dropdown'}) %}
<a href="#"{{ macros.attributes(attributes) }}>
{% if item.attribute('icon') is not empty %}
<i class="{{ item.attribute('icon') }}"></i>
{% endif %}
{{ block('label') }}
<b class="caret"></b>
</a>
{% endblock %}
{% block label %}{{ item.label|trans }}{% endblock %}
After just include rendering template for menu:
{{ knp_menu_render('AppBundle:Builder:mainMenu', {
'currentClass': 'active',
'template': 'extended_knp_menu.html.twig'
}) }}
Hope it helped you.
So I have Twig running on top of Symfony 2.7. In the output html I'd like to have a few modules of text and in the last module I want some summary from all the previous ones (some data from all the previous modules' Entities) and I figured I'd append these summaries to a global variable while generating modules themselves to avoid a second loop. The code I'm using:
{% extends 'base.html.twig' %}
{% set list = '' %} {# HERE I SET A GLOBAL VAR #}
{% block body %}
<section>
<h2>{% trans %}MODULES{% endtrans %}</h2>
{% for m in plan.Modules %}
{{- block('module') -}}
{{ list }} {# HERE JUST FOR TESTING - IT'S EMPTY #}
{% endfor %}
</section>
{{ list }} {# HERE I WANT IT DISPLAYED, YET IT'S EMPTY :( #}
{% endblock %}
{% block module %}
<h3>{{ m.Module.title }}</h3>
{# HERE SOME MODULE TEXT I GET FROM COMPLICATED RELATIONS #}
{% if m.Module.list %}
{% set temp %}
{{ m.Module.shortTitle }}<br/>
{% endset %}
{% set list = list~temp %}
{% for l in m.Module.list %}
{% set temp %}
{{ l }}
{% endset %}
{% set list = list~temp %}
{% endif %}
{% endfor %}
{% endif %}
{{ list }} {# HERE IT'S WORKING #}
{% endblock %}
Any ideas?
I have a symfony page where I display a form, and I would like to add an
* in the labels of all fields that are required.
So I have my form.php.twig, that looks like this:
{% extends "::layout.php.twig" %}
{% block body %}
<div class="row">
{% use 'form_div_layout.html.twig' with form_label as base_form_label %}
{% block form_label %}
{{ block('base_form_label') }}
{% if required %}
<span class="required" title="This field is required">*</span>
{% endif %}
{% endblock %}
</div>
{% endblock %}
I followed the exact documentation of the symfony cookbook on how to customise labels, which is http://symfony.com/doc/current/cookbook/form/form_customization.html#cookbook-form-theming-methods.
But I keep getting this error
Variable "label" does not exist in form_div_layout.html.twig at line 206
I don't have any label variable in the code of my form.php.twig so I don't see why I get this error. And when I remove the
{{ block('base_form_label') }}
I get
Variable "required" does not exist in ATPlatformBundle:Session:create.php.twig
Can anyone help me on this? I don't see where is my mistake? I am not looking to customise it with css, but to add the * .
I have checked the form_div_layout.html.twig at line 206 and this is what there is
{%- block form_label -%}
{% if label is not sameas(false) -%}
{% if not compound -%}
{% set label_attr = label_attr|merge({'for': id}) %}
{%- endif %}
{% if required -%}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %}
{%- endif %}
{% if label is empty -%}
{%- if label_format is not empty -%}
{% set label = label_format|replace({
'%name%': name,
'%id%': id,
}) %}
{%- else -%}
{% set label = name|humanize %}
{%- endif -%}
{%- endif -%}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ translation_domain is sameas(false) ? label : label|trans({}, translation_domain) }}</label>
{%- endif -%}
{%- endblock form_label -%}
Thank you in advance.
Did you try defining the label from inside your form builder?! The below field is a required one because unless you mention required => false, your field will be rendered as required.
Something like:
->add('name', 'text', array('label' => '* name'))
With Twig, you should have to test the existence of a variable before using it:
Variable "required" does not exist
{% if required is defined and ... }
You need to test this because you field isn't always required.
if you want further information, you have this page of the documentation:
http://twig.sensiolabs.org/doc/tests/defined.html
Ok, so at the end, I didn't manage to change labels within my form.php.twig (which is the template I use to display my form), but I used another technique.
I created a new file named fields.php.twig, which I put in MyBundle/Resources/views/Form.
At the beginning of my fields.php.twig, I then added
{% extends 'form_div_layout.html.twig' %}
and below it, I added
{% block form_label %}
{{ parent() }}
{% if required %}
<span> * </span>
{% endif %}
{% endblock form_label %}
Then in my form.php.twig, I added
{% form_theme form 'MyBundle:Form:fields.php.twig' %}
{{ form_start(form) }}
Myform here with {{ form_label(form.property) }}
{{ form_errors(form.property) }}
{{ form_widget(form.property) }}
{{ form_end(form) }}
It works perfectly, but I had to create the fields.php.twig.
I have an entity field (visitors) in a form. I would like to highlight a person according to its status.
->add('visitors', 'entity', array(
'class' => "T2m3Bundle:Person",
'multiple' => true,
'property' => 'entireName',
'required' => false,
)
I have a method getStatus, that returns 4 states. On every state I want to put different "style" in an option tag.
Here is the example:
<select>
<option value="1">Mary</option>
<option value="2" class="alert alert-danger">John</option>
<option value="3" class="alert alert-success">Peter</option>
<option value="4" class="alert alert-warning">Robert</option>
</select>
So I want to do something like this:
{% for visitor in form.visitors %}
{% set index = visitor.vars.value %}
{% set entity = form.visitors.vars.choices[index].data %}
{% if entity.status == 1 %}
{{ form_widget(visitor) }}
<span class="alert alert-warning">{{ entity.entireName }}</span>
{% endif %}
{% if entity.status == 2 %}
{{ form_widget(visitor) }}
<span class="alert alert-danger">{{ entity.entireName }}</span>
{% endif %}
....
{% endfor %}
This example works for 'expanded' => true but not for collapse type. I would be really thankful for an answer.
Thanks in advance!
EDIT:
Expanded use this template with form_widget(...):
{% block choice_widget_expanded -%}
<div {{ block('widget_container_attributes') }}>
{%- for child in form %}
{{- form_widget(child) -}}
{{- form_label(child) -}}
{% endfor -%}
</div>
{% endblock choice_widget_expanded %}
but I don't know how to render manually collapsed type, where there is no {{- form_widget(...) -}}
Try to override the twig choice widget : refer to this link to find the according block. It's something like this
{% block choice_widget_collapsed -%}
{% if required and placeholder is none and not placeholder_in_choices and not multiple -%}
{% set required = false %}
{%- endif -%}
<select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
{% if placeholder is not none -%}
<option value=""{% if required and value is empty %} selected="selected"{% endif %}>{{ placeholder|trans({}, translation_domain) }}</option>
{%- endif %}
{%- if preferred_choices|length > 0 -%}
{% set options = preferred_choices %}
{{- block('choice_widget_options') -}}
{% if choices|length > 0 and separator is not none -%}
<option disabled="disabled">{{ separator }}</option>
{%- endif %}
{%- endif -%}
{% set options = choices -%}
{{- block('choice_widget_options') -}}
</select>
{%- endblock choice_widget_collapsed %}
{% block choice_widget_options -%}
{% for group_label, choice in options %}
{%- if choice is iterable -%}
<optgroup label="{{ group_label|trans({}, translation_domain) }}">
{% set options = choice %}
{{- block('choice_widget_options') -}}
</optgroup>
{%- else -%}
{# HERE we define the class based on the status #}
{% if status is defined %}
{% if status == 1 %}
{% set status_class = 'alert alert-warning' %}
{% elseif status == 2 %}
{% set status_class = 'alert alert-danger' %}
{% elseif ... %}
....
{% endif %}
{% endif %}
<option value="{{ choice.value }}"{% if choice is selectedchoice(value) %} selected="selected"{% endif %} {% if status_class is defined %} class="{{ status_class }}" {% endif %} >{{ choice.label|trans({}, translation_domain) }}</option>
{%- endif -%}
{% endfor %}
{%- endblock choice_widget_options %}
Finally in your template, call the widget like this :
{% for visitor in form.visitors %}
{% set index = visitor.vars.value %}
{% set entity = form.visitors.vars.choices[index].data %}
{{ form_widget(visitor, {'status': entity.status}) }}
....
{% endfor %}
This is not tested, but should work. Let me know.
In my form template:
{% block field_label %}
{% spaceless %}
<label {{ block('widget_attributes') }}></label>
{% endspaceless %}
{% endblock %}
I need to add some extra class attributes and, of course, display the label (possibly translated). I've found that {{ block('widget_attributes') }} stores a string of attributes of the actual label.
Any chance to get an array type to easily modify it? Where i can find all block names to customize the appearance to fit my needs? Thanks for helping.
Nevermind Notepad++ helped me to find the file:
vendor\symfony\src\Symfony\Bridge\Twig\Resources\views
Three types of label defined:
{# Labels #}
{% block generic_label %}
{% spaceless %}
{% if required %}
{% set attr = attr|merge({'class': attr.class|default('') ~ ' required'}) %}
{% endif %}
<label{% for attrname,attrvalue in attr %} {{attrname}}="{{attrvalue}}"{% endfor %}>{{ label|trans }}</label>
{% endspaceless %}
{% endblock %}
{% block field_label %}
{% spaceless %}
{% set attr = attr|merge({'for': id}) %}
{{ block('generic_label') }}
{% endspaceless %}
{% endblock field_label %}
{% block form_label %}
{% spaceless %}
{{ block('generic_label') }}
{% endspaceless %}
{% endblock form_label %}