Symfony 3.4 Ajax in Form event - php

In my project, the form allow user select a Map in a SelectBox. When Map Selectbox change, the options in GroupLayer Selectbox also change depend on which Map is selected. I see exactly Symfony document for my case in: How to Dynamically Modify Forms Using Form Events
Howerver, in example code:
$formModifier = function (FormInterface $form, Sport $sport = null) {
$positions = null === $sport ? array() : $sport->getAvailablePositions();
$form->add('position', EntityType::class, array(
'class' => 'App\Entity\Position',
'placeholder' => '',
'choices' => $positions,
));
};
I don't know where the getAvailablePositions() function should be and what is the returning of this function?. I think this function will be placed in Sport Entity. Is that right, in Sport Entity, could I query the Position Entity with Doctrine ORM queryBuilder?

with this formModifier you only change the fields that your form have. I don't know where you have the relation between Map and GroupLayer, but this relation is what you need to search. For example, if you have a OneToMany relation beetween the entities you can do:
$map->getGroupLayers();
this was the choices for the selector.
In the other hand you can use a custom method from the GroupLayer repository with the map as parameter or a service that search for the related GroupLayers from a map, it's up to you and your architecture.
Edit #1
With your new info i guess that your code seem near like this:
$formModifier = function (FormInterface $form, Map $map = null) {
$groupLayers = null === $map ? array() : $map->getGroupLayers();
$form->add('position', EntityType::class, array(
'class' => 'App\Entity\GroupLayer',
'placeholder' => '',
'choices' => $groupLayers,
));
};
$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->getMap());
}
);
$builder->get('sport')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$map = $event->getForm()->getData();
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier($event->getForm()->getParent(), $map);
}
);
I hope this can help you

Related

How to set a data by default in a drop-down list

In a project symfony3.4, I have tow entitys : Personne and Nationalite
In my form drop-down list I want select by default Nationalite 'French'.
PersonneType.php:
...
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('nationalite', EntityType::class, array(
'class' => 'AppBundle:Nationalite',
'choice_label' => 'libelle',
'required' => false,
'empty_data' => function(NationaliteRepository $repo) {
return $repo->getNationaliteParDefaut();
}
))
->add...
In NationaliteRepository.php:
...
public function getNationaliteParDefaut(){
$qb = $this->createQueryBuilder('n');
$qb->where($qb->expr()->eq('n.codeInsee', ':code_insee'))
->setParameter('code_insee', 99100); //99100 is France code_insee
return $qb->getQuery()->getOneOrNullResult();
}
...
This methode generate the following error:
Catchable Fatal Error: Argument 1 passed to
AppBundle\Form\PersonneType::AppBundle\Form{closure}() must be an
instance of AppBundle\Repository\NationaliteRepository, instance of
Symfony\Component\Form\Form given, called in /var/www/...vendor/symfony/symfony/src/Symfony/Component/Form/Form.php on line 620 and defined
The best way for entities/objects is to set the default option on the object class itself, either in the entity itself or before form creation, or using some builder/factory.
// let say thisis the class which will be filled with the natinalite
Class Person {
private $name
// ... some other fields ...
private $nationalite
public function setNationalite(Nationalite $nationale)
{
$this->nationalite = $nationalite
}
}
Form creation object method
$person = new Person();
$person->setNationalite($repo->getNationaliteParDefaut());
// lets say personType is your form, that also has field nationalite
$form = $this->createForm(PersonType:class, $person);
With this setup the form will be prepopulated with default data, from the object, including nationalite, and the user can still easilily change it.
Non object method
Another way of doing what you want is using form events.
// in your form definition
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$form->add('nationalite', EntityType::class, array(
'class' => 'AppBundle:Nationalite',
'choice_label' => 'libelle',
'required' => false,
'data' => $repo->getNationaliteParDefaut()
)
}
About empty_data
Empty data only works if the the form field is empty, eg. the user didn't select anything so the value from empty data will be used
ref: https://symfony.com/doc/current/reference/forms/types/text.html#empty-data
Try preferred_choices option https://symfony.com/doc/3.4/reference/forms/types/entity.html#preferred-choices . The preferred_choices appear on top of the list but won't be selected. If you want an item selected, build the form with a POPO (Plain Object PHP Object) with the default nationality you want.

