I want to make a "two" level dynamic symfony form with this structure:
skillCard: EntityType
session: EntityType
turn: ChoiceType
When I choose the skillCard, I populate the session field, after choosing session I populate the turn field.
At this moment I made this
$builder->add('skillCard', EntityType::class, [
'class' => SkillCard::class,
'choices' => $student->getSkillCards()->filter(function (SkillCard $skillCard) {
return $skillCard->getStatus() != EnumSkillCard::EXPIRED &&
count($skillCard->getSkillCardModulesNotPassed()) > 0;
}),
'placeholder' => ''
]);
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $formEvent) {
/** #var Booking */
$data = $formEvent->getData();
$this->addSessionField($formEvent->getForm(), $data->getSkillCard());
$this->formModifierBySession($formEvent->getForm(), $data->getSession());
}
);
$builder->get('skillCard')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $formEvent) {
$skillCard = $formEvent->getForm()->getData();
$this->addSessionField($formEvent->getForm()->getParent(), $skillCard);
}
);
protected function addSessionField(FormInterface $form, SkillCard $skillCard = null)
{
$builder = $form->getConfig()->getFormFactory()->createNamedBuilder('session', EntityType::class, null, [
'auto_initialize' => false,
'class' => Session::class,
'placeholder' => '',
'query_builder' => function (EntityRepository $er) use ($skillCard) {
$qb = $er->createQueryBuilder('s');
if (is_null($skillCard)) {
$qb->where('s = 0');
} else {
$qb->where('s.certification = :id')
->andWhere('s.subscribeExpireDate > :date')
->setParameter('id', $skillCard->getCertification())
->setParameter('date', new DateTime());
}
return $qb;
},
]);
$builder->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $formEvent) {
$session = $formEvent->getForm()->getData();
$this->formModifierBySession($formEvent->getForm()->getParent(), $session);
}
);
$form->add($builder->getForm());
}
protected function formModifierBySession(FormInterface $form, Session $session = null)
{
$form->add('turn', ChoiceType::class, [
'choices' => is_null($session) ? [] : range(0, $session->getRounds()),
]);
}
This is the javascript code (for ajax calls):
$(document).ready(function () {
const $skillCard = $('#booking_skillCard');
const $form = $('form[name = "booking"]');
$skillCard.change(function () {
sendAjax($skillCard, $form, function (html) {
replaceFormField('#booking_module', html);
replaceFormField('#booking_session', html);
sessionField($form);
});
});
});
function sessionField($form) {
const $session = $('#booking_session');
$session.change(function () {
sendAjax($session, $form, function (html) {
replaceFormField('#booking_turn', html);
});
});
}
function sendAjax($jObj, $form, successCallBack) {
let data = {};
data[$jObj.attr('name')] = $jObj.val();
console.log($form);
$.ajax({
url: $form.prop('action'),
type: $form.prop('method'),
data: data,
success: successCallBack
})
}
function replaceFormField(selector, html) {
$(selector).replaceWith(
$(html).find(selector)
);
}
When I choose the skillCard, no problems, but when I choose the session I get a TrasformationFailedException: "Unable to reverse value for property path "session": The choice "x" does not exist or is not unique", so the turn field is not populated because $session = $formEvent->getForm()->getData(); is null.
Any suggestion is appreciate.
I have a form which has a dynamic field :
<?php
namespace AppBundle\Form;
//uses ...
class AnnonceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('titre')
->add('description')
->add('groupeCompetence', EntityType::class, [
'class' => 'AppBundle\Entity\GroupeCompetences',
'choice_label' => 'nom',
'placeholder' => 'Sélectionnez votre type de service',
])
->add('prix')
->add('serviceADistance')
->add('ajouter', SubmitType::class);
$formModifier = function (FormInterface $form, GroupeCompetences $groupeCompetences=null){
$competences = null === $groupeCompetences ? array() : $groupeCompetences->getCompetences();
$form->add('competence', EntityType::class, array(
'class' => 'AppBundle\Entity\Competence',
'choice_label' => 'nom',
'placeholder' => 'Choisir une compétence',
'choices' => $competences,
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. CompetenceChoisie
$data = $event->getData();
//var_dump($data);
//die();
$formModifier($event->getForm(), $data->getGroupeCompetence());
}
);
$builder->get('groupeCompetence')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$groupeCompetences = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $groupeCompetences);
}
);
}
}
I have this code in ajax :
<script>
var $groupeCompetence = $('#requete_prestataire_groupeCompetence');
// When sport gets selected ...
$groupeCompetence.change(function() {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected sport value.
var data = {};
data[$groupeCompetence.attr('name')] = $groupeCompetence.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 position field ...
$('#requete_prestataire_competence').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#requete_prestataire_competence')
);
// Position field now displays the appropriate positions.
}
});
});
</script>
In fact, competences is generated dynamicly depending on GroupeCompetence.
And I want to test this in PHPUnit.
I tried this :
public function testIndexRechercheUtilisateurNonConnecte()
{
$crawler = $this->client->request('GET', '/');
$form = $crawler->selectButton('requete_prestataire_Rechercher')->form();
$form['requete_prestataire[groupeCompetence]'] = 2;
$form['requete_prestataire[competence]'] = "";
$crawler = $this->client->submit($form);
$this->assertTrue($this->client->getResponse()->isRedirect());
$client->followRedirect();
/*$this->assertEquals(3, $crawler->filter('a [class = "btn-sm btn-primary"]')->count());*/
}
The problem is that : $form['requete_prestataire[competence]'] is generated dynamicly as I said.
I want to be able to do the ajax request in the test, and then test the output.
How can I proceed ?
Thanks in advance
I found the solution :
you have to disable validation, and send the form as it was generated :
$crawler = $this->client->request('GET', '/');
$form = $crawler->selectButton('requete_prestataire_Rechercher')->form();
$form['requete_prestataire[groupeCompetence]'] = 2;
$form['requete_prestataire[competence]']->disableValidation()->select(50);
$crawler = $this->client->submit($form);
Problem is that nothing is loaded in the municipality field, it goes undefined. In the AJAX code I get the value of the province well. But in the class addMunicipioField.php does not take the value of the $province, it is always nul
I am trying to make a registration form where part of the usual fields (name, nick, password, ...) I also add two dependent fields Municipality and Province.
The codec Controler:
class UserController extends Controller {
private $session;
public function __construct() {
$this->session = new Session();
}
public function registerAction(Request $request) {
if (is_object($this->getUser())) {
return $this->redirect('home');
}
$user = new DbUsuario();
$form = $this->createForm(RegistreUserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery('SELECT u FROM BackendBundle:DbUsuario u WHERE u.email = :email OR u.nick = :nick')
->setParameter('email', $form->get("email")->getData())
->setParameter('nick', $form->get("nick")->getData());
$user_isset = $query->getResult();
if (count($user_isset) == 0) {
$factory = $this->get("security.encoder_factory");
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword($form->get("password")->getData(), $user->getSalt());
$user->setPassword($password);
$user->setRole("ROLE_USER");
$user->setImagen(null);
$em->persist($user);
$flush = $em->flush();
if ($flush == null) {
$status = "Te has registrado correctamente";
$this->session->getFlashBag()->add("status", $status);
return $this->redirect("login");
} else {
$status = "No te has registrado correctamente";
}
} else {
$status = "Usuario ya esta registrado.";
}
} else {
$status = "No te has registrado correctamente.";
}
$this->session->getFlashBag()->add("status", $status);
}
return $this->render('AppBundle:User:register.html.twig', array(
"form" => $form->createView() # Genera el html del formulario.
));
}
The Entity that creates the form is DbUsuario, which has the idMunicipio field.
/** #var \BackendBundle\Entity\DbMunicipios */
private $idMunicipio;
/**
* Set idMunicipio
* #param \BackendBundle\Entity\DbMunicipio $idMunicipio
* #return DbUsuario
*/
public function setIdMunicipio(\BackendBundle\Entity\DbMunicipio $idMunicipio = null) {
$this->idMunicipio = $idMunicipio;
return $this;
}
/**
* Get idMunicipio
* #return \BackendBundle\Entity\DbMunicipio
*/
public function getIdMunicipio() {
return $this->idMunicipio;
}
Then the Entity Of DbMunicipio that connects with 'province' with :
/** #var \BackendBundle\Entity\DbProvincia */
private $provincia;
/**#param \BackendBundle\Entity\DbProvincia $provincia
* #return DbMunicipio
*/
public function setProvincia(\BackendBundle\Entity\DbProvincia $provincia = null){
$this->provincia = $provincia;
return $this;
}
// And implement this function.
public function __toString(){
return $this->getMunicipio();
}
/**#return \BackendBundle\Entity\DbProvincia */
public function getProvincia(){
return $this->provincia;
}
And the Entity DbProvincia that only has the fields (id (integer), slug (String) and province (String)).
I define the form as follows:
namespace AppBundle\Form;
use ....
class RegistreUserType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$factory = $builder->getFormFactory();
$builder->add('nombre', TextType::class, array('label' => 'Nombre',
'required' => 'required',
'attr' => array('class' => 'form-nombre form-control')
));
$builder->add('apellido', TextType::class, array('label' => 'Apellido',
'required' => 'required',
'attr' => array('class' => 'form-apellido form-control')
));
$builder->add('nick', TextType::class, array('label' => 'Nick',
'required' => 'required',
'attr' => array('class' => 'form-nick form-control nick-input')
));
$provinSubscriber = new AddProvinciaField($factory);
$builder->addEventSubscriber($provinSubscriber);
$muniSubscriber = new AddMunicipioField($factory);
$builder->addEventSubscriber($muniSubscriber);
$builder->add('email', EmailType::class, array('label' => 'Correo electrónico',
'required' => 'required',
'attr' => array('class' => 'form-email form-control')
));
$builder->add('password', PasswordType::class, array('label' => 'Password',
'required' => 'required',
'attr' => array('class' => 'form-password form-control')
));
$builder->add('Registrarse', SubmitType::class, array("attr" => array("class" => "form-submit btn btn-success")));
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'BackendBundle\Entity\DbUsuario'
));
}
public function getBlockPrefix() { return 'backendbundle_dbusuario'; }
}
I define within the AppBundle \ Form \ eventListener \ AddProvinciaField the classes called in the form:
namespace AppBundle\Form\EventListener;
use ....
use BackendBundle\Entity\DbProvincia;
class AddProvinciaField implements EventSubscriberInterface {
private $factory;
public function __construct(FormFactoryInterface $factory) {
$this->factory = $factory;
}
public static function getSubscribedEvents() {
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmit'
);
}
private function addProvinciaForm($form, $provincia) {
$form -> add('provincia', EntityType::class, array(
'class' => 'BackendBundle:DbProvincia',
'label' => 'Provincia',
'placeholder' => '_ Elegir _',
'auto_initialize' => false,
'mapped' => false,
'attr'=> array('class' => 'form-provincia form-control provincia-input'),
'query_builder' => function (EntityRepository $repository) {
$qb = $repository->createQueryBuilder('provincia');
return $qb;
}
));
}
public function preSetData(FormEvent $event){
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {return;}
$provincia = ($data->getIdMunicipio()) ? $data->getIdMunicipio()->getProvincia() : null ;
$this->addProvinciaForm($form, $provincia);
}
public function preSubmit(FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if (null === $data) { return;}
$provincia = array_key_exists('provincia-input', $data) ? $data['provincia-input'] : null;
$this->addProvinciaForm($form, $provincia);
}
}
And later I define AddMunicipioField.php:
namespace AppBundle\Form\EventListener;
use ....
use BackendBundle\Entity\DbProvincia;
class AddMunicipioField implements EventSubscriberInterface {
private $factory;
public function _construct(FormFactoryInterface $factory) {
$this->factory = $factory;
}
public static function getSubscribedEvents() {
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmit'
);
}
private function addMunicipioForm($form, $provincia) {
$form->add('idMunicipio', EntityType::class, array(
'class' => 'BackendBundle:DbMunicipio',
'label' => 'Municipio',
'placeholder' => '_ Elegir _',
'auto_initialize' => false,
'attr'=> array('class' => 'form-municipio form-control municipio-input'),
'query_builder' => function (EntityRepository $repository) use ($provincia) {
$qb = $repository->createQueryBuilder('idMunicipio')
->innerJoin('idMunicipio.provincia', 'provincia');
if ($provincia instanceof DbProvincia) {
$qb->where('idMunicipio.provincia = :provincia')
->setParameter('provincia', $provincia);
} elseif (is_numeric($provincia)) {
$qb->where('provincia.id = :provincia')
->setParameter('provincia', $provincia);
} else {
$qb->where('provincia.provincia = :provincia')
->setParameter('provincia', null);
}
return $qb;
}
));
}
public function preSetData(FormEvent $event){
$data = $event->getData();
$form = $event->getForm();
if (null === $data) { return; }
$provincia = ($data->getIdMunicipio()) ? $data->getIdMunicipio()->getProvincia() : null ;
$this->addMunicipioForm($form, $provincia);
}
public function preSubmit(FormEvent $event){
$data = $event->getData();
$form = $event->getForm();
if (null === $data) { return; }
$provincia = array_key_exists('provincia_input', $data) ? $data['provincia_input'] : null;
$this->addMunicipioForm($form, $provincia);
}
}
And finally the AJAX request:
$(document).ready(function(){
var $form = $(this).closest('form');
$(".provincia-input").change(function(){
var data = { idMunicipio: $(this).val() };
$.ajax({
type: 'POST',
url: $form.attr('action'),
data: data,
success: function(data) {
for (var i=0, total = data.length; i < total; i++) {
$('.municipio-input').append('<option value="' + data[i].id + '">' + data[i].municipio + '</option>');
}
}
});
});
});
I added var_dump and alert() in the code. This is the way output.
In this case, the value of province is always null.
addMunicipioField.php
public function preSetData(FormEvent $event){
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
$provincia = ($data->getIdMunicipio()) ? $data->getIdMunicipio()->getProvincia() : null ;
var_dump('presetdata');
var_dump($provincia);
$this->addMunicipioForm($form, $provincia);
}
AJAX:
$(document).ready(function(){
var $form = $(this).closest('form');
$(".provincia-input").change(function(){
alert($('.provincia-input').val()); // THIS IS CORRECT VALUE, INTEGER.
var data = { idMunicipio: $(this).val() };
$.ajax({
type: 'POST',
url: $form.attr('action'),
data: data,
success: function(data) {
alert(data);
alert(data.length); // THIS IS INCORRECT.
for (var i=0, total = data.length; i < total; i++) {
$('.municipio-input').append('<option value="' + data[i].id + '">' + data[i].municipio + '</option>');
}
}
});
});
});
Another point of view
The entities are the same.
In this case it works but I must press the send button. How could I do it without pressing the button, that it was automatic change?
The class RegistreUserType extends AbstractType I add the following lines.
$builder -> add('provincia', EntityType::class, array(
'class' => 'BackendBundle:DbProvincia',
'label' => 'Provincia',
'placeholder' => '_ Elegir _',
'auto_initialize' => false,
'mapped' => false,
'attr'=> array('class' => 'form-provincia form-control provincia-input'),
'query_builder' => function (EntityRepository $repository) {
$qb = $repository->createQueryBuilder('provincia');
return $qb;
}
));
$builder->add('idMunicipio', EntityType::class, array(
'class' => 'BackendBundle:DbMunicipio',
'label' => 'Municipio',
'placeholder' => '_ Elegir _',
'auto_initialize' => false,
'mapped' => false,
'attr'=> array('class' => 'form-municipio form-control municipio-input')
));
$builder->addEventSubscriber(new AddMunicipioField());
The new class AddMunicpioField():
class AddMunicipioField implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return array(
FormEvents::PRE_SUBMIT => 'preSubmit',
FormEvents::PRE_SET_DATA => 'preSetData',
);
}
public function preSubmit(FormEvent $event){
$data = $event->getData();
$this->addField($event->getForm(), $data['provincia']);
}
protected function addField(Form $form, $provincia){
$form->add('idMunicipio', EntityType::class, array(
'class' => 'BackendBundle:DbMunicipio',
'label' => 'Municipio',
'placeholder' => '_ Elegir _',
'auto_initialize' => false,
'mapped' => false,
'attr'=> array('class' => 'form-municipio form-control municipio-input'),
'query_builder' => function(EntityRepository $er) use ($provincia){
$qb = $er->createQueryBuilder('idMunicipio')
->where('idMunicipio.provincia = :provincia')
->setParameter('provincia', $provincia);
return $qb;
}
));
}
Codec Ajax:
$(document).ready(function () {
$('.provincia-input').change(function () {
var $form = $(this).closest('form');
var data = $('.provincia-input').serialize();
$.ajax({
url: $form.attr('action'),
type: 'POST',
data: data,
success: function (data) {
$('.municipio-input').replaceWith($(html).find('.municipio-input'));
}
});
});
});
I did not notice a field or a property called 'select_provincia' in neither your entity, nor the main form, so I will try guessing, that it probably should be called simply 'provincia', as that is the name for both the property in municipality entity and in the form subscriber for municipality. Also in AddMunicipioField.php you should change this code:
if ($provincia instanceof DbProvincia) {
$qb->where('idMunicipio.provincia = :provincia')
>setParameter('provincia', $provincia);
}
to this:
if ($provincia instanceof DbProvincia) {
$qb->where('idMunicipio.provincia = :provincia')
>setParameter('provincia', $provincia->getId());
}
since when querying you will be comparing provincia to the ID of province.
Further more, make sure you have implemented the __toString() method in the municipality entity, so that symfony would know how to convert that object to a string in order to show it in the select list.
Hope this helps :)
Seeing that you have added new information i will update my answer:
Firstly, In the AddMunicipioField.php you still have basically the same error:
the array key is going to be called the same way you name your field, in this case not 'provincia_input', but 'provincia'. You can see the data that was posted to you by calling "dump($data); die;" just before you check if the array key exists (check for a key name "provincia", as you can see the name matches what you have specified when adding the field to the form (AddProvinciaField.php):
$form -> add('provincia', EntityType::class
Another thing I have noticed in the first js snippet you have posted is that in this part of code:
$(".provincia-input").change(function(){
var data = { idMunicipio: $(this).val() };
$.ajax({
type: 'POST',
url: $form.attr('action'),
data: data,
success: function(data) {
for (var i=0, total = data.length; i < total; i++) {
$('.municipio-input').append('<option value="' + data[i].id + '">' + data[i].municipio + '</option>');
}
}
});
});
you are taking the input from $(".provincia-input") and sending it as a value for a field called "idMunicipio", which in you situation I don't think makes any sense.
Lastly, I will discus the errors that were made in the last piece of the JS you've posted:
$(document).ready(function () {
$('.provincia-input').change(function () {
var $form = $(this).closest('form');
var data = $('.provincia-input').serialize();
$.ajax({
url: $form.attr('action'),
type: 'POST',
data: data,
success: function (data) {
$('.municipio-input').replaceWith($(html).find('.municipio-input'));
}
});
});
});
First of all, class names are not supposed to be used for identifying the fields that you are using. By definition they are supposed to be used multiple time in the document and describe only style, which might lead to some unexpected behaviour as your codebase grows. Please assign proper ID values to the inputs that you are going to be querying and especially replacing so that you could identify them correctly.
Secondly, please refer to the JS code posted in the official Symfony tutorial by following this link. As you can see the proper way to post data back to the server is not by sending a lone property like you are trying to do in this line:
var data = $('.provincia-input').serialize();
but rather by sending the property as a part of the forms data. So as in the tutorial I've posted, please first create an empty data object:
var data = {};
then add the province value to it:
data[$(this).attr('name')] = $(this).val();
Thirdly, this part of code is clearly incorrect:
success: function (data) {
$('.municipio-input').replaceWith($(html).find('.municipio-input'));
}
As you can see the html variable is undefined in that part of code. This of course is because the variable that you are supposed to be using in this case is called data (the response that you have gotten from the server). So please change it to this:
success: function (data) {
$('.municipio-input').replaceWith($(data).find('.municipio-input'));
}
Lastly, if you are still learning SF and web programming, I would like to suggest taking the bottom up approach to advance your programming knowledge instead, since this case is pretty complex and issues that prevented your code from working still require deeper understanding of the technologies you are using. I would personally suggest reading up on HTML attribute usage, Symfony form handling, read up on what data is available to you during each Symfony form event and maybe try using the dumper component of symfony more to debug your code, since var_dump is really a very inefficient way to debug SF code (would have solved many problems for you).
Solved!!
In my form I added the call to the two new classes:
$builder -> addEventSubscriber(new AddMunicipioFieldSubscriber('idMunicipio'));
$builder -> addEventSubscriber(new AddProvinceFieldSubscriber('idMunicipio'));
The firth select is province, this is the class:
class AddProvinceFieldSubscriber implements EventSubscriberInterface {
private $propertyPathToMunicipio;
public function __construct($propertyPathToMunicipio) {
$this->propertyPathToMunicipio = $propertyPathToMunicipio;
}
public static function getSubscribedEvents() {
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmit'
);
}
private function addProvinceForm($form, $Province = null) {
$formOptions = array(
'class' => 'BackendBundle:DbProvincia',
'mapped' => false,
'label' => 'Provincia',
'attr' => array(
'class' => 'class_select_provincia',
),
);
if ($Province) {
$formOptions['data'] = $Province;
}
$form->add('provincia', EntityType::class, $formOptions);
}
public function preSetData(FormEvent $event){
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
$accessor = PropertyAccess::createPropertyAccessor();
$municipio = $accessor->getValue($data, $this->propertyPathToMunicipio);
$provincia = ($municipio) ? $municipio->getIdMunicipio()->getProvincia() : null;
$this->addProvinceForm($form, $provincia);
}
public function preSubmit(FormEvent $event){
$form = $event->getForm();
$this->addProvinceForm($form);
}
}
The second class is Municipi:
class AddMunicipioFieldSubscriber implements EventSubscriberInterface {
//put your code here
private $propertyPathToMunicipio;
public function __construct($propertyPathToMunicipio){
$this->propertyPathToMunicipio = $propertyPathToMunicipio;
}
public static function getSubscribedEvents(){
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmit'
);
}
private function addCityForm($form, $province_id){
$formOptions = array(
'class' => 'BackendBundle:DbMunicipio',
'label' => 'Municipio',
'attr' => array(
'class' => 'class_select_municipio',
),
'query_builder' => function (EntityRepository $repository) use ($province_id) {
$qb = $repository->createQueryBuilder('municipio')
->innerJoin('municipio.provincia', 'provincia')
->where('provincia.id = :provincia')
->setParameter('provincia', $province_id)
;
return $qb;
}
);
$form->add($this->propertyPathToMunicipio, EntityType::class, $formOptions);
}
public function preSetData(FormEvent $event){
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
$accessor = PropertyAccess::createPropertyAccessor();
$municipio = $accessor->getValue($data, $this->propertyPathToMunicipio);
$province_id = ($municipio) ? $municipio->getIdMunicipio()->getProvincia()->getId() : null;
$this->addCityForm($form, $province_id);
}
public function preSubmit(FormEvent $event){
$data = $event->getData();
$form = $event->getForm();
$province_id = array_key_exists('provincia', $data) ? $data['provincia'] : null;
$this->addCityForm($form, $province_id);
}
}
The controled add this function:
public function municipioTestAction(Request $request){
$provincia_id = $request->get('provincia_id');
$em = $this->getDoctrine()->getManager();
$provincia = $em->getRepository('BackendBundle:DbMunicipio')->findByProvinceId($provincia_id);
return new JsonResponse($provincia);
}
Where the function findByProvinceId, I create it as a repository of the entity DbMunicipio.
class DbMunicipioRepository extends EntityRepository{
public function findByProvinceId($provincia_id){
$query = $this->getEntityManager()->createQuery("
SELECT muni
FROM BackendBundle:DbMunicipio muni
LEFT JOIN muni.provincia provin
WHERE provin.id = :provincia_id
")->setParameter('provincia_id', $provincia_id);
return $query->getArrayResult();
}
}
And de codec AJAX.
$(document).ready(function () {
$(".class_select_provincia").change(function(){
var data = {
provincia_id: $(this).val()
};
$.ajax({
type: 'POST',
url: URL+'/municipio-test',
data: data,
success: function(data) {
var $muni_selector = $('.class_select_municipio');
alert(data);
$muni_selector.html('<option>Ciudad</option>');
for (var i=0, total = data.length; i < total; i++) {
$muni_selector.append('<option value="' + data[i].id + '">' + data[i].municipio + '</option>');
}
}
});
});
});
I have a form that accepts multiple arrays of id's, when then should insert it into the database. Here is the code:
This is part of the collection type
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('records', CollectionType::class, array(
'entry_type' => EntityType::class,
'entry_options' => array(
'class' => Document::class
)
))
->add('submit', SubmitType::class);
}
This is part of the javascript that sends a AJAX POST request:
function addDocument(runsheet, documents) {
var params = {};
params.runsheet_documents = {};
params.runsheet_documents.records = documents;
return $http.post(API_CONFIG.url + '/runsheets/' + runsheet + '/documents', params)
.success(function(current, response) {
return true;
})
.error(function(response) {
return false;
}
);
}
This is my controller for the route:
public function postDocumentsAction(Request $request, Runsheet $runsheet)
{
$form = $this->createForm(RunsheetDocumentsType::class, $runsheet);
$form->submit($request->request->all());
$runsheet = $form->getData();
$this->get('title.api.runsheet')->update($runsheet);
$this->get('title.logger.event')->flush();
}
Whenever I try to send an AJAX POST request I get this:
Using Symfony2.3.4 and PHP5.6.3
People, I've been looking for this issue for a while now and yes, I've found some similar ones and even found this in the Cookbook.
Now you'd say, "This guy is pretty slow", bingo, I am. Please help me out because I can't seem to get this or any other example I've encountered to help me in my own problem.
What I need is to populate a select field when the user selects an item from another select field. All this happens in a standard-CRUDgenerated-Symfony2 form. Both selects stand for an entity collection each(Zone and UEB), being Zone the independent one.
Community: Stop talking and give me the code!
Me: OK, here is what I have so far:
//ReferenceController.php
public function newAction() {
$entity = new Reference();
$form = $this->createCreateForm($entity);
return $this->render('CCBundle:Reference:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
public function createAction(Request $request) {
$entity = new Reference();
$form = $this->createCreateForm($entity);
$form->bind($request);
/*
var_dump($form->get('UEB')->getData());
var_dump($form->get('UEB')->getNormData());
var_dump($form->get('UEB')->getViewData());
die();
*/
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('reference_show', array('id' => $entity->getId())));
}
return $this->render('CCBundle:Reference:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
private function createCreateForm(Reference $entity) {
$form = $this->createForm(new ReferenceType(), $entity, array(
'action' => $this->generateUrl('reference_create'),
'method' => 'POST',
));
return $form;
}
And
//ReferenceType.php
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('suffix')
->add('zone', null, array(
'required' => true,
))
;
//What follows is for populating UEB field accordingly,
//whether it's a "createForm" or an "editForm"
if ($options['data']->getId() !== null) {
$formModifier = function (FormInterface $form, Zone $zone = null) {
$UEBs = null === $zone ? array() : $zone->getUEBs();
$form->add('UEB', 'entity', array(
'required' => true,
'label' => 'UEB',
'class' => 'CCBundle:UEB',
// 'empty_value' => '',
'choices' => $UEBs,
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($formModifier) {
$data = $event->getData();
$formModifier($event->getForm(), $data->getZone());
});
} else {
$formModifier = function (FormInterface $form) {
$form->add('UEB', 'entity', array(
'required' => true,
'label' => 'UEB',
'class' => 'CCBundle:UEB',
'query_builder' =>
function(EntityRepository $er) {
return $er->createQueryBuilder('u')
->where('u.zone = :zone')
->setParameter('zone', $er->findFirstZone());
}
)
);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($formModifier) {
$formModifier($event->getForm());
});
}
And
//base.js
var goalURL = "" + window.location;
if (goalURL.slice(-13) === 'reference/new' || goalURL.match(/reference\/\d+\/edit$/))
{
//case new reference
goalURL = goalURL.replace('reference/new', 'reference/update_uebs/');
//case edit reference
goalURL = goalURL.replace(/reference\/\d+\/edit/, 'reference/update_uebs/');
//this is the function run every time the "new" or "edit" view is loaded
//and every time the Zone select field is changed
var runUpdateUEBs = function() {
$.getJSON(goalURL, {id: $('#cc_ccbundle_reference_zone').val()}, function(response) {
$('#cc_ccbundle_reference_UEB').children('option').remove();
var non_selected_options = [];
var index = 0;
$.each(response, function(key, val) {
var option = $('<option selected="selected"></option>');
option.text(val);
option.val(key);
option.prop('selected', 'selected');
option.appendTo($('#cc_ccbundle_reference_UEB'));
non_selected_options[index++] = $(option);
});
var amount = non_selected_options.length;
if (amount > 1)
$.each(non_selected_options, function(key, val) {
if (amount - 1 === key)
val.attr('selected', false);
});
});
};
runUpdateUEBs();
$('#cc_ccbundle_reference_zone').bind({
change: runUpdateUEBs
});
}
And
//ReferenceController.php
//this is where "goalURL" goes
function updateUEBsAction() {
$id = $this->getRequest()->get('id');
$em = $this->getDoctrine()->getManager();
$uebs = $em->getRepository('CCBundle:UEB')->findBy(array('zone' => $id));
$ids_and_names = array();
foreach ($uebs as $u) {
$ids_and_names[$u->getId()] = $u->getName();
}
return new \Symfony\Component\HttpFoundation\Response(json_encode($ids_and_names));
}
With this I can load the UEBs corresponding the Zone being shown at the moment and every time a new Zone is selected alright, but only visually, not internally, hence:
the select populates fine but when I submit the form it doesn't go through with it and outputs "This value is not valid" on the UEB field and the
var_dump($form->get('UEB')->getData());
var_dump($form->get('UEB')->getNormData());
var_dump($form->get('UEB')->getViewData());
die();
from above outputs
null
null
string <the_value_of_the_option_tag> (length=1)
I need to know how to populate the select AND the internal data to be submitted too.
Thanks for bearing with this simple explanation.
I'm listening(reading).
Here is the answer I was looking for, it looks a lot like the one in the cookbook but somehow I understood this one better and I was able to apply it to my own problem, it only needed a few tweaks in the ajax call and the corresponding action, but only regarding my own problem.
thanks to all who cared to read my question and special thanks to Joshua Thijssen for his post.