简体   繁体   中英

Symfony Dynamic form Submitted Forms

I'm following the tutorial at http://symfony.com/doc/current/form/dynamic_form_modification.html#dynamic-generation-for-submitted-forms

The idea is I got 3 class Animal => Spieces => Race During the creation of a new animal, I would like to change dynamically the choice for the Race depending of the Spieces.

Here are my Classes : Race

class Race
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="nom", type="string", length=255, unique=true)
     */
    private $nom;

    /**
     * @ORM\ManyToOne(targetEntity="Rendy\AppBundle\Entity\Espece", inversedBy="race")
     * @ORM\JoinColumn(name="espece_id", referencedColumnName="id", onDelete="CASCADE", nullable=false)
     */
    private $espece;

Class Animal

class Animal
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="Nom", type="string", length=30)
     */
    private $nom;

    /**
     * @var Espece
     *
     *
     * @ORM\ManyToOne(targetEntity="Espece")
     * @ORM\JoinColumn(name="espece_id", referencedColumnName="id", onDelete="CASCADE")
     */
    private $espece;

    /**
     * @var string
     *
     * @ORM\ManyToOne(targetEntity="Race")
     * @ORM\JoinColumn(name="race_id", referencedColumnName="id", onDelete="CASCADE")
     */
    private $race;

And class Spieces :

class Espece
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
    /**
     * @var string
     *
     * @ORM\Column(name="nom", type="string", length=255, unique=true)
     */
    private $nom;


    /**
     * @ORM\OneToMany(targetEntity="Race", mappedBy="espece")
     * @ORM\JoinColumn(name="race_id", referencedColumnName="id")
     */
    private $race;

   /**
     * Get race
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getRace()
    {
        return $this->race;
    }

Here is my Controller

public function newAction(Request $request)
    {
        $animal = new Animal();

        $form = $this->createForm(AnimalType::class, $animal);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
          // do something after
                      }

        return $this->render('RendyAppBundle:animal:new.html.twig', array(
            'form' => $form->createView(),
        ));
    }

Here my AnimalType Form

class AnimalType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('nom');


        $builder->add('espece', EntityType::class, array(
            'class'         => 'RendyAppBundle:Espece',
            'choice_label'  => 'nom',
            'placeholder'   => '',
            ));

        $formModifier = function (FormInterface $form, Espece $espece = null) {
            $race = null === $espece ? array() : $espece->getRace();

            $form->add('race', EntityType::class, array(
                'class'         => 'RendyAppBundle:Race',
                'choice_label'  => 'nom',
                'placeholder'   => '',
                'choices'       => $race,
            ));
        };
        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($formModifier) {

                $data = $event->getData();
                $formModifier($event->getForm(), $data->getEspece());
            }
        );

        $builder->get('espece')->addEventListener(
            FormEvents::POST_SUBMIT,
            function (FormEvent $event) use ($formModifier) {
                $espece = $event->getForm()->getData();
                $formModifier($event->getForm()->getParent(), $espece);
            }
        );

End the view !

{% block body%}

    {{ form_start(form) }}
        {# render the task's only field: description #}
        {{ form_row(form.nom) }}
        {{ form_row(form.espece, {'id': 'test','attr': {'onChange': 'changed()'}}) }}
        {{ form_row(form.race, {'id': 'animal_race', 'attr': { 'class': 'form-control'}}) }}
        {{ form_row(form.sexe) }}
        {{ form_row(form.age) }}

        {{ form_row(form.puce) }}
        {{ form_row(form.poids) }}

        <h3>Comportement</h3>
        <ul class="comportement">
            {# iterate over each existing tag and render its only field: name #}
            {% for comportement in form.comportement %}
                <li>{{ form_row(comportement.name) }}</li>
            {% endfor %}
        </ul>
    {{ form_end(form) }}



{% endblock %}

{% block ajax %}
<script>

function changed() {
    var espece = $('#test');
    // ... retrieve the corresponding form.
    var $form = $('#form');

    console.log($form);
    // Simulate form data, but only include the selected sport value.
    var data = {};
    data[espece.attr('nom')] = espece.val();
    // Submit data via AJAX to the form's action path.
    jQuery.ajax({
        url : $form.attr('action'),
        type: "POST",
        data: data,
        success: function (html) {
            console.log(html)
            $("#animal_race").replaceWith(
                // ... with the returned one from the AJAX response.
                $(html).find("#animal_race")
            );
            // Position field now displays the appropriate positions.
        }
    });
}
</script>
{% endblock %}

The Ajax function is called when I change the value of "Especes" but my field Race is still blank... (my block ajax is after the block javascripts)

The Ajax request : POST Parameters Key Value undefined "2"

For information when I do a simple Animal->getEspece()->getRace(); I got an Array with the good information.

I googled, try, googled try and I think I miss something.

Thank you for your help

You are sending the post-request (in the AJAX call) to the same URL (the form-action url) as the page is loaded from (the newAction's Request). This could work if your newAction method would actually be sending data to incoming POST/GET requests, but this is not at all the case here.

I recommend to always create a separate route (and thus separate controller method) specifically for the POST request. You also cannot send the data (that is in an object when you retrieve it from the database) 'as is'. You are gonna have to convert it into for example, JSON. You could potentially convert your whole object directly to JSON ( https://symfony.com/doc/current/components/serializer.html ), but it might be better to only get the information that you need. For this you can create an array that holds this information

All in all, your controller method would look something like this:

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;

public function getRacesJsonAction(Request $request)
{
    //Get the race data here that you want to send (edit to your own needs)
    $race = $Animal->getEspece()->getRace();

    //Convert the entity directly to a JSON
    $jsonContent = $serializer->serialize($race, 'json');

    //create a response 
    $response = new Response($jsonContent);
    $response->headers->set('Content-Type', 'application/json');

    return $response;
}

In your frontend you are gonna have to decode the json into a javascript array like so:

  $.ajax({
      type: 'POST',
      url: url,
      data: {
          data: dataObj
      },
      dataType: 'json',
      success: function(data) {
        console.log('success');
        //Obj is the array with information now
        obj = JSON.parse(json);
      },
      error: function(data) {
        console.log('fail');
      }
  });

However you cannot directly fill this information that is in the obj-array. You are gonna have to parse it, since every entry in an HTML-dropdown is wrapped like this:

<ul>
</li>race1</li>
</li>race2</li>
</li>race3</li>
</ul>

Its a extensive implementation. I might have made errors in the syntax, so beware. I also recommend you to use 'FOSJsRoutingBundle', it enables you to create url's from routes in javascript. Something you would likely use to convert the route's path into a javascript variable that holds the url.

Good luck!

Here is my code who is working

Controller :

<?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

use AppBundle\Entity\Animal;
use AppBundle\Form\Type\AnimalType;
// ...

class MeetupController extends Controller
{
    /**
     * @Route("/animal")
     */
    public function animalAction(Request $request)
    {
        $meetup = new Animal();
        $form = $this->createForm(AnimalType::class, $meetup);
        $form->handleRequest($request);
        if ($form->isValid()) {
            // ... save the meetup, redirect etc.
        }

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

    // ...
}