Symfony, how to use form event to validate dynamic client-side form

I'm using the select2 plugin with ajax to have a dynamic field on my form, but when i submit the it return me an error "This value is not valid", which is normal cause i use the ChoiceType with an empty array() in the choices options on creation. According to this part of the symfony doc, the form event is my savior, so trying to use it but it look like something wrong with my code and can't really see what.
So My Question Is :
HOW to pass the choices possibility to the field, for the form to be valid.
My form Type
class ArticleType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//My other field
//My functions to add the field with the possible choices
$formModifier = function (FormInterface $form, $imageValue) use ($options) {
if ($imageValue !== null) {
$listImages = $this->getChoiceValue($imageValue, $options);
if (!$listImages) {
$form->get('image')->addError(new FormError(
'Nous n\'avons pas pu trouver l\'image, veuiller choisir une autre'
));
}
} else {
$listImages = array();
}
//die(var_dump($listImages)); //Array of Image
$form->add('image', ChoiceType::class, array(
'attr' => array(
'id' => 'image'),
'expanded' => false,
'multiple' => false,
'choices' => $listImages));
};
$formModifierSubmit = function (FormInterface $form, $imageValue) use ($options) {
if ($imageValue !== null) {
$listImages = $this->getChoiceValue($imageValue, $options);
if (!$listImages) {
$form->get('image')->addError(new FormError(
'Nous n\'avons pas pu trouver l\'image, veuiller choisir une autre'
));
}
} else {
$form->get('image')->addError(new FormError(
'Veuillez choisir une image s.v.p.'
));
}
//die(var_dump($listImages)); //Array of Image object
$config = $form->get('image')->getConfig();
$opts = $config->getOptions();
$chcs = array('choices' => $listImages);
//die(var_dump($chcs)); //output an array with a 'choices' keys with array value
array_replace($opts, $chcs); //not work
//array_merge($opts, $chcs); //not work
//die(var_dump($opts)); //replacements/merge are not made
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be the entity Article
$data = $event->getData();
$formModifier($event->getForm(), $data->getImage());
}
);
//$builder->get('image')->addEventListener( //give error cause the field image don't exist
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($formModifierSubmit) {
$imageVal = $event->getData();
//die(var_dump($imageVal)); //return all the submitted data field in an array
//But when change this event to Submit it return the Article model populated by the submitted data, EXCEPT the image field which have null as value
$formModifierSubmit($event->getForm(), $imageVal['image']);
}
);
}
public function getChoiceValue($imageValue, $options)
{
$listImages = $options['em']->getRepository('AlmotivAppBundle:Image')->findBy(array(
'id' => $imageValue
));
return $listImages; //array of Image object
}
[...]
}
For Info
My image field is not depending on any other field like the doc example, so i need to populate the choices options on PRE_SUBMIT event to give the possible choice.
And also image have a ManyToOne relation in my Article entity
class Article implements HighlightableModelInterface
{
//some properties
/**
* #ORM\ManyToOne(targetEntity="Image\Entity\Path", cascade={"persist"})
* #Assert\Valid()
*/
private $image;
}
If i'm in the bad way let me know cause i'm out of idea now, i try much thing, like
array_replace with the options in the configuration of the field but didn't wrong.
make an ajax request to the url of the form action url : $form.attr('action'), i think it will load the choices option with the possible of <option> but my select is still returned with none <option>.
and much more (can't remmenber).
And also i'm using the v3.1 of the framework with the v4.0.3 of the select2 plugin, if need more info just ask and thx for reading and trying help.
Edit
Just add some info to be more clear
You making things way too complicated. In your documentation example they add eventListener for already existing form field ('sport') and you are adding it to only later added field which does not exist (your 'image' field and 'position' field from the documentation example).
You should use EntityType and if you need (which I'm not if sure you are) filter your images using query_builder option, for validation add constraints (example with controller).
class ArticleType extends AbstractType {
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// $builder
// My other field
$imageFieldFunction = $this->getImageFieldFunction();
$builder->addEventListener(FormEvents::PRE_SET_DATA, $imageFieldFunction);
$builder->addEventListener(FormEvents::PRE_SUBMIT, $imageFieldFunction);
}
private function getImageFieldFunction()
{
return function(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
//when your data_class is Article
$image = $data->getImage();//depending on your Article class
/*if you are using data_class => null
$image = $data['image'];
*/
$imageId = $image ? $image->getId() : 0;
$builder->add('image', EntityType::class , array(
'class' => 'AlmotivAppBundle:Image',
'attr' => array(
'id' => 'image'
) ,
'expanded' => false,
'multiple' => false,
'constraints' => new NotBlank(),
'query_builder' => function (EntityRepository $er) use ($imageId) {
return $er->createQueryBuilder('i')
->where('i.id = :image_id')
->setParameter('image_id', $imageId);
}
));
}
}
}

Entity is empty in formModifier on form events

English is not my native language, sorry for that.
I have a meet entity (rendezVous) and in this entity, i have two others mapped entities doctor(docteur) and customer(client).
I want to change the list of doctors when choosing a customer.
For that, I create a form events in my RendezVousType, but the problem is when i choose a customer, the Client entity is empty in my formModifier.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('client', EntityType::class, array(
'class' => 'AppBundle:Client',
'placeholder' => '',
));
$formModifier = function (FormInterface $form, Client $client = null) {
$idEspece = null === $client ? 0 : $client->getId();
$form->add('docteur', EntityType::class, array(
'class' => 'AppBundle:Docteur',
'placeholder' => '',
'query_builder' => function (DocteurRepository $er) use ($idEspece) {
return $er->getByClientEspece($idEspece);
},
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$data = $event->getData();
$formModifier($event->getForm(), $data->getClient());
}
);
$builder->get('client')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$client = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $client);
}
);
}
When I set a default value for $idEspece, the query builder returns the correct list.
Your form type is valid but Symfony don't do that automatically using ajax, then the doctor only appear after form submission.
Then to get the list of doctors using ajax you need add some additional logic.
Firstly, create a action in your controller to get the list of doctors and return a json
/**
* #Route(name="get_doctors", path="/get_doctors" )
*/
public function getDoctorsAction(Request $request)
{
$client = $request->get('rendez_vous')['client'];
$clients = $this->getDoctrine()
->getRepository('AppBundle:Docteur')
->getByClientEspece($client)
->select('s.id', 's.name')
->getQuery()
->getArrayResult();
$indexedClients = array_column($clients, 'name', 'id');
return new JsonResponse($indexedClients);
}
Add the following jquery plugin in your scripts.
https://appelsiini.net/projects/chained/
<script type="text/javascript" src="{{ asset('js/jquery.chained.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('js/jquery.chained.remote.min.js') }}"></script>
Add the following javascript after your form to initialize the jquery plugin in your doctor input:
{{ form(form) }}
<script>
$("#rendez_vous_docteur").remoteChained({
parents: "#rendez_vous_client",
url: '{{ path('get_doctors') }}',
clear: true,
loading: "Loading..."
});
</script>
ADIVSE: review and update as needed ids and parameters used in the example.
Thanks for you help, but I solved my issue by myself by simply doing a
php app/console cache:clear
I'm sorry, if you loose your time for this
PS: I followed this tutorial on symfony doc for create form events.

