动态选择类型(select2 + AJAX)

问题描述 投票:0回答:1

我需要一个表单字段来从数千个实体中进行选择,因此像 select2(使用 AJAX)这样的动态选择系统非常适合。

我的 AJAX 端点工作正常,但自定义表单类型不起作用:

class Select2AjaxDataCategoryType extends AbstractType
{
    /**
     * @var EntityManagerInterface
     */
    private $entityManager;
    /**
     * @var RouterInterface
     */
    private $router;

    public function __construct(EntityManagerInterface $entityManager,
                                RouterInterface $router)
    {
        $this->entityManager = $entityManager;
        $this->router = $router;
    }

    public function getParent()
    {
        return ChoiceType::class;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->resetModelTransformers();
        $builder->resetViewTransformers();
        $builder->addModelTransformer(new CallbackTransformer(
            function (?DataCategory $dc) {
                dump('model transform is called ' . ($dc ? $dc->getId()->toString() : 'null'));
                return $dc ? $dc->getId()->toString() : '';
            },
            function ($id) : ?DataCategory{
                dump('model reversetransform is called ' . $id);
                $dc = $this->entityManager->getRepository(DataCategory::class)->find($id);
                if($dc === null)
                    throw new TransformationFailedException("Konnte keine Datenkategorie mit ID $id finden");
                return $dc;
            }
        ));

        $builder->addViewTransformer(new CallbackTransformer( // Identity !!!
            function ($dc) {
                dump('view transform is called ' . $dc);
                return $dc;
            },
            function ( $id) {
                dump('view reversetransform is called ' . $id);
                return $id;
            }
        ));
        $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) { // makes validation pass
            $data = $event->getData();

            dump($data); // select2'd id, correct 
            dump($event->getForm()->getName()); // name of my form field

            $event->getForm()->getParent()->add( // so this is lik "overwriting"? Documented nowhere :-/
                $event->getForm()->getName(),
                ChoiceType::class,
                ['choices' => [$data => $data]]);
            $event->getForm()->getParent()->get($event->getForm()->getName())->setData($data);
        });

    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setRequired('currentDataCategory');
        $resolver->setAllowedTypes('currentDataCategory', [DataCategory::class]);
        $resolver->setDefaults([
            'attr' => [
                'data-ajax' => '1',
                'data-ajax-endpoint' => $this->router->generate('data-category-manage-select2')
            ]
        ]);
    }
}

使用这个表单类型的时候,似乎可以工作,但是最终没有返回实体对象,而是

null
。然而,根据 symfony 调试工具栏,收到了该值:

转储还表明视图和模型转换器被调用:

为了完整起见(我希望我们能找到一个完美的解决方案并帮助其他人),这是我的js代码(它有效):

$('select[data-ajax=1]').select2({
    theme: "bootstrap4",
    placeholder: "Bitte wählen",
    ajax: {
        url: function() { return $(this).data('ajax-endpoint');},
        dataType: 'json',
        data: function (params) {
            var query = {
                search: params.term,
                page: params.page || 0
            }

            // Query parameters will be ?search=[term]&page=[page]
            return query;
        }
    }
});
php symfony jquery-select2 symfony-forms jquery-select2-4
1个回答
0
投票

我已经解决了问题,这是我的完整解决方案:

$('select[data-ajax=1]').select2({
        theme: "bootstrap4",
        placeholder: "Bitte wählen",
        ajax: {
            url: function() { return $(this).data('ajax-endpoint');},
            dataType: 'json',
            data: function (params) {
                var query = {
                    search: params.term,
                    page: params.page || 0
                }

                // Query parameters will be ?search=[term]&page=[page]
                return query;
            }
        }
    });

新的表单类型固定为一个类

DataCategory
,并且适用于单选和多选。 我在 select2 前端和标准
EntityType
之间建立了区别(主要是出于测试原因,因为新的基于 select2 的方法不允许使用 symfony 的
Client
(WebTestCase) 的 PHPUnit 测试):如果少于 50 个DB 中的 DataCategory 实体,该字段回退到
EntityType

class Select2AjaxDataCategoryType extends AbstractType
{
    /**
     * @var EntityManagerInterface
     */
    private $entityManager;
    /**
     * @var RouterInterface
     */
    private $router;

    private $transformCallback;
    public function __construct(EntityManagerInterface $entityManager,
                                RouterInterface $router)
    {
        $this->entityManager = $entityManager;
        $this->router = $router;
        $this->transformCallback = function ($stringOrDc) {
            if (is_string($stringOrDc)) return $stringOrDc;
            else                       return $stringOrDc->getId()->toString();
        };
    }