animal.html.twig

{% block body %}
{{ form_start(form) }}

   {{ form_row(form.espece) }}
   {{ form_row(form.race) }}
    {# ... #}
{{ form_end(form) }}

<script   src="https://code.jquery.com/jquery-2.2.4.min.js"   integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="   crossorigin="anonymous"></script>
<script>
    var $espece = $('#animal_espece');
    // When especegets selected ...
    $espece.change(function() {
        console.log("Dans la fonction Change");
        // ... retrieve the corresponding form.
        var $form = $(this).closest('form');
        // Simulate form data, but only include the selected espece value.
        var data = {};
        data[$espece.attr('name')] = $espece.val();

        // Submit data via AJAX to the form's action path.
        $.ajax({
            url : $form.attr('action'),
            type: $form.attr('method'),
            data : data,
            success: function(html) {
                // Replace current race field ...
                $('#animal_race').replaceWith(
                        // ... with the returned one from the AJAX response.
                        $(html).find('#animal_race')
                );
                // race field now displays the appropriate positions.
            }
        });
    });
</script>
{% endblock %}

AnimalType

<?php

namespace AppBundle\Form\Type;

use AppBundle\Entity\Espece;
use AppBundle\Entity\Animal;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormInterface;

// ...

class AnimalType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('espece', EntityType::class, array(
                'class'       => 'AppBundle:Espece',
                'placeholder' => '',
            ))
        ;

        $formModifier = function (FormInterface $form, Espece $sport = null) {
            $positions = null === $sport ? array() : $sport->getAvailablePositions();

            $form->add('race', EntityType::class, array(
                'class'       => 'AppBundle:Race',
                'placeholder' => '',
                'choices'     => $positions,
            ));
        };

        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($formModifier) {
                // this would be your entity, i.e. SportMeetup
                $data = $event->getData();

                $formModifier($event->getForm(), $data->getEspece());
            }
        );

        $builder->get('espece')->addEventListener(
            FormEvents::POST_SUBMIT,
            function (FormEvent $event) use ($formModifier) {
                $sport = $event->getForm()->getData();
                $formModifier($event->getForm()->getParent(), $sport);
            }
        );
    }

}

Espece Entity

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

/**
 * Espece
 *
 * @ORM\Table(name="espece")
* @ORM\Entity(repositoryClass="AppBundle\Repository\EspeceRepository")
 */
class Espece
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
    /**
     * @var string
     *
     * @ORM\Column(name="nom", type="string", length=255, unique=true)
     */
    private $nom;

   /**
    * @var Collection|Race[]
    *
    * @ORM\OneToMany(targetEntity="Race", mappedBy="espece")
    */
   protected $racesdisponibles;


    public function __constructor()
    {
        $this->racesdisponibles = new ArrayCollection();
    }

    public function __toString()
    {
        return $this->nom;
    }

And Race Entity

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Race
 *
 * @ORM\Table(name="race")
  * @ORM\Entity(repositoryClass="AppBundle\Repository\RaceRepository")
 */
class Race
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="nom", type="string", length=255, unique=true)
     */
    private $nom;

    /**
     * @var Espece
     * @ORM\ManyToOne(targetEntity="Espece", inversedBy="racesdisponibles")
     * @ORM\JoinColumn(name="espece_id", referencedColumnName="id", onDelete="CASCADE", nullable=false)
     */
    private $espece;

    public function __toString()
    {
        return $this->nom;
    }

If needed send me a message and I will send you the Bundle :) My mistake was here

data[espece.attr('nom')] = espece.val();

Regarding the DOM the good anwser is

data[espece.attr('name')] = espece.val();

I thought that "name" was an attribute to sport entity and no to the <div name="beeeeer">

Hope that will help someone

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM