【问题标题】:Symfony2 : Radio buttons in a collectionSymfony2:集合中的单选按钮
【发布时间】:2014-01-26 21:37:21
【问题描述】:

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

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

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

在我的标签实体中,我添加了一个布尔属性,用于定义标签是否是主要标签:

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

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

【问题讨论】:

  • 我投了昏迷答案,但我认为@forgottenbas 的建议非常重要。您是否意识到,在您的方案中,如果标签成为一个实体的主要标签,那么它将成为所有实体的主要标签,因为标签存储属性和许多实体都可以用一个标签进行标记?如果它不是您想要的,则逗号答案仍然有效,但您不应将该字段映射到 Tag 实体,只需获取 $form->getData() 并在所有者的 $main 属性中检查 main 并设置标签。 (我猜是任务;)?)
  • 如果标签只能用于一项任务,那么您应该添加另一个实体来保存主要属性并链接其他实体。无论如何,我的简单解决方案都是有效的。

标签: symfony symfony-forms


【解决方案1】:

也许与multiple form option 有关,但可能需要对您的集合表单和标签实体进行一些调整。

【讨论】:

    【解决方案2】:

    您应该注意的第一件事 - 在您的方案中,如果标签成为一个实体的 main 它将成为所有实体的主要标签,因为标签存储属性和少数实体可以用一个标签进行标记.

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

    【讨论】:

      【解决方案3】:

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

      标签类型

      ->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 确保只有一个主标签。

      【讨论】:

        【解决方案4】:

        您没有从正确的角度解决问题。如果应该有一个主标签,那么这个属性不应该加在Tag实体本身,而应该加在包含它的实体中!

        我说的是与具有 tags 属性的表单相关的 data_class 实体。这是应该具有 ma​​inTag 属性的实体。

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

        所以,在我看来,您应该有一个包含您的实例的 ma​​inTag 属性和一个包含所有其他标签的 tags 属性。

        这样做的问题是您的收藏字段将不再包含主标签。因此,您还应该创建一个特殊的 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 中是不容易做到的。

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

        【讨论】:

          猜你喜欢
          • 2017-05-02
          • 2016-11-19
          • 1970-01-01
          • 2013-03-01
          • 2013-12-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多