Symfony2:集合中的单选按钮

时间:2014-01-08 16:43:46

标签: symfony symfony-forms

在我的应用程序中,我使用collection字段类型创建了一个表单:

$builder->add('tags', 'collection', array(
   'type' => new TagType(),
   'label' => false,
   'allow_add' => true,
   'allow_delete' => true,
   'by_reference' => false
));

使用一些JQuery,这段代码可以正常工作,但现在我想选择其中一个动态标签,使其成为“主要标签”。

在我的Tag实体中,我添加了一个布尔属性,用于定义标记是否为main:

/**
 * @ORM\Column(name="main", type="boolean")
 */
private $main;

但在我看来,每行现在都包含一个复选框。所以我可以选择多个主标签。如何在单选按钮中转换此复选框?

4 个答案:

答案 0 :(得分:13)

你没有从正确的角度解决问题。如果应该有主标记,则不应在标记实体本身中添加此属性,而应在包含它的实体中添加此属性!

我说的是与具有标签属性的表单相关的data_class实体。这是应该具有 mainTag 属性的实体。

如果定义正确,这个新的 mainTag 属性将不是布尔值,因为它将包含标记实例,因此不会与复选框条目相关联。

所以,就我看来,你应该有一个包含你的实例的 mainTag 属性和一个包含所有其他标签的标签属性。

问题在于您的收藏字段将不再包含主标记。因此,您还应创建一个特殊的getter getAllTags ,它将您的主标记与所有其他标记合并,并将您的集合定义更改为:

$builder->add('allTags', 'collection', array(
    'type' => new TagType(),
    'label' => false,
    'allow_add' => true,
    'allow_delete' => true,
    'by_reference' => false
));

现在,我们如何添加收音机盒,您可能会问?为此,您必须生成一个新字段:

$builder->add('mainTag', 'radio', array(
    'type' => 'choice',
    'multiple' => false,
    'expanded' => true,
    'property_path' => 'mainTag.id', // Necessary, for 'choice' does not support data_classes
));

这些是基础知识,但它从这里开始变得更加复杂。这里真正的问题是你的表单是如何显示的。在同一个字段中,您可以混合集合的常规显示和该集合的父表单的选择字段的显示。这将迫使您使用form theming

要允许一些空间可重用,您需要创建自定义字段。关联的data_class:

class TagSelection
{
    private mainTag;

    private $tags;

    public function getAllTags()
    {
        return array_merge(array($this->getMainTag()), $this->getTags());
    }

    public function setAllTags($tags)
    {
        // If the main tag is not null, search and remove it before calling setTags($tags)
    }

    // Getters, setters
}

表单类型:

class TagSelectionType extends AbstractType
{
    protected buildForm( ... )
    {
        $builder->add('allTags', 'collection', array(
            'type' => new TagType(),
            'label' => false,
            'allow_add' => true,
            'allow_delete' => true,
            'by_reference' => false
        ));

        // Since we cannot know which tags are available before binding or setting data, a listener must be used
        $formFactory = $builder->getFormFactory();
        $listener = function(FormEvent $event) use ($formFactory) {

            $data = $event->getForm()->getData();

            // Get all tags id currently in the data
            $choices = ...;
            // Careful, in PRE_BIND this is an array of scalars while in PRE_SET_DATA it is an array of Tag instances

            $field = $this->factory->createNamed('mainTag', 'radio', null, array(
                'type' => 'choice',
                'multiple' => false,
                'expanded' => true,
                'choices' => $choices,
                'property_path' => 'mainTag.id',
            ));
            $event->getForm()->add($field);
        }

        $builder->addEventListener(FormEvent::PRE_SET_DATA, $listener);
        $builder->addEventListener(FormEvent::PRE_BIND, $listener);
    }

    public function getName()
    {
        return 'tag_selection';
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'TagSelection', // Adapt depending on class name
            // 'prototype' => true,
        ));
   }
}

最后,在表单主题模板中:

{% block tag_selection_widget %}
    {% spaceless %}
    {# {% set attr = attr|default({})|merge({'data-prototype': form_widget(prototype)}) %} #}
    <ul {{ block('widget_attributes') }}>
        {% for child in form.allTags %}
        <li>{{ form_widget(form.mainTag[child.name]) }} {{ form_widget(child) }}</li>
        {% endfor %}
    </ul>
    {% endspaceless %}
{% endblock tag_selection_widget %}

最后,我们需要在您的父实体中包含最初包含标记的那个:

class entity
{
    // Doctrine definition and whatnot
    private $tags;

    // Doctrine definition and whatnot
    private $mainTag;

    ...
    public setAllTags($tagSelection)
    {
        $this->setMainTag($tagSelection->getMainTag());
        $this->setTags($tagSelection->getTags());
    }

    public getAllTags()
    {
        $ret = new TagSelection();
        $ret->setMainTag($this->getMainTag());
        $ret->setTags($this->getTags());

        return $ret;
    }

    ...
}

以原始形式:

$builder->add('allTags', new TagSelection(), array(
    'label' => false,
));

我认识到我提出的解决方案很冗长,但在我看来它是最有效的。你想要做的事情在Symfony中无法轻易完成。

您还可以注意到评论中有一个奇怪的“原型”选项。我只想在你的情况下强调一个非常有用的“集合”属性:prototype选项包含你的集合的空白项,并替换占位符。这允许使用javascript,更多信息here快速添加收藏字段中的新项目。

答案 1 :(得分:5)

这不是正确的解决方案,但因为您使用jQuery来添加/删除...

<强> TagType

->add('main', 'radio', [
    'attr' => [
        'class' => 'toggle'
        ],
     'required' => false
    ])

<强>的jQuery

div.on('change', 'input.toggle', function() {

    div
    .find('input.toggle')
    .not(this)
    .removeAttr('checked');
});

http://jsfiddle.net/coma/CnvMk/

并使用callback constraint确保只有一个主要标记。

答案 2 :(得分:1)

你应该警惕的第一件事 - 它在你的方案中如果标签成为一个实体的 main 它将是所有实体的主要因为标签存储属性和少数实体可以用一个标签标记

这里最简单的决定是在您的实体中的代码附近创建新属性main_tag,在您的表单中创建隐藏字段main_tag(ID为Data transformer的ID)并填充并更改此使用jQuery的字段(例如,将其设置为标记点击或清除主标记删除)

答案 3 :(得分:0)

可能与multiple form option有关,但可能需要对您的收藏表单和代码实体进行一些调整。