@@ -486,7 +486,10 @@ sport like this::
486
486
public function buildForm(FormBuilderInterface $builder, array $options)
487
487
{
488
488
$builder
489
- ->add('sport', 'entity', array(...))
489
+ ->add('sport', 'entity', array(
490
+ 'class' => 'AcmeDemoBundle:Sport',
491
+ 'empty_value' => '',
492
+ ));
490
493
;
491
494
492
495
$builder->addEventListener(
@@ -497,12 +500,18 @@ sport like this::
497
500
// this would be your entity, i.e. SportMeetup
498
501
$data = $event->getData();
499
502
500
- $positions = $data->getSport()->getAvailablePositions();
503
+ $sport = $data->getSport();
504
+ $positions = (null === $sport) ? array() : $sport->getAvailablePositions();
501
505
502
- $form->add('position', 'entity', array('choices' => $positions));
506
+ $form->add('position', 'entity', array(
507
+ 'class' => 'AcmeDemoBundle:Position',
508
+ 'empty_value' => '',
509
+ 'choices' => $positions,
510
+ ));
503
511
}
504
512
);
505
513
}
514
+ // ...
506
515
}
507
516
508
517
When you're building this form to display to the user for the first time,
@@ -547,13 +556,20 @@ The type would now look like::
547
556
public function buildForm(FormBuilderInterface $builder, array $options)
548
557
{
549
558
$builder
550
- ->add('sport', 'entity', array(...))
559
+ ->add('sport', 'entity', array(
560
+ 'class' => 'AcmeDemoBundle:Sport',
561
+ 'empty_value' => '',
562
+ ));
551
563
;
552
564
553
- $formModifier = function(FormInterface $form, Sport $sport) {
554
- $positions = $sport->getAvailablePositions();
565
+ $formModifier = function(FormInterface $form, Sport $sport = null ) {
566
+ $positions = (null === $sport) ? array() : $sport->getAvailablePositions();
555
567
556
- $form->add('position', 'entity', array('choices' => $positions));
568
+ $form->add('position', 'entity', array(
569
+ 'class' => 'AcmeDemoBundle:Position',
570
+ 'empty_value' => '',
571
+ 'choices' => $positions,
572
+ ));
557
573
};
558
574
559
575
$builder->addEventListener(
@@ -579,17 +595,119 @@ The type would now look like::
579
595
}
580
596
);
581
597
}
598
+ // ...
582
599
}
583
600
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.
601
+ You can see that you need to listen on these two events and have different
602
+ callbacks only because in two different scenarios, the data that you can use is
603
+ available in different events. Other than that, the listeners always perform
604
+ exactly the same things on a given form.
605
+
606
+ One piece that is still missing is the client-side updating of your form after
607
+ the sport is selected. This should be handled by making an AJAX call back to
608
+ your application. Assume that you have a sport meetup creation controller::
609
+
610
+ // src/Acme/DemoBundle/Controller/MeetupController.php
611
+ // ...
612
+
613
+ /**
614
+ * @Route("/meetup")
615
+ */
616
+ class MeetupController extends Controller
617
+ {
618
+ /**
619
+ * @Route("/create", name="meetup_create")
620
+ * @Template
621
+ */
622
+ public function createAction(Request $request)
623
+
624
+ {
625
+ $meetup = new SportMeetup();
626
+ $form = $this->createForm(new SportMeetupType(), $meetup);
627
+ $form->handleRequest($request);
628
+ if ($form->isValid()) {
629
+ // ... save the meetup, redirect etc.
630
+ }
631
+
632
+ return array('form' => $form->createView());
633
+ }
634
+ // ...
635
+ }
587
636
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.
637
+ The associated template uses some JavaScript to update the ``position `` form
638
+ field according to the current selection in the ``sport `` field. To ease things
639
+ it makes use of `jQuery `_ library and the `FOSJsRoutingBundle `_:
640
+
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
+ }
672
+ });
673
+ });
674
+ </script>
675
+
676
+ The last piece is implementing a controller for the
677
+ ``meetup_positions_by_sport `` route returning the positions as JSON according
678
+ to the currently selected sport. To ease things again the controller makes use
679
+ of the :doc: `@ParamConverter </bundles/SensioFrameworkExtraBundle/annotations/converters >`
680
+ listener to convert the submitted sport ID into a ``Sport `` object::
681
+
682
+ // src/Acme/DemoBundle/Controller/MeetupController.php
683
+ // ...
684
+ use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
685
+
686
+ /**
687
+ * @Route("/meetup")
688
+ */
689
+ class MeetupController extends Controller
690
+ {
691
+ // ...
692
+ /**
693
+ * @Route("/{id}/positions.json", name="meetup_positions_by_sport", options={"expose"=true})
694
+ */
695
+ public function positionsBySportAction(Sport $sport)
696
+ {
697
+ $result = array();
698
+ foreach ($sport->getAvailablePositions() as $position) {
699
+ $result[] = array($position->getId(), $position->getName());
700
+ }
701
+
702
+ return new JsonResponse($result);
703
+ }
704
+ }
705
+
706
+ .. note ::
707
+
708
+ The returned JSON should not be created from an associative array
709
+ (``$result[$position->getId()] = $position->getName()) ``) as the iterating
710
+ order in JavaScript is undefined and may vary in different browsers.
593
711
594
712
.. _cookbook-dynamic-form-modification-suppressing-form-validation :
595
713
@@ -622,3 +740,6 @@ all of this, use a listener::
622
740
623
741
By doing this, you may accidentally disable something more than just form
624
742
validation, since the ``POST_SUBMIT `` event may have other listeners.
743
+
744
+ .. _`jQuery` : http://jquery.com
745
+ .. _`FOSJsRoutingBundle` : https://github.com/FriendsOfSymfony/FOSJsRoutingBundle
0 commit comments