@@ -471,6 +471,7 @@ sport like this::
471471 // src/Acme/DemoBundle/Form/Type/SportMeetupType.php
472472 namespace Acme\DemoBundle\Form\Type;
473473
474+ use Symfony\Component\Form\AbstractType;
474475 use Symfony\Component\Form\FormBuilderInterface;
475476 use Symfony\Component\Form\FormEvent;
476477 use Symfony\Component\Form\FormEvents;
@@ -481,7 +482,10 @@ sport like this::
481482 public function buildForm(FormBuilderInterface $builder, array $options)
482483 {
483484 $builder
484- ->add('sport', 'entity', array(...))
485+ ->add('sport', 'entity', array(
486+ 'class' => 'AcmeDemoBundle:Sport',
487+ 'empty_value' => '',
488+ ))
485489 ;
486490
487491 $builder->addEventListener(
@@ -492,12 +496,19 @@ sport like this::
492496 // this would be your entity, i.e. SportMeetup
493497 $data = $event->getData();
494498
495- $positions = $data->getSport()->getAvailablePositions();
499+ $sport = $data->getSport();
500+ $positions = null === $sport ? array() : $sport->getAvailablePositions();
496501
497- $form->add('position', 'entity', array('choices' => $positions));
502+ $form->add('position', 'entity', array(
503+ 'class' => 'AcmeDemoBundle:Position',
504+ 'empty_value' => '',
505+ 'choices' => $positions,
506+ ));
498507 }
499508 );
500509 }
510+
511+ // ...
501512 }
502513
503514When you're building this form to display to the user for the first time,
@@ -530,21 +541,28 @@ The type would now look like::
530541 namespace Acme\DemoBundle\Form\Type;
531542
532543 // ...
533- use Acme\DemoBundle\Entity\Sport;
534544 use Symfony\Component\Form\FormInterface;
545+ use Acme\DemoBundle\Entity\Sport;
535546
536547 class SportMeetupType extends AbstractType
537548 {
538549 public function buildForm(FormBuilderInterface $builder, array $options)
539550 {
540551 $builder
541- ->add('sport', 'entity', array(...))
552+ ->add('sport', 'entity', array(
553+ 'class' => 'AcmeDemoBundle:Sport',
554+ 'empty_value' => '',
555+ ));
542556 ;
543557
544- $formModifier = function(FormInterface $form, Sport $sport) {
545- $positions = $sport->getAvailablePositions();
558+ $formModifier = function(FormInterface $form, Sport $sport = null ) {
559+ $positions = null === $sport ? array() : $sport->getAvailablePositions();
546560
547- $form->add('position', 'entity', array('choices' => $positions));
561+ $form->add('position', 'entity', array(
562+ 'class' => 'AcmeDemoBundle:Position',
563+ 'empty_value' => '',
564+ 'choices' => $positions,
565+ ));
548566 };
549567
550568 $builder->addEventListener(
@@ -570,17 +588,78 @@ The type would now look like::
570588 }
571589 );
572590 }
591+
592+ // ...
593+ }
594+
595+ You can see that you need to listen on these two events and have different
596+ callbacks only because in two different scenarios, the data that you can use is
597+ available in different events. Other than that, the listeners always perform
598+ exactly the same things on a given form.
599+
600+ One piece that is still missing is the client-side updating of your form after
601+ the sport is selected. This should be handled by making an AJAX call back to
602+ your application. Assume that you have a sport meetup creation controller::
603+
604+ // src/Acme/DemoBundle/Controller/MeetupController.php
605+ namespace Acme\DemoBundle\Controller;
606+
607+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
608+ use Symfony\Component\HttpFoundation\Request;
609+ use Acme\DemoBundle\Entity\SportMeetup;
610+ use Acme\DemoBundle\Form\Type\SportMeetupType;
611+ // ...
612+
613+ class MeetupController extends Controller
614+ {
615+ public function createAction(Request $request)
616+ {
617+ $meetup = new SportMeetup();
618+ $form = $this->createForm(new SportMeetupType(), $meetup);
619+ $form->handleRequest($request);
620+ if ($form->isValid()) {
621+ // ... save the meetup, redirect etc.
622+ }
623+
624+ return $this->render(
625+ 'AcmeDemoBundle:Meetup:create.html.twig',
626+ array('form' => $form->createView())
627+ );
628+ }
629+
630+ // ...
573631 }
574632
575- You can see that you need to listen on these two events and have different callbacks
576- only because in two different scenarios, the data that you can use is available in different events.
577- Other than that, the listeners always perform exactly the same things on a given form.
633+ The associated template uses some JavaScript to update the ``position `` form
634+ field according to the current selection in the ``sport `` field:
635+
636+ .. configuration-block ::
637+
638+ .. code-block :: html+jinja
639+
640+ {# src/Acme/DemoBundle/Resources/views/Meetup/create.html.twig #}
641+ {{ form_start(form) }}
642+ {{ form_row(form.sport) }} {# <select id="meetup_sport" ... #}
643+ {{ form_row(form.position) }} {# <select id="meetup_position" ... #}
644+ {# ... #}
645+ {{ form_end(form) }}
646+
647+ .. include :: /cookbook/form/dynamic_form_modification_ajax_js.rst.inc
648+
649+ .. code-block :: html+php
650+
651+ <!-- src/Acme/DemoBundle/Resources/views/Meetup/create.html.php -->
652+ <?php echo $view['form']->start($form) ?>
653+ <?php echo $view['form']->row($form['sport']) ?> <!-- <select id="meetup_sport" ... -->
654+ <?php echo $view['form']->row($form['position']) ?> <!-- <select id="meetup_position" ... -->
655+ <!-- ... -->
656+ <?php echo $view['form']->end($form) ?>
657+
658+ .. include :: /cookbook/form/dynamic_form_modification_ajax_js.rst.inc
578659
579- One piece that may still be missing is the client-side updating of your form
580- after the sport is selected. This should be handled by making an AJAX call
581- back to your application. In that controller, you can submit your form, but
582- instead of processing it, simply use the submitted form to render the updated
583- fields. The response from the AJAX call can then be used to update the view.
660+ The major benefit of submitting the whole form to just extract the updated
661+ ``position `` field is that no additional server-side code is needed; all the
662+ code from above to generate the submitted form can be reused.
584663
585664.. _cookbook-dynamic-form-modification-suppressing-form-validation :
586665
0 commit comments