【问题标题】:Dynamic Generation for Submitted Forms with Form events使用表单事件动态生成提交的表单
【发布时间】:2017-04-01 00:42:29
【问题描述】:

我对 FormEvents 有一点问题,我想要动态填充 3 个字段。 我解释一下,我有3个字段:项目>框>单元格,用户选择一个项目,框列表更新,他选择一个框,单元格列表更新。

为此,我使用 FormEvent 就像文档中所说的 (http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#cookbook-form-events-submitted-data)

但我有一个问题,只有一个字段动态更新,它可以工作,但不能用于 2 个字段...实际上用户可以选择一个项目,当他这样做时,框字段会更新。但是,当他选择一个框时,单元格字段没有更新......

但是,我找到了一些允许它工作的东西,只需在 ->add() 中更改一些内容并反转为 ->add()。但我不想要它。

我的代码是:

$builder
    ->add('project', EntityType::class, array(
        'class' => 'AppBundle\Entity\Project',
        'choice_label' => 'name',
        'placeholder' => '-- select a project --',
        'mapped' => false,
    ))
    ->add('box', EntityType::class, array(
        'class' => 'AppBundle\Entity\Box',
        'choice_label' => 'name',
        'placeholder' => '-- select a box --',
        'choices' => [],
    ))
    ->add('cell', ChoiceType::class, array(
        'placeholder' => '-- select a cell --',
    ))
;

当我将其更改为:

    builder
    ->add('box', EntityType::class, array(
        'class' => 'AppBundle\Entity\Box',
        'choice_label' => 'name',
        'placeholder' => '-- select a box --',
        //    'choices' => [],
    ))
    ->add('project', EntityType::class, array(
        'class' => 'AppBundle\Entity\Project',
        'choice_label' => 'name',
        'placeholder' => '-- select a project --',
        'mapped' => false,
    ))

    ->add('cell', ChoiceType::class, array(
        'placeholder' => '-- select a cell --',
    ))
;

它的工作......但我想要一个空的盒子列表,我想要盒子之前的项目......

稍微精确一点,这个表单作为 CollectionType 嵌入到其他表单中。

该类型的所有代码:

    <?php

namespace AppBundle\Form;

use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class TubeType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('project', EntityType::class, array(
                'class' => 'AppBundle\Entity\Project',
                'choice_label' => 'name',
                'placeholder' => '-- select a project --',
                'mapped' => false,
            ))
            ->add('box', EntityType::class, array(
                'class' => 'AppBundle\Entity\Box',
                'choice_label' => 'name',
                'placeholder' => '-- select a box --',
                'choices' => [],
            ))
            ->add('cell', ChoiceType::class, array(
                'placeholder' => '-- select a cell --',
            ))
        ;

        // MODIFIER
        $boxModifier = function (FormInterface $form, $project) {
            $boxes = (null === $project) ? [] : $project->getBoxes();

            $form->add('box', EntityType::class, array(
                'class' => 'AppBundle\Entity\Box',
                'choice_label' => 'name',
                'placeholder' => '-- select a box --',
                'choices' => $boxes,
            ));
        };

        $cellModifier = function(FormInterface $form, $box) {
            $cells = (null === $box) ? [] : $box->getEmptyCells();

            $form->add('cell', ChoiceType::class, array(
                'placeholder' => '-- select a cell --',
                'choices' => $cells,
            ));
        };

        // FORM EVENT LISTENER
        $builder->get('project')->addEventListener(
            FormEvents::POST_SUBMIT,
            function(FormEvent $event) use ($boxModifier) {
                $project = $event->getForm()->getData();

                $boxModifier($event->getForm()->getParent(), $project);
            }
        );

        $builder->get('box')->addEventListener(
            FormEvents::POST_SUBMIT,
            function(FormEvent $event) use ($cellModifier) {
                $box = $event->getForm()->getData();

                $cellModifier($event->getForm()->getParent(), $box);
            }
        );
    }

    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Tube'
        ));
    }
}

非常感谢您的帮助:)

