Symfony form how to add class to form group - forms

Using:
->add('name', TextType::class, [
'label' => 'Name',
'required' => true,
'attr' => [
'class' => 'myclass'
],
])
I'm adding the class "myclass" to the <input> element, getting:
<div class="form-group">
<label ...>Name</label>
<input type="text" required="required" class="myclass" ...>
</div>
What if I wanted to add the "myclass" class not to the input itself, but to its <div class="form-group"> container, in order to get:
<div class="form-group myclass">
<label ...>Name</label>
<input type="text" required="required" ...>
</div>
What's the best way to accomplish that?

I actually found a way of doing it in PHP (Symfony 4.4.2). Class has to go into 'row_attr' instead of 'attr'. For the given example it'd be:
->add('name', TextType::class, [
'label' => 'Name',
'required' => true,
'row_attr' => [
'class' => 'myclass'
],
])
related docs: https://symfony.com/doc/current/reference/forms/types/text.html#row-attr

In my case I needed to override the form_row block:
{% block form_row -%}
<div class="form-group myclass{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">
{{- form_label(form) -}}
{{- form_widget(form) -}}
{{- form_errors(form) -}}
</div>
{%- endblock form_row %}
as suggested, the docs helps to understand why.

As #Yoshi pointed out, in the form definition itself you cannot add classes to the container div.
The right way to customize form rendering is to override the base block via Twig as explained here: http://symfony.com/doc/current/cookbook/form/form_customization.html#how-to-customize-an-individual-field (BTW I suggest you to read carefully the whole article to know everything you need to know about customizations).
In the overridden block you have also the widget data if you want e.g. to add a class with the field id:
{% block _product_name_widget %}
<div class="text_widget {{ id }}">
{{ block('form_widget_simple') }}
</div>
{% endblock %}
in this example above, {{ id }} will be replaced with the widget (field) id.

Related

Why does Symfony 3.4 form displays EntityType choices with label although 'label' => false was used?

I am working on an existing Symfony 3.4 based project and trying to add and render a new form. Although the 'label' => false option was used, the fields are rendered including a label. Why?
// Symfony
class SomeController extends Controller {
public function userListAction(Request $request) {
$users = $someService->getUsers();
$formBuilder = $this->createFormBuilder()
->add('users', EntityType::class, [
'label' => false, // also tested '' and 'someLabel'
'class' => 'AppBundle:User',
'choices' => $users,
'multiple' => true,
'expanded' => true,
]);
$variables = array(
'form' => $formBuilder->getForm()->createView(),
);
return $this->render('AppBundle:Pages:user_list.html.twig', $variables);
}
}
// Twig
{% extends 'AppBundle::layout.html.twig' %}
{% block page_content %}
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
{% endblock %}
This shows a list of checkboxes for all users including the username as label.
Where does Symfony get the information to use the username as label? As far as I know no custom form widget was defined for the User class. Is there any way to check this for sure? Maybe there is something hidden in the vendor bundles like FOSUserBundle?
Why is the 'label' => false option ignored?
Edit:
Different ways of rendering the form does not solve the problem:
{{ form_start(form) }}
{{ form_row(form) }}
{{ form_end(form) }}
Result:
<div id="form_users">
<div class="form-group">
<div class="checkbox">
<label for="form_users_547">
<input type="checkbox" id="form_users_547" name="form[users][]" value="547">
someUserName
</label>
</div>
</div>
</div>
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
Result:
<div id="form_users">
<div class="checkbox">
<label for="form_users_547">
<input type="checkbox" id="form_users_547" name="form[users][]" value="547">
someUserName
</label>
</div>
</div>
{{ form_start(form) }}
{% for userFormView in form.users %}
{{ form_row(userFormView) }}
{% endfor %}
{{ form_end(form) }}
Result:
Basically the same as before with form_row
You need to use ‘choice_label’ => ‘YOUR PROPERTY PATH’ in the field options.
Pretty match is written in the docs: https://symfony.com/doc/current/reference/forms/types/entity.html#choice-label
If the entity class cast to string then is used if is not it will throw an exception. It looks like your entity User cast to the user name and that’s why it works.
You should try to use {{ form_row(form) }} which should render the whole field correctly.

