5

I am using event listeners to dynamically modify a form. I want to add another event listener to a field that was added dynamically. Im not sure how to accomplish this.

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('first_field','choice',array(
        'choices'=>array('1'=>'First Choice','2'=>'Second Choice')
    ));

    $builder->addEventListener(FormEvents::PRE_SET_DATA, array($this, 'preSetData'));
    $builder->get('first_field')->addEventListener(FormEvents::POST_SUBMIT, array($this, 'postSubmit'));
}

public function preSetData(FormEvent $event)
{
    $form = $event->getForm();
    $form->add('second_field','choice',array(
        'choices'=>array('1'=>'First Choice','2'=>'Second Choice')
    ));
    //Some how add an event listener to this field

}

public function postSubmit(FormEvent $event)
{
    $form = $event->getForm()->getParent();
    $form->add('second_field','choice',array(
        'choices'=>array('1'=>'First Choice','2'=>'Second Choice')
    ));
    //Some how add an event listener to this field
}

I have trie just using the $builder in the buildForm function to add the event listener to the second_field but because the field doesnt exist when the form is initially generated it throws an error.

If i try and add the new event listener inside the first event listener by doing:

$form->get('second_field')->addEventListener(...)

Then i get the error:

Call to undefined method Symfony\Component\Form\Form::addEventListener() 

Any suggestions would be welcome.

Chase
  • 9,289
  • 5
  • 51
  • 77
  • Are you trying to add a third field based on the second field? – Brayden Williams Aug 18 '14 at 01:56
  • Yes, and the second field is based on the first. Similar to Select Country -> then based on country selected the field states is added and available States are populated based on country. Then once a state is selected a City field would be added and available cities would be populated into it. – Chase Aug 18 '14 at 01:59
  • You might want to look at the [CraueFormFlowBundle](https://github.com/craue/CraueFormFlowBundle) – keyboardSmasher Aug 18 '14 at 02:05
  • Not a bad idea, I would however like to avoid adding a new vendor if possible though. Its just 1 more dependency to rely on. However if i cant get this working natively i may give it a try. – Chase Aug 18 '14 at 02:11
  • @keyboardSmasher I am not sure why you recommend the CraueFormFlowBundle. It's for making multi-step forms. This question is about adding fields dynamically to a form based on underlying data. – Brayden Williams Aug 18 '14 at 02:17
  • @BraydenWilliams The bundle looks like it should be able to accomplish what i want. Its just a lot of fluff that i dont need as well. – Chase Aug 18 '14 at 02:25
  • @Chausser, Did you able to call POST_SUBMIT event listener to dynamically added field? I have similar issue and I am struggling a bit! – Jeet Nov 22 '14 at 07:42

3 Answers3

16

If, is it actually.

FormInterface does't have the addEventListener method, but FormBuilderIntreface have it. If you want to add any listener, you should to create form field by form builder.

For example:

   // create builder for field
   $builder = $form->getConfig()->getFormFactory()->createNamedBuilder($name, $type, null, array(
       /** any options **/
       'auto_initialize'=>false // it's important!!!
   ));
   // now you can add listener
   $builder->addEventListener(FormEvents::POST_SUBMIT, $yourCallbackHere)

   // and only now you can add field to form  
   $form->add($builder->getForm());
Lev Semin
  • 243
  • 2
  • 10
3

I have just spent half of my working day struggling on this. I'm using symfony 3.2.x and the best thing that helped me is this answer.

I had a triple dependece (country,state,region,zipcode) i've solved all inside the same FormType:

    /** @var  EntityManager $em */
private $em;

/**
 * GaraFormType constructor.
 *
 * @param EntityManager $em
 */
public function __construct(EntityManager $em)
{
    $this->em        = $em;
}


    public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('country', EntityType::class, array(
            'class'       => 'Country',
            ...more attrs
        ));

    $this->stateAjaxFieldFormListener($builder);
    $this->cityAjaxFieldFormListener($builder);
    $this->zipCodeAjaxFieldFormListener($builder);
}

Each of those functions handles one of the dynamic fields, and they are all the same as follows:

private function stateAjaxFieldFormListener(FormBuilderInterface $builder)
{
    $localizationFormModifier = function (FormInterface $form, Country $country = null) {
        $stateInCountry = $this->em->getRepository("State")->findBy(array("country" => $country));

        if ($form->has('state') && ! $form->isSubmitted()) {
            $form->remove('state');
        }
        $form
            ->add('state', EntityType::class, array(
                'choices'     => $stateInCountry,
                'class'       => 'State',
            ));
    };
    $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($localizationFormModifier) {
        /** @var ClienteTemp $data */
        $data    = $event->getData();
        $country = null !== $data ? $data->getCountry() : null;

        $localizationFormModifier($event->getForm(), $country);
    });
    $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($localizationFormModifier) {
        $data      = $event->getData();
        $countryId = array_key_exists('country', $data) ? $data['country'] : null;
        $country   = $this->em->getRepository("Country")->find($countryId);
        $localizationFormModifier($event->getForm(), $country);
    });
}

Just change entity references for the other two functions: cityAjaxFieldFormListener and zipCodeAjaxFieldFormListener

Diego
  • 1,610
  • 1
  • 14
  • 26
0

You don't really need to add the event listener to first_field or second_field themselves. You can keep the event listener on the parent form and check both the submitted and the set data for whether or not it includes data for first_field. If it does, add the second_field. In the same listener, check if data has been set or submitted to second_field. If it has, add the third field.

There is a similar concept outlined in the documentation here: http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#cookbook-form-events-submitted-data

  • 2
    I have gone over that guide 100 times, The problem i have is still you have to attach to the `post_submit` listener. And the only way to do that according to that guide is to attach it to the field directly. There in lies the problem, since i cant attach the `post_submit` listener to a field that has been dynamically added by a different listener. – Chase Aug 18 '14 at 02:43
  • This works fine for the `pre_set_data` event because that is attached to the parent form and i can check the nesting all the way down. The issue just comes with the `post_submit` event – Chase Aug 18 '14 at 02:45
  • Why do you have to do the check in post_submit? I have cascading country/state/city fields in one of my apps and I check in pre_submit. – Brayden Williams Aug 18 '14 at 06:52
  • I'll try it without and let you know – Chase Aug 18 '14 at 07:54