Symfony2: init EntityType field with empty data

I have a field (EntityType => select in the view) in my FormBuilder that I want it to be in initialized with empty data so I can fill it after that in the view via ajax.
So I have read symfony's documentation about EntityType and I found the choices attribute that receives an array of data, so I gave it an empty one 'choices' => array() and it did the trick.
Now the problem is when I submit the form, symfony don't know anymore the type of the field and give me null.
This is the builder:
$buidler->add('supplier', EntityType::class, array(
'class' => 'SBC\TiersBundle\Entity\Supplier',
'attr' => array(
'class' => 'uk-select uk-select-supplier'
),
'choices' => array(),
))
As you can see the the type of the field is SBC\TiersBundle\Entity\Supplier but after submit symfony gives me null !
What should I do to achieve my goal?
All right, this is the solution:
First, I need to pass the EntityManager to my form, and to do this I have created a service:
services:
payment.supplier.form:
class: SBC\PaymentBundle\Form\PaymentSupplierType
tags:
- { name: form.type, alias: form_em }
arguments: ["#doctrine.orm.entity_manager"]
Then call the EntityManager in the __construct function:
private $em;
private $supplier;
function __construct(EntityManager $em)
{
$this->em = $em;
}
Second, I need to add two events to the form:
PRE_SUBMIT (to get supplier's code and create Supplier object using the EntityManager):
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function(FormEvent $event){
$data = $event->getData();
$code = $data['supplier'];
$this->supplier = $this->em->getRepository('TiersBundle:Supplier')->find($code);
}
);
And finally, use the POST_SUBMIT event to set the supplier object in the submitted data:
$builder->addEventListener(
FormEvents::POST_SUBMIT,
function(FormEvent $event){
$object = $event->getData();
$object->setSupplier($this->supplier);
$event->setData($object);
}
);
Thanx to Виталий Бойко who gave me a hint about the form events.
So this is what I did with my knowledge and if you have a better solution please share it with us.
Symfony by default use security for forms, so if you didn't have choices in form builder, you can't pass custom choices to the form after render only via javascript, because you get not valid form. You need to create eventlistener for the form. Check this link for more informationenter link description here, here you can find how to add choices.
P.S. sorry for my English)