Create form based on EntityType with option to add quantity

I'm trying to setup a Symfony form that will allow a user to select a number of elements adding the wished quantity. I would like to be able to have a FormType which would be somewhere between an EntityType and the IntegerType. Meaning I need to have a list of elements based on a query_builder to select only part of my products, but I don't just want to be able to select the product but say how many I want of a given number of products.
I've been able to create a form based on the options I send. For each product in my array I add an IntegerType field to my form using the builder. This allows me to show a list of products and ask the client the number of elements he wants.
Now the problem I have is adding detailed data from the product in the list as I don't know the forms field names I can't interact with the label. If I could add something allowing me to say that the 'label' could show as raw html, I could concatenate the wished data in the label.
Here is my current form:
class OfferRequestStepMultipleHardwareType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// instead of haveing set fields I create them based on the $option['step']->getProducts() data
foreach ($options['step']->getProducts() as $product){
$builder->add($product->getId().'-qty', IntegerType::class, [
'mapped' => false,
'attr' => [
'value' => 0,
'class' => 'longlist',
'min' => 0
],
'row_attr' => [
'class' => 'longlist'
],
'label' => $product->getNumber() // ideally I would do some thing like '<div>'.$product->getNumber().'</div><div>'.$product->getDescription().'</div>' and then show it as raw in the form template
]);
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => OfferRequest::class,
'step' => StepHardware::class,
]);
}
}
The problem is this generates code a bit like this:
<div id="offer_request_step_multiple_hardware">
<div class="longlist"><label for="offer_request_step_multiple_hardware_27-qty" class="required">Product 1</label><input type="number" id="offer_request_step_multiple_hardware_27-qty" name="offer_request_step_multiple_hardware[27-qty]" required="required" value="0" class="longlist"
min="0"></div>
<div class="longlist"><label for="offer_request_step_multiple_hardware_28-qty" class="required">Product 2</label><input type="number" id="offer_request_step_multiple_hardware_28-qty" name="offer_request_step_multiple_hardware[28-qty]" required="required" value="0" class="longlist"
min="0"></div>
<div class="longlist"><label for="offer_request_step_multiple_hardware_29-qty" class="required">Product 3</label><input type="number" id="offer_request_step_multiple_hardware_29-qty" name="offer_request_step_multiple_hardware[29-qty]" required="required" value="0" class="longlist"
min="0"></div>
<div class="longlist"><label for="offer_request_step_multiple_hardware_30-qty" class="required">Product 4</label><input type="number" id="offer_request_step_multiple_hardware_30-qty" name="offer_request_step_multiple_hardware[30-qty]" required="required" value="0" class="longlist"
min="0"></div><input type="hidden" id="offer_request_step_multiple_hardware__token" name="offer_request_step_multiple_hardware[_token]" value="PZaPfxKNSV-TjftRgjAw1K8XCUr7Dvkrp57kWTMBJ64"></div>
I've also tried to create a form theming in the template to change the way the Integer widget show with this:
{% form_theme form _self %}
{% block question %}
<h1>{{ offer.lastStep.Question }}</h1>
{% endblock %}
{% block integer_widget %}
<div class="name_row">
{{ form_label(form)|raw }}
{{ form_errors(form) }}
{{ form_widget(form) }}
{{ form_help(form) }}
</div>
{% endblock %}
Without any success as when I concatenate and try to add the filter "raw" to it, the code is still changed autoescaped. The problem is then I've found information on how to set a specific label for a specific field of a form, but again I generate the form on the go and have no way of knowing the field names (like described here). Any suggestions on how to have this work?
Ideally I would like to be able to create a FormType based on the EntityType which would allow to add an Integer instead of selecting the Entity elements...
Any help would be nice!
Labels in Symfony forms are escaped at some point before the rendering of the template, so applying the twig raw filter won't prevent html entities from being escaped.
What you can do is create a new twig filter, to apply the php function html-entity-decode (following code untested):
Twig/HtmlDecodeExtension.php
<?php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class HtmlDecodeExtension extends AbstractExtension
{
public function getFilters()
{
return [
new TwigFilter('htmldecode', [$this, 'htmlDecode']),
];
}
public function htmlDecode($value)
{
return html_entity_decode($value);
}
}
If you use the default configuration with services autowiring, you don't have to register the new extension; in the other case, do it in config/services.yaml
Now you can use the filter in your form template instead of raw, and put whatever html you like in the label:
{% form_theme form _self %}
{% block question %}
<h1>{{ offer.lastStep.Question }}</h1>
{% endblock %}
{% block integer_widget %}
<div class="name_row">
{{ form_label(form)|htmldecode }}
{{ form_errors(form) }}
{{ form_widget(form) }}
{{ form_help(form) }}
</div>
{% endblock %}

