I have the following code in a Twig template:
{% macro option_display(type) -%}
{% if server.options_array.options.{{type}}.name is defined %}
{{server.options_array.options.{{type}}.name}} +${{server.options_array.options.~type~.price}}
{%else%}
None
{% endif %}
{%- endmacro %}
I'm getting this error:
Twig_Error_Syntax: Expected name or number
How should I get it to expand the variable 'type' in that context? I've tried using ~type~ as well (concat).
would be something like $server['options_array']['options'][$type]['name']; in PHP.
Just use the bracket notation:
{% macro option_display(type) -%}
{% if server.options_array.options[type].name is defined %}
{{ server.options_array.options[type].name }} +${{ server.options_array.options[type].price }}
{% else %}
None
{% endif %}
{%- endmacro %}
You probably want to also check whether server.options_array.options[type] is defined before checking whether server.options_array.options[type].name is defined.
Furthermore, like the documentation of the macro tag says, "as with PHP functions, macros don't have access to the current template variables." So you probably need to pass the server variable to the macro as well.
So here's a more complete example:
{% macro option_display(server, type) -%}
{% set option = server.options_array.options[type]|default(null) %}
{% if option.name is defined %}
{{ option.name }} +${{ option.price }}
{% else %}
None
{% endif %}
{%- endmacro %}
{% from _self import option_display %}
{{ option_display(server, 'foo') }}
{{ option_display(server, 'bar') }}
Notice that I'm using a helper variable option and the default filter. Without the filter, you'd get a Twig_Error_Runtime exception if the server.options_array.options array doesn't have the key you are using. (Whether you get this exception or not depends on the value of the environment option strict_variables.)
See TwigFiddle
I have Symfony3 app and I am making a simple form the code in the twig is as follows
{{ form_start(edit_form) }}
{{ form_widget(edit_form) }}
<input type="submit" value="Edit" />
{{ form_end(edit_form) }}
Pretty simple. What this code creates is a form and each form field is within it's own <div> which is fine, but if the type is date here is what the generated html looks like
<div>
<label class="required">Term</label>
<div id="appbundle_project_term">
<select id="appbundle_project_term_year" name="appbundle_project[term][year]"></select>
<select id="appbundle_project_term_year" name="appbundle_project[term][month]"></select>
<select id="appbundle_project_term_year" name="appbundle_project[term][day]"></select>
</div>
</div>
What bugs me is the inner div created for the date type field. Is there a way in the FormBuilder to keep the type date but remove this inner div without using javascript to handle it or in the twig template. Simply to say - "inner tag => span".
This is pretty generic question as I am looking for a way to usually change the auto generated tags, but if needed here is how this form field is created in form builder
add('term',DateType::class, array(
'widget' => 'choice',
'label'=>"Term",
'data'=>$project->getTerm()
))
You can override form rendering, there are few ways.
The simplest one is overriding form theme widget block (in this case date_widget) and setting form_theme to _self.
Basic example:
{% form_theme form _self %}
{% block date_widget %}
<span>
{% if widget == 'single_text' %}
{{ block('form_widget_simple') }}
{% else %}
{# rendering 3 fields for year, month and day #}
{{ form_widget(form.year) }}
{{ form_widget(form.month) }}
{{ form_widget(form.day) }}
{% endif %}
</span>
{% endblock %}
{% block content %}
{# ... form rendering #}
{{ form_row(form.someDateField) }}
{% endblock %}
My layout.html.twig:
{{ render(controller('AcmeDemoBundle:Page:mainmenu')) }}
The Page controller retrieves all pages from the Doctrine and renders mainmenu.html.twig with a set of pages.
My mainmenu.html.twig:
{% if menu_pages is defined %}
{% for page in menu_pages %}
<li class="{% if app.request.attributes.get('_internal') == '_page_show' and app.request.get('id') == page.id %}active{% endif %}">{{ page.title|e }}</li>
{% endfor %}
{% endif %}
But no active class is displayed. As far as I understand the problem is in internal routing. How to fix that?
Better do not use {{ render(controller('AcmeDemoBundle:Page:mainmenu')) }}. It work more fast and comfortable when you use services instead. You can create a service which will show menu on any page where you include them. And in service you can easy get access to current _route for add active class.
But if you really need to use {{ render(controller('AcmeDemoBundle:Page:mainmenu')) }}, so you need pass to them a current request like:
{{ render(controller('AcmeDemoBundle:Page:mainmenu', {'request': app.request})) }}
and then in action pass request to twig:
public function mainmenuAction($request) {
return $this->render('AcmeDemoBundle:Page:mainmenu.html.twig', array('request' => $request));
}
and in twig use this request:
{% if menu_pages is defined %}
{% for page in menu_pages %}
<li class="{% if request.get('_route') == '_page_show' and request.get('id') == page.id %}active{% endif %}">{{ page.title|e }}</li>
{% endfor %}
{% endif %}
I must use a Twig variable as a property for another Twig variable.
In a for loop, I get the properties of a specific entity and want to use those properties to get the property-content for an entity variable in an other for loop.
Some code to make this clear:
{% for entity in entities %}
{{entity.foo}}, {{entity.bar}}<br />
{% for property in specialdynamicproperties %}
{{entity.property}} <!-- property has the content foobar for example, I want to use it as the property accessor for entity -->
{% endfor %}
{% endfor %}
Thanks.
The attribute function is what you are looking for.
Edit:
{{ attribute(object, method) }}
{{ attribute(object, method, arguments) }}
{{ attribute(array, item) }}
{% for object in objects %}
{% for column in columns %}
{{ attribute(object, column) }} {# equivalent to php $object[$column] #}
{% endfor %}
{% endfor %}
Using Twig Attribute Function (Twig > 1.2)
I am trying to implement something like this:
<div>
<input type="checkbox" name="checkbox" id="checkbox_id" />
<label for="checkbox_id">I agree to the Terms of Service</label>
</div>
The closest I've come to implement this is through:
<div>
{{ form_widget(form.agreeWithTos) }}
<label for="{{ form.agreeWithTos.vars.id }}">I agree to the Terms of Service</label>
</div>
Is there a better way? Having to specify {{ form.agreeWithTos.vars.id }} is inelegant. :)
Solved this problem using the following code in my form-theme:
{# ---- form-theme.html.twig #}
{% block checkbox_row %}
{% spaceless %}
<div>
{{ form_errors(form) }}
<label class="checkbox" for="{{ form.vars.id }}">
{{ form_widget(form) }}
{{ label|default(form_label(form)) | raw }}
</label>
</div>
{% endspaceless %}
{% endblock %}
in your Form-Template you can then use:
{% form_theme form '::form-theme.html.twig' %}
{{form_row(form.termsOfServiceAccepted, {
'label' : 'I have read and agree to the Terms and conditions'
})
}}
this way, the block from the form-theme would apply to any checkbox on the page. If you need to also use the default-theme, you can add a parameter to enable special-rendering:
{# ---- form-theme.html.twig #}
{% block checkbox_row %}
{% spaceless %}
{% if not useTosStyle %}
{{ parent() }}
{% else %}
{# ... special rendering ... #}
{% endif %}
{% endspaceless %}
{% endblock %}
which would be used like this:
{% form_theme form '::form-theme.html.twig' %}
{{form_row(form.termsOfServiceAccepted, {
'useTosStyle' : true,
'label' : 'I have read and agree to the Terms and conditions'
})
}}
Thanks to a recent commit to Symfony, you can use label_html from Symfony 5.1 onward:
{{ form_label(
form.privacy,
'I accept the privacy terms.',
{
'label_html': true,
},
) }}
I've been beating my head over this then had a eureka moment. The easiest way to do this–BY FAR–is to create a Twig extension.
Here's my Twig code:
{# twig example #}
{% block form_label %}
{% set label = parent() %}
{{ label|unescape|raw }}
{% endblock %}
and PHP:
<?php
new Twig_SimpleFilter('unescape', function($value) {
return html_entity_decode($value);
});
A couple notes:
This unescapes all previously escaped code. You should definitely re-escape afterwards as necessary.
This requires an extends tag for your target form theme in your own custom form theme which you can use in your subsequent forms. Put the twig code in a custom form theme and replace references to your other form theme/themes with this one.
Overall this is the fewest lines of code for the biggest and best outcome that I've been able to find. It's also ultra-portable and DRY: You can extend any form theme and the label will change without you changing the rest of your code.
Another very simple approach is to override the form theme directly in the template which renders the form. Using {% form_theme form _self %} it is as simple as this:
{% form_theme form _self %}
{% block form_label %}
{{ label | raw }}
{% endblock %}
{{ form_start(form) }}
See the corresponding section in the docs:
https://symfony.com/doc/current/form/form_customization.html#method-1-inside-the-same-template-as-the-form
The easiest way to customize the [...] block is to customize it directly in the template that's actually rendering the form.
By using the special {% form_theme form _self %} tag, Twig looks inside the same template for any overridden form blocks. [...]
The disadvantage of this method is that the customized form block can't be reused when rendering other forms in other templates. In other words, this method is most useful when making form customizations that are specific to a single form in your application. If you want to reuse a form customization across several (or all) forms in your application, read on to the next section.
Another approach is to use a simple Twig replacement:
{% set formHtml %}
{{ form(oForm) }}
{% endset %}
{{ formHtml|replace({'[link]': '', '[/link]': ''})|raw }}
In your form, you have something like this in order to make it work:
$formBuilder->add('conditions', CheckboxType::class, [
'label' => 'Yes, I agree with the [link]terms and conditions[/link].'
]
);
Of course, you may change [link] to anything else. Please note that you do not use HTML tags ;-)
I think you are looking for form theming. That way you are able to style each part of form, in an independent file, anyway you want and then just render it in "elegant" way, row by row with {{ form_row(form) }} or simply with {{ form_widget(form) }}. It's really up to you how you set it up.
Symfony 4.2
TWIG:
{% block main %}
....
{% form_theme form _self %}
...
{{ form_row(form.policy, {'label': 'security.newPassword.policy'|trans({"%policyLink%":policyLink, "%termsLink%":termsLink})}) }}
...
{% endblock %}
{% block checkbox_radio_label %}
<label{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}>
{{- widget|raw }} {{ label|unescape|raw }}
</label>
{% endblock checkbox_radio_label %}
PHP:
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class AppExtension extends AbstractExtension
{
public function getFilters()
{
return [
new TwigFilter('unescape', function ($value) {
return html_entity_decode($value);
}),
];
}
}
So form theming is pretty complicated. The easiest thing I've found is to just suppress the field's label ('label'=> false in Symfony form class) and then just add the html label in the twig html.
You could leverage form theming in another way: you could move the <label> tag outside the form_label() function.
Create a custom form theme, and for checkboxes only move the <label> tag outside the form_label function:
{% block checkbox_row %}
<label>{{ form_label(form) }}</label>
{{ form_errors(form) }}
{{ form_widget(form) }}
{% endblock checkbox_row %}
{% block checkbox_label %}
{{ label }}
{% endblock checkbox_label %}
Now, in your teplate, override the label of your checkbox, and thus effectively inject HTML into the label function:
{% form_theme form 'yourtheme.html.twig' _self %}
{% block _your_TOS_checkbox_label %}
I agree with terms and conditions
{% endblock _your_TOS_checkbox_label %}