Symfony form collection rendering - php

I'm using symfony 2.3
I have form with field of type "collection"
Visualization code in twig:
{% for field in form.fields %}
{{ form_row(field.name) }}
{% endfor %}
Everything work, expect when form.fields is empty.
Then nothing is visualized in twig loop, witch is fine.
But at the end of the form there is "label" for the element "form.fields". Only label.
Workaround:
{% for field in form.fields %}
{{ form_row(field.name) }}
{% endfor %}
<div class="hidden">
{{ form_row(form.fields) }}
If there are elements, they will be rendered in the loop.
{{ form_row }} will be empty, because all elemets are iterated in the loop above.
But if form.fields is empty then there is "hidden" (in the div) label.
What I'm missing !? Why this is happening !?
Hidden div content:
<div class="form-group"><label class="col-sm-2 control-label required">name</label><div class="col-sm-10"><div id="my-id" data-prototype=""></div></div></div>
Builder config:
$builder->add(
'fieldDataMappers',
'collection',
array(
'type' => new FieldDataType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
)
);

As you have correctly guessed, the Symfony TwigBridge keep track of what is rendered and what is not. This is useful, since there is a function called form_rest(form), which is especially useful for printing hidden form field, and to prevent the "great jupiter! I forgot to print that field!" moments. :) You often find form_rest at the end of the form, just before the submit button.
Also consider that the collection IS a composite form type, which contains a variable list of child form. When the for loop is not triggered, since the form type is empty, the call to {{ form_row(form.fields) }} print out the collection form type. By default, this will print (you've guessed it) the collection label and an empty div. On the other hand, when the collection is not empty, Symfony will consider the collection as rendered, since all children are already rendered (see FormView::isRendered)
You can take a look into Symfony standard theme form_div_layout.html.twig, especially the blocks form_row (which show label printing) and form_widget_compound (the div and the for loop).
So, if you just need to hide the label (quick and dirty, some div are still there), just use:
$builder->add(
'fieldDataMappers',
'collection',
array(
'type' => new FieldDataType(),
'label' => false,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
)
);
Or better, simply output the whole collection widget, without row:
{{ form_widget(form.fieldDataMappers) }}
Or even better, you print the whole collection with:
{{ form_row(form.fieldDataMappers) }}
...and then add a Twig theme to customize the collection output with something like (note the name syntax, and the missing form_label call):
{% block collection_row -%}
<div>
{{- form_errors(form) -}}
{{- form_widget(form) -}}
</div>
<div class="hidden">Something here?</div>
{%- endblock collection_row %}
Hope this help!

{# src/Acme/TaskBundle/Resources/views/Task/new.html.twig #}
{# ... #}
{{ form_start(form) }}
{# render the task's only field: description #}
{{ form_row(form.description) }}
<h3>Tags</h3>
<ul class="tags">
{# iterate over each existing tag and render its only field: name #}
{% for tag in form.tags %}
<li>{{ form_row(tag.name) }}</li>
{% endfor %}
</ul>
{{ form_end(form) }}
{# ... #}
Symfony2 cookbook
http://symfony.com/doc/current/cookbook/form/form_collections.html
Also the field of the collection is named fieldDataMappers not field.
So i think it should be
{% for field in form.fieldDataMappers %}
{{ form_row(field.name) }}
{% endfor %}

{{ form_label(form.emails) }}
<ul id="email-fields-list"
data-prototype="{{ form_row(form.emails.vars.prototype)|e }}"
data-widget-tags="{{ '<ol></ol>'|e }}"
data-widget-counter="{{ form.emails|length }}">
{% for email in form.emails %}
<ol>
{{ form_errors(email) }}
{{ form_row(email) }}
</ol>
{% endfor %}
</ul>
<button type="button" class="add-another-collection-widget" data-list-selector="#email-fields-list">Add email</button>
{{ form_widget(form.emails) }}
I just add {{ form_widget(form.emails) }} after block thant handles adding to collection and no more label on the end of form.
Cheers

I solved this with :
{{ form_label(form.collection) }}
{% for element in form.collection %}
{{ form_widget(element) }}
{% else %}
{{ form_widget(form.collection) }}
{% endfor %}
(a bit late, I know, but still a problem with Symfony 5)

Related

Symfony3 form builder form field in span instead of div

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 %}

Symfony: Custom Twig Rendering for Entity Type

I have an issue with rendering an Entity Type in a Symfony Form.
Here is what I call in the form type:
$builder
->add('categories', EntityType::class, array
(
'class' => 'AppBundle\Entity\ArticleCategory',
'choice_label' => 'name',
'expanded' => true,
'multiple' => true,
'constraints' => array
(
new NotBlank(array('message' => 'Select Category'))
)
))
This is the template I am trying to do:
{% form_theme form _self %}
{% block _article_categories_entry_row %}
<div class="ckbox ckbox-default">
{{ form_widget(form) }}
{{ form_label(form) }}
{{ form_errors(form) }}
</div>
{% endblock %}
In the same template I am doing
{{ form_start(form) }}
{{ form_row(form.categories) }}
{{ form_end(form) }}
The weird thing happens when I use block _article_categories_entry_widget and it picks it up however it renders the label twice. Any ideas how to manage to situation ? In this scenario above it doesn't render or adds the ckbox class at all ! :(
You can loop through your form.categories in template as below and render checkboxes as you want.
{{ form_start(form) }}
{% for category in form.categories %}
{{ form_label(category) }}
{{ form_widget(category) }}
{% endfor %}
{{ form_end(form) }}
As you named your custom block _article_categories_entry_row, I assume, your form is named ArticleType.
The custom block for a specific form field should be named with following pattern:
_<form_name>_<field_name>_<part_name>
Possible parts as row, errors, widget, label. You're using row here.
But the entry part of block name is unnecessary.
Try with:
{% block _article_categories_row %}
You can find more details in Symfony Documentation

How to display subcategories from entity?

I have two entities: Supplier and Category joined ManyToMany.
Next, I have a form builder class where I add EntityType::class.
Category structure:
id, categoryName, parentId - where parentIds value can be:
0 - head category
1 - subcategory
etc
I need to display (in twig template) categories with structure:
Category1
Subcategory1
Subcategory2
Category2
Subcategory3
Subcategory4
etc. where Category are some kind of header and subcategory are checkboxes.
Please somebody give me a tip how to do this.
Building on what we discussed in the comments, the following worked in my test environment:
InSupplierType::buildForm:
->add('categories', EntityType::class, [
'class' => Category::class,
'choice_label' => 'name',
'group_by' => 'parent',
'multiple' => true,
'expanded' => true
])
Though without form customization this would only render the checkboxes without headers. This is discussed here: https://github.com/symfony/symfony/issues/5489#issuecomment-194943922
Implementing the fix by MaxE17677 the view would look something like this:
{% extends 'base.html.twig' %}
{% form_theme form _self %}
{# #see: https://github.com/symfony/symfony/issues/5489#issuecomment-194943922 #}
{%- block choice_widget_expanded -%}
<div {{ block('widget_container_attributes') }}>
{% for name, choices in form.vars.choices %}
{% if choices is iterable %}
<label class="choice_category">
<strong>
{{ choice_translation_domain is same as(false) ? name : name|trans({}, choice_translation_domain) }}
</strong>
</label>
<div>
{% for key,choice in choices %}
{{ form_widget(form[key]) }}
{{ form_label(form[key]) }}
{% endfor %}
</div>
{% else %}
{{- form_widget(form[name]) -}}
{{- form_label(form[name], null, {translation_domain: choice_translation_domain}) -}}
{% endif %}
{% endfor %}
{%- endblock choice_widget_expanded -%}
{% block body %}
<div class="container">
{{ form_start(form) }}
{{ form_row(form.categories) }}
{{ form_end(form) }}
</div>
{% endblock %}
Preview:
The crucial thing is that you use group_by feature of the ChoiceType. To get this to work with your current entities you might need to also use the query_builder setting of EntityType. Something like:
// ...
'query_builder' => function (EntityRepository $er) {
return $er
->createQueryBuilder('c')
->where('c.parentId = 1')
;
},
along with:
// ...
'group_by' => function ($category) {
// *pseudo-code*
return $category->getParent()->getName();
}

How can I render an entity (choice <select>) field as a <ul> field in Twig?

Symfony renders an entity field type like a choice dropdown - a select, basically. However, the CSS framework that I'm using defines a sort of 'select' as a ul and li as the options. The Custom Field Type documentation gives no help on this scenario.
I'm converting my code from manual HTML rendering of the form dropdown to symfony form's version using twig and form_widget(). However, I want a ul and li instead of a select.
The manual way of creating my dropdown is:
<ul class='dropdown-menu'>
{% for locator in locators %}
<li>
<a href="#" data-id="{{locator.getId() }}">
{{ locator.getName() }}
</a>
</li>
{% endfor %}
</ul>
That's how I would render my dropdown manually before using symfony forms. It looks like this:
I like it. I think it looks awesome. Now, if I'm using Symfony forms, I can just use this instead:
{{ form_start(form) }}
{{ form_widget(form.locator) }} {# This is my locator dropdown #}
{{ form_widget(form.target) }} {# Ignore this #}
{{ form_end(form) }}
The problem is that this renders this instead:
I can't add my custom CSS here because this is rendered as a select instead of an unordered list and lis.
In case it may help, here's my form type being built:
/**
* {#inheritDoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('target')
->add('locator', 'entity', [
'class' => 'Application\Model\Entity\Locator',
'query_builder' => function(EntityRepository $repo) {
return $repo->createQueryBuilder('e');
},
'empty_value' => 'Locator'
])
->add('save', 'submit', ['label' => 'Save']);
$builder->setAction($this->urlGenerator->generate('page_create_element', [
'suiteId' => $options['suiteId'], 'pageId' => $options['pageId']
]))->setMethod('POST');
}
The Question: Is there any way I can have the form commands above auto-generate my ul / li requirement instead of selects, or do I have to render this manually instead and ignore the symfony forms component for this?
Thanks to some of the posters above, there was some information from Form Theming, but it wasn't exactly enough to go along with so I had to do a little bit of digging on github.
According to the documentation, Symfony uses twig templates to render the relevant bits of a form and it's containing elements. These are just {% block %}s in twig. So the first step was to find where a select button is rendered within the symfony codebase.
Form Theming
Firstly, you create your own theme block in it's own twig file and you apply this theme to your form with the following code:
{% form_theme my_form_name 'form/file_to_overridewith.html.twig %}
So if I had overridden {% block form_row %} in the file above, then when I called {{ form_row(form) }} it would use my block instead of Symfony's default block.
Important: You don't have to override everything. Just override the things you want to change and Symfony will fall back to it's own block if it doesn't find one in your theme.
The Sourcecode
On github I found the source code for Symfony's "choice widget". It's a little complex but if you follow it through and experiment a little bit you'll see where it goes.
Within the choice_widget_collapsed block, I changed the select to uls and options to lis. Here's the theme file I created, note the minor differences described above:
{# Symfony renders a 'choice' or 'entity' field as a select dropdown - this changes it to ul/li's for our own CSS #}
{%- block choice_widget_collapsed -%}
{%- if required and empty_value is none and not empty_value_in_choices and not multiple -%}
{% set required = false %}
{%- endif -%}
<ul {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
{%- if preferred_choices|length > 0 -%}
{% set options = preferred_choices %}
{{- block('choice_widget_options') -}}
{%- if choices|length > 0 and separator is not none -%}
<li disabled="disabled">{{ separator }}</li>
{%- endif -%}
{%- endif -%}
{%- set options = choices -%}
{{- block('choice_widget_options') -}}
</ul>
{%- 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 -%}
<li value="{{ choice.value }}"{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice.label|trans({}, translation_domain) }}</li>
{%- endif -%}
{% endfor %}
{%- endblock choice_widget_options -%}
Rendering
Now I can render my form with the following:
{{ form_widget(form.locator, {'attr': {'class': 'dropdown-menu'}}) }}
This uses my theme for the choice dropdown which contains ul and li tags instead of select and option ones. Pretty simple once you know where to look for the original code! The rendered HTML:
<ul id="elementtype_locator" name="elementtype[locator]" required="required" class="dropdown-menu">
<li value="1">id</li>
<li value="2">name</li>
<li value="3">xpath</li>
</ul>
I also had to remove one of the lines that put 'Locator' at the top of the dropdown as there were four dropdown choices (including the empty_data one) instead of three.

HTML in Symfony2 form labels instead of plain text

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 %}

Categories