Symfony 4 форма: три динамика c выберите поле - PullRequest
0 голосов
/ 06 марта 2020

Здравствуйте, я использую Symfony 4. Мне удалось связать до двух блоков выбора с событиями формы, но мне нужно иметь три динамических c блока выбора. Это отношения между моими организациями: Страна -> Провинция -> Город. Они связаны с сущностью «Человек», например: enter image description here

Когда я добавляю нового человека, я могу выбрать страну и обновить выпадающий список провинции в соответствии с выбором страны; то же самое для выпадающего списка City после того, как я выбрал провинцию. Я сделал все для страны и провинции, следуя официальному руководству Symfony здесь https://symfony.com/doc/current/form/dynamic_form_modification.html#dynamic -генерация для отправленных форм Как мне уметь добавлять третий выпадающий список?

Это это моя страна:

<?php

namespace App\Entity\Geo;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;


/**
 * @ORM\Entity(repositoryClass="App\Repository\Geo\CountryRepository")
 */
class Country
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank()
     */
    private $name;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Geo\Province", mappedBy="country")
     * @ORM\JoinColumn(nullable=false)
     */
    private $provinces;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Geo\City", mappedBy="country")
     * @ORM\JoinColumn(nullable=false)
     */
    private $cities;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Geo\Person", mappedBy="country")
     * @ORM\JoinColumn(nullable=false)
     */
    private $persons;

    /**
     * @return mixed
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param mixed $name
     */
    public function setName($name): void
    {
        $this->name = $name;
    }

    /**
     * @return mixed
     */
    public function getProvinces()
    {
        return $this->provinces;
    }

    /**
     * @param mixed $provinces
     */
    public function setProvinces($provinces): void
    {
        $this->provinces = $provinces;
    }

    /**
     * @return mixed
     */
    public function getCities()
    {
        return $this->cities;
    }

    /**
     * @param mixed $cities
     */
    public function setCities($cities): void
    {
        $this->cities = $cities;
    }

    /**
     * @return mixed
     */
    public function getPersons()
    {
        return $this->persons;
    }

    /**
     * @param mixed $persons
     */
    public function setPersons($persons): void
    {
        $this->persons = $persons;
    }

}

Это моя провинция:

<?php

namespace App\Entity\Geo;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;


/**
 * @ORM\Entity(repositoryClass="App\Repository\Geo\ProvinceRepository")
 */
class Province
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank()
     */
    private $name;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Geo\Country", inversedBy="provinces")
     * @ORM\JoinColumn(nullable=false)
     */
    private $country;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Geo\City", mappedBy="province")
     * @ORM\JoinColumn(nullable=false)
     */
    private $cities;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Geo\Person", mappedBy="province")
     * @ORM\JoinColumn(nullable=false)
     */
    private $persons;

    public function __toString() {
        return $this->name;
    }

    /**
     * @return mixed
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param mixed $name
     */
    public function setName($name): void
    {
        $this->name = $name;
    }

    /**
     * @return mixed
     */
    public function getCountry()
    {
        return $this->country;
    }

    /**
     * @param mixed $country
     */
    public function setCountry($country): void
    {
        $this->country = $country;
    }

    /**
     * @return mixed
     */
    public function getCities()
    {
        return $this->cities;
    }

    /**
     * @param mixed $cities
     */
    public function setCities($cities): void
    {
        $this->cities = $cities;
    }

    /**
     * @return mixed
     */
    public function getPersons()
    {
        return $this->persons;
    }

    /**
     * @param mixed $persons
     */
    public function setPersons($persons): void
    {
        $this->persons = $persons;
    }


}

Это моя структура города:

<?php

namespace App\Entity\Geo;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;


/**
 * @ORM\Entity(repositoryClass="App\Repository\Geo\CityRepository")
 */
