I wanted to ask how I can achieve dynamic cascading children dependency using a structure similar to what is given in Symfony cookbook: dynamic_form_modification_using_form_events but with an additional field, which is being populated by data based on which option was chosen by the user. I managed to make it work with dependency similar to sports->position, but I can't manage to make it work with structure categories->categoryChildren->childCategoryChildren, where both categoryChildren and childCategoryChildren are dynamically created. When I try to make an event listener for categoryChildren, I'm getting an error:
The child with the name "categoryChildren" does not exist.
This is my form type:
/**
* Class AddAdType
* @package App\Ad\AdBundle\Form\Type
*/
class CreateAdType extends AbstractType
{
/**
* @param CategoryRepository $categoryRepository
*/
public function __construct(
CategoryRepository $categoryRepository,
)
{
$this->categoryRepository = $categoryRepository;
}
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('description')
->add('price')
->add('category', EntityType::class, [
'class' => Category::class,
'choices' => $this->categoryRepository->findBy(['parent' => null]),
'choice_label' => 'name',
'required' => true,
'placeholder' => 'Wybierz kategorie',
]);
$this->categoryChildrenListener($builder);
$this->childCategoryChildrenListener($builder);
}
/**
* @param FormBuilderInterface $builder
*/
public function categoryChildrenListener(FormBuilderInterface $builder)
{
$formModifier = function (FormInterface $form, Category $category = null) {
$categoryChildren = null === $category ? [] : $this->categoryRepository->findBy(['parent' => $category]);
$form->add('categoryChildren', EntityType::class, [
'class' => Category::class,
'choice_label' => 'name',
'choices' => $categoryChildren,
'mapped' => false,
'attr' => [
'onChange' => "getNewVal(this);"
]
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$data = $event->getData();
$formModifier($event->getForm(), $data->getCategory());
}
);
$builder->get('category')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$ad = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $ad);
}
);
}
/**
* @param FormBuilderInterface $builder
*/
public function childCategoryChildrenListener(FormBuilderInterface $builder)
{
$formChildCategoryModifier = function (FormInterface $form, Category $categoryChild = null) {
$childCategoryChildren = null === $categoryChild ? [] : $this->categoryRepository->findBy(['parent' => $categoryChild]);
$form->add('childCategoryChildren', EntityType::class, [
'class' => Category::class,
'choice_label' => 'name',
'choices' => $childCategoryChildren,
'mapped' => false
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formChildCategoryModifier) {
$data = $event->getData();
$formChildCategoryModifier($event->getForm(), $data->getCategory());
}
);
$builder->get('categoryChildren')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formChildCategoryModifier) {
$ad = $event->getForm()->getData();
$formChildCategoryModifier($event->getForm()->getParent(), $ad);
}
);
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Ad::class,
'csrf_protection' => false
]);
}
}
And this is my entity:
/**
* Class Ad
* @package App\Ad\AdBundle\Entity\Ad
*
* @ORM\Entity
* @ORM\Table(name="ad")
*/
class Ad
{
//[...]
/**
* @ORM\ManyToOne(targetEntity="App\Ad\CategoryBundle\Entity\Category")
* @ORM\JoinColumn(name="category_id", nullable=false, referencedColumnName="id")
*/
private $category;
private $categoryChildren;
private $childCategoryChildren;
//[...]
/**
* @return Category|null
*/
public function getCategory(): ?Category
{
return $this->category;
}
/**
* @param Category|null $category
* @return $this
*/
public function setCategory(?Category $category): self
{
$this->category = $category;
return $this;
}
/**
* @param Category|null $categoryChildren
*/
public function setCategoryChildren(?Category $categoryChildren): self
{
$this->categoryChildren = $categoryChildren;
return $this;
}
/**
* @return Category|null
*/
public function getCategoryChildren(): ?Category
{
return $this->categoryChildren;
}
/**
* @param Category|null $childCategoryChildren
*/
public function setChildCategoryChildren(?Category $childCategoryChildren): self
{
$this->childCategoryChildren = $childCategoryChildren;
return $this;
}
/**
* @return Category|null
*/
public function getChildCategoryChildren(): ?Category
{
return $this->childCategoryChildren;
}
}
This is my ajax for category select:
let $category = $('#create_ad_category');
let $childCategory = $('#create_ad_categoryChildren');
checkEmptySelect();
if ( $category.change() ) {
$category.change(function() {
let $form = $(this).closest('form');
let data = {};
data[$category.attr('name')] = $category.val();
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
complete: function(html) {
$('#create_ad_categoryChildren').replaceWith(
$(html.responseText).find('#create_ad_categoryChildren')
);
checkEmptySelect()
$('#create_ad_categoryChildren').prepend('<option value="" selected="selected"> ' +
'Select a subcategory of ' + $category.find("option:selected").text() + ' ...</option>');
}
});
});
}
function checkEmptySelect() {
if ( !$('#create_ad_categoryChildren').val() ) {
$('.subcategories').hide();
// $('.childCategories').hide();
} else {
$('.subcategories').show();
}
}
And this is my ajax for categoryChildren field:
function getNewVal(item) {
let $form = $(this).closest('form');
let data = {};
data[$category.attr('name')] = $category.val();
data[$childCategory.attr('name')] = item.value;
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
complete: function(html) {
$('#create_ad_childCategoryChildren').replaceWith(
$(html.responseText).find('#create_ad_childCategoryChildren')
);
checkEmptySelect()
$('#create_ad_childCategoryChildren').prepend('<option value="" selected="selected"> ' +
'Select a subcategory of ' + $('#create_ad_categoryChildren').find("option:selected").text() + ' ...</option>');
}
});
}
I'm using Symfony 6.0. Any help will be appreciated, cheers.