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