Symfony — рендеринг пользовательского поля form_widget

Короче говоря, после долгих исследований по этому поводу и нахождения некоторой информации о том, как расширить существующие типы полей, или наследовать от них, или изменить некоторые вещи в бэкэнде, но абсолютно ничего для фактического рендеринга во внешнем интерфейсе, я пришел сюда, чтобы задать вопрос.

Краткое объяснение «проблемы» под рукой: Мне нужно поле EntityType (ChoiceType — HTML Select), чтобы использовать мою собственную логику фильтрации и динамически извлекать результаты из вызова ajax, мгновенно заменяя параметры, перечисленные в раскрывающемся списке.

Текущий код (работает): в FormType.php

//in buildForm
{
    $builder->add('trainer', EntityType::class, [
            'class' => Trainer::class,
            'choices' => $training->trainer_list ?? [],
            'label' => 'seminar.trainer.form.trainer.label',
            'placeholder' => 'form.trainer.placeholder',
            'required' => false,
            'attr' => ['class' => 'trainer2select'] // has no outcome whatsoever?!
        ])
    $builder->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'onPreSubmit']);
}

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

    $trainer = $this->em->getRepository(Trainer::class)->find($data['trainer']);

    $form->add('trainer', EntityType::class, [
        'class' => Trainer::class,
        'data' => $trainer,
        'label' => 'seminar.trainer.form.trainer.label',
        'placeholder' => 'form.trainer.placeholder',
        'required' => false,
    ]);
}

И в ветке:

{% if field == 'trainer' %}
     {{ form_row(attribute(form, field), {'id': 'trainer'}) }}
{% else %}
     {{ form_row(attribute(form, field)) }}
{% endif %}

{% block javascripts %}
<script>
    var lastTime;
    var timeoutEvents = [];
    $(document).ready(() => {
        function trainer_changed() {
            let input = event.target;
            lastTime = Date.now();

            timeoutEvents.push(setTimeout(() => {
                if (Date.now() - lastTime < 150)
                    return;
                jQuery.ajax({
                    url: '{{ path('trainer_select_ajax') }}',
                    type: 'GET',
                    data: {
                        search: input.value,
                        start: {{ seminar.event.start.date | date('Y-m-d') }},
                        end: {{ seminar.event.end.date | date('Y-m-d') }}
                    },
                    success: function (trainers) {
                        let trainer = $('#trainer');
                        trainer.get(0).options.length = 1;  // reset all options, except for the default
                        trainers.forEach(tr => {
                            trainer.append(new Option(tr.text, tr.id));
                        });
                        let search = $($("input.select2-search__field").get(1));
                        if (search.get(0)) {
                            search.get(0).oninput = null;  // detach our event handler so we don't loop
                            search.trigger('input');  // rebuild the dropdown choices
                            search.get(0).oninput = trainer_changed;  // reattach our event handler
                        }
                    }
                });
                lastTime = Date.now();
                timeoutEvents.forEach(e => {
                    clearTimeout(e);
                });
            }, 200));
        }
        function select_opened() {
            let trainerinput = $('input.select2-search__field').get(1);
            if (trainerinput) {
                trainerinput.oninput = trainer_changed;
            }
        }
        $('#select2-trainer-container').click(select_opened);
    });
</script>
{% endblock %}

Итак, видимо, поле EntityType отображается с использованием расширения select2. Очевидно, я могу заменить функциональность на javascript, но я хотел бы просто определить свой собственный «AjaxEntityType», который form_widget отображает так, как я этого хочу. Что-то, что я могу использовать в нескольких проектах, не используя некоторые глупые хаки, такие как предоставление имени класса по умолчанию и вызов javascript, изменяющий этот рендеринг после глобальной загрузки страницы. Итак... как дела?

Ресурсы, которые я проверил, оказались в основном бесполезными для того, чего я хочу достичь: https://symfony.com/doc/current/form/form_customization.html, https://symfony.com/doc/current/form/form_themes.html и многие другие.

Изменить для уточнения: оптимально то, что я ищу, - это минималистичный пример пользовательского FieldType, который всегда отображается как <select id = "FieldTypeWidget">, на котором всегда будет вызываться $('#FieldTypeWidget').select2({ajax: {foo}, searchFunction: {bar}});

на https://github.com/tetrans/select2entity-комплект я могу найти пример того, как предоставить эту функциональность в пакете, но есть ли более простой способ только в моем приложении?

будет ли это полезно: symfony.com/doc/current/form/dynamic_form_modification.html ?

Markownikow 12.03.2019 21:26

как вы видите в моем коде, я уже использую события формы, чтобы получить правильную сущность в форме в бэкэнде. Что я хочу, так это определить пользовательский EntityType, который всегда, где бы он ни использовался, будет отображаться так, как я его определяю (где и как?) - до сих пор я даже не мог найти, как symfony form_widget решает отображать свои собственные типы

mhpcc 12.03.2019 21:38
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
2
998
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я бы сказал, что то, на что вы смотрите, объясняется в эта страница документации Symfony.

Вот их пример, немного измененный для ваших нужд:

src/Форма/Тип/AjaxEntityType.php

<?php

namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EntityType;

class AjaxEntityType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            /**
             * This is the default of your field, 
             * add or remove based on your needs, 
             * your goal, is to only keep sensible defaults 
             * that you want on every single objects of this class
             */
            'required' => false,
        ]);
    }

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

Вот где происходит «волшебство»:
Когда ваш класс называется Какой бы ни был тип имени, Symfony просто удалит из него часть Тип и нормализует его (упрощенно, lcfirst).
Так что Какой бы ни был тип имени закончится как любоеимя.
Затем вам просто нужно знать, что элементы формы вызываются при рендеринге form_widget, чтобы в конечном итоге получить блок с правильным именем: что угодноName_widget

шаблоны/форма/fields.html.twig

{% use 'form_div_layout.html.twig' %}

{% block ajaxEntity_widget %}
    {{ parent() }}
    <script>
        $('#{{ form.vars.id }}').select2({ajax: {foo}, searchFunction: {bar}});
    </script>
{% endblock %}

Также обратите внимание на полезный совет со страницы документации:

You can further customize the template used to render each children of the choice type. The block to override in that case is named "block name" + entry + "element name" (label, errors or widget) (e.g. to customize the labels of the children of the Shipping widget you'd need to define {% block shipping_entry_label %} ... {% endblock %}).

И также помните, как отмечено позже на той же странице, что переопределение шаблона формы должно быть правильно зарегистрировано:

конфигурация/пакеты/twig.yaml

twig:
    form_themes:
        - 'form/fields.html.twig' 
        # you might have this configuration already, 
        # for example, if you use bootstrap theming. 
        # If so, just copy the configured template path stated here 
        # in the 'use' statement of the file form/fields.html.twig

Тогда просто используйте его:

$builder->add('trainer', AjaxEntityType::class, [ class => Trainer::class, ]);

Стоит также прочитать:

блин, я почему-то пропустил эту часть при просмотре документации, потому что веточка не казалась правильным местом для поиска. большое спасибо!

mhpcc 13.03.2019 00:28

Другие вопросы по теме