Symfony Dynamically change field attributes after submission - php

so I Have this Symfony Form field :
$builder
->add('field1', CheckboxType::class, [
'required' => false,
])
->add('field2', NumberType::class, [
'disabled' => !$entite->getField1(),
'required' => $entite->getField1(),
]);
When the form is build, the field2 attributes are set depending on the entity field1 value.
Then, in the form, field2 HTML attributes are dynamically changes using javascript : if field1 value change, when add or remove 'disabled' / 'required" attributes accordingly.
The problem is, if the field2 is disabled when the form is build and then when enabled id with js, if we submit the form, in PHP the field2 is still disabled.
So I also change attributes in php, by adding this in the FormType:
...
$builder->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'preSubmit']);
....
public function preSubmit(FormEvent $event)
{
$data = $event->getData(); // here $data['field2'] has the correct value
$form = $event->getForm(); // here $form->get('field2') value is null and disabled is true
// trying to remove the field
$form->remove('field2');
// And then re-add it with new attributes
$form
->add('field2', NumberType::class, [
'data' => $data['field2'],
'disabled' => !$data['field1'],
'required' => $data['field1'],
]);
}
So is there a bettey way to change attributes fields after submission (without removing and adding the field with new attributes) ??
Edit
So this works until I set an incorrect value type for field2.
field2 is a NumberType, and is I set an alpha value (like 'azerty')
instead of returning the formview with the fild error message, I get a Symfony Error :
A cycle was detected. Listeners to the PRE_SET_DATA event must not call getData() if the form data has not already been set. You should call getData() on the FormEvent object instead.
How I can prevent that and get the field error message instead.
Thanks.

Related

Symfony6 - add iput field after submitting form

i have an ChoiceType::class input field in my form with, now just as an example, two choices:
'choices' => ['type1' => '1', 'type2' => '2']
now when the user select type2 i want to add an exta TextType::class inputfield to the form.
But i dont want to show the input field before and i want it to be required if selected type2 and not if selected type1.
I hope it make sense, i try it to to with javascript and set the attribute to hidden or not, but
then the form is not been send because of the required attribute.
I tried it with form events but did not get it to work in that way.
Thanks
You were on the right way, you have to do it in Javascript. You just need to manage the attr required in Javascript so that the form does not block you with something like this:
Remove the required attribute from a field: document.getElementById("id").required = false;
Make a field required : document.getElementById("id").required = true;
And you can check if the form can be sumitted with : document.getElementById("idForm").reportValidity();.
I using implementation of conditional fields with data-attributes, e.g.:
->add('typeField', EnumType::class, [
'label' => 'Type',
'class' => MyTypeEnum::Class,
])
->add('someField', TextField::class, [
'data-controller' => 'depends-on',
'data-depends-on' => 'my_form_typeField',
'data-depends-value' => MyTypeEnum::OTHER->value,
])
On frontend JS stimulus controller show/hide someField depend on typeField value.
And validation() function in object ('data_class' in formType) make custom validation, e.g.:
/**
* #Assert\Callback
*/
public function validate(ExecutionContextInterface $context)
{
if ($this->typeField !== MyTypeEnum::OTHER) {
$context->buildViolation('message')->atPath('typeField')->addViolation();
}
}

Symfony 3 forms: how to set default value for a textarea widget

I want to set the value on a textarea widget.
How i can set default value for textarea in Symfony 3
for TextType(input type="text") i can use value parameter but for textarea i can not!!!how i can set default value for textarea.
this is my buildForm
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('linkdin', TextType::class, array('attr' => array('placeholder' =>
'linkdin','class' => 'form-control width100','value' =>
MainPageType::$content1[0]['linkdin'])))
->add('addres', CKEditorType::class, array('attr' => array('required' =>
'false','name'=>'editor1' ,'id' => 'editor1','class' => 'ckeditor','empty_data'
=> MainPageType::$content1[0]['addres'])))
.
.
Assuming you are using Symfony 3.4, there's quite a good documentation for that.
Long story short, you should use data:
$builder->add('token', TextareaType::class, array(
'data' => 'abcdef',
));
As the docs say:
The data option always overrides the value taken from the domain data (object) when rendering. This means the object value is also overriden when the form edits an already persisted object, causing it to lose its persisted value when the form is submitted.
You can transfer the variable with data to the formType in controller like that
$form = $this->createForm(Form::class,$YourData);
If you’re using the form for both saving a new record and editing an existing one, chances are that you’ve found the ‘data’ option (Alex’s solution) to be limited, because the fields are overridden with the default data while editing an existing record.
One of the solutions is to set the default data manually in the new() action in your controller, but only on the GET call, not POST.
$form = $this->createForm(MyType::class, $dto);
$form->handleRequest($request);
if($form->isSubmitted()) {
if($form->isValid()) {
// Save data
}
} else {
// Set default value
$form->get('date')->setData(
new\DateTime(’now’)
);
}

How do I specify default values in a Symfony form

I am trying to specify a default value in a form so when creating an entity the form field has a value (not null or empty). However when the entity is being edited it should obviously show the stored value and not the default.
My entity initializes itself as part of the construction - so when an entity is new and not yet persisted these values should be set.
How do I inform the FormType to use the default value over the persisted state? Everything I try seems to suggest it's one or the other not both?
How is this done Symfony 3.2+???
EDIT |
controller:
public function newAction (Request $request)
{
$quoteItem = new QuoteItem();
$form = $this->createForm('UniflyteBundle\Form\QuoteItemType', $quoteItem, ['allow_extra_fields' => true]);
$form->add('QuoteFlight', QuoteFlightType::class);
}
Form type:
public function configureOptions (OptionsResolver $resolver)
{
$resolver->setDefaults([
//'data' => new \UniflyteBundle\Entity\QuoteFlight()
'data_class' => QuoteFlight::class
]);
}
public function buildForm (FormBuilderInterface $builder, array $options)
{
$builder
->add('specMajorSetupCharge', null, [
//'empty_data' => QuoteFlight::SPEC_MAJOR_SETUP_CHARGE,
'data' => QuoteFlight::SPEC_MAJOR_SETUP_CHARGE,
'label' => '* Setups Charge'
])
// ...
}
http://symfony.com/doc/current/components/form.html#setting-default-values
If you need your form to load with some default values (or you're building an "edit" form), simply pass in the default data when creating your form builder.
$quoteItem = new QuoteItem();
$quoteItem->getQuoteFlight()->setSpecMajorSetupCharge(QuoteFlight::SPEC_MAJOR_SETUP_CHARGE).
$form = $this->createForm(QuoteItemType::class, $quoteItem);
// ...
Using data option is no good, because:
http://symfony.com/doc/current/reference/forms/types/form.html#data
The data option always overrides the value taken from the domain data (object) when rendering. This means the object value is also overriden when the form edits an already persisted object, causing it to lose it's persisted value when the form is submitted.
So the recomendation is to set explicitly the data in the underlined object on initialization, either in __constructor() or before bind the object to the form.
To answer my own question and avoid confusion for anyone in the future:
$quoteItem = new QuoteItem();
// THIS LINE WAS MISSING
$quoteItem->setQuoteFlight(new QuoteFlight());
$form = $this->createForm('UniflyteBundle\Form\QuoteItemType', $quoteItem, ['allow_extra_fields' => true]);
$form->add('QuoteFlight', QuoteFlightType::class);
Without the added line the QuoteFlight entity was NULL when the form rendered during create.

"Unable to reverse value for property path" for Symfony2 form ChoiceType field on Select2

Long story short, in Symfony 2.8 I've got Movie entity with actors field, which is ArrayCollection of entity Actor (ManyToMany) and I wanted the field to be ajax-loaded Select2.
When I don't use Ajax, the form is:
->add('actors', EntityType::class, array(
'class' => Actor::class,
'label' => "Actors of the work",
'multiple' => true,
'attr' => array(
'class' => "select2-select",
),
))
And it works.
I tried to put there an empty Select field:
->add('actors', ChoiceType::class, array(
'mapped' => false,
'multiple' => true,
'attr'=>array(
'class' => "select2-ajax",
'data-entity'=>"actor"
)
))
The Select2 Ajax works, everything in DOM looks the same as in previous example, but on form submit I get errors in the profiler: This value is not valid.:
Symfony\Component\Validator\ConstraintViolation
Object(Symfony\Component\Form\Form).children[actors] = [0 => 20, 1 => 21]
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Unable to reverse value for property path "actors": Could not find all matching choices for the given values
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Could not find all matching choices for the given values
The funny part is the data received is the same as they were when it was an EntityType: [0 => 20, 1 => 21]
I marked field as not mapped, I even changed field name to other than Movie entity's field name. I tried adding empty choices, I tried to leave it as EntityType but with custom query_builder, returning empty collection. Now I'm out of ideas.
How should I do it?
EDIT after Raymond's answer:
I added DataTransformer:
use Doctrine\Common\Persistence\ObjectManager;
use CompanyName\Common\CommonBundle\Entity\Actor;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class ActorToNumberTransformer implements DataTransformerInterface
{
private $manager;
public function __construct(ObjectManager $objectManager)
{
$this->manager = $objectManager;
}
public function transform($actors)
{
if(null === $actors)
return array();
$actorIds = array();
foreach($actors as $actor)
$actorIds[] = $actor->getId();
return $actorIds;
}
public function reverseTransform($actorIds)
{
if($actorIds === null)
return array();
foreach($actorIds as $actorId)
{
$actor = $this->manager->getRepository('CommonBundle:Actor')->find($actorId);
if(null === $actor)
throw new TransformationFailedException(sprintf('An actor with id "%s" does not exist!', $actorId));
$actors[] = $actor;
}
return $actors;
}
}
Added it at the end of the MovieType buildForm():
$builder->get('actors')
->addModelTransformer(new ActorToNumberTransformer($this->manager));
$builder->get('actors')
->addViewTransformer(new ActorToNumberTransformer($this->manager));
And added service:
common.form.type.work:
class: CompanyName\Common\CommonBundle\Form\Type\MovieType
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: form.type }
Nothing changed. On form submit, reverseTransform() gets the proper data, but profiler shows the same error. That's a big mistery for me now...
You'll need to add a DTO (Data Transformer ) to transform the value received from your form and return the appropriate object .
Since you're calling the value from Ajax it doesn't recognized it anymore as a an object but a text value.
Examples :
Symfony2 -Use of DTO
Form with jQuery autocomplete
The correct way isn't Data Transformer but Form Events, look here:
http://symfony.com/doc/current/form/dynamic_form_modification.html#form-events-submitted-data
In the example you have the field sport (an entity, like your Movie) and the field position (another entity, like actors).
The trick is to use ajax in order to reload entirely the form and use
PRE_SET_DATA and POST_SUBMIT.
I'm using Symfony 3.x but I think it's the same with 2.8.x
When you add data transformers and nothing seems to change, it sounds like the data never goes through your data transformers. The transformation probably fails before your new data transformers are called. Try to add a few lines to your code:
$builder->get('actors')->resetViewTransformers();
$builder->get('actors')->resetModelTransformers();
// and then add your own

Symfony unmapped entity form field not having data

In Symfony 2.6, I am using an entity form type that is unmapped:
$form
->add(
'myEntity', // Form field name
'entity',
[
'mapped' => false, // Not mapped
'class' => 'MyVendor\MyBundle\Entity\MyEntity',
'choices' => $MyEntityCollection, // list of MyEntity
'property' => 'name',
'empty_value' => 'Please select MyEntity',
'empty_data' => null,
'attr' => [
'label' => 'My label'
]
]
);
This allows user to properly select an item of MyEntity or leave it blank. According to that, I am adding a EventSubscriber to modify the preSubmitted data if any value is selected, and leave it as it is if no choice has been made.
Here is the eventSubscriber:
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
FormEvents::PRE_SUBMIT => 'preSubmitData'
];
}
/**
* #param FormEvent $event
*/
public function preSubmitData(FormEvent $event)
{
if( null === ($entity = $event->getForm()->get( 'myEntity' )->getData() ) ){
return;
}
// Set value if field has been defined
$event
->getForm()
->setData( $entity )
;
}
If user selects a choice other than blank, when I debug the preSubmitData function:
$event->getForm()->get('entity')->getData() gives null
$event->getData() gives an array having as 'entity' key the selected entity ID (just the scalar value)
My questions are:
Shouldn't $event->getForm()->get('entity')->getData() have the selected entity?
Why is $event->getForm()->get('entity')->getData() giving null if $event->getData() has at least the entity ID in it?
Is there any way to get the entity here (as it happens with the mapped entities) without having to call the entity manager and querying the entity via its ID?
Thanks in advance!
Edit
For the big picture, in my global form (other fields not described here) I have 2 depending fields:
A select A (not described here) with some options from a tree. This option does exist in the global form entity as a property.
A second B select named myEntity (described here). It doesn't exist as the global form entity as a property, thus the mapped = false. If any choice is made here, then the first select (A)'s option is overridden by this one. Else the first choice remains as the entity property value.
Hope is clearer now.
Ok, it was giving null because we are on the preSubmit event, and here data sent is not yet mapped within an entity.
Changing the event to submit gives the mapped entity as needed.

Categories