class City
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\NotBlank()
     */
    private $name;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Geo\Province", inversedBy="cities")
     * @ORM\JoinColumn(nullable=false)
     */
    private $province;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Geo\Country", inversedBy="cities")
     * @ORM\JoinColumn(nullable=false)
     */
    private $country;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Geo\Person", mappedBy="city")
     * @ORM\JoinColumn(nullable=false)
     */
    private $persons;

    /**
     * @return mixed
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param mixed $name
     */
    public function setName($name): void
    {
        $this->name = $name;
    }

    /**
     * @return mixed
     */
    public function getProvince()
    {
        return $this->province;
    }

    /**
     * @param mixed $province
     */
    public function setProvince($province): void
    {
        $this->province = $province;
    }

    /**
     * @return mixed
     */
    public function getCountry()
    {
        return $this->country;
    }

    /**
     * @param mixed $country
     */
    public function setCountry($country): void
    {
        $this->country = $country;
    }

    /**
     * @return mixed
     */
    public function getPersons()
    {
        return $this->persons;
    }

    /**
     * @param mixed $persons
     */
    public function setPersons($persons): void
    {
        $this->persons = $persons;
    }

}

Это моя форма для добавления a Person (PersonType. php)

<?php


namespace App\Form\Geo;

use App\Entity\Geo\Person;
use App\Entity\Geo\Country;
use App\Entity\Geo\Province;
use App\Entity\Geo\City;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;

class PersonType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class, ['label' => "Name"])
            ->add('country', EntityType::class, [
                'class' => Country::class,
                'choice_label' => function(Country $country) {
                    return $country->getName();
                },
                'placeholder' => 'Choose a Country'
            ])
            ;

        $formModifier = function (FormInterface $form, Country $country = null) {
            $provinces = null === $country ? [] : $country->getProvinces();

            $form->add('province', EntityType::class, [
                'class' => Province::class,
                'placeholder' => 'Choose a Province',
                'choices' => $provinces,
            ]);
        };

        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($formModifier) {
                $data = $event->getData();

                $formModifier($event->getForm(), $data->getCountry());
            }
        );

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

                $formModifier($event->getForm()->getParent(), $country);
            }
        );
        $builder->add( 'save', SubmitType::class);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' =>Person::class
        ]);
    }
}

Это мой шаблон веточки (person-add. html .twig)

{% extends 'base.html.twig' %}

{% block title %}Add Person{% endblock %}

{% block body %}

    {{ form_start(form) }}

    {{ form_row(form.name) }}
    {{ form_row(form.country) }}
    {{ form_row(form.province) }}

    {{ form_end(form) }}


    <script>
        $(document).ready(function() {
            var $country = $('#person_country');

            // When sport gets selected ...
            $country.change(function () {
                // ... retrieve the corresponding form.
                var $form = $(this).closest('form');
                // Simulate form data, but only include the selected sport value.
                var data = {};
                data[$country.attr('name')] = $country.val();
                // Submit data via AJAX to the form's action path.
                $.ajax({
                    url: $form.attr('action'),
                    type: $form.attr('method'),
                    data: data,
                    success: function (html) {
                        // Replace current position field ...
                        $('#person_province').replaceWith(
                            // ... with the returned one from the AJAX response.
                            $(html).find('#person_province')
                        );
                        // Position field now displays the appropriate positions.
                    }
                });
            })
        });
    </script>

{% endblock %}

Благодаря этой записи Мне удалось изменить свой файл формы PersonType. php, например:

<?php


namespace App\Form\Geo;

use App\Entity\Geo\Person;
use App\Entity\Geo\Country;
use App\Entity\Geo\Province;
use App\Entity\Geo\City;
use App\Repository\Geo\CityRepository;
use App\Repository\Geo\ProvinceRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;

class PersonType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class)
            //
            ->add('country', EntityType::class, [
                'class' => Country::class,
                'label' => 'Country',
                'required' => true,
                'choice_label' => function(Country $country) {
                    return $country->getName();
                },
                'invalid_message' => 'You must select a Country',
                'placeholder' => 'Select Country',
            ]);

