Variable does not exist within Symfony Form Theme block override - symfony

I am building a form which contains a series of categories used to search some content, defined as a ChoiceType within my Symfony FormType.
I pass the category list along with some other data (per-category count) (defined as the variable "aggs" in my controller) into a Twig template and create a form theme, overriding the choice_widget_options block for my categories drop-down so that I can display the extra data at render time, thus:
{% form_theme form.categories _self %}
{% block choice_widget_options %}
{% if choices is defined %}
{% for choice in choices %}
<option value="{{ choice.value }}">{{ choice.label }} {{ aggs }}</option>
{% endfor %}
{% endif %}
{% endblock choice_widget_options %}
Why is it that my block here cannot access the top-level variables defined in my controller? It can see the "app" global variable, but not the controller-defined ones.
Thanks!

More elegant solution is to override the buildView method of AbstractType class.
Just add:
$view->vars[‘aggs’] = YOUR AGGS VAR;
And the use it in your form as:
<option value="{{ choice.value }}">{{ choice.label }} {{ form.vars.aggs }}</option>
Basically you pass the variable to your form and then you use it in twig. The way you pass it to the form type is up to you. It can be a dependency injection or via the form options from the controller.

Related

Overriding checkbox form field producing duplicate label and no field in Symfony 4.4

I am having trouble overriding a checkbox field in my Symfony form inside my Twig template.
I am building the field with the following code in my Twig template:
{{ form_row(form.legal, {
'label' : 'personal.form.fields.legal'|trans,
}) }}
In the same template I have the following block where I am attempting to customise the label. Note the translation above includes HTML which is why I need the raw filter.
{% block _crmpiccobundle_details_legal_label %}
{% apply spaceless %}
<label{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}>
{{ label|unescape|raw }}
</label>
{% endapply %}
{%- endblock %}
Unfortunately, this doesn't work and bizarrely leaves me with no checkbox and a duplicate label and I can't work out why.
Looks like you are using a bootstrap based form theme (like bootstrap_4_layout.html.twig or bootstrap_3_layout.html.twig)
Try doing like this:
{% block _crmpiccobundle_details_legal_label %}
{%- if widget is defined -%}
{{ widget|raw }}
{% apply spaceless %}
<label{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}>
{{ label|unescape|raw }}
</label>
{% endapply %}
{%- endif -%}
{%- endblock %}
In bootstrap layout, widget part needs to be wrapped into label, so the theme calls the same block twice, first for _label part and second time for _widget. For the second call, theme provides widget variable, which you have to render yourself (otherwise you wouldn't see your checkbox). Also, you have to suppress label being rendered twice, which could be done just by checking whether widget is defined.
See how the original block also checks whether widget is defined to avoid double label rendering:
https://github.com/symfony/symfony/blob/e2f430dfb4c0c8cdde01ed111f4f0851e268ab5a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig#L83

Toggle html validation globally

I've made a couple of twig extensions but I'm stumped on this one.
I have the following template logic that I want to make into an extension.
I need reuse this logic into many different forms instead of copying and pasting the following code everywhere:
{% if html5validation is not defined %}
{{ form_start(some_form) }}
{% else %}
{% if html5validation %}
{{ form_start(some_form) }}
{% else %}
{{ form_start
(
company, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{% endif %}
With the above code from the controller I can do the following to turn the html5 validator on and off:
$this->render(..., array(html5validation => false));
I want put the template logic into the twig extension below...
I just don't know if it's possible to implement what I've done above in a twig extension.
class HTML5Validation extends \Twig_Extension
{
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('html5validation', array($this, 'setValidation')),
);
}
public function setValidation($boolean)
{
//Implement the same logic as the twig template.
}
public function getName()
{
return 'html5validator';
}
}
The short answer is no - you can't do this using a twig extension, it's not what they're meant for.
Looking at your template fragment I'd say you need to customise the form_start block. To do this see Symfony Form Theming and How to customise form rendering.
EDIT: This solution does not work if your customised code requires local twig variables - only global twig variables are available for form theming. You can define your own twig globals in config.yml or in a twig extension.
For example, to override form_start globally, you find the default definition of the form_start block in form_div_layout.html.twig, copy it into your own form theme file e.g. YourBundle/Form/fields.html.twig, modify it as required and and update the twig configuration to apply your form theme file. Something like this:
{# src/YourBundle/Form/fields.html.twig #}
{% extends 'form_div_layout.html.twig' %}
{% block form_start -%}
{% if html5validation is not defined %}
{{ parent() }}
{% else %}
{% if html5validation %}
{{ parent() }}
{% else %}
{{ parent
(
company, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{% endif %}
{%- endblock form_start %}
Config:
# app/config/config.yml
twig:
form:
resources:
- 'YourBundle:Form:fields.html.twig'
I actually found a better way to do what I wanted.
As a plus it works globally instead of having to populate more fields into your controller!
In YourBundle/Resources/views/validation.toggle.html.twig
{% extends 'form_div_layout.html.twig' %}
{% block form_start -%}
{% if html5validation is defined and html5validation == false %}
{% set attr = attr|merge({'novalidate': 'novalidate'}) %}
{% endif %}
{{ parent() }}
{%- endblock form_start %}
Then if you want to turn off html5 validation across the whole website:
# app/config/config.yml
twig:
global:
html5validation: false
Or
Even better just use it in your dev_config.yml if you want validation on by default on production mode but the ability to toggle validation on and off for dev mode.
# app/config/dev_config.yml
twig:
global:
html5validation: false
resources:
- 'YourBundle::validation.toggle.html.twig'
Finally use it in your twig template normally:
{% form_theme your_form 'YourBundle::validation.toggle.html.twig' %}
form_start(your_form)
Reusable and non invasive, exactly like I wanted it. :)
I got the hint from:
https://github.com/symfony/symfony/issues/11409#issuecomment-49358377
In the absence of a more elegant solution, you can always put the twig fragment given in your question into a separate file and use twig include from your various forms. The included fragment has access to the variables from the surrounding context:
{# YourBundle/Resources/views/form_start.html.twig #}
{% if html5validation is not defined %}
{{ form_start(some_form) }}
{% else %}
{% if html5validation %}
{{ form_start(some_form) }}
{% else %}
{{ form_start
(
company, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{% endif %}
Then in the twig file for the form:
{% include 'YourBundle::form_start.html.twig' %}
If you typically pass a 'form' variable into render() in your controller(s) then you can use that in your form_start fragment. Otherwise you can pass the appropriate form in as a variable:
{% include 'YourBundle::form_start.html.twig' with {'form': localForm} %}

Symfony2 - access form from a widget block template

Following is how the form is rendered.
<fieldset class="properties">
{% block form_content %}
{{ form_widget(form) }}
{% endblock %}
</fieldset>
Now I can access any form field in this template, like {{ form.description }}, it's all good . But here I have a collection field in this form, let's call it collection, I have built a custom field type for this, the block template for this custom type is customCollect_widget, everything until this point is fine, but if I want to access the collection object in this widget template, I got an error saying the field name does not exist in the form object.
Here's my widget template:
{% block customCollect_widget %}
{% spaceless %}
{% for aa in form.collections %}
<div>something</div>
{% endfor %}
....
<% endblock %}
The problem, as I figured, is that the form isn't the same object that's passed to the code above. Is there any workaround to it?
Ha, I solved it. In the collection type widget template, the form variable is referencing the form field type itself, in this case, the collection type. So to loop through the collection in the widget block, you just do {% for child in form %}.

Get choices from select in twig

I'm new in Symfony2 and Twig, really I only markup the views of some forms. I have created a generic forms.twig.html with some macros for render type of input.
Then I pass to the select type the options list for selects options, but I cant get this options in the vars array from symfony2. ¿Is this possible?
Macro
{% macro select(name, options, selected) %}
<div class="innerB">
<select class="form-control" name="{{name}}">
{% for option in options %}
{% if option.value == selected %}
<option value="{{option.value}}" selected>{{ option.value }}</option>
{% else %}
<option value="{{option.value}}">{{ option.value }}</option>
{% endif %}
{% endfor %}
</select>
</div>
{% endmacro %}
And this is the twig:
<!-- Print selects -->
{% if type == 'choice' %}
{{ macroforms.label(item.vars.name, item.vars.label) }}
{{ macroforms.select(item.vars.name, item.vars.choices, item.vars.selected?) }}
{% endif %}
Updated: This works... The problem was type of element, "choice" and not "collection", but now, I want to access each element for the collection, which are choices... Any idea?

Symfony2 inherit from two form templates

I've set my forms to use a custom template (that produces a DL) and that works fine
twig:
form:
resources:
- 'myBundle:Form:row.html.twig'
But then I want to customise one row of one form and I think I have to do this
{% form_theme edit_form _self, ' %}
{% block _startYear_row %}
{% block form_row %}
{{ parent() }}
<div id="startCalendaryear"></div>
{% endblock %}
{% endblock %}
That make the entire form inherit from _self and loses the customisation of row.html.twig.
How can I set a custom template for all forms while still overriding a single row on one of them?

Resources