Symfony form entity with allow add

I'm building an application with Symfony 2.3.
I have a Booking entity witch is related to a Customer entity by a ManyToOne relation.
In my form, i would like to be able to select one existing customer or create a new one.
For exemple by having a "new customer" option in my customer select who will display the customer form with javascript for exemple.
In fact i'm trying to build an Entity form field with an "allow_add" option like in the collection form field.
Any idea of how i can do that ?
Thank you very much.
Thanks for contributing. I found a way to achieve it !
The solution is to have one field with the Customer form, it has to be mapped, and an entity field en the Customer entity but not mapped.
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) use ($company) {
$form = $event->getForm();
$data = $event->getData();
$form->add('customer_list', 'entity',
[
'class' => 'SomeBunlde\Entity\Customer',
'label' => 'Client',
'property' => 'fullName',
'expanded' => false,
'multiple' => false,
'mapped' => false,
'query_builder' => function(EntityRepository $er) use ($company)
{
return $er->getByCompanyQueryBuilder($company);
},
]
)
;
if ($data->getCustomer() === null) {
$form->add('customer', new CustomerType());
}
}
After i add an extra option to the Entity form field overloading the finishView method :
public function finishView(FormView $view, FormInterface $form, array $options)
{
array_unshift($view->children['customer_list']->vars['choices'], new SfFormExt\ChoiceView('test', 'new', 'Nouveau client'));
}
Then i add two event listeners, a pre_submit to delete the mapped embeded customer form and its data :
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function(FormEvent $event) use ($em) {
$data = $event->getData();
if ($data['customer_list'] !== 'new') {
unset($data['customer']);
$event->getForm()->remove('customer');
// setting data w/out customer to avoid extra-field error
$event->setData($data);
}
}
);
and a bind event to attach the existing customer to the booking :
$builder->addEventListener(
FormEvents::BIND,
function(FormEvent $event) use ($em) {
$form = $event->getForm();
$data = $event->getData();
if (!$form->has('customer')) {
$existing_customer = $form->get('customer_list')->getData();
if ($existing_customer instanceof Customer) {
$data->setCustomer($existing_customer);
}
}
}
);
I know it may not be state of the art code but it works pretty well.
Edit : I had an issue with this technique because when the customer_list is set to new, it throws an error. I didn't find a way to avoid this error (If you have any idea of how i can achieve this !) so i decided to modify the pre_submit to set to '' the data of customer_list value if we are in the new customer case, then i detect, in the controller, if there is a form validation error on the new client form in order to correctly display it.
I think the best way to do that is managing this workflow with javascript.
If you user choose to create a new customer, you open a create new customer form in a modal and via Ajax create the new customer. The response of the create action returns the id in the response which will be used by you to create the booking with the newly created customer.
The trick is: you will always create a booking from an existing customer. Your user can create a new customer in the process, but in fact it'll be created before you create the booking record.

Categories