Modify label_attr field in Symfony 5 form builder - forms

When building a form in symfony form builder changing the choice attribute can be done.
However, for the label attribute this doesn't seem possible.
Here is how i modify the choice:
$builder->add('type', EntityType::class, [
'class' => Resourcetype::class,
'multiple' => true,
'expanded' => true,
'choice_attr' => function (?Resourcetype $type) {
return ['class' => $type->getSafeName() . '-parent parent' : $type->getSafeName()
];
});
Is this possible for the label_attr field?

EntityType doesn't provide option for modifying choice label attributes.
You should do it yourself.
1. Simple solution
Iterate over choices in templating engine, one by one and render it yourselves. Obtain entity from choice and set label attribute.
{{ form_start(form) }}
{%- for choice in form.choices %}
<div>
{% set entity = form.choices.vars.choices[choice.vars.value].data %}
{{ form_widget(choice) }}
{{ form_label(choice, null, {
label_attr: {class: 'test-' ~ entity.number}
}) }}
</div>
{% endfor -%}
{{ form_end(form) }}
2. Clean solution
Create custom type extending EntityType:
https://symfony.com/doc/current/form/create_custom_field_type.html
Create new option in type definition allowing closures e.g. "choice_label_attr" and pass closure to view:
// src/Form/Type/CustomEntityType.php
namespace App\Form\Type;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class CustomEntityType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setRequired('choice_label_attr');
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['choice_label_attr'] = $options['choice_label_attr']
}
public function getParent(): string
{
return EntityType::class;
}
}
Extend template for choice type:
https://symfony.com/doc/current/form/form_themes.html#applying-themes-to-all-forms
Use "choice_label_attr" callback inside extended template:
{% use "bootstrap_4_layout.html.twig" %}
{% block custom_entity_widget_expanded -%}
<div {{ block('widget_container_attributes') }}>
{%- for child in form %}
{{- form_widget(child) -}}
{{- form_label(child, null, {class: choice_label_attr(form.choices.vars.choices[child.vars.value].data), translation_domain: choice_translation_domain}) -}}
{% endfor -%}
</div>
{%- endblock custom_entity_widget_expanded %}
More info: https://github.com/symfony/symfony/blob/5.x/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_4_layout.html.twig
Usage example:
use App\Form\Type\CustomEntityType ;
$builder->add('type', CustomEntityType::class, [
'class' => Resourcetype::class,
'multiple' => true,
'expanded' => true,
'choice_attr' => function (?Resourcetype $type) {
return [
'class' => sprintf('%s-parent parent', $type->getSafeName()) : $type->getSafeName()
];
});
Solution 2. is written from head and can contain some bugs but I hope you get the idea.
Both solution are using Twig and Bootstrap 4 form layout, but it is not requirement.

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

Symfony 2, Custom Form Widgets For Single inputs?

UPDATE/EDIT
This is what I have done, but for some reason its not working. So the services is as follows,
clearfix.extended_type:
class: Bundle\Form\ClearfixExtension
tags:
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FormType }
Now I am sure this is wrong, I have not used services much and still trying to get my head around them.
I ave also built the class, see answer below for anyone else reading this, with the right namespace.
I have also done the following to the block,
{%- block form_row -%}
<div class="other classes here {% if clearfix %}clearfix"{% else %}"{% endif %}>
{{- form_widget(form) -}}
{{- form_label(form) -}}
{{- form_errors(form) -}}
</div>
{%- endblock form_row -%}
But all I get from my form when I try to render it is, "Variable "clearfix" does not exist in....."
So what have I done wrong? I guessing it has something to do with the service setup?
Thanks
ok, this is doing my head in a little, has I have gone over and over the docs but I am either doing it completely wrong or its what I want to do is just not do able?
So I am using Symfony 2.7, and have it setup to point to a forms twig file, so that I can override some of the defaults. This is working fine without any problems. However I now want to be able to render some of the rows with an added clearfix class. This is what I am rending right now,
{%- block form_row -%}
<div>
{{- form_widget(form) -}}
{{- form_label(form) -}}
{{- form_errors(form) -}}
</div>
{%- endblock form_row -%}
That renders all my rows with the right classes, but I want to be able to use the following on some selected rows
{%- block _clerfix -%}
<div class="clearfix">
{{- form_widget(form) -}}
{{- form_label(form) -}}
{{- form_errors(form) -}}
</div>
{%- endblock _clerfix -%}
And then I call this with the block_name within the Abstract Type of the form that I want to build with the added clearfix class, like so,
->add('password', 'password', ['label' => 'Password',
'required' => false,
'block_name' => 'clearfix',
'attr' => ['autocomplete' => 'off','class' => 'Passwordd', 'aria-required' => 'true' ]
])
Is what I want doable? I don't really want to add that clearfix into the user form I am building now, as that will not keep my code very DRY, as there will be (one would think so) places where I will need to re-use that block to render other fields?
All help most welcome.
Thanks.
You're can create form extension for defining option to apply clearfix
Symfony 2.8
class ClearfixExtension extends AbstractTypeExtension
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['clearfix'] = $options['clearfix'];
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('clearfix', false);
}
public function getExtendedType()
{
return FormType::class;
}
}
services.yml
clearfix_extension:
class: Bundle\Form\ClearfixExtension
tags:
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FormType }
Symfony 2.7
class ClearfixExtension extends AbstractTypeExtension
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['clearfix'] = $options['clearfix'];
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('clearfix', false);
}
public function getExtendedType()
{
return 'form';
}
}
services.yml
clearfix_extension:
class: Bundle\Form\ClearfixExtension
tags:
- { name: form.type_extension, alias: form }
and check this options in twig
{%- block form_row -%}
<div {% if clearfix %}class="clearfix"{% endif %}>
{{- form_widget(form) -}}
{{- form_label(form) -}}
{{- form_errors(form) -}}
</div>
{%- endblock form_row -%}

