我怎样才能避免将attr应用到我选择领域的所有选项


How can I avoid applying attr to all options of my choice field?

在Symfony 2.7之前,choice字段的attr值仅应用于字段本身,即呈现的<select>元素。我用它来给这个元素应用类来定义它的样式。

在Symfony 2.7中这个行为被改变了。现在,<select>元素的所有<option>子元素也获得相同的属性(提交更改)和类。


说明一下,让代码如下:

<?php echo $view['form']->widget($form['myField'], ['attr' => ['class' => "text ui-widget-content ui-corner-all"]]); ?>

下面是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>

下面是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>

我应用的类不适合<option>元素,因为它们为实际字段定义了边界等。请注意,这些是由jQuery UI定义的类,所以我不能轻易地更改它们的定义。

避免将这些类应用于choice字段的所有<option>元素,同时仍将其应用于<select>元素的最简单方法是什么?

感谢@user2268997关于choice_attr的评论,我找到了相关的博客文章《Symfony 2.7中的新功能:选择表单类型重构》,其中详细介绍了(截至目前未记录)choice_attr选项的使用。

似乎Symfony在呈现字段时将choice_attr中的属性与attr中的属性合并。这意味着我们需要覆盖choice_attr中的class属性。

我尝试在我定义attr的代码旁边这样做,但没有运气。似乎您需要在表单类型定义中这样做。以下是添加choice_attr选项后的表单摘录:

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" => ""]; }
            ]);
}
结果和我所希望的一样。我可能还会将它重构为我自己的自定义表单类型,这样我就不需要在我的包中重复它了。

我现在决定创建一个自定义的choice类型,具有上面描述的期望行为,并在我的整个应用程序中使用它。

这是我的选择类型:

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" => ""]; });
    }
}

我不想重构所有现有的表单来使用这个新类型,所以我选择用我的选择类型替换symfony提供的选择类型。这可以通过修改choice表单类型的服务配置来实现。为此,我为我的bundle创建了一个编译器传递。

进一步阅读:创建编译器通道

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');
    }
}

现在剩下要做的就是在bundle中注册编译器传递。

进一步阅读:如何在bundle中使用编译器传递

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());
    }
}

就是这样。现在我所有的choice字段都使用我的自定义类,这确保了attr中的CSS类设置不会传播到我的<option>元素。

可能有一个更简单的解决方案,但您可能想看看表单主题。覆盖choice_widget_options的模板,这样类就不会应用到选项标签上。

{%- block choice_widget_options -%}
    {% for group_label, choice in options %}
        {%- if choice is iterable -%}
            <optgroup label="{{ choice_translation_domain is sameas(false) ? group_label : group_label|trans({}, choice_translation_domain) }}">
                {% set options = choice %}
                {{- block('choice_widget_options') -}}
            </optgroup>
        {%- else -%}
            {% set attr = choice.attr %}
            <option value="{{ choice.value }}" {# DELETE THIS PART: {{ block('attributes') }}#}{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice_translation_domain is sameas(false) ? choice.label : choice.label|trans({}, choice_translation_domain) }}</option>
        {%- endif -%}
    {% endfor %}
{%- endblock choice_widget_options -%}