【问题标题】:Extending EntityType to allow extra choices set with AJAX calls扩展 EntityType 以允许使用 AJAX 调用设置额外的选择
【发布时间】:2015-07-22 14:12:50
【问题描述】:

我尝试创建一个 Symfony 自定义类型扩展核心“entity”类型。

但我想将它与 Select2 版本 4.0.0 一起使用(ajax 现在可以与“select”html 元素一起使用,而不是像以前那样与隐藏的“input”一起使用)。

  • 此类型应创建一个空选择,而不是扩展“实体”类型的完整实体列表。

这可以通过设置选项来实现(参见 configureOption):

'choices'=>array()
  • 通过编辑附加到表单的对象,它应该使用对象的当前数据填充选择。我解决了这个问题,但只是针对使用以下 buildView 方法的视图...

Select2 识别 html "select" 的内容,并使用 ajax 进行工作。 但是当表单被发回时,Symfony 无法识别所选的选项,(因为不允许?)

Symfony\Component\Form\Exception\TransformationFailedException

    Unable to reverse value for property path "user": The choice "28" does not exist or is not unique

我尝试了几种使用 EventListeners 或 Subscribers 的方法,但找不到有效的配置。

使用 Select2 3.5.* 我解决了表单事件的问题并覆盖了隐藏的表单类型,但是在这里扩展实体类型要困难得多。

如何构建我的类型以让它管理我的实体的反向转换?

自定义类型:

<?php
namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

use Symfony\Component\Form\ChoiceList\View\ChoiceView;

class AjaxEntityType extends AbstractType
{
    protected $router;

    public function __construct($router)
    {
        $this->router = $router;
    }

   /**
    * {@inheritdoc}
    */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {   
        $builder->setAttribute('attr',array_merge($options['attr'],array('class'=>'select2','data-ajax--url'=>$this->router->generate($options['route']))));
    }

    /**
    * {@inheritdoc}
    */
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        $view->vars['attr'] = $form->getConfig()->getAttribute('attr');
        $choices = array();
        $data=$form->getData();
        if($data instanceOf \Doctrine\ORM\PersistentCollection){$data = $data->toArray();}
        $values='';
        if($data != null){
            if(is_array($data)){
                foreach($data as $entity){
                    $choices[] = new ChoiceView($entity->getAjaxName(),$entity->getId(),$entity,array('selected'=>true));
                }
            }
            else{
                $choices[] = new ChoiceView($data->getAjaxName(),$data->getId(),$data,array('selected'=>true));
            }
        }

        $view->vars['choices']=$choices;
    }

   /**
    * {@inheritdoc}
    */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setRequired(array('route'));
        $resolver->setDefaults(array('choices'=>array(),'choices_as_value'=>true));
    }

    public function getParent() {
        return 'entity';
    }

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

父表单

<?php
namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class AlarmsType extends AbstractType
{
   /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name','text',array('required'=>false))
            ->add('user','ajax_entity',array("class"=>"AppBundle:Users","route"=>"ajax_users"))
            ->add('submit','submit');
    }

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

    /**
     * @return string
     */
    public function getName()
    {
        return 'alarms';
    }
}

