为表单元素添加事件监听器,该表单元素是由事件监听器添加的。

11

我正在构建一个Symfony应用程序,并使用表单事件与一些jquery/ajax来完成“州/市/区”的选择。但是,我遇到了一个小问题。我正在使用格式"省份-> 城市-> 郊区"。据我所知我的代码没什么问题,但当执行到添加一个城市选择的监听器的部分时,它会抛出以下错误:

名称为 "physicalCity" 的子项不存在。

显然,当我尝试向新创建的字段添加事件侦听器时,它会发生这种情况,因此是在由事件侦听器创建的元素上添加事件侦听器?下面是代码的一部分... 我做错了什么?任何帮助将不胜感激!

public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
            ->add('schoolName')
            ->add('physicalProvince', 'entity', array(
                'mapped' => false,
                'class' => 'MY\MainBundle\Entity\Province',
                'empty_value' => 'Select a province',
                'attr' => array(
                    'class' => 'province',
                    'data-show' => 'physical-city',
                )
            ));

        /*
         * For the physical cities
         */
        $physicalCityModifier = function(FormInterface $form, Province $province = null) {
            if (null !== $province)
                $cities = $province->getCities();
            else
                $cities = array();

            $form->add('physicalCity', 'entity', array(
                'mapped' => false,
                'class' => 'MY\MainBundle\Entity\City',
                'empty_value' => 'Select a province first',
                'choices' => $cities,
                'attr' => array(
                    'class' => 'city physical-city',
                    'data-show' => 'physical-suburb'
                )
            ));
        };

        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function(FormEvent $event) use ($physicalCityModifier) {
                $data = $event->getData();
                if (is_object($data->getPhysicalSuburb()))
                    $province = $data->getPhysicalSuburb()->getCity()->getProvince();
                else
                    $province = null;

                $physicalCityModifier($event->getForm(), $province);
            }
        );

        $builder->get('physicalProvince')->addEventListener(
            FormEvents::POST_SUBMIT,
            function (FormEvent $event) use ($physicalCityModifier) {
                $province = $event->getForm()->getData();
                $physicalCityModifier($event->getForm()->getParent(), $province);
            }
        );

        /*
         * For the physical suburbs
         */
        $physicalSuburbModifier = function(FormInterface $form, City $city = null) {
            if (null !== $city)
                $suburbs = $city->getSuburbs();
            else
                $suburbs = array();

            $form->add('physicalSuburb', null, array(
                'choices' => $suburbs,
                'empty_value' => 'Select a city first',
                'attr' => array(
                    'class' => 'physical-suburb'
                ),
            ));
        };

        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function(FormEvent $event) use ($physicalSuburbModifier) {
                $data = $event->getData();
                if (is_object($data->getCity()))
                    $city = $data->getCity();
                else
                    $city = null;

                $physicalSuburbModifier($event->getForm(), $city);
            }
        );

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

                $physicalSuburbModifier($event->getForm()->getParent(), $city);
            }
        );
}
2个回答

18
如果有其他人遇到类似的问题,我最终通过为每个字段创建事件订阅器并从这个网站获得帮助(为那些不懂西班牙语的人翻译一下)解决了它。
基本上,我为每个字段创建了一个新的订阅器类,包括省份,然后在每个订阅器内部创建了一个查询构建器,用前面的字段值填充其值。代码如下所示。
AddProvinceFieldSubscriber.php
class AddProvinceFieldSubscriber implements EventSubscriberInterface {
    private $factory;
    private $fieldName;
    private $type;

    public function __construct(FormFactoryInterface $factory, $fieldName) {
        $this->factory = $factory;
        $this->fieldName = $fieldName . 'Province';
        $this->type = $fieldName;
    }

    public static function getSubscribedEvents() {
        return array(
            FormEvents::PRE_SET_DATA => 'preSetData',
            FormEvents::PRE_SUBMIT => 'preSubmit',
        );
    }

    private function addProvinceForm(FormInterface $form, $province) {
        $form->add($this->factory->createNamed($this->fieldName, 'entity', $province, array(
            'class' => 'MyThing\MainBundle\Entity\Province',
            'mapped' => false,
            'empty_value' => 'Select a province',
            'query_builder' => function (EntityRepository $repository) {
                $qb = $repository->createQueryBuilder('p');
                return $qb;
            },
            'auto_initialize' => false,
            'attr' => array(
                'class' => 'province ' . $this->type .'-province',
                'data-show' => $this->type . '-city',
            )
        )));
    }

    public function preSetData(FormEvent $event) {
        $form = $event->getForm();
        $data = $event->getData();

        if (null === $data)
            return;

        $fieldName = 'get' . ucwords($this->type) . 'Suburb';
        $province = ($data->$fieldName()) ? $data->$fieldName()->getCity()->getProvince() : null;
        $this->addProvinceForm($form, $province);
    }

    public function preSubmit(FormEvent $event) {
        $form = $event->getForm();
        $data = $event->getData();

        if (null === $data)
            return;

        $province = array_key_exists($this->fieldName, $data) ? $data[$this->fieldName] : null;
        $this->addProvinceForm($form, $province);
    }
}

AddCityFieldSubscriber.php

class AddCityFieldSubscriber implements EventSubscriberInterface {
    private $factory;
    private $fieldName;
    private $provinceName;
    private $suburbName;
    private $type;

    public function __construct(FormFactoryInterface $factory, $fieldName) {
        $this->factory = $factory;
        $this->fieldName = $fieldName . 'City';
        $this->provinceName = $fieldName . 'Province';
        $this->suburbName = $fieldName . 'Suburb';
        $this->type = $fieldName;
    }

