@@ -474,8 +474,10 @@ The meetup is passed as an entity field to the form. So we can access each
474474sport like this::
475475
476476 // src/Acme/DemoBundle/Form/Type/SportMeetupType.php
477+
477478 namespace Acme\DemoBundle\Form\Type;
478479
480+ use Symfony\Component\Form\AbstractType;
479481 use Symfony\Component\Form\FormBuilderInterface;
480482 use Symfony\Component\Form\FormEvent;
481483 use Symfony\Component\Form\FormEvents;
@@ -487,9 +489,9 @@ sport like this::
487489 {
488490 $builder
489491 ->add('sport', 'entity', array(
490- 'class' => 'AcmeDemoBundle:Sport',
492+ 'class' => 'AcmeDemoBundle:Sport',
491493 'empty_value' => '',
492- ));
494+ ))
493495 ;
494496
495497 $builder->addEventListener(
@@ -501,16 +503,17 @@ sport like this::
501503 $data = $event->getData();
502504
503505 $sport = $data->getSport();
504- $positions =( null === $sport) ? array() : $sport->getAvailablePositions();
506+ $positions = null === $sport ? array() : $sport->getAvailablePositions();
505507
506508 $form->add('position', 'entity', array(
507- 'class' => 'AcmeDemoBundle:Position',
509+ 'class' => 'AcmeDemoBundle:Position',
508510 'empty_value' => '',
509- 'choices' => $positions,
511+ 'choices' => $positions,
510512 ));
511513 }
512514 );
513515 }
516+
514517 // ...
515518 }
516519
@@ -545,30 +548,31 @@ new field automatically and map it to the submitted client data.
545548The type would now look like::
546549
547550 // src/Acme/DemoBundle/Form/Type/SportMeetupType.php
551+
548552 namespace Acme\DemoBundle\Form\Type;
549553
550554 // ...
551- use Acme\DemoBundle\Entity\Sport;
552555 use Symfony\Component\Form\FormInterface;
556+ use Acme\DemoBundle\Entity\Sport;
553557
554558 class SportMeetupType extends AbstractType
555559 {
556560 public function buildForm(FormBuilderInterface $builder, array $options)
557561 {
558562 $builder
559563 ->add('sport', 'entity', array(
560- 'class' => 'AcmeDemoBundle:Sport',
564+ 'class' => 'AcmeDemoBundle:Sport',
561565 'empty_value' => '',
562566 ));
563567 ;
564568
565569 $formModifier = function(FormInterface $form, Sport $sport = null) {
566- $positions =( null === $sport) ? array() : $sport->getAvailablePositions();
570+ $positions = null === $sport ? array() : $sport->getAvailablePositions();
567571
568572 $form->add('position', 'entity', array(
569- 'class' => 'AcmeDemoBundle:Position',
573+ 'class' => 'AcmeDemoBundle:Position',
570574 'empty_value' => '',
571- 'choices' => $positions,
575+ 'choices' => $positions,
572576 ));
573577 };
574578
@@ -595,6 +599,7 @@ The type would now look like::
595599 }
596600 );
597601 }
602+
598603 // ...
599604 }
600605
@@ -608,6 +613,15 @@ the sport is selected. This should be handled by making an AJAX call back to
608613your application. Assume that you have a sport meetup creation controller::
609614
610615 // src/Acme/DemoBundle/Controller/MeetupController.php
616+
617+ namespace Acme\DemoBundle\Controller;
618+
619+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
620+ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
621+ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
622+ use Symfony\Component\HttpFoundation\Request;
623+ use Acme\DemoBundle\Entity\SportMeetup;
624+ use Acme\DemoBundle\Form\Type\SportMeetupType;
611625 // ...
612626
613627 /**
@@ -620,7 +634,6 @@ your application. Assume that you have a sport meetup creation controller::
620634 * @Template
621635 */
622636 public function createAction(Request $request)
623-
624637 {
625638 $meetup = new SportMeetup();
626639 $form = $this->createForm(new SportMeetupType(), $meetup);
@@ -631,47 +644,87 @@ your application. Assume that you have a sport meetup creation controller::
631644
632645 return array('form' => $form->createView());
633646 }
647+
634648 // ...
635649 }
636650
637651The associated template uses some JavaScript to update the ``position `` form
638652field according to the current selection in the ``sport `` field. To ease things
639653it makes use of `jQuery `_ library and the `FOSJsRoutingBundle `_:
640654
641- ..code-block ::html+jinja
642-
643- {# src/Acme/DemoBundle/Resources/views/Meetup/create.html.twig #}
644- {{ form_start(form) }}
645- {{ form_row(form.sport) }} {# <select id="meetup_sport" ... #}
646- {{ form_row(form.position) }} {# <select id="meetup_position" ... #}
647- {# ... #}
648- {{ form_end(form) }}
649-
650- {# ... Include jQuery and scripts from FOSJsRoutingBundle ... #}
651- <script>
652- $(function(){
653- // When sport gets selected ...
654- $('#meetup_sport').change(function(){
655- var $position = $('#meetup_position');
656- // Remove current position options except first "empty_value" option
657- $position.find('option:not(:first)').remove();
658- var sportId = $(this).val();
659- if (sportId) {
660- // Issue AJAX call fetching positions for selected sport as JSON
661- $.getJSON(
662- // FOSJsRoutingBundle generates route including selected sport ID
663- Routing.generate('meetup_positions_by_sport', {id: sportId}),
664- function(positions) {
665- // Append fetched positions associated with selected sport
666- $.each(positions, function(key, position){
667- $position.append(new Option(position[1], position[0]));
668- });
669- }
670- );
671- }
655+ ..configuration-block ::
656+
657+ ..code-block ::html+jinja
658+
659+ {# src/Acme/DemoBundle/Resources/views/Meetup/create.html.twig #}
660+
661+ {{ form_start(form) }}
662+ {{ form_row(form.sport) }} {# <select id="meetup_sport" ... #}
663+ {{ form_row(form.position) }} {# <select id="meetup_position" ... #}
664+ {# ... #}
665+ {{ form_end(form) }}
666+
667+ {# ... Include jQuery and scripts from FOSJsRoutingBundle ... #}
668+ <script>
669+ $(function(){
670+ // When sport gets selected ...
671+ $('#meetup_sport').change(function(){
672+ var $position = $('#meetup_position');
673+ // Remove current position options except first "empty_value" option
674+ $position.find('option:not(:first)').remove();
675+ var sportId = $(this).val();
676+ if (sportId) {
677+ // Issue AJAX call fetching positions for selected sport as JSON
678+ $.getJSON(
679+ // FOSJsRoutingBundle generates route including selected sport ID
680+ Routing.generate('meetup_positions_by_sport', {id: sportId}),
681+ function(positions) {
682+ // Append fetched positions associated with selected sport
683+ $.each(positions, function(key, position){
684+ $position.append(new Option(position[1], position[0]));
685+ });
686+ }
687+ );
688+ }
689+ });
672690 });
673- });
674- </script>
691+ </script>
692+
693+ ..code-block ::html+php
694+
695+ <!-- src/Acme/DemoBundle/Resources/views/Meetup/create.html.php -->
696+
697+ <?php echo $view['form']->start($form) ?>
698+ <?php echo $view['form']->row($form['sport']) ?> <!-- <select id="meetup_sport" ... -->
699+ <?php echo $view['form']->row($form['position']) ?> <!-- <select id="meetup_position" ... -->
700+ <!-- ... -->
701+ <?php echo $view['form']->end($form) ?>
702+
703+ <!-- ... Include jQuery and scripts from FOSJsRoutingBundle ... -->
704+ <script>
705+ $(function(){
706+ // When sport gets selected ...
707+ $('#meetup_sport').change(function(){
708+ var $position = $('#meetup_position');
709+ // Remove current position options except first "empty_value" option
710+ $position.find('option:not(:first)').remove();
711+ var sportId = $(this).val();
712+ if (sportId) {
713+ // Issue AJAX call fetching positions for selected sport as JSON
714+ $.getJSON(
715+ // FOSJsRoutingBundle generates route including selected sport ID
716+ Routing.generate('meetup_positions_by_sport', {id: sportId}),
717+ function(positions) {
718+ // Append fetched positions associated with selected sport
719+ $.each(positions, function(key, position){
720+ $position.append(new Option(position[1], position[0]));
721+ });
722+ }
723+ );
724+ }
725+ });
726+ });
727+ </script>
675728
676729The last piece is implementing a controller for the
677730``meetup_positions_by_sport `` route returning the positions as JSON according
@@ -680,15 +733,21 @@ of the :doc:`@ParamConverter </bundles/SensioFrameworkExtraBundle/annotations/co
680733listener to convert the submitted sport ID into a ``Sport `` object::
681734
682735 // src/Acme/DemoBundle/Controller/MeetupController.php
736+
737+ namespace Acme\DemoBundle\Controller;
738+
683739 // ...
684740 use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
741+ use Symfony\Component\HttpFoundation\JsonResponse;
742+ use Acme\DemoBundle\Entity\Sport;
685743
686744 /**
687745 * @Route("/meetup")
688746 */
689747 class MeetupController extends Controller
690748 {
691749 // ...
750+
692751 /**
693752 * @Route("/{id}/positions.json", name="meetup_positions_by_sport", options={"expose"=true})
694753 */