Get input type in Twig to determine the label

I have this
{% block form_row %}
<div class="form-group">
{{ form_label(form) }}
{{ form_widget(form) }}
</div>
{% endblock form_row %}
Its used to override the main Twig form fields.
But I need the label to be different depending on the type of form field that is being rendered.
How can I get that here and then call something else instead of form_label ?
I essentially want to be able to do this, this is because the label it would appear comes after the input for checkboxes, but I want to reverse this / customise it.
{% block form_row %}
<div class="form-group">
{% if(type is checkbox) %}
// checkbox label here
{% else %}
{{ form_label(form) }}
{% endif %}
{{ form_widget(form) }}
</div>
{% endblock form_row %}
You can override the blocks that are used to render a specific form type.
For example, if you want to override the label template of an email input, you should override the email_label block:
{% block email_label %}
This is the template used for all email input
{% endblock %}
{% block form_label %}
This is the fallback template for all other input types
{% endblock %}
You can check which blocks you can override for a specific form view by looking into form.vars.block_prefixes.
For example, for a "personnal_email" field of type "email", it'll contain :
array:4 [▼
0 => "form"
1 => "text"
2 => "email"
3 => "_form_personnal_email"
]
which mean you can override blocks (starting with the less specific one) form_(widget|label|error), text_(widget|label|error), email_(widget|label|error) and _form_personnal_email_(widget|label|error) (the last one is usefull to override the rendering of a very specific field).
Does it answer your question?
UPDATE
here's what you have to do:
{% block form_row %}
<div class="form-group">
{{ form_label(form) }}
{{ form_widget(form) }}
</div>
{% endblock %}
{% block checkbox_label %}
<!-- Your checkbox specific label -->
{% endblock %}
You cannot access type in the form_row block as it's only defined in sub blocks of form_widget (see here for example)
You can use custom format:
<div class="form-group">
{{ form_label(form.your_value, 'Your Title of field', { 'label_attr': {'class': 'col-sm-3 control-label'} }) }}
<div class="col-sm-9">
{{ form_widget(form.your_value, { 'attr': {'class': 'form-control selectJS'} }) }}
</div>
</div>
or you can use FormType (if you generate entity, this is file in Form folder), like a:
<?php
namespace Ens\YourBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class NoticeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title','text', array(
'label'=>'Title',
'label_attr'=>array( 'class'=>'col-sm-3 control-label' ),
'attr'=>array( 'class'=> 'form-control')
))
->add('text', 'textarea', array(
'label'=>'Text',
'label_attr'=>array( 'class'=>'col-sm-3 control-label' ),
'attr'=>array( 'class'=> 'form-control')
))
->add('keep_on_top','checkbox', array(
'label'=>'Keep on top',
'required'=>false
))
->add('start', 'date', array(
'attr'=>array( 'class'=> 'hidden')
))
->add('end', 'date', array(
'attr'=>array( 'class'=> 'hidden')
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Ens\YourBundle\Entity\Notice'
));
}
/**
* #return string
*/
public function getName()
{
return 'notice';
}
}

