@@ -476,6 +476,7 @@ sport like this::
476476 // src/Acme/DemoBundle/Form/Type/SportMeetupType.php
477477 namespace Acme\DemoBundle\Form\Type;
478478
479+ use Symfony\Component\Form\AbstractType;
479480 use Symfony\Component\Form\FormBuilderInterface;
480481 use Symfony\Component\Form\FormEvent;
481482 use Symfony\Component\Form\FormEvents;
@@ -486,7 +487,10 @@ sport like this::
486487 public function buildForm(FormBuilderInterface $builder, array $options)
487488 {
488489 $builder
489- ->add('sport', 'entity', array(...))
490+ ->add('sport', 'entity', array(
491+ 'class' => 'AcmeDemoBundle:Sport',
492+ 'empty_value' => '',
493+ ))
490494 ;
491495
492496 $builder->addEventListener(
@@ -497,12 +501,19 @@ sport like this::
497501 // this would be your entity, i.e. SportMeetup
498502 $data = $event->getData();
499503
500- $positions = $data->getSport()->getAvailablePositions();
504+ $sport = $data->getSport();
505+ $positions = null === $sport ? array() : $sport->getAvailablePositions();
501506
502- $form->add('position', 'entity', array('choices' => $positions));
507+ $form->add('position', 'entity', array(
508+ 'class' => 'AcmeDemoBundle:Position',
509+ 'empty_value' => '',
510+ 'choices' => $positions,
511+ ));
503512 }
504513 );
505514 }
515+
516+ // ...
506517 }
507518
508519When you're building this form to display to the user for the first time,
@@ -539,21 +550,28 @@ The type would now look like::
539550 namespace Acme\DemoBundle\Form\Type;
540551
541552 // ...
542- use Acme\DemoBundle\Entity\Sport;
543553 use Symfony\Component\Form\FormInterface;
554+ use Acme\DemoBundle\Entity\Sport;
544555
545556 class SportMeetupType extends AbstractType
546557 {
547558 public function buildForm(FormBuilderInterface $builder, array $options)
548559 {
549560 $builder
550- ->add('sport', 'entity', array(...))
561+ ->add('sport', 'entity', array(
562+ 'class' => 'AcmeDemoBundle:Sport',
563+ 'empty_value' => '',
564+ ));
551565 ;
552566
553- $formModifier = function(FormInterface $form, Sport $sport) {
554- $positions = $sport->getAvailablePositions();
567+ $formModifier = function(FormInterface $form, Sport $sport = null ) {
568+ $positions =null === $sport ? array() : $sport->getAvailablePositions();
555569
556- $form->add('position', 'entity', array('choices' => $positions));
570+ $form->add('position', 'entity', array(
571+ 'class' => 'AcmeDemoBundle:Position',
572+ 'empty_value' => '',
573+ 'choices' => $positions,
574+ ));
557575 };
558576
559577 $builder->addEventListener(
@@ -579,17 +597,78 @@ The type would now look like::
579597 }
580598 );
581599 }
600+
601+ // ...
602+ }
603+
604+ You can see that you need to listen on these two events and have different
605+ callbacks only because in two different scenarios, the data that you can use is
606+ available in different events. Other than that, the listeners always perform
607+ exactly the same things on a given form.
608+
609+ One piece that is still missing is the client-side updating of your form after
610+ the sport is selected. This should be handled by making an AJAX call back to
611+ your application. Assume that you have a sport meetup creation controller::
612+
613+ // src/Acme/DemoBundle/Controller/MeetupController.php
614+ namespace Acme\DemoBundle\Controller;
615+
616+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
617+ use Symfony\Component\HttpFoundation\Request;
618+ use Acme\DemoBundle\Entity\SportMeetup;
619+ use Acme\DemoBundle\Form\Type\SportMeetupType;
620+ // ...
621+
622+ class MeetupController extends Controller
623+ {
624+ public function createAction(Request $request)
625+ {
626+ $meetup = new SportMeetup();
627+ $form = $this->createForm(new SportMeetupType(), $meetup);
628+ $form->handleRequest($request);
629+ if ($form->isValid()) {
630+ // ... save the meetup, redirect etc.
631+ }
632+
633+ return $this->render(
634+ 'AcmeDemoBundle:Meetup:create.html.twig',
635+ array('form' => $form->createView())
636+ );
637+ }
638+
639+ // ...
582640 }
583641
584- You can see that you need to listen on these two events and have different callbacks
585- only because in two different scenarios, the data that you can use is available in different events.
586- Other than that, the listeners always perform exactly the same things on a given form.
642+ The associated template uses some JavaScript to update the ``position `` form
643+ field according to the current selection in the ``sport `` field:
644+
645+ ..configuration-block ::
646+
647+ ..code-block ::html+jinja
648+
649+ {# src/Acme/DemoBundle/Resources/views/Meetup/create.html.twig #}
650+ {{ form_start(form) }}
651+ {{ form_row(form.sport) }} {# <select id="meetup_sport" ... #}
652+ {{ form_row(form.position) }} {# <select id="meetup_position" ... #}
653+ {# ... #}
654+ {{ form_end(form) }}
655+
656+ ..include ::/cookbook/form/dynamic_form_modification_ajax_js.rst.inc
657+
658+ ..code-block ::html+php
659+
660+ <!-- src/Acme/DemoBundle/Resources/views/Meetup/create.html.php -->
661+ <?php echo $view['form']->start($form) ?>
662+ <?php echo $view['form']->row($form['sport']) ?> <!-- <select id="meetup_sport" ... -->
663+ <?php echo $view['form']->row($form['position']) ?> <!-- <select id="meetup_position" ... -->
664+ <!-- ... -->
665+ <?php echo $view['form']->end($form) ?>
666+
667+ ..include ::/cookbook/form/dynamic_form_modification_ajax_js.rst.inc
587668
588- One piece that may still be missing is the client-side updating of your form
589- after the sport is selected. This should be handled by making an AJAX call
590- back to your application. In that controller, you can submit your form, but
591- instead of processing it, simply use the submitted form to render the updated
592- fields. The response from the AJAX call can then be used to update the view.
669+ The major benefit of submitting the whole form to just extract the updated
670+ ``position `` field is that no additional server-side code is needed; all the
671+ code from above to generate the submitted form can be reused.
593672
594673.. _cookbook-dynamic-form-modification-suppressing-form-validation :
595674