smugford
smugford

Reputation: 769

Finalizing Symfony2 Dynamic Form Modification

After many days of trying to figure out the Symfony to Cookbook entry ... http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html

I finally almost got the third example to work with the following code in my ProductType.php file (see below).

When I add a Game there is a corresponding server and category list that come up when I hit the submit button. The new fields only show up if I force an error by putting in invalid input into one of the fields.

I have two questions. If all the data that is not in the $formModifier variable is valid it submits the data and I do not see the "Server", and "Category" choices. How do I make sure 'server' and 'category' are required even though I do not need them to show up until the Game is selected.

The second question is in the cookbook entry it says "One piece that may still be missing is the client-side updating of your form after the sport is selected. This should be handled by making an AJAX call back to your application. In that controller, you can submit your form, but instead of processing it, simply use the submitted form to render the updated fields."

Since my event modifier is on "Submit" how do I submit to a controller entry with the selected game (id) and only render the fields that are only required after the game is selected?

Thanks in Advance.

Scott

<?php

namespace Acme\MainBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\FormInterface;
use Acme\MainBundle\Entity\Game;

class ProductsType extends AbstractType {

    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
                ->add('name')
                ->add('price')
                ->add('description')
                ->add('game')

        ;

        // ** Build Form Interface Based On Selected Game **//

        $formModifier = function(FormInterface $form, Game $game, $game_id) {

                    // ** Add Servers Based On Game Id ** // 
                    $form->add('server', 'entity', array(
                        'class' => 'Acme\MainBundle\Entity\GameServers',
                        'query_builder' => function(EntityRepository $er) use ($game_id) {
                            $query = $er->createQueryBuilder('i')
                                    ->select(array('i'))
                                    ->where('i.game_id = :game_id')
                                    ->setParameter('game_id', $game_id)
                                    ->orderBy('i.name', 'ASC');
                            return $query;
                        },
                            )
                    );
                    // ** Build Category List Based on Game Id Submitted ** //
                    // Refactor this so that is nothing is selected that it Doesn't Build Category List **/        
                    $form->add('category', 'entity', array(
                        'class' => 'Acme\MainBundle\Entity\GameCategories',
                        'query_builder' => function(EntityRepository $er) use ($game_id) {
                            $query = $er->createQueryBuilder('i')
                                    ->select(array('i'))
                                    ->where('i.game_id = :game_id')
                                    ->setParameter('game_id', $game_id)
                                    ->orderBy('i.name', 'ASC');

                            return $query;
                        },
                            )
                    );
                };

        //** Checks for Games That Are Submitted and Adds Servers and Categories Based on Game Selection **//
        $builder->get('game')->addEventListener(
                FormEvents::POST_SUBMIT, function(FormEvent $event) use ($formModifier) {
                    // Get Form Data to Pass Back to Modifier
                    $game = $event->getForm()->getData();
                    // Get Game Id to pass back to Form Modifier
                    $game_id = $event->getForm()->getData()->getId();
                    $formModifier($event->getForm()->getParent(), $game, $game_id);
                }
        );
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver) {
        $resolver->setDefaults(array(
            'data_class' => 'Acme\MainBundle\Entity\Products'
        ));
    }

    /**
     * @return string
     */
    public function getName() {
        return 'acme_mainbundle_products';
    }

}

Upvotes: 1

Views: 4322

Answers (2)

smugford
smugford

Reputation: 769

I've found the answer to the ajax part with this SO entry How to handle Ajax forms with collections in Symfony2.3

here is my implementation

javascript

$(document).ready(function () {
    $('#acme_mainbundle_products_game').change(function(){
       if($(this).val()){
           $.ajax({
        type: "POST",
        url: "{{ url('productsCreateSubmit') }}",
        data: $(this).serialize(),
        success: function(data) {
            $('#dynamic_fields').html(data);
        }
    });

    return false; 
        }
    });
});

twig in /new.html.twig

{% extends '::base.html.twig' %}

{% block body -%}

    <h1>Products creation</h1>
    {{ form_start(form) }}
    <hr />
        {{ form_label(form.name) }}
        {{ form_errors(form.name) }}
        {{ form_widget(form.name) }}
    <hr />
        {{ form_label(form.price) }}
        {{ form_errors(form.price) }}
        {{ form_widget(form.price) }}
    <hr />
        {{ form_label(form.description) }}
        {{ form_errors(form.description) }}
        {{ form_widget(form.description) }}
    <hr />
        {{ form_label(form.game) }}
        {{ form_errors(form.game) }}
        {{ form_widget(form.game) }}
    <hr />
    <div id="dynamic_fields">Stuff Will Go Here</div>
{{ form_end(form) }}

serverslist.twig.html

<hr />
 {{ form_label(form.server) }}
 {{ form_errors(form.server) }}
 {{ form_widget(form.server) }}
<hr />
<hr />
 {{ form_label(form.category) }}
 {{ form_errors(form.category) }}
 {{ form_widget(form.category) }}
<hr />

and finally the controller entry

/**
 * @Route("/products/create/submit", name="productsCreateSubmit")
 */
public function productsCreateSubmitAction(Request $request) {
    $products = new Products();

    $form = $this->createForm(new ProductsType(), $products);
    $form->handleRequest($request);

    return $this->render('AcmeMainBundle:Products:serverslist.html.twig', array(
                'form' => $form->createView(),
    ));
}

I still need to finish the validation but that is how I did the ajax to only render the required fields. It seems right and it works but if anyone has any feedback on how to do it better i'd love to hear the feedback.

Upvotes: 1

Amine
Amine

Reputation: 271

i think theres somthing wrong when getting $game Object use it like this:

 //** Checks for Games That Are Submitted and Adds Servers and Categories Based on Game Selection **//
    $builder->get('game')->addEventListener(
            FormEvents::POST_SUBMIT, function(FormEvent $event) use ($formModifier) {
                // Get Form Data to Pass Back to Modifier
                $game = $event->getForm()->getData()->getGame();
                // Get Game Id to pass back to Form Modifier
                $game_id = $game->getId();
                $formModifier($event->getForm()->getParent(), $game, $game_id);
            }
    );

and you don't use $game object, you use only $game_id in $formModifier. so you can change your function like this:

 $formModifier = function(FormInterface $form, $game_id) {
    //Adding other widgets here
 }

Upvotes: 0

Related Questions