Symfony2 Update Specific Field in Form Collection

I have an interesting problem. I couldn't find any solution in stackoverflow and also google. I have an Entity User and User have some metas. So I created a UserMeta Entity and also UserMetaValue. In user form there is lots of tabs. And I used these metas in the tabs. Some of them in first tabs, some of them in other tabs. And all tabs have their own form. When I bind a form on active tab, active tab metas update, others changed to NULL.
StudentPersonalType.php
namespace ATL\UserBundle\Form\Type;
use ATL\CommonBundle\Utils\Shortcut;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormBuilderInterface;
class StudentPersonalType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add("first_name", null, array(
"label" => "İsim",
"required" => true,
"attr" => array(
"class" => "span10"
)
))->add("last_name", null, array(
"label" => "Soyisim",
"required" => true,
"attr" => array(
"class" => "span10"
)
))->add("username", null, array(
"label" => "Öğrenci Numarası",
"required" => true,
"attr" => array(
"class" => "span10"
)
))->add("email", null, array(
"label" => "Email",
"required" => true,
"attr" => array(
"class" => "span10"
)
))->add('metas', 'collection', array(
'label' => "Metas",
'type' => new UserMetaType()
));
}
public function getName(){
return "personal";
}
public function setDefaultOptions(OptionsResolverInterface $resolver){
$resolver->setDefaults(array(
"data_class" => "ATL\UserBundle\Entity\User"
));
}
}
StudentEducationType.php
namespace ATL\UserBundle\Form\Type;
use ATL\CommonBundle\Utils\Shortcut;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormBuilderInterface;
class StudentEducationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('metas', 'collection', array(
'label' => "Metas",
'type' => new UserMetaType(),
'by_reference' => false
));
}
public function getName(){
return "education";
}
public function setDefaultOptions(OptionsResolverInterface $resolver){
$resolver->setDefaults(array(
"data_class" => "ATL\UserBundle\Entity\User"
));
}
}
And twig
<div id="personal-info" class="tab-pane row-fluid active">
<form style="margin:20px 0 0 0;" class="ajaxForm form-horizontal form-row-seperated" action="{{ formAction }}">
{{ form_row(form.first_name) }}
{{ form_row(form.last_name) }}
{{ form_row(form.email) }}
{{ form_row(form.username) }}
{% for meta in form.metas %}
{% if meta.value.vars.label in formValues.personal %}
{{ form_widget(meta) }}
{% endif %}
{% endfor %}
{{ form_row(form._token) }}
<div class="form-actions" style="margin-bottom:0;">
<button class="btn blue" type="submit"><i class="icon-ok"></i> Kaydet</button>
</div>
</form>
</div>
<div id="education-info" class="tab-pane row-fluid">
<form style="margin:20px 0 0 0;" class="ajaxForm form-horizontal form-row-seperated" action="{{ formAction }}">
{% for meta in educationForm.metas %}
{% if meta.value.vars.label in formValues.education %}
{{ form_widget(meta) }}
{% endif %}
{% endfor %}
{{ form_row(educationForm._token) }}
<div class="form-actions" style="margin-bottom:0;">
<button class="btn blue" type="submit"><i class="icon-ok"></i> Kaydet</button>
</div>
</form>
</div>
I filter collection fields by check it in twig file with IF statement.
I ask my question again, How could I use metas in different form in same page without affecting the others?
You have done half the job of filtering out the irrelevant fields for rendering. But you also need to filter out the irrelevant fields for form binding.
When you bind the request to the form it is expecting values for every UserMeta entity because there is a field for all of them in the UserMetaType collection. You'll need to remove all the UserMetaType forms which don't correspond to one of your submitted values. It's probably best to this with a FormEvents::PRE_BIND listener.
You can see a simpler example of this at Form: Avoid setting null to non submitted field. It will be slightly more complex for you, because you'll have to iterate through the collection of UserMetaType forms and remove the ones which you don't want bound.