Symfony Dynamic Forms with ajax - php

I am trying to do a form in which I will be able to add as many product options as I want (so collection with prototype). For this I need two fields, one Option Definition and second with Option values.
I have got entities like: Product, Options, Options Definitions and Product Options Join (I decided to use two ManyToOne instead of one ManyToMany, so in Product Definitions Join I got ManyToMany to Options and ManyToMany to Products).
When I am adding option to product, I want first to choose Option Definition and then update select box with Options. This is everything working alright, but the problem is that when I want to edit product then it is showing me default option definitions. For example, when I was creating product I added product with option definition size and the value of size was 40. When I going to edit product I am getting option definition color and size 40.
Here is on pictures what I mean:
And in second row there should be a Size instead of Colour.
Here you can also see how I got connected tables:
ProductOptionsType Class
public function buildForm(FormBuilderInterface $builder, array $options)
{
$factory = $builder->getFormFactory();
$builder
->add('optiondefinitions', 'entity', array(
'mapped' => false,
'class' => 'ePOS\ProductsBundle\Entity\OptionsDefinitions',
'query_builder' => function ($repository) { return $repository->createQueryBuilder('p')->orderBy('p.name', 'ASC'); },
'property' => 'name',
'attr' => array('class' => 'option-definitions')))
;
$refreshOptions = function ($form, $option) use ($factory) {
$form->add($factory->createNamed('options', 'entity', null, array(
'class' => 'ePOS\ProductsBundle\Entity\Options',
'property' => 'value',
'label' => false,
'auto_initialize' => false,
'query_builder' => function ($repository) use ($option) {
$qb = $repository->createQueryBuilder('options')
->innerJoin('options.optionDefinition', 'optionDefinitions');
if($option instanceof OptionsDefinitions) {
$qb = $qb->where('options.optionDefinition = :optiondefinitions')
->setParameter('optiondefinitions', $option);
} elseif(is_numeric($option)) {
$qb = $qb->where('options.id = :option_id')
->setParameter('option_id', $option);
} else {
$qb = $qb->where('options.optionDefinition = 1');
}
return $qb;
}
)));
};
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) use ($refreshOptions, $factory) {
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return ;
}
$refreshOptions($form, $data->getOptions()->getOptionDefinition());
});
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($refreshOptions) {
$form = $event->getForm();
$data = $event->getData();
if($data == null)
$refreshOptions($form, null); //As of beta2, when a form is created setData(null) is called first
if($data instanceof Location) {
$refreshOptions($form, $data->getOptions()->getOptionDefinition());
}
});
$builder->addEventListener(FormEvents::PRE_BIND, function (DataEvent $event) use ($refreshOptions) {
$form = $event->getForm();
$data = $event->getData();
if(array_key_exists('optiondefinitions', $data)) {
$refreshOptions($form, $data['optiondefinitions']);
}
});
}

Related

is it possible to assign a value to a field added with EventListener symfony in $builder symfony?