//**************** Start Province Form
        $addProvinceForm = function (FormInterface $form, $country_id) {
            // it would be easier to use a Park entity here,
            // but it's not trivial to get it in the PRE_SUBMIT events
            $form->add('province', EntityType::class, [
                'class' => Province::class,
                'label' => 'Province',
                'required' => true,
                'invalid_message' => 'Choose a Province',
                'placeholder' => null === $country_id ? 'Choose a Country first' : 'Select Province',
                'query_builder' => function (ProvinceRepository $repository) use ($country_id) {
                    return $repository->createQueryBuilder('p')
                        ->innerJoin('p.country', 'c')
                        ->where('c.id = :country')
                        ->setParameter('country', $country_id)
                        ;
                }
            ]);
        };
        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($addProvinceForm) {
                $country = $event->getData()->getCountry();
                $country_id = $country ? $country->getId() : null;
                $addProvinceForm($event->getForm(), $country_id);
            }
        );
        $builder->addEventListener(
            FormEvents::PRE_SUBMIT,
            function (FormEvent $event) use ($addProvinceForm) {
                $data = $event->getData();
                $country_id = array_key_exists('country', $data) ? $data['country'] : null;
                $addProvinceForm($event->getForm(), $country_id);
            }
        );
//**************** End Province Form

//**************** Start City Form
        $addCityForm = function (FormInterface $form, $province_id) {
            $form->add('city', EntityType::class, [
                'class' => City::class,
                'label' => 'City',
                'required' => true,
                'invalid_message' => 'You must choose a City',
                'placeholder' => null === $province_id ? 'Choose a Province first' : 'Choose a City',
                'query_builder' => function (CityRepository $repository) use ($province_id) {
                    return $repository->createQueryBuilder('ci')
                        ->innerJoin('ci.province', 'pr')
                        ->where('pr.id = :province')
                        ->setParameter('province', $province_id)
                        ;
                }
            ]);
        };
        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($addCityForm) {
                $province = $event->getData()->getProvince();
                $province_id = $province ? $province->getId() : null;
                $addCityForm($event->getForm(), $province_id);
            }
        );
        $builder->addEventListener(
            FormEvents::PRE_SUBMIT,
            function (FormEvent $event) use ($addCityForm) {
                $data = $event->getData();
                $province_id = array_key_exists('province', $data) ? $data['province'] : null;
                $addCityForm($event->getForm(), $province_id);
            }
        );
//**************** End City Form

        $builder->add( 'save', SubmitType::class);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' =>Person::class
        ]);
    }
}

Раскрывающийся список Провинция работает, как и ожидалось, при первом выборе страны. Проблема в выпадающем списке Город: ничего не меняется после выбора провинции. Если все в порядке с запросом, выполненным внутри файла PersonType. php, я думаю, что я делаю что-то не так с javascript. Вот мой код:

    <script>
            $(document).ready(function() {
                var $country = $('#person_country');
                var $province = $('#person_province');

                // When country gets selected ...
                $country.change(function () {
                    // ... retrieve the corresponding form.
                    var $form = $(this).closest('form');
                    // Simulate form data, but only include the selected country value.
                    var data = {};
                    data[$country.attr('name')] = $country.val();
                    // Submit data via AJAX to the form's action path.
                    $.ajax({
                        url: $form.attr('action'),
                        type: $form.attr('method'),
                        data: data,
                        success: function (html) {
                            // Replace current province field ...
                            $('#person_province').replaceWith(
                                // ... with the returned one from the AJAX response.
                                $(html).find('#person_province')
                            );
                        }
                    });
                });

                // When province gets selected ...
                $province.change( function () {
                    // ... retrieve the corresponding form.
                    var $form = $(this).closest('form');
                    // Simulate form data, but only include the selected province value.
                    var data = {};
                    data[$province.attr('name')] = $province.val();
                    // Submit data via AJAX to the form's action path.
                    $.ajax({
                        url: $form.attr('action'),
                        type: $form.attr('method'),
                        data: data,
                        success: function (html) {
                            // Replace current city field ...
                            $('#person_city').replaceWith(
                                // ... with the returned one from the AJAX response.
                                $(html).find('#person_city')
                            );
                        }
                    });
                });
            });
        </script>
...