I am still new to Symfony and Php, so I am using the CheckboxType code from Symfony docs.
Currently no message is appearing if a user attempts to register and the checkbox is unchecked, but it will still prevent the user from making a account.
(1) I would like a error message to appear next to the checkbox in red stating the box must be checked in order to proceed. I would also like to customize this message.
Thank you!
Register.html
{% extends 'base.html.twig' %}
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>Register!</h1>
{{ form_start(form) }}
{{ form_row(form.username) }}
{{ form_row(form.email) }}
{{ form_row(form.plainPassword.first, {
'label': 'Password'
}) }}
{{ form_row(form.plainPassword.second, {
'label': 'Repeat Password'
}) }}
Terms of service
{{ form_widget(form.termsAccepted) }}
cancel
<button type="submit" class="btn btn-primary" formnovalidate>
Register
</button>
<br></br>
<p>Privacy Policy
{{ form_end(form) }}
</div>
</div>
</div>
{% endblock %}
RegistrationForm.php
class UserRegistrationForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class)
->add('username', TextType::class)
->add('plainPassword', RepeatedType::class, ['type' => PasswordType::class])
->add('termsAccepted', CheckboxType::class, array(
'mapped' => false,
'constraints' => new IsTrue(),));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
'validation_groups' => ['Default', 'Registration']
]);
}
}
The problem is that you are using:
{{ form_widget(form.termsAccepted) }}
for rendering the checkbox. This will only render the widget, whereas:
{{ form_row(form.termsAccepted) }}
as is used by all the user fields would contain the label, the widget and the error message. If you want to keep the widget, e.g. because using form_row messes up the template somehow you could render the errors individually using:
{{ form_errors(form.termsAccepted) }}
You might also want to check out the documentation on Form Customization.
Related
In Easy Admin, I already have a list/edit form of users. I want to add an extra form to change password of any member user. (password, repeat password, submit)
In the documentation custom forms are told to be entity specific. For example, to create a custom product form, you create a custom controller:
easy_admin:
entities:
# ...
Product:
controller: AppBundle\Controller\ProductController
# ...
But this solution doesn't fit to my problem. I already set a user form and use that form.
I can set an event listener and manage saving the password but I'm stuck with adding this simple form.
Firstly, you need a controller to handle requests associated with your users (create one if you don't have it already)
/**
* #Route("/user/change-password", name="change_password")
*/
public function changePassword(Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
$changePasswordModel = new ChangePassword();
$form = $this->createForm(ChangePasswordType::class, $changePasswordModel);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$user = $entityManager->find(User::class, $this->getUser()->getId());
$user->setPassword(
$passwordEncoder->encodePassword(
$user,
$form->get('newPassword')->getData()
)
);
$entityManager->persist($user);
$entityManager->flush();
return $this->redirect('/?entity=User&action=show&id='. $this->getUser()->getId());
}
return $this->render('admin/theme/changePassword/change_password.html.twig', array(
'changePasswordForm' => $form->createView(),
));
}
Then I created a form type that looks like this:
class ChangePasswordType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('oldPassword', PasswordType::class, [
'required' => true,
'label' => 'Type your current password',
])
->add('newPassword', RepeatedType::class, [
'type' => PasswordType::class,
'invalid_message' => 'Passwords do not match.',
'first_options' => ['label' => 'Type your new password'],
'second_options' => ['label' => 'Retype your new password']
]);
}
public function setDefaultOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => ChangePassword::class,
));
}
public function getName()
{
return 'change_passwd';
}
}
I created a form model with custom validation, you can edit this however you like
class ChangePassword
{
/**
* #SecurityAssert\UserPassword(
* message = "Wrong value for your current password!"
* )
*/
public $oldPassword;
/**
* #Assert\Length(
* min = 6,
* minMessage = "Password must be at least 6 characters long!"
* )
*/
public $newPassword;
}
Lastly, extend your base twig with someting like this
{% extends 'base.html.twig' %}
{% block title %}Change password
{% endblock %}
{% block stylesheets %}
{{ parent() }}
{{ encore_entry_link_tags('change-password') }}
{% endblock %}
{% block body %}
{{ parent() }}
<body id="{% block body_id %}{% endblock %}">
<div class="container-fluid h-100">
<div class="row justify-content-center align-items-center h-100">
<div class="col col-sm-8 col-md-8 col-lg-6 col-xl-4">
{{ form_start(changePasswordForm, {'attr':{'class':'form-signin'}}) }}
{{ form_row(changePasswordForm.oldPassword, {'attr': {'class':'form-control mb-2'} }) }}
{{ form_row(changePasswordForm.newPassword.first, {'attr': {'class':'form-control mb-2'} }) }}
{{ form_row(changePasswordForm.newPassword.second, {'attr': {'class':'form-control mb-2'} }) }}
<button class="btn btn-dark btn-lg btn-block mt-3" type="submit">Change password</button>
{{ form_end(changePasswordForm) }}
</div>
</div>
</div>
</body>
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('change-password') }}
{% endblock %}
I created a button on my show user action that takes you to /user/change-password and that is pretty much it.
So I have this simple form:
class CreditType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('credits', EntityType::class, [
'class' => Product::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('product')
->orderBy('product.amount');
},
'expanded' => true,
'choice_label' => function ($key) {
return $key->amount.' credits voor maar '.$key->price;
}
]);
}
}
which I use in the following Controller:
public function credits(Request $request)
{
$form = $this->createForm(CreditType::class);
$form->handleRequest($request);
if ($form->isSubmitted()) {
throw new \Exception('It works!');
}
return $this->render(
'credits/credits_widget.html.twig',
[
'form' => $form->createView(),
]
);
}
And use this in my XXX.html.twig
{{ render(controller('App\\Controller\\DetailsController:credits')) }}
I have been working with symfony for 4 weeks now so not that long.
I want to call some functions when the form is submitted only nothing that I place in the if statement is working, it doesn't throw any exception at the moment. Am I not seeing something or is it not possible to do this when I render a form?
EDIT:
my credits_widget.html.twig
{% block credits %}
{{ form_start(form.credits) }}
{{ form_widget(form.credits) }}
{% if app.user %}
<button type="submit">Koop nu!</button>
{% else %}
<button type="button">Login en koop</button>
<button type="button">Registreer en koop</button>
{% endif %}
{{ form_end(form.credits) }}
{% endblock %}
The problem is that the form is rendered without action and when submitted, it is submitted by default to the root route from which it was rendered. So the credits action is not being called. You should change the action of the form like this:
public function credits(Request $request)
{
// set current url path as form's action
$form = $this->createForm(CreditType::class, null, , [
'action' => $request->getRequestUri()
]);
$form->handleRequest($request);
if ($form->isSubmitted()) {
throw new \Exception('It works!');
}
return $this->render(
'credits/credits_widget.html.twig',
[
'form' => $form->createView(),
]
);
}
EDIT: You are currently rendering subform form.credits when you should really render form as it is passed from your controller. Your credits_widget.html.twig template should look like this:
{% block credits %}
{{ form_start(form) }}
{{ form_widget(form) }}
{% if app.user %}
<button type="submit">Koop nu!</button>
{% else %}
<button type="button">Login en koop</button>
<button type="button">Registreer en koop</button>
{% endif %}
{{ form_end(form) }}
{% endblock %}
I have an issue with a Symfony Form Collection field. I have a parent form which has two fields which are form collections. Everything is working absolutely fine, except when I submit my form with invalid data. The errors for the form collection fields are output beneath the form on the page. I have been reading through the documentation for error_bubbling on these fields and realise that for CollectionType fields, it defaults to true. I have therefore set it to false on each field and still the errors are not mapped to the fields on the form.
The collection fields are able to be added dynamically to the page through javascript on the front end. What I have noticed, is that in my markup, before I have even submitted the form, there are two erroneous <div class=""form-group">'s added to the base of my markup which I am not outputting in my template. When the form is submitted and is not valid, then the errors are being output within these divs.
The code;
ItemFormType;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('shop', ShopType::class, [
'data_class' => Shop::class,
'label' => false,
])
->add('purchase', PurchaseType::class, [
'data_class' => Purchase::class,
'label' => false,
])
->add('missing_items', CollectionType::class, [
'entry_type' => MissingItemFormType::class,
'allow_add' => true,
'allow_delete' => true,
'label' => false,
'prototype' => true,
'error_bubbling' => false,
])
->add('replaced_items', CollectionType::class, [
'entry_type' => ReplacedItemFormType::class,
'allow_add' => true,
'allow_delete' => true,
'label' => false,
'prototype' => true,
'error_bubbling' => false,
])
->add('submit', SubmitType::class)
->getForm();
}
/**
* Get the form name.
*
* #return string
*/
public function getName(): string
{
return 'missing_form';
}
/**
* Set form options.
*
* #param OptionsResolver $resolver
*
* #return void
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => null,
'error_bubbling' => false
]);
}
Controller;
/**
* #Route("/", name="homepage")
*
* #param ClaimMailer $mailer
* #param Request $request
*
* #return Response
*/
public function indexAction(ClaimMailer $mailer, Request $request): Response
{
$purchase = [
'shop' => new Shop(),
'purchase' => new Purchase(),
];
$form = $this->createForm(MissingFormType::class, $purchase);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->store($form, $purchase);
// Send confirmation email.
$mailer->send(
$purchase['purchase']->getEmail(),
$purchase['shop']->getName(),
$purchase['purchase']->getClaimReferenceNumber()
);
return $this->render('form/form_complete.html.twig', [
'purchase_id' => $purchase['purchase']->getPurchaseReferenceNumber(),
]);
}
return $this->render('form/purchase_form.html.twig', [
'form' => $form->createView(),
]);
}
/**
* Store form data.
*
* #param Form $form
* #param array$claim
*
* #return void
*/
public function store(Form $form, $purchase){}
The Template;
{% block _missing_form_missing_items_entry_row %}
{% for field in form %}
<td>
{{ form_row(field) }}
</td>
{% endfor %}
{% endblock %}
{% block _missing_form_replaced_items_entry_row %}
{% for field in form %}
<td>
{{ form_row(field) }}
</td>
{% endfor %}
{% endblock %}
{% block website_body %}
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-body">
{{ form_start(form) }}
<div class="row">
<div class="col-lg-6">
{{ form_row(form.shop.name) }}
{{ form_row(form.shop.accountNumber) }}
{{ form_row(form.shop.email) }}
{{ form_row(form.shop.addressLine1) }}
</div>
<div class="col-lg-6">
{{ form_row(form.shop.addressLine2) }}
{{ form_row(form.shop.town) }}
{{ form_row(form.shop.county) }}
{{ form_row(form.shop.postcode) }}
</div>
</div>
<div class="row">
<h3>Missing Items</h3>
<table class="table missing_items">
<tbody class="missing_items" data-prototype="{{ form_row(form.missing_items.vars.prototype)|e('html_attr') }}"></tbody>
</table>
</div>
<div class="row">
<div class="col-lg-6">
{{ form_row(form.purchase.receivedReplacement) }}
</div>
<table class="table replacement-items">
<tbody class="replacement_items" data-prototype="{{ form_row(form.replaced_items.vars.prototype)|e('html_attr') }}"></tbody>
</table>
</div>
<div class="row">
<div class="col-lg-6">
{{ form_row(form.submit) }}
</div>
</div>
{{ form_end(form) }}
{% embed 'form/components/terms_and_conditions.html.twig' %}{% endembed %}
</div>
</div>
</div>
</div>
{% endblock %}
Any assistance would be greatly appreciated! Tried everything to get the errors in the correct place.
So for anyone else out there, I have found the answer to this conundrum.
Basically, whilst my JS was rendering the form onto the page via the "prototype" attribute on my <tbody>, as far as Symfony was aware, I was not explicitly outputting the fields into my template. As a result, all of the errors for the item fields were being spat out when calling 'form_end(form)' at the end of the template.
As form_end(form) calls form_rest() behind the scenes, it basically outputs any of the fields for the form which have not been explicitly rendered - as a result the validation errors and their respective fields were being output at the end of the form on the page!
By explicitly outputting these fields within the template, the errors and associated fields were displayed correctly in the form as so;
<tbody class="missing_items" data-prototype="{{ form_row(form.missing_items.vars.prototype)|e('html_attr') }}">
{% for field in form.missing_items %}
<tr class="item">
<td>{{ form_row(field.quantity) }}</td>
<td>{{ form_row(field.description) }}</td>
<td>{{ form_row(field.invoiceNumber) }}</td>
<td>{{ form_row(field.invoiceDate) }}</td>
<td>{{ form_row(field.deliveryDate) }}</td>
</tr>
{% endfor %}
</tbody>
I do hope this helps somebody else out there who finds themselves in the same predicament!
Is there any possibility in symfony2 to display a readonly-field of an entity as a label rather than a text field or whatever?
I am currently using the following code, but using a disabled text field does not feel very sophisticated:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('readonlyfield', 'text', array('property_path' => 'readonlyfield.displayString','disabled' => true));
$builder->add('editablefield', 'textarea');
}
I am using symfony 2.4.4..
EDIT:
I am using this form type from within another form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('mySubEntities', 'collection', array('type' => new MySubEntryType()));
$builder->add('save_to_db', 'submit', array('label' => 'Submit'));
}
twig file:
{{ form_start(form) }}
{% for row in form.mySubEntities %}
<li>
<ul>
<table>
<tr><td>{{ form_label(row.readonlyfield) }} </td><td>{{ form_widget(row.readonlyfield) }}</td></tr>
<tr><td>{{ form_label(row.editablefield) }}</td><td> {{ form_widget(row.editablefield) }}</td></tr>
</table>
</ul>
</li>
{% endfor %}
{{ form_end(form) }}
You can access value in form using {{ form.vars.value.yourfield }}.
You have a nested/collection so use {{ row.vars.value.yourfield }}
Why don't you just render the entity property in your template instead of making it part of a form? You could make your own FieldType but that's not what I recommand. If you want the value to be displayed as a form like file I would use the readonly property.
When I am trying to add password filed in my form type class:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text');
$builder->add('email', 'email');
$builder->add('password', 'password');
$builder->add('terms', 'checkbox', array(
'mapped' => false,
'constraints' => new NotBlank()
));
}
and then render this field in twig template {{ form_row(register.password) }} it appears like text filed. But I need it to be the password type <input type='password'...
Also form type is attached to entity with password attribute:
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=32, nullable=false)
*/
private $password;
What is the reason?
The reason is that I use form theme:
{% block text_row %}
<div>
{{ block('form_label') }}
<div>
{{ block('form_widget_simple') }}
</div>
</div>
{% endblock %}
So I add new block:
{% block password_row %}
<div>
{{ block('form_label') }}
<div>
{{ block('password_widget') }}
</div>
</div>
{% endblock %}
And now all is OK
You should use Type to define field Type in $builder->add('child', 'type', ...)
$builder->add('username', TextType::class, array('label' => 'Username'))
->add('password', PasswordType::class, array('label' => 'Password'))
and in Twig
{{ form(form) }}
Not sure if this is the correct way, but did you try specifying a type to the field?
$builder->add('password', 'password', array('type'=>'password');