    public function getParent()
    {
        if($this->entityManager->getRepository(DataCategory::class)->count([]) > 50)
            return ChoiceType::class;
        else
            return EntityType::class;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if($this->entityManager->getRepository(DataCategory::class)->count([]) > 50) {

            $builder->addModelTransformer(new CallbackTransformer(
                function ($dc) {
                    /** @var $dc DataCategory|DataCategory[]|string|string[] */
                    /** @return string|string[] */
                    dump('model transform', $dc);
                    if($dc === null) return '';

                    if(is_array($dc)) {
                        return array_map($this->transformCallback, $dc);
                    } else if($dc instanceof Collection) {
                        return $dc->map($this->transformCallback);
                    } else {
                        return ($this->transformCallback)($dc);
                    }
                },
                function ($id) {
                    dump('model reversetransform', $id);
                    if (is_string($id)) {
                        $dc = $this->entityManager->getRepository(DataCategory::class)->find($id);
                        if ($dc === null)
                            throw new TransformationFailedException("Konnte keine Datenkategorie mit ID $id finden");
                        dump($dc);
                        return $dc;
                    } else {
                        $ret = [];
                        foreach($id as $i){
                            $dc = $this->entityManager->getRepository(DataCategory::class)->find($i);
                            if ($dc === null)
                                throw new TransformationFailedException("Konnte keine Datenkategorie mit ID $id finden");
                            $ret[] = $dc;
                        }
                        return $ret;
                    }
                }
            ));
            $builder->resetViewTransformers();

            $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
                $dataId = $event->getData();
                dump('presubmit', $dataId, $event->getForm()->getConfig()->getOptions()['choices']);
                if(empty($dataId))
                    return;

                $name = $event->getForm()->getName();

                if (is_array($dataId)) { // multiple-true-case
                    if (!empty(array_diff($dataId, $event->getForm()->getConfig()->getOptions()['choices']))) {
                        $options = $event->getForm()->getParent()->get($name)->getConfig()->getOptions();
                        $options['choices'] = array_combine($dataId, $dataId);
                        $event->getForm()->getParent()->add($name, Select2AjaxDataCategoryType::class, $options);
                        $event->getForm()->getParent()->get($name)->submit($dataId);
                        $event->stopPropagation();
                    }
                } else { // multiple-false-case
                    if($dataId instanceof DataCategory){
                        $dataId = $dataId->getId()->toString();
                        throw new \Exception('Hätte ich nicht erwartet, sollte string sein');
                    }
                    if (!in_array($dataId, $event->getForm()->getConfig()->getOptions()['choices'])) {
                        $options = $event->getForm()->getParent()->get($name)->getConfig()->getOptions();
                        $options['choices'] = [$dataId => $dataId];
                        $event->getForm()->getParent()->add($name, Select2AjaxDataCategoryType::class, $options);
                        $event->getForm()->getParent()->get($name)->submit($dataId);
                        $event->stopPropagation();
                    }
                }
            });
//            $builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
//                dump("pre set data", $event->getData());
//            });
        } else {

        }
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        if($this->entityManager->getRepository(DataCategory::class)->count([]) > 50) {
            $resolver->setDefaults([
                'attr' => [
                    'data-ajax' => '1',
                    'data-ajax-endpoint' => $this->router->generate('data-category-manage-select2')
                ],
                'choices' => function (Options $options) {
                    $data = $options['data'];
                    dump('data', $data);
                    if($data !== null) {
                        if(is_array($data) || $data instanceof Collection){
                            $ret = [];
                            foreach ($data as $d) {
                                $ret[$d->description . ' (' . $d->name . ')'] = $d->getId()->toString();
                            }
                            dump($ret);
                            return $ret;
                        } else if ($data instanceof DataCategory){
                            return [$data->description . ' (' . $data->name . ')' => $data->getId()->toString()];
                        } else {
                            throw new \InvalidArgumentException("Argument unerwartet.");
                        }
                    } else {
                        return [];
                    }
                }
            ]);
        } else {
            $resolver->setDefaults([
                'class' => DataCategory::class,
                'choice_label' => function ($cat, $key, $index) { return DataCategory::choiceLabel($cat);},
                'choices' => function (Options $options) {

                    return $this->entityManager->getRepository(DataCategory::class)->getValidChildCategoryChoices($options['currentDataCategory']);
                }
            ]);
        }
    }
}

使用这个新类型时设置

'data'
选项非常重要,否则choices选项设置不正确:

$builder->add('summands', Select2AjaxDataCategoryType::class,[
        'currentDataCategory' => $mdc,
        'data' => $mdc->summands->toArray(),
        'multiple' => true,
        'required' => false,
        'label' => 'Summierte Kategorien',
    ]);
© www.soinside.com 2019 - 2024. All rights reserved.