How to render same form multiple times in Symfony2? - php

I have a template with multiple carts. There can be a variable amount of carts, there's no fixed limit.
In each cart I want to have a form where the user can select a country. If he submits the form the shipping costs should be established.
Now I'm doing the following to achieve it in twig:
{% for cart in carts %}
{# Some template stuff #}
{{ form_start(form) }}
<div class="form-input">
<label for="country" class="middle-color">Country <span class="active-color">*</span></label>
{{ form_widget(form.country) }}
{{ form_end(form) }}
{% endfor %}
This is my form builder:
$form = $this->createFormBuilder()
->add('country', 'choice', array('choice_list' => $choiceList, 'label' => 'country',
'attr' => array('class' => "custom-selectbox dark-color light-gradient")))
->getForm();
Now the problem is that this logic works fine for the first cart, but there's no form displayed for further carts. How can I deal with this?

I came across this and another question about the similar issue. You can find my first answer for a solution here.
To wrap it up, I did not call the createView() function on the form in the controller, as usually done when passing the form to the view, but in the twig view itself.
E.g. in your controller action you do return the form object itself:
return $this->render('AppBundle:Cart:list.html.twig', ['formObject' => $form];
and in your view you would set the form in each loop:
{% for cart in carts %}
{# Some template stuff #}
{% set form = formObject.createView %}
{{ form_start(form) }}
<div class="form-input">
<label for="country" class="middle-color">Country <span class="active-color">*</span></label>
{{ form_widget(form.country) }}
{{ form_end(form) }}
{% endfor %}

You should use collection form type. Here is a guide to start with How to Embed a Collection of Forms
P.S. Notice that after rendering a form widget Form component marks it as rendered and does not render once more.

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

Symfony/TWIG: Unable to set custom theme for form with dynamic ID (createNamedBuilder)

I wanted to replace Symfony's default radio button widget with Bootstrap's Radio Button Group. I was trying to achieve that by setting a custom theme for an individual field in Symfony's form created with createNamedBuilder function. I failed, because custom field themes require the ID of the field which is dynamic in my case. I know that having variables in theme names is impossible in TWIG, but maybe there is an alternative approach that I could use to resolve my issue.
CODE
I have a Controller which creates multiple instances of one form type in a loop. The name of the form is dynamically created by concatenating requestform_ and the ID of the form:
public function listAction(Request $request)
{
$entityManager = $this->getDoctrine()->getManager();
$requestForms = $entityManager->getRepository('AppBundle:RequestForm')->findBy(array(), array('id' => 'ASC'));
$forms = array();
foreach ($requestForms as $requestForm) {
$formBuilder = $this->get('form.factory')->createNamedBuilder('requestform_'.$requestForm->getId(), RequestFormType::class, $requestForm);
$form = $formBuilder->getForm()->handleRequest($request);
$forms[] = $form->createView();
}
return $this->render('form/index.html.twig', array('forms' => $forms));
}
buildForm function of the RequestFormType prepares radio button widget and looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('status', EntityType::class, array(
'class' => 'AppBundle:Status',
'choice_label' => 'name',
'expanded' => true,
'multiple' => false
));
}
The Controller renders the following template:
{% block content %}
{% for form in forms %}
{{ form_start(form) }}
{{ form_widget(form.status) }}
{{ form_end(form) }}
{% endfor %}
{% endblock %}
Which is themed to show Bootstrap's button group selector instead of radio buttons:
{% block _requestform_1_status_widget %}
<div id={{ form.vars.id }} class="btn-group" data-toggle="buttons">
{% for status in form.children %}
<label for={{ status.vars.id }} class="btn btn-primary required custom-button-radio">
<input type="radio" id={{ status.vars.id }} name={{ status.vars.full_name }} required="required" value={{ status.vars.value }}>
{{ status.vars.label }}
</label>
{% endfor %}
</div>
{% endblock _requestform_1_status_widget %}
As you may have noticed this will work only for requestForm ID 1 and will not work for other ID values due to TWIG not allowing variables in block names, i.e. {% block _requestform_VARIABLE_status_widget %}.
Any help much appreciated! Thanks
My gut feeling is that you might want to have only one form that includes a CollectionType field with entry_type = RequestFormType::class, and then in the twig, apply a form to the "outer" form as a whole. You should still be able to manage things the way you wanted.

Symfony form collection rendering

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)

Customising form rows in a collection form field

I am trying to customise a specific form row in my form layout. From the symfony cookbook it tells me, if i understand correctly, that I can modify the form by adding a code block in my form theme file.
I am adding the field to the form like this:
->add('editions', 'collection',
array('type' => new EditionType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
)
)
I generate the form rows in the template in the following way, I need the data-prototype tag because the user needs to possibility to add an edition when viewing the form:
<div class="editions" data-prototype="{{ form_widget(form.editions.vars.prototype)|e }}">
{% for edition in form.editions %}
<section class="edition-container">
{{ form_label(edition.isbn) }}
{{ form_errors(edition.isbn) }}
{{ form_widget(edition.isbn) }}
</section>
{% endfor %}
</div>
To customise the rows for the 'editions' fields I tried the following:
<!-- Custom form theme for textarea label rows -->
{% block textarea_label %}
{% spaceless %}
<div {{ block('label_container_attributes') }}>
<label class="label">{{ label }}</label>
</div>
{% endspaceless %}
{% endblock %}
<!-- Custom form theme for edition field textarea label rows -->
{% block _booklist_editions_label %}
{% spaceless %}
<div {{ block('label_container_attributes') }}>
<label class="label">{{ label }}!</label>
</div>
{% endspaceless %}
{% endblock %}
Unfortunately the second block isn't working, the first block however does work. What I did notice is that the outer div from the editions field does not have an id attribute with the field name in it.
This makes me think that the solution to the problem could be one of these:
Custom form theme block has the wrong name, don't know what other name I should give it then.
The id's is not added to the outer div, therefor it's not 'linking' the custom form style.
Could someone explain me how I can add a custom form theme to my 'editions' field using an external form theme file or how I can achieve this in another way.

Categories