    public static function getSubscribedEvents() {
        return array(
            FormEvents::PRE_SET_DATA => 'preSetData',
            FormEvents::PRE_SUBMIT => 'preSubmit',
        );
    }

    private function addCityForm(FormInterface $form, $city, $province) {
        $form->add($this->factory->createNamed($this->fieldName, 'entity', $city, array(
            'class' => 'MyThing\MainBundle\Entity\City',
            'empty_value' => 'Select a city',
            'mapped' => false,
            'query_builder' => function (EntityRepository $repository) use ($province) {
                $qb = $repository->createQueryBuilder('c')
                                ->innerJoin('c.province', 'province');
                if ($province instanceof Province) {
                    $qb->where('c.province = :province')
                       ->setParameter('province', $province);
                } elseif (is_numeric($province)) {
                    $qb->where('province.id = :province')
                       ->setParameter('province', $province);
                } else {
                    $qb->where('province.provinceName = :province')
                       ->setParameter('province', null);
                }

                return $qb;
            },
            'auto_initialize' => false,
            'attr' => array(
                'class' => 'city ' . $this->type . '-city',
                'data-show' => $this->type . '-suburb',
            )
        )));
    }

    public function preSetData(FormEvent $event) {
        $data = $event->getData();
        $form = $event->getForm();

        if (null === $data) {
            return;
        }

        $fieldName = 'get' . ucwords($this->suburbName);
        $city = ($data->$fieldName()) ? $data->$fieldName()->getCity() : null;
        $province = ($city) ? $city->getProvince() : null;
        $this->addCityForm($form, $city, $province);
    }

    public function preSubmit(FormEvent $event) {
        $data = $event->getData();
        $form = $event->getForm();

        if (null === $data)
            return;

        $city = array_key_exists($this->fieldName, $data) ? $data[$this->fieldName] : null;
        $province = array_key_exists($this->provinceName, $data) ? $data[$this->provinceName] : null;
        $this->addCityForm($form, $city, $province);
    }
}

最后是AddSuburbFieldSubscriber.php

class AddSuburbFieldSubscriber implements EventSubscriberInterface {
    private $factory;
    private $fieldName;
    private $type;

    public function __construct(FormFactoryInterface $factory, $fieldName) {
        $this->factory = $factory;
        $this->fieldName = $fieldName . 'Suburb';
        $this->type = $fieldName;
    }

    public static function getSubscribedEvents() {
        return array(
            FormEvents::PRE_SET_DATA => 'preSetData',
            FormEvents::PRE_SUBMIT => 'preSubmit',
        );
    }

    private function addSuburbForm(FormInterface $form, $city) {
        $form->add($this->factory->createNamed($this->fieldName, 'entity', null, array(
            'class' => 'MyThing\MainBundle\Entity\Suburb',
            'empty_value' => 'Select a suburb',
            'query_builder' => function (EntityRepository $repository) use ($city) {
                $qb = $repository->createQueryBuilder('s')
                                ->innerJoin('s.city', 'city');

                if ($city instanceof City) {
                    $qb->where('s.city = :city')
                       ->setParameter('city', $city);
                } elseif (is_numeric($city)) {
                    $qb->where('city.id = :city')
                       ->setParameter('city', $city);
                } else {
                    $qb->where('city.cityName = :city')
                       ->setParameter('city', null);
                }
                    $sql = $qb->getQuery()->getSQL();

                return $qb;
            },
            'auto_initialize' => false,
            'attr' => array(
                'class' => 'suburb ' . $this->type . '-suburb',
            ),
        )));
    }

    public function preSetData(FormEvent $event) {
        $data = $event->getData();
        $form = $event->getForm();

        if (null === $data)
            return;

        $fieldName = 'get' . ucwords($this->fieldName);
        $city = ($data->$fieldName()) ? $data->$fieldName()->getCity() : null;
        $this->addSuburbForm($form, $city);
    }

    public function preSubmit(FormEvent $event) {
        $data = $event->getData();
        $form = $event->getForm();

        if (null === $data)
            return;

        $city = array_key_exists($this->type . 'City', $data) ? $data[$this->type . 'City'] : null;
        $this->addSuburbForm($form, $city);
    }
}

我需要添加一些额外的东西,但你可以理解其主要内容。

在我的表单类型中,我只需添加以下内容:

$builder
    ->addEventSubscriber(new AddProvinceFieldSubscriber($factory, 'postal'))
    ->addEventSubscriber(new AddCityFieldSubscriber($factory, 'postal'))
    ->addEventSubscriber(new AddSuburbFieldSubscriber($factory, 'postal'))
//...

祝大家开心!希望这能帮助到某些人。

另外,我添加了data-show属性来简化我的AJAX过程,以防有人想知道。


这在您编辑此内容时是否有效?对我来说没有。另外,您可以回复一下您使用的Symfony版本吗? - Jeet
是的,它对我起作用了。我相信这是Sf 2.4,但记不清了。尝试并玩弄查询构建器决策案例。也许你的情况有些奇怪。还要安装XDebug!它会帮助很多。 - iLikeBreakfast
2
非常感谢您提供的示例。它完美地解决了我表单中的一个问题,即多个字段以链接/步进方式动态确定下一个选择。 (官方Symfony示例仅支持1级深度,没有链接/步进。) - 完成后我会发布URL以展示它的示例。 - Adambean

0

对于那些喜欢简单的人,您可以通过将逻辑放入控制器来将任何领域与另一个相关联。

因此,您可以使用ajax检查特定选择的任何更改,并将结果发送到控制器中的方法,该方法将返回从数据库获取的结果。

然后,您的依赖选择框将包含与父选择相关的特定选项。


4
代码示例将为你的答案增加更多价值。 - Mikhail Chibel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接