Как я могу избежать применения attr ко всем параметрам выбранного поля?

До Symfony 2.7 значение attr для поля choice применялось только к самому полю, то есть к отображаемому элементу <select>. Я использовал это, чтобы применить классы к этому элементу, чтобы стилизовать его.

В Symfony 2.7 это поведение было изменено. Теперь все <option> дочерние элементы элемента <select> также получают одинаковые атрибуты (фиксация изменения) и поэтому классы.


Для некоторого пояснения, пусть это будет код:

<?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, поэтому я не могу легко изменить их определение.

Как проще всего избежать применения этих классов ко всем элементам <option> поля choice, но при этом применить их к элементу <select>?


person Chris    schedule 17.08.2015    source источник
comment
Возможно, это ошибка. Потому что я ничего не смог найти в UPGRADE-2.7 для этого, и эта функциональность - это то, что должна делать опция 'choice_attr'.   -  person user2268997    schedule 17.08.2015
comment
@user2268997 user2268997 Да, я тоже ничего не нашел, но, глядя на код, который я связал, мне кажется, что это сделано намеренно.   -  person Chris    schedule 17.08.2015
comment
Я мало что знаю о php-шаблонах, но похоже, что «attr» разрешается из $choice, который является экземпляром ChoiceView, который создается DefaultChoiceListFactory, а параметр choice_attr передается этой фабрике в определении ChoiceType (если вы не поставляете свои ChoiceList конечно)   -  person user2268997    schedule 18.08.2015


Ответы (2)


Благодаря комментарию @user2268997 о choice_attr я нашел соответствующую запись в блоге Новое в Symfony 2.7: Рефакторинг типа формы выбора , в котором подробно описывается использование (на данный момент недокументированного) параметра choice_attr.

Кажется, Symfony объединяет атрибуты в choice_attr с атрибутами в attr при рендеринге поля. Это означает, что нам нужно перезаписать атрибут class в choice_attr.

Я попытался сделать это в коде рядом с тем, где я определяю 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. Для этого я создал проход компилятора для своего пакета.

Дополнительная литература: Создание прохода компилятора

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

Теперь осталось только зарегистрировать проход компилятора в бандле.

Дополнительная литература: Как работать с проходами компилятора в пакетах

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 используют мой пользовательский класс, который гарантирует, что класс CSS, установленный в attr, не распространяется на мои элементы <option>.

person Chris    schedule 17.08.2015

Может быть более простое решение, но вы можете взглянуть на Темы форм. Переопределите шаблон для selection_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 -%}
person LorenzSchaef    schedule 17.08.2015
comment
OP не использует шаблоны веток. - person user2268997; 17.08.2015
comment
Тогда вы можете переопределить шаблон php, я думаю. - person LorenzSchaef; 17.08.2015
comment
Спасибо, мне это тоже пришло в голову. Однако я надеялся, что смогу пропустить перезапись этого (довольно сложного) шаблона, так как мне, вероятно, придется сохранить его для будущих версий. - person Chris; 17.08.2015