Home > Enterprise >  Tree Dynamically Form Symfony
Tree Dynamically Form Symfony

Time:12-01

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,
    ]);
}
}
  • Related