动态ChoiceType(select2 + AJAX) [英] Dynamic ChoiceType (select2 + AJAX)

查看:99
本文介绍了动态ChoiceType(select2 + AJAX)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I need a form field to choose from thousands of entities, so a dynamic choice system like select2 (with AJAX) is perfectly suited.

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

My AJAX endpoint works fine, but the custom form type does not work:

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调试工具栏,将接收到该值:

When using this form type, it seems to work, but finally no entity object is returned, but null. According to symfony debug toolbar however, the value is received:

另外,转储指示视图和模型转换器被称为:

Also the dumps indicate that the view and model transformers were called:

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

For the sake of completeness (I hope we'll find a perfect solution and help others), here is my js code (it works):

$('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;
        }
    }
});

推荐答案

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

I have solved the problem, here is my complete solution:

$('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

The new form type is fixed for one class DataCategory, and works both for single and multiple select's. I have build-in a distinction between select2 frontend and the standard EntityType (mainly for testing reasons, because the new select2 based approach does not allow PHPUnit tests that use symfony's Client (WebTestCase)): If there are less than 50 DataCategory entities in the DB, the field falls back to 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'选项非常重要,否则,不能正确设置options选项:

It is very important to set the 'data' option when using this new type, otherwise the choices option is not correctly set:

$builder->add('summands', Select2AjaxDataCategoryType::class,[
        'currentDataCategory' => $mdc,
        'data' => $mdc->summands->toArray(),
        'multiple' => true,
        'required' => false,
        'label' => 'Summierte Kategorien',
    ]);

这篇关于动态ChoiceType(select2 + AJAX)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