Symfony 3 nested collection templating with Twig - symfony

how i can templating nested collection?.
I create templating parent collection with block:
{% block _group_match_groupMatchType_entry_row %}
In this block i have collection:
<div class="js-collection-parrent round text-right" data-prototype="{{ form_row(form.matchResult.vars.prototype)|e('html_attr') }}">
How i can get every entry row of matchResult collection which has parent collection groupMatchType?
GroupMatchType
$builder
->add('groupMatchType', CollectionType::class, [
'entry_type' => MatchType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
])
MatchType
$builder
->add('matchResult', CollectionType::class, [
'entry_type' => MatchResultType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
])
And view
{% block _group_match_groupMatchType_entry_row %}
<div class="js-collection-parrent round text-right" data-prototype="{{ form_row(form.matchResult.vars.prototype)|e('html_attr') }}"></div>
{% endblock %}
I have to find name of above block (maybe like _group_match_groupMatchType_entry_row_matchResult_entry_row)

Ok i solved that. The block should be named
_group_match_groupMatchType_entry_matchResult_entry_row

Getting all sub-elements in a form on twig is simple:
{% for element in form.elements %}
{# Do something with this element #}
{# But think that it will represent the form element #}
{% endfor %}
real example:
{% for movie in form.movies %}
{{ form_widget(movie.title, {'attr' : {'class':'form-control'}}) }}
{{ form_row(movie.id) }}
{% endfor %}
Don't really know if is what you need, but I believe it's an approach to what you mean.

Related

Symfony 4 length validator translate error message

I created a new project using the Symfony website skeleton and created a new registration form, following the Symfony tutorial:
https://symfony.com/doc/current/doctrine/registration_form.html#registrationformtype
Inside the twig.yaml I configured the bootstrap_4_layout:
twig:
...
form_themes: ['bootstrap_4_layout.html.twig']
In my RegistrationFormType class I'd like to translate the error messages in case the validation fails:
->add('plainPassword', RepeatedType::class, [
'required' => true,
'type' => PasswordType::class,
'first_options' => ['label' => 'register.password'],
'second_options' => ['label' => 'register.password_repeat'],
'mapped' => false,
'constraints' => [
new Length([
'min' => 6,
'max' => 4096,
'minMessage' => 'register.password_min_length'
]),
],
])
Unfortunately the text (register.password_min_length) is not translated. While te label 'register.password' and 'register.password_repeat' is translated like expected. After checking the bootstrap_4_layout.html.twig I noticed that the error message is not translated, so I tried to write a custom form theme:
{% form_theme registrationForm _self %}
{% block form_errors -%}
{%- if errors|length > 0 -%}
<span class="{% if form is not rootform %}invalid-feedback{% else %}alert alert-danger{% endif %} d-block">
{%- for error in errors -%}
<span class="d-block">
<span class="form-error-icon badge badge-danger text-uppercase">{{ 'Error'|trans({}, 'validators') }}</span> <span class="form-error-message">{{ error.message|trans() }}</span>
</span>
{%- endfor -%}
</span>
{%- endif %}
{%- endblock form_errors %}
Now the error is translated but the {{ limit }} is not replaced with the actual value.
Does somebody had a similiar problem? Also within the Symfony Demo application I couldn't find a solution.
I found a very useful project on Github:
https://github.com/giorgiopagnoni/symfony4-user
I can recommend this project to everyone who has similar problems like me. While checking the code everything get's more and more clear.

Form with a collection same entity type

I would want a form for my entity « X ». This entity own a relationship OneToMany with many entities of type « X ». It's a relationship parent <-> children.
When I create my form simply, it works.
class XType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", "text", array("label" => "Nom"))
->add("children", "collection", array(
"type" => new XType(),
"by_reference" => false));
}
}
Then, I would like add easily new entity in my collection with the option « allow_add », and used the prototype to add in javascript. This is my form with the « allow_add » option.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", "text", array("label" => "Nom"))
->add("children", "collection", array(
"type" => new XType(),
"allow_add" => true,
"by_reference" => false));
}
When I execute with or without called the prototype, I have an webserver error. It's XDebug which kick my request because the recursive call is too big. There are cascade call.
What's the best solution to resolve my problem ?
I can't really say where your problem is, but should be somewhere around javascript and the collection prototype. Also, when you allow items to be added, you usually allow to allow_delete too.
Take a look at my example:
$builder->add('book', 'collection', array(
'type' => new BookType(),
'allow_add' => true,
'allow_delete' => true,
'prototype_name' => '__prototype__',
'by_reference' => false,
'error_bubbling' => false
);
To the template where you render form, include this:
{% block javascript %}
{{ parent() }}
{% javascripts
'#ProjectSomeBundle/Resources/public/js/form/collection.js'
%}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
{% endblock %}
And the collection.js file holds this:
$(function($) {
$addButton = $('button.add');
$collection = $addButton.parent().children().first();
$addButton.click(function () {
var prototype = $($collection.data('prototype').replace(/__prototype__/g, function() { return (new Date()).getTime(); }));
prototype.appendTo($collection.children('ul'));
});
$('body').on('click', 'button.remove', function () {
$(this).parent().remove();
});
});
Also, you should be using those customised collection widgets, which you pass to twig. Nothing fancy, it will be rendered as unordered list with items, to make items easily accessible.
config.yml:
twig:
form:
resources:
- 'ProjectSomeBundle:Form:fields.html.twig'
fields.html.twig:
{% extends 'form_div_layout.html.twig' %}
{% block collection_widget %}
{% import "ProjectSomeBundle:Macro:macro.html.twig" as macro %}
{% spaceless %}
<div class="collection">
{% if prototype is defined %}
{% set attr = attr|merge({'data-prototype': block('prototype_widget') }) %}
{% endif %}
<div {{ block('widget_container_attributes') }}>
<ul>
{% for row in form %}
{{ macro.collection_item_widget(row) }}
{% endfor %}
</ul>
{{ form_rest(form) }}
</div>
<div class="clear"></div>
<button type="button" class="add">{% trans %}Add{% endtrans %}</button>
</div>
<div class="clear"></div>
{% endspaceless %}
{% endblock collection_widget %}
{% block prototype_widget %}
{% spaceless %}
{{ macro.collection_item_widget(prototype) }}
{% endspaceless %}
{% endblock prototype_widget %}
You can notice it uses macro, so here it is:
macro.html.twig
{% macro collection_item_widget(fields) %}
<li>
{% set fieldNum = 1 %}
{% for field in fields %}
<div class="field_{{ fieldNum }}">
{{ form_label(field) }}
{{ form_errors(field) }}
{{ form_widget(field) }}
</div>
{% set fieldNum = fieldNum + 1 %}
{% endfor %}
<button type="button" class="remove">{% trans %}Delete{% endtrans %}</button>
<div class="clear"></div>
</li>
{% endmacro %}
This is a full example, I hope you find it useful and works for you.
I ran into the same issue (my web server crashes, as well, due to too many recursive calls). My quick workaround was to simply create a dummy (cloned) type that doesn't contain the recursive field (this works for me since I was interested only into the 1st level children).
So, if this scenario is applicable to you, as well, you could change your code as follows:
class XType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", "text", array("label" => "Nom"))
->add("children", "collection", array(
"type" => new XTypeWithoutChildren(),
"by_reference" => false));
}
}
class XTypeWithoutChildren extends XType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", "text", array("label" => "Nom"));
}
}