【问题讨论】:

    标签: symfony


    【解决方案1】:

    您应该使用$builder-&gt;addEventListener。对于多个字段,您需要做的就是在 FormEvents::PRE_SET_DATA 事件处理程序中拥有动态字段。此外,使用父字段数据,如文档中所述获取子字段选择。

    我使用这种方法在分层字段中生成国家、州和城市实体。让我知道它是否有帮助或您需要更多信息。

    编辑:对于更大的逻辑,您可以使用eventSubscriber,这将使您的代码保持干净,您还可以在其他地方重用表单的动态部分。

    对于多个依赖的分层字段,只需通过eventSubscriber类中的条件添加即可。

    使用代码 sn-p 更新

    这里是在 Symfony 2.7 中为我工作的代码 sn-p 的演练

    注意:我不替换文档中描述的动态 html 字段,而是通过 jQuery 我只是根据选定的父选项收集子选项并填写它们。提交后,表单会根据eventSubscriber 上下文识别正确的子选项。所以你可以这样做:

    在您的父表单类型(您拥有所有 3 个字段)中调用 eventSubscriber 而不是定义这 3 个字段:

    $builder->add(); // all other fields..
    $builder->addEventSubscriber(new DynamicFieldsSubscriber());
    

    按照文档中的定义创建一个eventSubscriber,这里的文件名为DynamicFieldsSubscriber

    <?php
    namespace YourBundle\Form\EventListener;
    
    use Symfony\Component\Form\FormEvent;
    use Symfony\Component\Form\FormEvents;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    use Doctrine\ORM\EntityRepository;
    use Symfony\Component\Form\FormInterface;
    
    class DynamicFieldsSubscriber implements EventSubscriberInterface
    {
    
        /**
         * Define the events we need to subscribe
         * @return type
         */
        public static function getSubscribedEvents()
        {
            return array(
                FormEvents::PRE_SET_DATA => 'preSetData', // check preSetData method below
                FormEvents::PRE_SUBMIT => 'preSubmitData', // check preSubmitData method below
            );
        }
    
        /**
         * Handling form fields before form renders.
         * @param FormEvent $event
         */
        public function preSetData(FormEvent $event)
        {
            $location = $event->getData();
            // Location is the main entity which is obviously form's (data_class)
            $form = $event->getForm();
    
            $country = "";
            $state = "";
            $district = "";
    
            if ($location) {
                // collect preliminary values for 3 fields.
                $country = $location->getCountry();
                $state = $location->getState();
                $district = $location->getDistrict();
            }
            // Add country field as its static.
            $form->add('country', 'entity', array(
                'class' => 'YourBundle:Country',
                'label' => 'Select Country',
                'empty_value' => ' -- Select Country -- ',
                'required' => true,
                'query_builder' => function (EntityRepository $er) {
                    return $er->createQueryBuilder('c')
                            ->where('c.status = ?1')
                            ->setParameter(1, 1);
                }
            ));
            // Now add all child fields.
            $this->addStateField($form, $country);
            $this->addDistrictField($form, $state);
        }
    
        /**
         * Handling Form fields before form submits.
         * @param FormEvent $event
         */
        public function preSubmitData(FormEvent $event)
        {
            $form = $event->getForm();
            $data = $event->getData();
    
            // Here $data will be in array format.
    
            // Add property field if parent entity data is available.
            $country = isset($data['country']) ? $data['country'] : $data['country'];
            $state = isset($data['state']) ? $data['state'] : $data['state'];
            $district = isset($data['district']) ? $data['district'] : $data['district'];
    
            // Call methods to add child fields.
            $this->addStateField($form, $country);
            $this->addDistrictField($form, $state);
        }
    
        /**
         * Method to Add State Field. (first dynamic field.)
         * @param FormInterface $form
         * @param type $country
         */
        private function addStateField(FormInterface $form, $country = null)
        {
            $countryCode = (is_object($country)) ? $country->getCountryCode() : $country;
            // $countryCode is dynamic here, collected from the event based data flow.
            $form->add('state', 'entity', array(
                'class' => 'YourBundle:State',
                'label' => 'Select State',
                'empty_value' => ' -- Select State -- ',
                'required' => true,
                'attr' => array('class' => 'state'),
                'query_builder' => function (EntityRepository $er) use($countryCode) {
                    return $er->createQueryBuilder('u')
                            ->where('u.countryCode = :countrycode')
                            ->setParameter('countrycode', $countryCode);
                }
            ));
        }
    
        /**
         * Method to Add District Field, (second dynamic field)
         * @param FormInterface $form
         * @param type $state
         */
        private function addDistrictField(FormInterface $form, $state = null)
        {
            $stateCode = (is_object($state)) ? $state->getStatecode() : $state;
            // $stateCode is dynamic in here collected from event based data flow.
            $form->add('district', 'entity', array(
                'class' => 'YourBundle:District',
                'label' => 'Select District',
                'empty_value' => ' -- Select District -- ',
                'required' => true,
                'attr' => array('class' => 'district'),
                'query_builder' => function (EntityRepository $er) use($stateCode) {
                    return $er->createQueryBuilder('s')
                            ->where('s.stateCode = :statecode')
                            ->setParameter('statecode', $stateCode);
                }
            ));
        }
    }
    

    在此之后,您需要编写jQuery events,它应该在更改父选项时显式更新子选项,您在提交表单时不应该遇到任何错误。

    注意 :以上代码为在此处发布而提取和更改。在需要时注意namespace 和变量。

    【讨论】:

    • 谢谢,但只是一个问题,当您在 EventListener 中生成字段时,如何在此添加侦听器?因为,当我在 PRE_SET_DATA 中添加字段时,我无法执行 "$builder->get('project')->addEventListener(" 因为该字段尚不存在。:/
    • 如果我尝试使用builder-&gt;addEventListener(FormEvents::POST_SUBMIT,function (FormEvent $event) {,我在使用$event-&gt;getData();时没有数据
    • 我已经更新了我的答案,请浏览答案中的文档链接。如果您仍然需要帮助,请发表评论。我可以稍后添加一些代码sn-p。
    • 好的,我试过了,但我还有其他问题......它返回给我:你不能将孩子添加到提交的表单......如果你有一个功能示例我'好吧,因为从 2 天以来我尝试了很多东西......而且我不明白为什么它不起作用......
    • 哦,是的,谢谢,我终于明白了,当我使用 POST_SUBMIT 时,我可以拥有对象,但我不知道为什么我的对象总是空的。但是使用 PRE_SUBMIT,我可以获得用户数据。我们进行“序列化”。没关系。现在,只需找到如何在里面有一个 EntityManager,因为我需要一个对象及其方法。之后,我认为它完成了:) 非常感谢。
    猜你喜欢
    • 1970-01-01
    • 2013-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-07
    • 1970-01-01
    • 2012-01-02
    • 2010-11-03
    相关资源
    最近更新 更多