Skip to content

Commit f47a7c3

Browse files
committed
Updates & Fixes after public review
1 parent 2533f29 commit f47a7c3

File tree

1 file changed

+103
-44
lines changed

1 file changed

+103
-44
lines changed

cookbook/form/dynamic_form_modification.rst

+103-44
Original file line numberDiff line numberDiff line change
@@ -474,8 +474,10 @@ The meetup is passed as an entity field to the form. So we can access each
474474
sport 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.
545548
The 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
608613
your 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

637651
The associated template uses some JavaScript to update the ``position`` form
638652
field according to the current selection in the ``sport`` field. To ease things
639653
it 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

676729
The 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
680733
listener 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
*/

0 commit comments

Comments
 (0)