I am trying to make a registration form where apart from the usual fields name, alias, password ( nombre
, nick
and password
)... I also add two dependent fields, Municipio
and Provincia
.
The problem is that it only shows multiple messages of undefined in the municipality field.
The entity ( Entity
) that creates the form is DbUsuario
, which has the field idMunicipio
.
/** @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 DbMunicipio
that connects with the province:
/** @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;
}
/**@return \BackendBundle\Entity\DbProvincia */
public function getProvincia(){
return $this->provincia;
}
And the Entity DbProvincia
that only has the fields id
( integer
), slug
( String
) and provincia
( String
).
I define the form as follows:
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
use AppBundle\Form\EventListener\AddProvinciaField;
use AppBundle\Form\EventListener\AddMunicipioField;
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 inside AppBundle\Form\eventListener the classes, called in the form:
AddProvinciaField
namespace AppBundle\Form\EventListener;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
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 Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
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);
var_dump($provincia);
} elseif (is_numeric($provincia)) {
$qb->where('provincia.id = :provincia')
->setParameter('provincia', $provincia);
var_dump($provincia);
} else {
$qb->where('provincia.provincia = :provincia')
->setParameter('provincia', null);
var_dump($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->addMunicipioForm($form, $provincia);
}
public function preSubmit(FormEvent $event){
$data = $event->getData();
$form = $event->getForm();
if (null === $data) { return; }
$provincia = array_key_exists('select_provincia', $data) ? $data['select_provincia'] : null;
$this->addMunicipioForm($form, $provincia);
}
}
And finally the AJAX request :
$(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 (html) {
$('.municipio-input').replaceWith($(html).find('.municipio-input'));
}
});
});
});
Resolved:
In the form I call the two new classes that I will define below to create the two dependent selects. 'idMunicipio' is what the field is called in the Entity.
First select, select provinces.
Second select dependent on provinces, select municipalities.
In the controller I add the following function to collect the id value of the province and generate the dql query.
I create the findByProvinceId function as a new repository type class in the entities where I add my new function to do the search. It returns an array with the DbMunicipio entities that I am looking for.
And the AJAX code.