Before Symfony 2.7, the attr
value for a choice
field applied only to the field itself, i.e. the <select>
element that was rendered. I used this to apply classes to this element to style it.
In Symfony 2.7 this behavior was changed. Now, all <option>
children of the <select>
element also get the same attributes (commit of the change) and therefore classes.
For some clarification, let this be the code:
<?php echo $view['form']->widget($form['myField'], ['attr' => ['class' => "text ui-widget-content ui-corner-all"]]); ?>
Then this is the output of Symfony <=2.6:
<select class="text ui-widget-content ui-corner-all" name="myField">
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</select>
And this is the output of Symfony >= 2.7:
<select class="text ui-widget-content ui-corner-all" name="myField">
<option value="1" class="text ui-widget-content ui-corner-all">Option 1</option>
<option value="2" class="text ui-widget-content ui-corner-all">Option 2</option>
</select>
The classes I apply are not suitable for <option>
elements as they define borders and the like for the actual field. Note that these are classes defined by jQuery UI so I can't easily change their definition.
What is the easiest way to avoid applying these classes to all <option>
elements of a choice
field while still applying it to the <select>
element?
Thanks to the comment about choice_attr
by @user2268997 I found the related blog post New in Symfony 2.7: Choice form type refactorization which details the use of the (as of now undocumented) choice_attr
option.
It seems Symfony merges the attributes in choice_attr
with the ones in attr
when rendering the field. This means we need to overwrite the class
attribute in choice_attr
.
I tried doing this in the code next to where I define attr
but had no luck. It seems you need to do this in your form type definition. Here is an excerpt from my form after adding the choice_attr
option:
namespace MyBundle\Form;
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('roles',
'entity',
[
'class' => 'MyBundle:Role',
'choice_label' => 'name',
'multiple' => true,
'choice_attr' => function () { return ["class" => ""]; }
]);
}
The result is as I had hoped. I will probably also refactor this to my own custom form type so I do not need to repeat it all over my bundle.
I have now decided to create a custom choice
type with the desired behavior described above and use that one throughout my application.
Here is my choice type:
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ChoiceNoOptAttrType extends ChoiceType {
public function configureOptions(OptionsResolver $resolver) {
parent::configureOptions($resolver);
$resolver->setDefault("choice_attr", function () { return ["class" => ""]; });
}
}
I did not feel like refactoring all my existing forms to use this new type, so instead I opted to replace the Symfony-provided choice type with mine. This can be achieved by modifying the service configuration for the choice
form type. To do this, I created a compiler pass for my bundle.
Further reading: Creating a Compiler Pass
namespace MyBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class MyCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition("form.type.choice");
$definition->setClass('MyBundle\Form\ChoiceNoOptAttrType');
}
}
Now all that is left to do is register the compiler pass in the bundle.
Further reading: How to Work with Compiler Passes in Bundles
namespace MyBundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use MyBundle\DependencyInjection\Compiler\MyCompilerPass;
class MyBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new MyCompilerPass());
}
}
And this is it. Now all my choice
fields are using my custom class which makes sure that the CSS class set in attr
is not propagated to my <option>
elements.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With