My scenario is the following:
If the user choose true from "maxRedemptionForDiscount" and type "0" into the "maxRedemptionForDiscountValue" there should be an error message rendering to the specific field (at the position of the TextType field)
This is the form with an eventListener:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'maxRedemptionForDiscount',
ChoiceType::class,
[
'placeholder' => false,
'multiple' => false,
'choices' => [
true => 'discount.form_fields.set_max_redemptions',
false => 'discount.form_fields.unlimited',
],
'label' => 'discount.form_fields.max_redemption_for_discount',
'translation_domain' => 'entities',
'required' => false,
'error_bubbling' => true,
'attr' => [
'class' => 'maxRedemptionForDiscountSelect',
],
]
)->add(
'maxRedemptionForDiscountValue',
TextType::class,
[
'label' => 'discount.form_fields.set_max_redemptions',
'translation_domain' => 'entities',
'required' => false,
]
)->addEventListener(
FormEvents::PRE_SUBMIT,
[$this, 'onPreSubmit']
);
}
and this is the onPreSubmit function:
/**
* #param FormEvent $event
*/
public function onPreSubmit(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if ($data['maxRedemptionForDiscount'] == 1) {
if ($data['maxRedemptionForDiscountValue'] == 0) {
$form->addError(new FormError('error message'));
}
}
$event->setData($data);
}
Here is the twig code:
{{ form_row(form.maxRedemptionForDiscount) }}
<div id="maxRedemptionForDiscountValue">
{{ form_row(form.maxRedemptionForDiscountValue) }}
</div>
This render a error message above the form.
But what I want i to render a error message to the specific field.
This does not work:
$form->get('maxRedemptionForDiscountValue')->addError(new FormError('error message'));
If I try this the error message will disappear at the top of my form, but not showing up at the specific field position.
What I am doing wrong here?
First, you should set error_bubbling to false (or remove it as it's default behavior).
As documentation states
If true, any errors for this field will be passed to the parent field or form. For example, if set to true on a normal field, any errors for that field will be attached to the main form, not to the specific field.
Particularly for ChoiceType
Set that error on this field must be attached to the field instead of the parent field (the form in most cases).
Second, you should add error to specific form field
$form
->get('maxRedemptionForDiscountValue')
->addError(new FormError('error message'));
Third, you should edit your template
<div id="maxRedemptionForDiscountValue">
{{ form_errors(form.maxRedemptionForDiscountValue) }}
{{ form_row(form.maxRedemptionForDiscountValue) }}
</div>
Related
In Symfony 4 form I have a form field declared with EntityType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('tags', EntityType::class, array(
'class' => Saptag::class,
'choice_label' => 'descr',
'required' => false,
'multiple' => true,
//'attr' => ['class' => 'selectpicker']
));
...
}
When form is submited my database is well updated. If I activate class 'selectpicker' then my field has the rendering I need in the template but field value do not update database when this class is used. No error occurs...
Any idea of how to handle that ?
EDIT: This issue occurs when form is placed crossed div element:
<div>
</form>
</div>
I have a form with a contact list. I want the field "first name" appear with the selected contact value after submit. My problem is that the field appear but I cant set the good data, the field always remains empty.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('contacts', ChoiceType::class, [
'label' => 'Contact',
'placeholder' => 'Choose a contact',
'choices' => $this->getContacts(),
'mapped' => false,
])
->setMethod('POST')
;
$builder->get('contacts')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
$contactId = $event->getData();
$parentForm = $event->getForm()->getParent();
$contactEntity = $exampleEm->getrepository(Contact::class)->find($contactId);
$firstName = $contactEntity->getFirstName();
// where can I set the 'contactFirstname' data ?
$parentForm
->add('contactFirstname', TextType::class, [
'label' => 'First name',
]);
})
;
}
How to enter the right data so that the field appears pre-filled?
Edit :
I found a method, but it's not terrible:
$parentForm
->add('contactFirstname', TextType::class, [
'label' => 'First name',
'empty_data' => $firstName,
]);
('data' => $firstNamedont work for me.)
$parentForm->get('contactFirstname')->setData($firstName); doesn't work either
Can't you simply set the 'data' option of your TextType field?
// ...
$contactEntity = $exampleEm->getrepository(Contact::class)->find($contactId);
$firstName = $contactEntity->getFirstName();
$parentForm
->add('contactFirstname', TextType::class, [
'label' => 'First name',
'data' => $firstname //here?
]);
EDIT:
According to this post submitted on github, the form field needs to be submitted in order to have it's data changed.
In one of his solutions, he uses the "empty_data" as you did.
In the other one, he adds the field to the builder. Hides it with display: "none"; until the data is submitted.
The docs say
the data of an unmapped field can also be modified directly:
$form->get('agreeTerms')->setData(true);
So try this:
$parentForm
->add('contactFirstname', TextType::class, [
'label' => 'First name',
]);
$parentForm->get('contactFirstname')->setData($firstName);
Maybe using a setter before creating your form ?
https://symfony.com/doc/current/forms.html#building-the-form
I am currently working on a user creation form.
A user has a profile attribute:
/**
* Many Users have One profile
* #ORM\ManyToOne(targetEntity="ProjectBundle\Entity\User\Profile", inversedBy="users")
* #ORM\JoinColumn(name="profile_id", referencedColumnName="id")
*/
private $profile;
This profile is chosen according to another select (on change action jquery) :
{% autoescape 'html'%}
{{ '<script id="tmpl_user_profile" type="text/x-jquery-tmpl">
<option value="${id}">${libelle}</option>
</script>'|raw }}
{% endautoescape %}
<script>
$('select#user_organisationMember').on('change',function(){
var value = this.value;
if (value == '') {
$('#user_profile').empty();
}
var urlAjax = "{{ path('admin_user_get_profile', { 'entity': "value" }) }}";
$.ajax({
url: urlAjax.replace("value",value),
method: "post"
}).done(function(msg){
$('#user_profile').empty();
$('#tmpl_user_profile').tmpl(JSON.parse(msg)).appendTo('#user_profile');
}) ;
});
</script>
Until then everything worked correctly !
The different profiles in the select tag changes well according to the other select.
Upon arrival on the page, I want the list of profiles to be empty.
So I adapted my form using Symfony's FormEvent.
This is my first use of FormEvent, I may have made a mistake!
My FormType :
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('lastname')
->add('firstname')
->add('gender',null,array('required'=>true))
->add('organisationMember',null,array(
'required' => true,
'choices' => $options['organisation'],
'group_by' => 'type_organisation',
'placeholder' => 'Choisissez votre organisation'
))
->add('job')
->add('mobile')
->add('phone')
->add('alert_failure')
->add('alert_full')
->add('alert_other')
->add('plainPassword', TextType::class, array('required' => true))
->add('email')
->add('profile', null, array(
'required' => true,
));
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$entity = $event->getData();
$form = $event->getForm();
if (!$entity || null === $entity->getId()) {
$form->remove('profile');
$form->add('profile', ChoiceType::class);
}
});
}
By default all the profiles of my database are loaded in the select to not get the error 'This value is incorrect'.
But I do not want the user to see all the profiles, so I remove it in the event and return the field empty.
But I still get the error 'This value is incorrect' because actually, since the base select is empty, the form does not found the value entered.
I would like to have a select which by default is empty, which is filled in Ajax and which does not show me the error 'This value is incorrect'.
How can I please do it?
Thanks
Add field not only on PRE_SET_DATA, but also on PRE_SUBMIT event. Check my answer on similar question.
I found a very simple solution, I do not know if it's a good idea, but it takes not a lot of lines.
I have delivered the default form
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('lastname')
->add('firstname')
->add('gender',null,array('required'=>true))
->add('organisationMember',null,array(
'required' => true,
'choices' => $options['organisation'],
'group_by' => 'type_organisation',
'placeholder' => 'Choisissez votre organisation'
))
->add('job')
->add('mobile')
->add('phone')
->add('alert_failure')
->add('alert_full')
->add('alert_other')
->add('plainPassword', TextType::class, array('required' => true))
->add('email')
->add('profile', null, array(
'required' => true,
'placeholder' => 'Choose an organisation !',
));
}
And on my view:
<div class="form-group{% if form.profile.vars.errors|length %} has-error{% endif %}">
<label for="{{ form.profile.vars.id }}" class="col-sm-3 control-label no-padding-right required">Profile(s) </label>
<div class="col-sm-9">
{% do form.profile.setRendered %}
<select name="user[profile]" id="user_profile" required class="form-control">
//We can do a loop if needed
</select>
{{ form_errors(form.profile) }}
</div>
</div>
This way I fully manage whether or not I want information in my select.
And it works for me.
I have created a form to invite new users. The form has no problem submitting and being handled correctly, until isValid() method returns errors after a submission. When this happens, the page is re-rendered correctly with the appropriate errors being shown. Unfortunately, the submit button then becomes un-responsive: It is still styled correctly (the style changes when it is disabled via jQuery, so that doesn't seem to be the problem), the correct submission URL still appears at the bottom-left of the navigator, but nothing happens when it is clicked.
The UserInviteType form building class:
class UserInviteType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('userBackground', ChoiceType::class, array(
'choices' => array(
'Professeur' => 'professor',
'Étudiant' => 'student',
'Employé de laboratoire' => 'labEmployee',
'Employé administratif' => 'adminEmployee',
'Autre' => 'other'
)))
->add('firstName', TextType::class)
->add('lastName', TextType::class)
->add('email', EmailType::class)
->add('misc', TextType::class, array(
'required' => false,
))
->add('level', EntityType::class, array(
'required' => false,
'class' => 'AspProfessorProfileBundle:Level',
'choice_label' => 'value',
'multiple' => false,
))
->add('canModify', CollectionType::class, array(
'entry_type' => CanModifyInitType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => true,
))
->add('save', SubmitType::class, array(
'disabled' => 'false',
))
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Asp\UserBundle\Entity\UserInvite',
'validation_groups' => array(
'Asp\UserBundle\Entity\UserInvite',
'determineValidationGroups'
),
));
}
}
The controller which handles page rendering and form submission:
public function usersAction(Request $request)
{
$user = new UserInvite();
$form = $this->get('form.factory')->create(UserInviteType::class, $user);
/* retreive all users - for user list rendering*/
$userManager = $this->get('fos_user.user_manager');
$users = $userManager->findUsers();
if ($request->isMethod('POST') && $form->handleRequest($request)->isValid()) {
$em = $this->getDoctrine()->getManager();
$data = $form->getData();
if ($data->getUserBackground() == 'professor' || $data->getUserBackground() == 'adminEmployee' || $data->getUserBackground() == 'other') {
$canMod = $user->getCanModify();
foreach ($canMod as $cM) {
$user->removeCanModify($cM);
}
}
$em->persist($user);
$em->flush();
$request->getSession()->getFlashBag()->add('notice', 'Invitation envoyé à '.$user->getFirstName().' '.$user->getLastName().'('.$user->getEmail().').');
return $this->redirectToRoute('asp_core_admin_users');
}
return $this->render('AspCoreBundle:Admin:users.html.twig', array(
'users' => $users,
'form' => $form->createView()
));
}
The Twig view rendering of the form:
<div class="user-init-form-container">
{{ form_start(form, {'attr': {'class': 'form-horizontal'}}) }}
{{ form_errors(form) }}
{# ....... Declare other form elements ....... #}
<div class="form-group">
<div class="col-sm-offset-3 col-sm-8 col-lg-offset-2 col-lg-9">
{{ form_widget(form.save, { 'id': 'submit_button', 'label': 'Inviter', 'attr': {'class': 'btn btn-primary'}}) }}
</div>
</div>
{{ form_end(form) }}
</div>
I do not do any jQuery interaction with the submit button directly (except when I wanted to see the disabled button styling to confirm this wasn't the problem).
I have looked around quite a bit and can't seem to find any topic anywhere discussing this. Hope one of you can spot what I am doing wrong, because I am really stumped right now!
Thank you.
Alex S.
Try to specify form action ?
{{ form_start(form, {'attr': {'id': 'myId'}, 'action': path('my_route')}) }}
Other methods if you prefer http://symfony.com/doc/current/form/action_method.html
I Figured it out.
It was due to a hidden field which was in-properly rendered after error generation. This hidden field suddenly became required and would prevent the submission of the form.
Thank you.
Alex
Here's what i'm trying to achieve. I'm using the SymfonyContrib\Bundle\LinkButtonBundle\LinkButtonBundle to add a simple back/cancel link to my form right beside the submit button. Problem is that I don't know how to get to my router so that I can use the generate method to generate route url's. Anyone have an idea how to either inject the router into my form or pass the URL from my controller where the form is created with $this->createform('my_form_foo')
<?php
namespace My\Form;
use \Symfony\Component\Form\AbstractType;
use \Symfony\Component\Form\FormBuilderInterface;
class Foo extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'username',
null,
[
'label' => 'Username',
'attr' => [
'placeholder' => 'User name',
],
'required' => true
]
)
->add('actions', 'form_actions', [
'buttons' => [
'save' => [
'type' => 'submit'
],
'cancel' => [
'type' => 'button',
'options' => [
'url' => '/', // This needs to be generated from a route
'label' => 'Back'
]
],
]
]);
}
/**
* #return string
*/
public function getName()
{
return 'my_form_foo';
}
}
In your specific case, it's a better practice to add buttons directly in the view.
(Something like that)
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit" value="Save" />
Back
{{ form_end(form) }}
You can inject the router into the form class if you register your form as a service in the service container.
See: http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html#creating-your-field-type-as-a-service
I suggest you to manage all the form actions in the Controller Class. As described in the doc here, you can add your button for flow-control:
$form = $this->createFormBuilder($task)
->add('name', 'text')
->add('save', 'submit')
->add('save_and_add', 'submit')
->getForm();
And manage the flow in the controller as:
if ($form->isValid()) {
// ... do something
// the save_and_add button was clicked
if ($form->get('save_and_add')->isClicked()) {
// probably redirect to the add page again
}
// redirect to the show page for the just submitted item
}
Hope this help