【问题讨论】:

    标签: symfony jquery-select2


    【解决方案1】:

    问题解决了。

    解决方案是在 PRE_SET_DATA 和 PRE_SUBMIT FormEvents 中使用 'choices'=>$selectedChoices 重新创建表单字段。

    可以使用 $event->getData() 从事件中检索选定的选项

    看看我创建的包,它实现了这个方法:

    Alsatian/FormBundle - ExtensibleSubscriber

    【讨论】:

      【解决方案2】:

      这是我的工作代码,它向用户 (EntityType) 添加了与标签 (TagType) 相关的功能,以填充来自 AJAX 调用 (jQuery Select2) 的选项。

      class TagType extends AbstractType
      {
          //...
          public function buildForm(FormBuilderInterface $builder, array $options)
          {
              $modifyForm = function ($form, $users) {
                  $form->add('users', EntityType::class, [
                      'class' => User::class,
                      'multiple' => true,
                      'expanded' => false,
                      'choices' => $users,
                  ]);
              };
              $builder->addEventListener(
                  FormEvents::PRE_SET_DATA,
                  function (FormEvent $event) use ($modifyForm) {
                      $modifyForm($event->getForm(), $event->getData()->getUsers());
                  }
              );
              $userRepo = $this->userRepo; // constructor injection
              $builder->addEventListener(
                  FormEvents::PRE_SUBMIT,
                  function (FormEvent $event) use ($modifyForm, $userRepo) {
                      $userIds = $event->getData()['users'] ?? null;
                      $users = $userIds ? $userRepo->createQueryBuilder('user')
                          ->where('user.id IN (:userIds)')->setParameter('userIds', $userIds)
                          ->getQuery()->getResult() : [];
                      $modifyForm($event->getForm(), $users);
                  }
              );
          }
          //...
      }
      

      【讨论】:

      • 你我的朋友是个天才,我花了好几个小时;几个小时的工作,你的解决方案很有效。干得好!
      【解决方案3】:

      这是我基于您的捆绑包的方法,仅适用于一种表单类型中的实体类型。 用法是

      MyType extends ExtensibleEntityType
      

      (不要忘记构建表单和配置选项的父调用)

      以及班级本身

      abstract class ExtensibleEntityType extends AbstractType
      {
          /**
           * @var EntityManagerInterface
           */
          private EntityManagerInterface $entityManager;
      
          /**
           * ExtensibleEntityType constructor.
           * @param EntityManagerInterface $entityManager
           */
          public function __construct(EntityManagerInterface $entityManager)
          {
              $this->entityManager = $entityManager;
          }
      
          public function getParent()
          {
              return EntityType::class;
          }
      
          /**
           * @param FormBuilderInterface $builder
           * @param array $options
           */
          public function buildForm(FormBuilderInterface $builder, array $options)
          {
              parent::buildForm($builder, $options);
              $builder->addEventListener(FormEvents::PRE_SET_DATA, [$this, 'preSetData']);
              $builder->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'preSubmit'], 50);
          }
      
          /**
           * @param FormEvent $event
           */
          public function preSetData(FormEvent $event)
          {
              $form = $event->getForm();
              $parent = $event->getForm()->getParent();
              $options = $form->getConfig()->getOptions();
              if (!$options['pre_set_called']) {
                  $options['pre_set_called'] = true;
                  $options['choices'] = $this->getChoices($options, $event->getData());
                  $parent->add($form->getName(), get_class($this), $options);
              }
          }
      
          /**
           * @param FormEvent $event
           */
          public function preSubmit(FormEvent $event)
          {
              $form = $event->getForm();
              $parent = $event->getForm()->getParent();
              $options = $form->getConfig()->getOptions();
              if (!$options['pre_submit_called']) {
                  $options['pre_submit_called'] = true;
                  $options['choices'] = $this->getChoices($options, $event->getData());
                  $parent->add($form->getName(), get_class($this), $options);
                  $newForm = $parent->get($form->getName());
                  $newForm->submit($event->getData());
              }
          }
      
          public function configureOptions(OptionsResolver $resolver)
          {
              parent::configureOptions($resolver);
              $resolver->setDefaults([
                  'multiple' => true,
                  'expanded' => true,
                  'choices' => [],
                  'required' => false,
                  'pre_set_called' => false,
                  'pre_submit_called' => false,
                  'validation_groups' => false,
              ]);
          }
      
          /**
           * @param array $options
           * @param $data
           * @return mixed
           */
          public function getChoices(array $options, $data)
          {
              if ($data instanceof PersistentCollection) {
                  return $data->toArray();
              }
              return $this->entityManager->getRepository($options['class'])->findBy(['id' => $data]);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-03-10
        • 2014-06-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-16
        • 1970-01-01
        • 2011-06-03
        相关资源
        最近更新 更多