Symfony form builder: How to iterate/render dynamic amount of text fields in twig? (Sylius - exta checkout step) - symfony

I added an extra checkout step to the Sylius checkout procedure and I am trying to add 1 text field per instance of an ordered item. Thus having put 3x itemA and 1x itemB in cart should spawn 4 text fields.
I have this code so far in the form builder (based on code from here: https://sf.khepin.com/2011/08/basic-usage-of-the-symfony2-collectiontype-form-field/)
$builder->add('myCollection', CollectionType::class, $formOptions);
$totalCounter = 0;
/** #var OrderItem $item */
foreach($order->getItems() as $item) {
for($itemCounter = 0 ; $itemCounter < $item->getQuantity(); $itemCounter++ ) {
$totalCounter++;
$builder->get('myCollection')->add('item_' . $totalCounter, TextType::class, $formOptions);
}
}
First Question: Is this the right approach for the form builder for my particular scenario, and secondly: If yes, how do I read from this in the twig template?
form.children.myCollection does exist but does not seem to contain these children, so that I could use them with the form_row function. What I would have expected is something like this:
{% set totalCounter = 0 %}
{% for item in order.items %}
{% for itemCounter in 1..item.quantity %}
{% set totalCounter = totalCounter + 1 %}
{{ form_row(form.children.myCollection['item_' ~ totalCounter ]) }}
{% endfor %}
{% endfor %}
Any idea how to do this? Thanks for any hint in advance!

Just saw my exact case is described in the beginning of the documentation of CollectionType https://symfony.com/doc/current/reference/forms/types/collection.html
The form builder part..
$builder->add('emails', CollectionType::class, [
// each entry in the array will be an "email" field
'entry_type' => EmailType::class,
// these options are passed to each "email" type
'entry_options' => [
'attr' => ['class' => 'email-box'],
],
]);
..and the twig part:
{{ form_label(form.emails) }}
{{ form_errors(form.emails) }}
<ul>
{% for emailField in form.emails %}
<li>
{{ form_errors(emailField) }}
{{ form_widget(emailField) }}
</li>
{% endfor %}

Related

Render choice options separately

I have researched quite a bit and also read the documentation but this thing remains unanswered.
I want to render choice-options separately in twig and a {%for%} loop doesn't seem to work.
Here's the situation:
I have 3 radio buttons which are supplemented with 3 different blocks of html each (therefore I cannot do a loop over the segments as they are different).
Form-Extract (form 'payment'):
array(
'choices' => array(
'paypal' => 'payment.method.paypal',
'bank' => 'payment.method.bank',
'check' => /** #Ignore */
),
'label' => 'payment.method',
'expanded' => 'true',
How could I access the different choice options in twig? I have tried
{{ form_widget(form.payment.method[bank]) }}
and also
{{ form_widget(form.payment.method.bank) }}
Thanks so much for your help.
Stefffen
You might want do it this way:
{% for method in form.payment %}
{% if method.vars.value == "paypal" %}
#Render it in a paypal awesome way
{{ form_widget(method) }}
{{ form_label(method) }}
{% else if method.vars.value == "bank" %}
#Render it in a bank awesome way
{{ form_widget(method) }}
{{ form_label(method) }}
{% endif %}
{% endfor %}
Hope it helps

FormComponent - Pass Entity with Form to Template

Lets say i have a one to many relation:
A company can own several pictures
I have company entity and image entity
Now, I want to display all pictures of a company within a template. And i also want to make theese pictures directly editable. I thought of adding a form to each Entity of the DoctrineArrayCollection and pass them to the template. In Template if somebody clicks on a picture the corresponding should be fade in, the should be able to edit the pictures description and pass it through ajax to a controller.
In my entity I added a field without annotations:
private $form;
public function setForm(MyPictureForm $form)
{
$this->form = $form;
}
public function getForm()
{
return $this->form;
}
Now in my controller I add a form instance to every picture of the company:
//office images with forms
$officeImages = array();
foreach($company->getOfficeImages() as $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
$image->setForm($form->createView());
array_push($officeImages, $image);
}
return $this->render('CompanyBundle:Company:Show/show.html.twig', array(
'company' => $company,
'officeImages' => $officeImages
));
}
And in my template i render it like this way:
{% for image in officeImages %}
<a href="#" title="{% if image.description %}{{ company.description }}{% else %}CLICK HERE FOR EDIT{% endif %}">
{% if image.image %}
<img src="{{ vich_uploader_asset(image, 'image') | imagine_filter('company_office_image_thumb') }}"
alt="{% if image.description %}{{ company.description }}{% endif %}"/>
{% else %}
{% image '#UserBundle/Resources/public/img/nopic_logo.jpg' output='/images/nopic_logo.jpg' %}
<img src="{{ asset_url }}" alt="Joblogo"/>
{% endimage %}
{% endif %}
</a>
{{ form(image.form) }}
{% else %}
<p>Es sind noch keine Images vorhanden</p>
{% endfor %}
At the end there is a lot if javascript stuff which handels the fade in / fade out of the form and their submitting.
Is this the correct way to handle my case? I think not cause passing a form for every picture seems like overhead?
The reason why I am working with forms instead of simply passing data out of a manual added input field is csrf protection and the smart usage of the form component.
As you said keeping form object in entity isn't a good idea. It should represents model data.
I've got two solutions for this:
I. Pass array of forms with image id as a key
$officeImages = array();
$imageForms = array();
foreach($company->getOfficeImages() as $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
$imageForms[$image->getId()] = $form->createView();
}
return $this->render('CompanyBundle:Company:Show/show.html.twig', array(
'company' => $company,
'officeImages' => $officeImages,
'imageForms' => imageForms
));
and in show.html.twig
{% for image in officeImages %}
{# your image display code #}
{{ form(imageForms[image.id]) }}
{% endfor %}
II. render partial for single image
in controller
public function showAndEditImageAction(Image $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
return $this->render(
'CompanyBundle:Company:Show/showAndEditImage.html.twig', array(
'image' => $image,
'imageForm' => $form->createView()
));
}
in twigs
{# CompanyBundle:Company:Show/showAndEditImage.html.twig #}
{# your image display code #}
{{ form(imageform) }}
{# CompanyBundle:Company:Show/show.html.twig #}
{% for image in officeImages %}
{{ render(controller('CompanyBundle:Company:showAndEditImage',
{ 'image': image })) }}
{% endfor %}

Directly access a form field's value when overriding widget in a twig template

What I want to do is get variables stored in form view.
{% form_theme edit_form _self %}
{% block field_widget %}
{% spaceless %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{# MY CODE #}
{% if type == "file" %}
<a class="BOpreview" href="{# NEED TO REPLACE VAR HERE #}">Aperçu</a>
{% endif %}
{# MY ATTEMPT #}
{{ form.title.get('value') }}
{{ form.vars.value.url }}
{% endspaceless %}
{% endblock field_widget %}
My form has properties like url, title, etc and I am trying to access them here to use it in the field widget block.
I searched for it and came on https://groups.google.com/forum/?fromgroups=#!topic/symfony2/onor9uFte9E that suggested:
{{ form.title.get('value') }}
{{ form.vars.value.url }}
which didn't work for me.
Note: If I do a var_dump on $form->createView() in my controller, I get:
object(Symfony\Component\Form\FormView)[331]
private 'vars' =>
array (size=15)
'value' =>
object(Panasonic\TestEtAvisBundle\Entity\Product)[168]
protected 'reviewArray' =>
object(Doctrine\ORM\PersistentCollection)[234]
...
protected 'testArray' =>
object(Doctrine\ORM\PersistentCollection)[221]
...
protected 'fbshareArray' =>
object(Doctrine\ORM\PersistentCollection)[317]
...
private 'id' => int 2
private 'name' => string 'Nom du produit' (length=14)
private 'title' => string '<span>Titre </span>' (length=19)
private 'image' => string 'bundles/testetavis/uploads/product/0d9d9550.png' (length=47)
private 'fbImage' => string 'bundles/testetavis/uploads/product/facebook//product_e928cd96.jpg' (length=65)
private 'description' => string '<span>Descriptif </span>' (length=24)
private 'url' => string 'http://www.google.com' (length=21)
private 'creationDate' =>
object(DateTime)[210]
...
private 'modificationDate' =>
object(DateTime)[209]
...
private 'isDeleted' => int 0
'attr' =>
array (size=0)
empty
'form' =>
&object(Symfony\Component\Form\FormView)[331]
'id' => string 'panasonic_testetavisbundle_producttype' (length=38)
'name' => string 'panasonic_testetavisbundle_producttype' (length=38)
'full_name' => string 'panasonic_testetavisbundle_producttype' (length=38)
I want to access that url for instance but can't seem to be able to do it after many variations. Including use of {{ value }}, {{ value.url }}
But inspite of vars, I can do {{ full_name }} and get panasonic_testetavisbundle_producttype.
Any ideas?
Edit2: I found out the real problem...
Edit3: Seeing that this question is quite popular I decided to clarify on what I attempted to do in case it helps someone in the same situation. If you rely strictly on what the question asks, as I stated from my research and that Besnik supported are indeed correct.
Now what I wanted to do is for every input type file, get url from object used to render form and append a preview link, using retrieved url, beside the input type file.
If you try to get the form var of an input type "file" like this "{{ form.vars.value.url }}" in my code, this doesn't work since, if I recall correctly, you receive a token instead of the url stored inside the object.
You can access the current data of your form via form.vars.value:
{{ form.vars.value.title }}
See Symfony2 Forms documentation: http://symfony.com/doc/current/book/forms.html#rendering-a-form-in-a-template
Dump vars by using dump function:
{{ dump(form.vars.value) }}
If you are using subforms or want to have a value of a specific field:
{{ form.FIELD.vars.VALUE }}
You can access values of the parent parent from a widget block using form.parent.vars
For example, we want to render the value from a type text field called primerNombre we will need
{{ form.vars.value.primerNombre }}
If we wanted to render the name of one of the children we will need
{% for hijo in form.hijos %}
<td><div align="left">{{ form_widget(hijo.vars.value.primerNombre) }}</div></td>
{% endfor %}
Good luck!
In Symfony > 3 you may use:
form.vars.value.Entity.someValue
Edit2:
Finally, I was indeed getting the value of the current row in {{ value }} here:
{% block field_widget %}
{% spaceless %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ **value** }}" {% endif %}/>
{# MY CODE #}
{% if type == "file" %}
<a class="BOpreview" href="{{ value }}">Aperçu</a>
{% endif %}
{% endspaceless %}
{% endblock field_widget %}
But in my case I get a token instead of the value since I am using input type file. This is due to a security measure in Symfony2.

Link in form label using route

In my registration form i have a checkbox "I accept the terms", and want to link the word "terms" to my terms page.
Is there a way to add a link to a form label, using a route? (preferably without injecting the container in the form)
As the solution above somehow didn't work for me I solved it using the solution suggested here: https://gist.github.com/marijn/4137467
OK, so here i how I did it:
{% set terms_link %}<a title="{% trans %}Read the General Terms and Conditions{% endtrans %}" href="{{ path('get_general_terms_and_conditions') }}">{% trans %}General Terms and Conditions{% endtrans %}</a>{% endset %}
{% set general_terms_and_conditions %}{{ 'I have read and accept the %general_terms_and_conditions%.'|trans({ '%general_terms_and_conditions%': terms_link })|raw }}{% endset %}
<div>
{{ form_errors(form.acceptGeneralTermsAndConditions) }}
{{ form_widget(form.acceptGeneralTermsAndConditions) }}
<label for="{{ form.acceptGeneralTermsAndConditions.vars.id }}">{{ general_terms_and_conditions|raw }}</label>
</div>
The best way is to overwrite the twig block used to render that specific label.
First, check the form fragment naming section of the docs. Then create a new block in your form template with the the appropriate name. Don't forget to tell twig to use it:
{% form_theme form _self %}
For the next step check the default form_label block.
You'll probably only need a portion of it, something like this (I'm leaving the default block name here):
{% block form_label %}
{% spaceless %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>
{{ label|trans({}, translation_domain) }}
</label>
{% endspaceless %}
{% endblock %}
As an option, you can do so:
->add('approve', CheckboxType::class, [
'label' => 'Text part without link',
'help' => 'And download it',
'help_html' => true,
])
In Symfony 5.1 there are new form improvements.
HTML contents are allowed in form labels!
HTML contents are escaped by default in form labels for security reasons. The new label_html boolean option allows a form field to include HTML contents in their labels, which is useful to display icons inside buttons, links and some formatting in checkbox/radiobutton labels, etc.
// src/Form/Type/TaskType.php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('save', SubmitType::class, [
'label' => ' Save',
'label_html' => true,
])
;
}
}
In your case you can set form label directly from template and pass the route there.
{{ form_widget(form.acceptTermsAndConditions, {
label: '' ~ "I accept ..."|trans ~ '',
label_html: true
})
}}
My solution was another:
form:
$builder
->add(
'agree_to_rules',
'checkbox',
[
'required' => true,
'label' => 'i_agree_to'
]
);
And html:
<span style="display:inline-block">
{{ form_widget(form.agree_to_rules) }}
</span>
<span style="display:inline-block">
rules
</span>
And looks the same :)
A very simple way to do it would be
{{ form_widget(form.terms, { 'label': 'I accept the terms and conditions' }) }}
You can also do this if you want to use translation
In your translation file for example messages.en.yml add
terms:
url: 'I accept the terms and conditions'
And in your view add
{{ form_widget(form.terms, { 'label': 'terms.url'|trans({'%url%': path('route_to_terms')}) }) }}

Adding "help" messages to fields

I'm trying to add some help messages after each field in form in symfony2.
I have read about one solution in official docs : http://symfony.com/doc/current/cookbook/form/form_customization.html#adding-help-messages
But this solution makes little sense, because we've need to create all form manually.
For example, it easy to define label: $formBuilder->add('myfieldname', 'text', array('label'=>'some my field label')); But how to pass help messages? (In other words, some custom variables)
A another method without another extension :
In your form builder class:
$builder->add('yourField',null, array('attr'=>array('help'=>'text help')))
In your form template rewrite:
{% block form_row %}
{% spaceless %}
{{ form_label(form) }}
{{ form_widget(form) }}
{% for attrname, attrvalue in attr %}
{% if attrname == 'help' %}
<span class="help-block">{{ attrvalue }}</span>
{% endif %}
{% endfor %}
{{ form_errors(form) }}
{% endspaceless %}
{% endblock form_row %}
$formBuilder->add('myFieldName', 'text', array('help' => 'My Help Message')); But it think you also need to add an extension that add this as a default option for all forms :
https://github.com/simplethings/SimpleThingsFormExtraBundle#helpextension
This makes you able to edit attributes directly from you FormTypes.
Since symfony 4.1 you can do :
$builder->add('email', null, [
'help' => 'Make sure to add a valid email',
]);
https://symfony.com/blog/new-in-symfony-4-1-form-field-help
You can use the solution in the official docs as you described.
But, the work is not complete yet. You have to create a Form Type Extention, based on this article: http://symfony.com/doc/current/cookbook/form/create_form_type_extension.html
After complete the Form Type Extention creation you can add Help Messages like this:
$form = $this->createFormBuilder()
->add('name', 'text', array(
'help' => 'this is a help message to user',
))
I think this is a native better solution.
Also, i recommend read this great article that shows you how to enable and set the help option in symfony2 forms:
http://toni.uebernickel.info/2012/11/03/how-to-extend-form-fields-in-symfony2.1.html
A little off topic but still useful if you're planning to use Bootstrap for your project then you can take advantage of some form helpers provided by the Mopa Bootstrap Bundle.
Demo: http://bootstrap.mohrenweiserpartner.de/mopa/bootstrap/forms/help_texts
GitHub: https://github.com/phiamo/MopaBootstrapBundle
Example:
<?php
$form = $this->get('form.factory')
->createNamedBuilder('form_name')
->setMethod('POST')
->add('testSelect', 'choice', [
'choices' => ['val1' => 'Value 1', 'val2' => 'Value 2'],
'required' => true,
'help_block' => 'Here some help text!!!'
])
->add('Save', 'submit')
->getForm();
return $form->createView();

Resources