使用表单事件动态生成已提交的表单

时间:2016-07-26 06:01:21

标签: symfony

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

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

但是我有一个问题,因为只有一个字段动态更新,它是有效的,但是对于2个字段没有...实际上用户可以选择一个项目,当他这样做时,框字段会更新。但是,当他选择一个盒子时,细胞区域没有更新......

但是,我找到了一些允许它工作的东西,只需改变 - > gt; 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'
        ));
    }
}

非常感谢你的帮助:)

1 个答案:

答案 0 :(得分:0)

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

我使用这种方法,用于在分层字段中生成国家,州和城市实体。如果有帮助或者您需要更多信息,请告诉我。

编辑:对于更大的逻辑,您可以使用eventSubscriber来保持代码清洁,并且还可以在其他地方重复使用表单的动态部分。

对于多个依赖的层次结构字段,只需通过eventSubscriber类中的条件添加它们。

使用代码段更新

以下是在Symfony 2.7中为我工作的代码片段的演练

注意:我没有替换文档中描述的动态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和变量。