Symfony2 - Collection type is displayed twice in my view

I have a form with a collection and I can see it properly when I use the following syntax:
form_row(form.items)
Then when I try to render it by myself, it is duplicated.
Here is my code in my twig file:
{{ form_errors(form) }}
{{ form_start(form) }}
... some fields here ...
<div id="{{ form.items.vars.id }}" class="collection items" data-prototype="{{ form_widget(form.items.vars.prototype)|e }}">
<h3>{{ form_label(form.items) }}</h3>
Add
{% for item in form.items %}
{{ form_row(item) }}
{% endfor %}
</div>
... some other fields here ...
{{ form_end(form) }}
Here is my code in my form object:
$builder->add('items', 'collection', array(
'type' => new ItemType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required' => true,
));
Add here is my output:
<!-- This is the one I need, I removed the prototype for the sake of clarity -->
<div id="bundle_data_items" class="collection items" data-prototype="...">
<h3>
<label class="required">Items</label>
</h3>
Add
</div>
<!-- This one is automatically added. The prototype is empty -->
<div>
<label class="required">Items</label>
<div id="bundle_data_items" data-prototype=""></div>
</div>
Do you have any ideas why this happens?
I followed that documentation. Did I miss something?
Edit:
This happens only when my collection is empty
I found the solution to that.
It turns out that when the collection is empty, the rendering of it never occurs. I could use sjagr solution but as there is some fields I want to render automatically, I cannot use it.
So my solution is the following:
{{ form_errors(form) }}
{{ form_start(form) }}
... some fields here ...
<div id="{{ form.items.vars.id }}" class="collection items" data-prototype="{{ form_widget(form.items.vars.prototype)|e }}">
<h3>{{ form_label(form.items) }}</h3>
Add
{% for item in form.items %}
{{ form_row(item) }}
{% else %}
{% do form.items.setRendered %}
{% endfor %}
</div>
... some other fields here ...
{{ form_end(form) }}
When your collection is empty, the for loop in {% for item in form.items %} never gets to execute, hence form_row(item) never happens. This means, to Twig/Symfony, you never actually outputted the field.
Then you do form_end at the end of the form. This is not usually a big deal, but from the docs:
This helper also outputs form_rest() unless you set render_rest to false
So, you must simply pass a false value for render_rest:
{{ form_end(form, {'render_rest': false}) }}
I'm not sure how form_widget did fix your problem, but I'm also not sure how you tried that alternative - perhaps you ran it outside of the for loop.
As an aside, you should consider making your own form.html.twig template file so you can reuse the markup format you've chosen for your collection. It will allow you to simply do form_widget(...) without having to break apart the pieces and your form_end will always consider the field to be outputted even if the collection is empty.
Unfortunately i cannot comment yet (no 50 rep), but perhaps this is caused the browser itself. A rendering issue due to markup someplace? See if the raw http response has the same thing. Since it works with form_row(form.items) but not your own, it "could be" that.
I would check if it's empty also before outputting it.
Similar thing happened to me, label of CollectionType was rendering twice when Collection is empty, but it was rendering once when there are data.
This is my configuration:
$builder->add('items', CollectionType::class, [
'entry_type' => NumberType::class,
'required' => true,
'label' => 'Fields',
'entry_options' => [
'label' => false,
],
'prototype' => true,
])
and in Twig I was displaying Collection label in this way:
{{ form_label(form.items) }} and rendering field widgets in somewhere else. In this case label was displayed twice, once where this code is placed, and the other at the end of form.
Solution is to change it to:
{{ form_row(form.items) }}
or if you need custom rendering for form widgets like in my case, following code will print only labels:
{% if 0 < (form.items|length) %}
{{ form_label(form.items) }}
{% else %}
{{ form_row(form.items) }}
{% endif %}