Access collection field in twig

How can I access a field in collection in twig
$builder
->add('name', 'text', array('required' => false))
->add('email', 'collection', array(
'type' => new UsersEmailAddressesType(),
'allow_add' => true
))
UserEmailAddressesType has two fields name and email, how can I access email field in twig ?
In the symfony cookbook there is an example on how to embed collections in forms. The solution there looks like this (adapted to your form example):
<ul>
{% for email in form.email %}
<li>{{ form_row(email.address) }}</li>
{% endfor %}
</ul>
Since you want to place the inputs next to each other you might want to check whether the loop.index is odd, even or divisibleby() for example like this:
{% for email in form.email %}
{% if loop.index is odd %}
<li class="float-left">
{% else %}
<li class="float-right">
{% endif %}
{{ form_row(email.address) }}</li>
{% endfor %}

Grouping checkboxes in Symfony2

It seems that Symfony2 Form component does not handle this common case. Below is what I want in my html
The code looks like :
->add('code', 'choice', array(
'choices' => array(
'Food' => array('pizza', 'burger', 'icecream'),
'Music' => array('poney', 'little', 'pocket'),
),
'multiple' => true,
'expanded' => true,
'required' => true
));
Which gives in reality the wrong output :
It's wierd because the case with expanded => false is correctly handled
How to handle that case please ?
Ok so here's the form_theme solution for this
{% block _user_code_widget %}
<div {{ block('widget_container_attributes') }}>
{% for name, choices in form.vars.choices %}
<ul>
<li class="checkbox_category">
<input type="checkbox" class="checkallsub" /> {{ name }}
</li>
{% for key,choice in choices %}
<li class="indent">
{{ form_widget(form[key]) }}
{{ form_label(form[key]) }}
</li>
{% endfor %}
</ul>
{% endfor %}
</div>
{% endblock %}
Of course the extra js layer is missing, but you get the idea.

Form theming a collection widget

I have a collection widget in my form. That's displayed like:
Teams 0 player1 inputfield
1 player2 inputfield
I would like to not display the word 'teams' and the '0' and the '1'.
I've got this block in my fields.html.twig template, but not really sure how to edit this.
{% 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 %}
{% block form_label %}
{% spaceless %}
<div class="hidden">
{{ block('generic_label') }}
</div>
{% endspaceless %}
{% endblock form_label %}
ChallengeType form:
class ChallengeType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('teams', 'collection', array(
'type' => new TeamType(),
'allow_add' => true
))
->add('place')
->add('date');
}
public function getName()
{
return 'challenge';
}
public function getDefaultOptions(array $options)
{
return array('data_class' => 'Tennisconnect\DashboardBundle\Entity\Challenge');
}
}
Thx.
Those lables are created in form_label block. I usually wrap them in a div and set them hidden when needed.
Edit:
There is a better solution :).
Change collection section of the ChallengeType.php with following
->add('teams', 'collection', array(
'type' => new TeamType(),
//label for Teams text
'attr' => array('class' => 'team-collection'),
//label for each team form type
'options' => array(
'attr' => array('class' => 'team-collection')
),
'allow_add' => true
))
Now those unwanted labels will have team-collection class. In your css file you can set display:none for label.team-collection. No need to change form theme block definition.

Resources