symfony2: Applying theme to individual field for collection type - symfony

Just applying a theme to form field is easy e.g.:
{% form_theme form _self %}
{% block _product_name_widget %}
{{ block('field_widget') }}
{% endblock %}
but what if the form field is of collection type? E.g.: product['models'][1][comment,
I have no ideas how to customize it. (This may be the first kind question here)
UPDATE: anwser here: Symfony2: collection form field type with data-prototype

As of Symfony 2.1 (it may work as well for 2.0 but not sure) to apply theme for collection you do this way:
Lets say we have a collection of products (we have multiple Product entities)
Controller:
$repository = $this->getDoctrine()->getRepository('ExampleBundle:Product');
$products = $repository->findAll();
$productCollection = new Products;
foreach ($products as $product) {
$productCollection->getProducts()->add($product);
}
$collection = $this->createForm(new ProductsType, $productCollection);
return $this->render('ExampleBundle:Default:index.html.twig', array(
'collection' => $collection->createView()
));
Your theme can look like this:
{% block _productsType_products_entry_name_row %}
<div class="yourDivName">{{ block('form_widget') }}</div>
{% endblock %}
{% block _productsType_products_entry_description_row %}
<div class="yourDivDescription">{{ block('form_widget') }}</div>
{% endblock %}
The trick is with "entry" where twig will do the job for you to apply above changes to each row and for each field you specify
Hope that helps!

You can do with override collection_widget block:
{% block collection_widget %}
{% spaceless %}
{% if prototype is defined %}
{% set attr = attr|merge({'data-prototype': form_row(prototype) }) %}
{% endif %}
{{ block('form_widget') }}
{% endspaceless %}
{% endblock collection_widget %}
If you want how to customize form collection type try to look at this Product Bundle

You can override the collection_widget theme for a single field by referencing the widget like this as well.
For example, if "categories" is a "collection" widget on a ProductType form, you can do this.
{% block _product_categories_widget %}
{% for child in form %}
{{- form_row(child) -}}
<hr>
{% endfor %}
{% endblock %}

Related

SonataAdminBundle templates - list and show fields template content duplication

My list and show fields contain the same content, but due the extending of base_list_field and base_show_field templates I have to create two separate templates.
list/location.html.twig:
{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}
{% block field %}
{{ object.getCity }}, {{ object.getCountry.getName }}
{% endblock %}
show/location.html.twig:
{% extends 'SonataAdminBundle:CRUD:base_show_field.html.twig' %}
{% block field %}
{{ object.getCity }}, {{ object.getCountry.getName }}
{% endblock %}
As you can see huge part of code is duplicated. Is there a way to check the page I am currently in in twig and then extend certain base template? In this case I would be able to use one file instead of two for same content.
In twig it's possible to extend/include a dynamic template :
{# do test here on which page you are or pass through controller #}
{% if true %}
{% set template = 'SonataAdminBundle:CRUD:base_show_field.html.twig' %}
{% else %}
{% set template = 'SonataAdminBundle:CRUD:base_list_field.html.twig' %
{% endif %}
{% extends template %}
{% block field %}
{{ object.getCity }}, {{ object.getCountry.getName }}
{% endblock %}
(edit) if you don't want the hardcoded if I would pass the template variable through the controller and change the twig template to something like
{% extends template|default('SonataAdminBundle:CRUD:base_show_field.html.twig') %}

Overriding twig blocks: the content of the overriden block is rendered as part of the document also

I'm reading this section about forms customization.
Now I want to custom a widget by myself so I have written the code below to overwrite the {% block money_widget %}. It works ok, but the content of my new block is also shown as you can see in the image. Why?
{% use 'form_div_layout.html.twig' %}
{% form_theme form _self %}
{% block money_widget %}
here my the content of the block
{% endblock %}
{% block content %}
{{ form(form) }}
{% endblock %}
This is the form type:
$builder
->add('user', null, array('label' => 'Cliente'))
->add('numberPlate', null, array('label' => 'NĂºmero matrĂ­cula', 'data' => 'prueba'))
->add('subtotal', MoneyType::class)
->add('tax', MoneyType::class, array('label' => 'I.V.A.'))
->add('total', MoneyType::class, array('label' => 'Total'))
->add('Guardar', SubmitType::class)
;
EDIT: I've found when this "problem" exactly happens: when the block to override the widget is inside template that is is being included using include. But why? Anyway, for this not to happen, I've created a template that works as a form theme, and add it this way for example:
{% form_theme form 'here_is_the_block_to_override_the_widget.html.twig' %}
in the template that I'm including using include
original widget in form_div_layout:
{%- block money_widget -%}
{{ money_pattern|replace({ '{{ widget }}': block('form_widget_simple') })|raw }}
{%- endblock money_widget -%}
your block:
{% block money_widget %}
here my the content of the block
{% endblock %}
When you override the original block in your own template, the block from your template will be shown whenever money widget needs to be rendered. So in your final page you see here my the content of the block, as expected. BTW, in your tutorial page there is no use statement, maybe that makes it confusing.
Because your template must extend a base template referencing block content.
Create a minimal base template in app/Ressources/views/base.html.twig with :
{% block content %}
{% endblock content %}
and extends it in your template like this:
{% extends '::baseEmail.html.twig' %}
{% use 'form_div_layout.html.twig' %}
{% form_theme form _self %}
{% block money_widget %}
here my the content of the block
{% endblock %}
{% block content %}
{{ form(form) }}
{% endblock %}

Twig variable doesn't exist in form_theme

I'm trying to override default Symfony2 form to implement the code below that will allow me to turn off html5 validation on and off by just passing a variable.
html5validation.toggle.html.twig
{% extends 'form_div_layout.html.twig' %}
{% block form_start -%}
{% if html5validation %}
{{ parent() }}
{% else %}
{{ parent
(
companyform, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{%- endblock form_start %}
index.html.twig
form_theme company_form 'AcmeDemoBundle:html5validation.toggle.html.twig'
form(companyform)
In my controller I have:
$this->render('AcmeDemoBundle:Index:index.html.twig', array('html5validation' => false, 'companyform' => ...
If I try to dump(html5validation) inside index.html.twig I get bool(false).
But when I try to include the form_theme company_form AcmeDemoBundle:html5validation.toggle.html.twig
I get the error:
Variable "html5validation" does not exist
Can controller variables not be used inside themes?
Turns out only global variables are allowed to be used in form_theme(s) :(
Toggle html validation globally

Custom form field template with twig

I'd like to create a custom template in twig to render a form field.
Example:
{{ form_row(form.field) }}
This can be overriden by form theming
{% block form_row %}
... custom code
{% endblock form_row %}
What I would like to do is this:
{% block custom_row %}
... custom code
{% endblock custom_row %}
and use it like this:
{{ custom_row(form.field }}
however, this throws an exception that method custom_row is not found.
My understanding is that this can be done with Twig extension, but I don't know how to register a block to be a function.
Update
what I actually want:
I use twitter bootstrap and a bundle which overrides all the form themes. And it renders a div around a radio, so it can't be inlined. So I wanted to do something like this:
copy their template and get rid of the div:
{% block inline_radio_row %}
{% spaceless %}
{% set col_size = col_size|default(bootstrap_get_col_size()) %}
{% if attr.label_col is defined and attr.label_col is not empty %}
{% set label_col = attr.label_col %}
{% endif %}
{% if attr.widget_col is defined and attr.widget_col is not empty %}
{% set widget_col = attr.widget_col %}
{% endif %}
{% if attr.col_size is defined and attr.col_size is not empty %}
{% set col_size = attr.col_size %}
{% endif %}
{% 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 %}
{% set label = name|humanize %}
{% endif %}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' radio-inline')|trim}) %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>
{{ block('radio_widget') }}
{{ label|trans({}, translation_domain) }}
</label>
{% else %}
{{ block('radio_widget') }}
{% endif %}
{{ form_errors(form) }}
{% endspaceless %}
{% endblock inline_radio_row %}
and then
{{ inline_radio_row(form.field) }}
I ended up just overriding the whole theme, and added ifs around the div in question, a the class (radio-inline). But I'm still wondering if there's a way to make this work. Seems like it makes you work so hard for something so simple.
Update 2
I found the functionality:
class FormExtension extends \Twig_Extension
{
public function getFunctions()
{
return array(
'inline_radio_row' => new \Twig_Function_Node(
'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode',
array('is_safe' => array('html'))
),
);
}
}
This does exactly what I want, but it says it's deprecated. Anyone knows an updated version of how to use this?
Update 3
Similar functionality can be also achieved with http://twig.sensiolabs.org/doc/tags/include.html
You can use the twig functions for each part of a form row:
form_label(form.field)
form_widget(form.field)
form_errors(form.field)
For example:
<div class="form_row">
{{ form_label(form.field) }} {# the name of the field #}
{{ form_errors(form.field) }} {# the field #}
{{ form_widget(form.field) }} {# the errors associated to the field #}
</div>
You can use form theming.
Step by step:
1. Form Type Class
Check the name in your class
public function getName() {
return 'hrQuestionResponse';
}
2. Include a custom theme in your template
{% form_theme form 'InterlatedCamsBundle:Form:fields.html.twig' %}
3. Find the block
Can be quite difficult. For the bootstrap bundle, as you seem to have found it is in ./vendor/braincrafted/bootstrap-bundle/Braincrafted/Bundle/BootstrapBundle/Resources/views/Form/bootstrap.html.twig and you have found the block radio_row. I have been finding the block by putting output in the source template and overriding more blocks than I need. In 2.7 there is a theme 'rendering call graph'.
4. Override the block
Copy the block from the master template and call it replace the standard term with the name of in the FormType found in step 1.
Don't forget to also change the endblock name.
e.g.
{% block hrQuestionResponse_widget %}
hrQuestionResponse_row
{% spaceless %}
{% set class = '' %}
...
{% endspaceless %}
{% endblock hrQuestionResponse_widget %}
In your case because you can only call form_widget() you will need to override _widget. You could extract only the content that you need, or you can override the block chain to radio_row.

How to pass a variable to form_theme?

I want to theme my form so that the field's label show the current locale, something like
Name (en) :
So I would like to rewrite block generic_label like that :
{# form_theme.html.twig #}
{% 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 }} (app.session.locale)</label>
{% endspaceless %}
{% endblock %}
and import it in my template :
{% form_theme options 'myBundle:Object:form_theme.html.twig' %}
but the app variable is not accessible in the form template.
How can I pass a variable to a form theme ?
In current version of twig (as for 2016) it is possible.
In your template use the following like this:
{{ form_row(form.content, {'testvar' : 'this is test variable'}) }}
Then, in your theme file, just use:
{{testvar}}
of course instead of form.content you will use the field name you need.
Cheers,
Chris
You need to create a form extension in order to get it done. Take a look at
http://toni.uebernickel.info/2011/11/25/how-to-extend-form-fields-in-symfony2.html
to learn how to create the extension.
To have access to session locale, make sure to inject the container. After that you'll be able to get any var value you want.
If the app variable is not available in the form theme it may be a bug. I suggest you create a ticket.
In the meantime you can use the current template as a theme. Something like...
{% form_theme form _self %}
{% block field_label %}
{% set attr = attr|merge({ 'for': id }) %}
{% if required %}
{% set attr = attr|merge({ 'class': attr.class|default('') ~ ' required' }) %}
{% endif %}
<label{% for attrname, attrvalue in attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ label|trans }} ({{ app.session.locale }})</label>
{% endblock %}
If you are using Symfony master (2.1) replace app.session.locale with app.request.locale.

Resources