Placeholder for form_widget choices on Twig

Using PHP on controller side, i can add a placeholder to my field using the createFormBuilder():
$form = $this->createFormBuilder($company)
->add('name', 'text')
->add('cities', 'entity', array(
'class' => 'NexBaseBundle:City',
'property' => 'name',
'placeholder' => 'Choose an option',
))
->getForm();
I am looking to add a placeholder using Twig, for simple fields i can use attr like this :
{{ form_widget(form.name, {'attr': {'placeholder': 'My placeholder'} }) }}
I have tried with this but no luck:
{{ form_widget(form.cities, {'attr': {'placeholder': 'Choose your city'} }) }}
You can't because the "select box" on html5 does not have an attribute "placeholder", your attribute "placeholder" in your formbuilder is an alternative of empty_value
https://github.com/symfony/symfony/issues/5791
On the createFormHandler its the empty_data option. You can use the placeholder option but its possible that you get some trouble in older Browsers.
Normally i disable that HTML5 functions and do it with Javascript.
http://www.hagenburger.net/BLOG/HTML5-Input-Placeholder-Fix-With-jQuery.html
Here is a cool jQuery plugin to fix that problem in older Browsers.
Setting the value from your controller make not really sense. You can set a key in your form and translate them for example.
You can add the attribute only on text fields. Your second method on a Selext-Box is not working because there is not attribute placeholder on that element. So you can use the placeholder in your FormType but that is changed to an option field in background.
Here is an example how I am currently adding placeholders (My select list can be different then yours, but you can play around of this snippet):
{% form_theme cart_form _self %}
{% block choice_widget %}
{% if form.vars.choices is defined %}
{% for choices in form.vars.choices %}
<div class="row">
<div class="col-md-6">
<select name="{{ form.vars.full_name }}" class="form-control">
<option value="__none">--Choose</option>
{% for choice in choices %}
<option value="{{ choice.value }}">{{ choice.label }}</option>
{% endfor %}
</select>
</div>
</div>
{% endfor %}
{% endif %}
{% endblock %}

add html class value with existing class in symfony2 form?

I have defined some filed in my FormType and each form element has class value like below
$builder->add('name', 'text', array(
'label' => 'Name',
'attr' => array('class' => 'form-control'),
));
in twig file wrote like this
{{ form_widget(form.name) }}
This code generate output like this
<input type='text' name='name' class='form-control' />
now I want to add extra class in this element like error class which generate following output
<input type='text' name='name' class='form-control error' />
For generating above output I wrote like code in Twig file-
{{ form_widget(form.name,{attr:('class'=>'error')}) }}
This code generate following output--
<input type='text' name='name' class='error' />
So I am confused, what should to get my expected result. Add extra class value to my form element in html.
My Expected output is
<input type='text' name='name' class='form-control error' />
You can add multiple classes inside the class attribute.
Like this:
$builder->add('name', 'text', array(
'label' => 'Name',
'attr' => array('class' => 'form-control error'),
));
Going by your comment on #Moshikaro's answer, you're probably going to have to use flow control to build up the class string, either in Twig or the Controller:
Twig
{% set errorClass = '' %}
{% if errorTest %}
{% set extraClass = ' error' %}
{% endif %}
{{ form_widget(form.name,{attr:('class'=>'form-control'~errorClass)}) }}
or maybe
{% set errorClass = '' %}
{% if errorTest %}
{% extraClass = ' error' %}
{% endif %}
{{ form_widget(form.name,{attr:('class'=>form.config.attributes.class~errorClass)}) }}
Controller
$errorClass = "";
if ($test)
{
$errorClass += " error";
}
$cssClasses = "form-control" + $errorClass;
$builder->add('name', 'text', array(
'label' => 'Name',
'attr' => array('class' => $cssClasses),
));
You get the idea, anyway. I've not been able to test any of that.