I have 5 tables: 1.event - id, name, location 2.location - id, country_id, county_id, city_id 3.country - id, name 4.county - id, name, country_id, 5. city - id, name, county_id
I can get to work for populate city select box
I have 2 form types EventLocationType and LocationType
Thank you in advance!
I have try to make city box to work but i don;t know how to do it! Thanks!
class EventLocationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name')
->add('location', LocationType::class, [
'label' => false,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => EventLocation::class,
]);
}
}
class LocationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('country', CountryTypeSelect::class, [
'label' => false,
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
])
->add('county', ChoiceType::class, [
'label' => false,
'placeholder' => 'Choose an option',
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
])
->add('city', ChoiceType::class, [
'label' => false,
'attr' => [ 'class' => 'form-control'],
'placeholder' => 'Choose an option',
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
]);
$formModifier = function (FormInterface $form, Country $country = null) {
$counties_array = [];
if($country != null) {
$g = new GeoNamesClient('djmichael');
[$countryGeoNames] = $g->countryInfo([
'country' => $country->getName(),
]);
$country_name = $countryGeoNames->geonameId;
$counties_json = $g->children(['geonameId' => $country_name]);
foreach($counties_json as $counties_j) {
//dd($counties_j->toponymName);
$counties_array[$counties_j->toponymName] = $counties_j->geonameId;
}
//dd($counties);
}
//var_dump($counties);
$counties = null === $counties_array ? [] : $counties_array;
$form->add('county', ChoiceType::class, [
'placeholder' => 'Choose an option',
'required' => false,
'attr' => [
'class' => 'form-control'
],
'choices' => $counties,
//'mapped' => false,
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. SportMeetup
$data = $event->getData();
//
$country = null;
if($data != null) {
$country = $data->getCountry();
//dd($data);
}
$formModifier($event->getForm(), $country);
}
);
$builder->get('country')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$country = $event->getForm()->getData();
if($country->getName() != null) {
$formModifier($event->getForm()->getParent(), $country);
} else {
$formModifier2($event->getForm(), $county);
}
//dd($country);
}
);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Location::class,
]);
}
}
var $country = $('#event_location_location_country_name');
var $token = $('#event_location__token');
var $county = $('#event_location_location_county');
// 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();
data[$token.attr('name')] = $token.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: data,
complete: function (html) {
//console.log(html.responseText);
// Replace current state field ...
$('#event_location_location_county').replaceWith(
// ... with the returned one from the AJAX response.
$(html.responseText).find('#event_location_location_county')
);
},
});
});
$county.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')] = $county.val();
data[$token.attr('name')] = $token.val();
console.log(data);
// Submit data via AJAX to the form's action path.
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: data,
complete: function (html) {
//console.log(html.responseText);
// Replace current state field ...
$('#event_location_city').replaceWith(
// ... with the returned one from the AJAX response.
$(html.responseText).find('#event_location_city')
);
},
});
});
#[ORM\Entity(repositoryClass: EventLocationRepository::class)]
class EventLocation
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 128, nullable: true)]
private ?string $name = null;
#[ORM\ManyToOne(inversedBy: 'eventLocations', cascade: ['persist', 'remove'])]
#[Assert\NotBlank]
#[ORM\JoinColumn(nullable: false)]
private ?Location $location = null;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
public function getLocation(): ?Location
{
return $this->location;
}
public function setLocation(?Location $location): self
{
$this->location = $location;
return $this;
}
}
#[ORM\Entity(repositoryClass: LocationRepository::class)]
class Location
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'locations', cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: false)]
#[Assert\NotBlank]
private ?Country $country = null;
#[ORM\ManyToOne(inversedBy: 'locations', cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: false)]
#[Assert\NotBlank]
private ?County $county = null;
#[ORM\ManyToOne(inversedBy: 'locations')]
#[ORM\JoinColumn(nullable: false)]
#[Assert\NotBlank]
private ?City $city = null;
#[ORM\OneToMany(mappedBy: 'location', targetEntity: EventLocation::class)]
private Collection $eventLocations;
public function __construct()
{
$this->eventLocations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getCountry(): ?Country
{
return $this->country;
}
public function setCountry(?Country $country): self
{
$this->country = $country;
return $this;
}
public function getCounty(): ?County
{
return $this->county;
}
public function setCounty(?County $county): self
{
$this->county = $county;
return $this;
}
public function getCity(): ?City
{
return $this->city;
}
public function setCity(?City $city): self
{
$this->city = $city;
return $this;
}
/**
* @return Collection<int, EventLocation>
*/
public function getEventLocations(): Collection
{
return $this->eventLocations;
}
public function addEventLocation(EventLocation $eventLocation): self
{
if (!$this->eventLocations->contains($eventLocation)) {
$this->eventLocations->add($eventLocation);
$eventLocation->setLocation($this);
}
return $this;
}
public function removeEventLocation(EventLocation $eventLocation): self
{
if ($this->eventLocations->removeElement($eventLocation)) {
// set the owning side to null (unless already changed)
if ($eventLocation->getLocation() === $this) {
$eventLocation->setLocation(null);
}
}
return $this;
}
}
#[ORM\Entity(repositoryClass: CountryRepository::class)]
class Country
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 128, nullable: true)]
private ?string $name = null;
#[ORM\OneToMany(mappedBy: 'country', targetEntity: County::class)]
private Collection $counties;
#[ORM\OneToMany(mappedBy: 'country', targetEntity: Location::class)]
private Collection $locations;
public function __construct()
{
$this->counties = new ArrayCollection();
$this->locations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
/**
* @return Collection<int, County>
*/
public function getCounties(): Collection
{
return $this->counties;
}
public function addCounty(County $county): self
{
if (!$this->counties->contains($county)) {
$this->counties->add($county);
$county->setCountry($this);
}
return $this;
}
public function removeCounty(County $county): self
{
if ($this->counties->removeElement($county)) {
// set the owning side to null (unless already changed)
if ($county->getCountry() === $this) {
$county->setCountry(null);
}
}
return $this;
}
/**
* @return Collection<int, Location>
*/
public function getLocations(): Collection
{
return $this->locations;
}
public function addLocation(Location $location): self
{
if (!$this->locations->contains($location)) {
$this->locations->add($location);
$location->setCountry($this);
}
return $this;
}
public function removeLocation(Location $location): self
{
if ($this->locations->removeElement($location)) {
// set the owning side to null (unless already changed)
if ($location->getCountry() === $this) {
$location->setCountry(null);
}
}
return $this;
}
}
#[ORM\Entity(repositoryClass: CountyRepository::class)]
class County
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 128, nullable: true)]
private ?string $name = null;
#[ORM\ManyToOne(inversedBy: 'counties')]
private ?Country $country = null;
#[ORM\OneToMany(mappedBy: 'county', targetEntity: City::class)]
private Collection $cities;
#[ORM\OneToMany(mappedBy: 'county', targetEntity: Location::class)]
private Collection $locations;
public function __construct()
{
$this->cities = new ArrayCollection();
$this->locations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
public function getCountry(): ?Country
{
return $this->country;
}
public function setCountry(?Country $country): self
{
$this->country = $country;
return $this;
}
/**
* @return Collection<int, City>
*/
public function getCities(): Collection
{
return $this->cities;
}
public function addCity(City $city): self
{
if (!$this->cities->contains($city)) {
$this->cities->add($city);
$city->setCounty($this);
}
return $this;
}
public function removeCity(City $city): self
{
if ($this->cities->removeElement($city)) {
// set the owning side to null (unless already changed)
if ($city->getCounty() === $this) {
$city->setCounty(null);
}
}
return $this;
}
/**
* @return Collection<int, Location>
*/
public function getLocations(): Collection
{
return $this->locations;
}
public function addLocation(Location $location): self
{
if (!$this->locations->contains($location)) {
$this->locations->add($location);
$location->setCounty($this);
}
return $this;
}
public function removeLocation(Location $location): self
{
if ($this->locations->removeElement($location)) {
// set the owning side to null (unless already changed)
if ($location->getCounty() === $this) {
$location->setCounty(null);
}
}
return $this;
}
}
#[ORM\Entity(repositoryClass: CityRepository::class)]
class City
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 128, nullable: true)]
private ?string $name = null;
#[ORM\ManyToOne(inversedBy: 'cities')]
private ?County $county = null;
#[ORM\OneToMany(mappedBy: 'city', targetEntity: Location::class)]
private Collection $locations;
public function __construct()
{
$this->locations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
public function getCounty(): ?County
{
return $this->county;
}
public function setCounty(?County $county): self
{
$this->county = $county;
return $this;
}
/**
* @return Collection<int, Location>
*/
public function getLocations(): Collection
{
return $this->locations;
}
public function addLocation(Location $location): self
{
if (!$this->locations->contains($location)) {
$this->locations->add($location);
$location->setCity($this);
}
return $this;
}
public function removeLocation(Location $location): self
{
if ($this->locations->removeElement($location)) {
// set the owning side to null (unless already changed)
if ($location->getCity() === $this) {
$location->setCity(null);
}
}
return $this;
}
}
CodePudding user response:
First part is working, ok, but in the second part i see in controller is not capturing anything - https://prnt.sc/I9Gl0Z6ooCo_ In FormType county post submit is not working.
class LocationType extends AbstractType
{ private $transformer;
public function __construct(IssueToStringTransformer $transformer)
{
$this->transformer = $transformer;
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('country', CountryType::class, [
'label' => false,
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
'attr' => [
'class' => 'form-control'
],
//'empty_data' => ''
//'mapped' => false,
])
->add('county', ChoiceType::class, [
'label' => false,
'placeholder' => 'Choose an option',
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
])
->add('city', ChoiceType::class, [
'label' => false,
'attr' => [ 'class' => 'form-control'],
'placeholder' => 'Choose an option',
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
]);
$builder->get('country')
->addModelTransformer($this->transformer);
$formModifier = function (FormInterface $form, Country $country = null) {
$counties_array = [];
// var_dump($country);
if($country != null) {
$g = new GeoNamesClient('djmichael');
[$countryGeoNames] = $g->countryInfo([
'country' => $country->getName(),
]);
$country_name = $countryGeoNames->geonameId;
$counties_json = $g->children(['geonameId' => $country_name]);
foreach($counties_json as $counties_j) {
//dd($counties_j->toponymName);
$counties_array[$counties_j->toponymName] = $counties_j->geonameId;
}
}
$counties = null === $counties_array ? [] : $counties_array;
//var_dump($counties);
$form->add('county', ChoiceType::class, [
'placeholder' => 'Choose an option',
//'required' => false,
'attr' => [
'class' => 'form-control'
],
'choices' => $counties,
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
]);
};
$builder->get('country')->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. SportMeetup
$data = $event->getData();
$country = null;
if($data != null) {
$country = $data->getCountry();
//dd($data);
}
$formModifier($event->getForm()->getParent(), $country);
}
);
$builder->get('country')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$country = $event->getForm()->getData();
if($country != null) {
//dd($country);
$formModifier($event->getForm()->getParent(), $country);
} else {
//dd($country);
}
}
);
$builder->get('county')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$county = $event->getForm()->getData();
dd($county);
if($county != null) {
//dd($country);
$formModifier($event->getForm()->getParent(), $county);
} else {
//dd($country);
}
}
);
}
CodePudding user response:
After spent some good time, i finally found the solution for Countries->Counties->Cities With the help from another stackoverflow post, i create a event Subscriber. I also use geoname.org and change from post_submit to pre_submit
So the final looks like this:
class DynamicFieldsSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmitData',
];
}
/**
* Handling form fields before form renders.
* @param FormEvent $event
*/
public function preSetData(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
//dd($data);
$country = null;
if($data != null) {
//$country = $data->getCountry();
dd($data);
}
$this->addCountyData($form, $country);
}
/**
* Handling form fields before form renders.
* @param FormEvent $event
*/
public function preSubmitData(FormEvent $event)
{
$location['country'] = $event->getData();
//dd($location);
if(array_key_exists('country', $location['country'])) {
//dd($country);
$countryObj = new Country();
$countryObj->setName($location['country']['country']);
//dd($country);
$this->addCountyData($event->getForm(), $countryObj);
} else {
$country = null;
$countyGeoId = $location['country']['county'];
//dd($countyGeoId);
$this->addCityData($event->getForm(), $countyGeoId);
}
}
/**
* Method to Add State Field. (first dynamic field.)
* @param FormInterface $form
* @param type $county
*/
private function addCountyData(FormInterface $form, Country $country = null) {
$countiesArray = [];
if($country != null) {
$geoNamesClient = new GeoNamesClient('djmichael');
[$countryGeoNames] = $geoNamesClient->countryInfo([
'country' => $country->getName(),
]);
$countryName = $countryGeoNames->geonameId;
$countiesJson = $geoNamesClient->children(['geonameId' => $countryName]);
foreach($countiesJson as $countyJson) {
$countiesArray[$countyJson->toponymName] = $countyJson->geonameId;
}
}
$counties = null === $countiesArray ? [] : $countiesArray;
$form->add('county', ChoiceType::class, [
'placeholder' => 'Choose an option',
'mapped' => false,
'attr' => [
'class' => 'form-control'
],
'choices' => $counties,
// 'data' => 'Tirana',
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
]);
}
/**
* Method to Add State Field. (first dynamic field.)
* @param FormInterface $form
* @param type $city
*/
private function addCityData(FormInterface $form, $county = null) {
$citiesArray = [];
if($county != null) {
$geoNamesClient = new GeoNamesClient('djmichael');
$citiesJson = $geoNamesClient->children(['geonameId' => $county]);
foreach($citiesJson as $cityJson) {
$citiesArray[$cityJson->toponymName] = $cityJson->toponymName;
}
}
$cities = null === $citiesArray ? [] : $citiesArray;
$form->add('city', ChoiceType::class, [
'placeholder' => 'Choose an option',
'mapped' => false,
'attr' => [
'class' => 'form-control'
],
'choices' => $cities,
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
]);
}
}
class LocationType extends AbstractType
{
private $transformer;
public function __construct(IssueToStringTransformer $transformer)
{
$this->transformer = $transformer;
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('country', CountryType::class, [
'label' => false,
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
'attr' => [
'class' => 'form-control'
],
//'empty_data' => ''
//'mapped' => false,
])
->add('county', EntityType::class, [
'class' => County::class,
'label' => false,
'placeholder' => 'Choose an option',
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
'mapped' => false,
])
->add('city', ChoiceType::class, [
'label' => false,
'attr' => [ 'class' => 'form-control'],
'placeholder' => 'Choose an option',
//'choices' => ['Tirana' => 'Tirana'],
'constraints' => [
new NotBlank([
'message' => 'not null',
]),
],
'mapped' => false,
]);
$builder->get('city')->resetViewTransformers();
$builder->get('country')->addModelTransformer($this->transformer);
$builder->addEventSubscriber(new DynamicFieldsSubscriber());
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Location::class,
]);
}
}