I would like to know if it is possible to automatically assign values ​​to added fields of type:
datetime
entity
Thanks for your help
public function buildForm(FormBuilderInterface $builder, array $options)
{
$user = $options['user']; // entity User
$player = $options['player']; // entity Player
$today = new DateTime('now');
$builder
->add('fieldA')
->add('fieldB')
->add('fieldC');
$builder
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($user, $player, $today) {
$form = $event->getForm();
$datas = $event->getData();
$form->add('today');
$form->add('user');
$form->add('player');
//dd($form); ok = 3 fields added
$datas['dateDuJour'] = $today;
$datas['user'] = $user;
$datas['player'] = $player;
//dd($datas); ok = 3 assigned values
$form->setData($datas);
question 1 : how to insert the data in the form
question 2 : pb from entity (object) to string
//dd($form, $datas);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Evaluation::class,
'user' => null,
'player' => null
]);
}
}
I thought about inserting the 3 fields with the type = hidden and using Data Transformer
I do not know what is the best practice?
if you have a concrete example
From what i can see, you have some form and you want to plug 3 data to the form on submit.
Depending on your database configuration, you can do 3 different way:
The best one is to use the mapping
Your evaluation have those 3 fields:
date
user
player
Then just add them to the original builder as hidden field whith default value what you have:
$builder->add('token', HiddenType::class, [
'data' => $today,
])->add('user', HiddenType::class, [
'data' => $user,
])->add('player', HiddenType::class, [
'data' => $player,
]);
As they are hidden, the security check will not autorise users to change those value plus thoise fields will be hiddent
It require those three fields exist in your entity
Second one is to use unmapped hidden field. Same a previous, but add 'mapped'` => false . Then you in your controller, you will have the value and use them as needed.
The third one is to not use them in your form (my favorite) but in your controller
public function addEvaluation(Request $request, EvaluationManager $evaluationManager): Response
{
$evaluation = new Evaluation();
$form = $this->createForm(EvaluationType::class, $evaluation);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$evaluation->setTime(new DateTime('now'))
->setUser($user)
->setPlayer($player);
$evaluationManager->save($evaluation);
return $this->redirectToRoute('evaluation_add');
}
return $this->render('/evaluation_add.twig', [
'form' => $form,
]);
}

Symfony 3.4 Ajax in Form event

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

Symfony : set fields as null if not submitted in Form

For this example, let's say I have an Vehicle Entity that can have the following values :
Type : Bicycle, Motorbike, Car
Fuel : integer
I create a new Vehicle object via a dynamic form. Please note that the field "fuel" will not be shown if the field type is set as "Bicycle".
I can successfully create this form thanks to Symfony documentation as some javascript :
<?php
$formModifier = function (FormInterface $form, $fields = null, array $options) {
foreach ($fields as $field_id => $field_value) {
if ($field_id == 'type') {
if ($field_value && $field_value == 'bicycle') {
$form->remove('gas');
} elseif ($field_value && $field_value == 'motorbike') {
$form->add('gas');
} elseif ($field_value && $field_value == 'car') {
$form->add('gas');
}
}
}
};
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($formModifier, $options) {
$formModifier($event->getForm(), $event->getData(), $options);
}
);
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier, $options) {
$vehicule = $event->getData();
$formModifier($event->getForm(), array(
'type' => $vehicule->getType(),
), $options);
}
);
$builder->add('type', ChoiceType::class, array(
'choices' => array(
'Bicycle' => 'Bicycle',
'Motorbike' => 'Motorbike',
'Car' => 'Car',
),
));
The data is then persisted with Doctrine.
However, there is a problem when I edit this form if the entity in the database already has a defined gas.
For example :
Entity in the database is a Motorbike with gas = 10.
I want to edit the form. The page show me two fields with Type and Gas. 3. I then choose "Bicycle". The field "Gas" disappear.
I submit the form. The attribute Type is updated and has now the value Bicycle. But not the attribute Gas, that stays at 10.
How can I set the gas attribute as zero in this case ?
Add a SUBMIT form event:
$builder->addEventListener(FormEvents::SUBMIT, function(FormEvent $event){
$data = $event->getData();
if($data['type'] == 'Bicycle'){
$data['gas'] = 0; //Note that in your entity, this field is named 'fuel'...
}
$event->setData($data);
});

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);
}
));
}
}
}

Symfony adding EventListener in a closure and getting NULL

I'm getting error "Call to a member function getAgent() on a non-object".
Here is my code in AgentsType.php:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('agents', 'entity', array(
'class' => 'MyBundle:agents',
'property' => 'name',));
$formModifier = function(FormInterface $form, agents $agent) {
$description = $agent->getDescription();
$form->add('description', 'text');
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) use ($formModifier) {
$data = $event->getData(); //ERROR HERE, returns NULL
$formModifier($event->getForm(), $data->getAgent()); // Exception HERE
}
);
$builder->get('agents')->addEventListener(
FormEvents::POST_SUBMIT,
function(FormEvent $event) use ($formModifier) {
$agent = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $agent);
}
);
}
$event->getData(); is NULL, I var_dumped $event and it's a very big amount of objects.
The purpose is to generate the form dynamically after choosing an agent entity in a select field, in this case a description field.
Can anyone advice me what to do with this error or why is getData() = NULL??
EDIT:
The main purpose of this is that the user can add/disable/update Agents. The main idea is to show just a select or dropdown field and an "Add Agent" button. If the user selects one name from the select field, then is the form for the Agents Entity filled with its data. That's the reason to have an EventListener, to detect any selection in the select field, and then show the data according to the user selection. Hope this clarifies a bit the goal.
Create a form type to display all agents.
// AgentsType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('agents', 'entity', array(
'class' => 'MyBundle:Agent',
'query_builder' => function(EntityRepository $rep) {
// some query for agents
};
'attr' => array('onselect' => 'loadAgentForm(this);')
)
);
}
And you will need some javascript to do an asynchronous call to another controller that returns a page, containing the AgentType form (not AgentsType).
function loadAgentForm(sender) {
// if you use jquery:
$("div#form-container").load(
("http://myurl.com/agent/editform?id=" + sender.value)
);
}
If there are any remaining questions feel free to ask!!!

Categories