I have a form with a bunch of many-to-many checkboxes. The form saves just fine (I checked the database to make sure), but then the checkboxes aren't checked in agreement with the database.
Is there something special I need to do to get the checkboxes to stay checked?
Here's my form definition:
<?php
namespace VNN\PressboxBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class PreferencesSportsICareAboutType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('interestingSports', 'entity', array(
'multiple' => true,
'expanded' => true,
'property' => 'name',
'class' => 'VNN\PressboxBundle\Entity\Sport',
'query_builder' => function(\VNN\PressboxBundle\Repository\SportRepository $er) {
return $er->createQueryBuilder('s')
->orderBy('s.name', 'ASC');
},
));
}
public function getName()
{
return 'vnn_pressboxbundle_preferencessportsicareabouttype';
}
}
And here's the relevant part of my template:
{% block form %}
<h2>Sports I Care About</h2>
<form action="{{ path('user_update_preferences', { 'sectionName': sectionName }) }}" method="post" {{ form_enctype(form) }} novalidate class="clearfix">
{% for error in errors %}
error: {{ error.messageTemplate }}
{% endfor %}
{% for field in form.interestingSports %}
<li>
{{ form_widget(field) }}
{{ form_label(field) }}
</li>
{% endfor %}
{{ form_rest(form) }}
<p><button type="submit">Submit</button></p>
</form>
{% endblock %}
It looks like your Type and your Template are well defined. You should check your entity relationships.
I have already implement the same behavior but using a One-To-Many relationship.
Edit by OP:
It was the entity relationships. I needed to change this
/**
* #ORM\ManyToMany(targetEntity="UserSportInterest", inversedBy="sports")
* #JoinTable(name="user_sport_interest",
* joinColumns={#JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="sport_id", referencedColumnName="id")}
* )
*/
private $sports;
to this (note targetEntity change)
/**
* #ORM\ManyToMany(targetEntity="Sport", inversedBy="sports")
* #JoinTable(name="user_sport_interest",
* joinColumns={#JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="sport_id", referencedColumnName="id")}
* )
*/
private $sports;
After that, it worked fine.
Related
I know there are lots of similar topics already.
What I tried:
checking my routes (php bin/console router:match url)
Overriding the ParamConverter "App\Entity\User object not found by the #ParamConverter annotation"
Understanding #ParamConverter & #security annotations
And more solutions that didn't work for me.
Here is not working fragment of the controller:
/**
* Edit action.
*
* #param Request $request HTTP request
* #param User $user User entity
* #param UserPasswordEncoderInterface $passwordEncoder Password encoder
*
* #return Response HTTP response
*
* #throws ORMException
* #throws OptimisticLockException
*
* #Route(
* "/{id}/password",
* methods={"GET", "PUT"},
* requirements={"id": "[1-9]\d*"},
* name="app_password",
* )
*/
public function editPasswordUser(Request $request, User $user, UserPasswordEncoderInterface $passwordEncoder): Response
{
if (($this->getUser() == $user) || (is_array($this->getUser()->getRoles()) && in_array(
'ROLE_ADMIN',
$this->getUser()->getRoles()
))) {
$role = $user->getRoles();
$form = $this->createForm(NewPasswordType::class, $user, ['method' => 'PUT']);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user->setPassword(
$passwordEncoder->encodePassword(
$user,
$user->getPassword()
)
);
$user->setUpdatedAt(new DateTime());
$this->userService->save($user);
$this->addFlash('success', 'message_updated_successfully');
return $this->redirectToRoute('detail_show');
}
return $this->render(
'security/password.html.twig',
[
'form' => $form->createView(),
'user' => $user,
'role' => $role,
]
);
} else {
return $this->redirectToRoute('detail_show');
}
}
Twig file:
{% extends 'base.html.twig' %}
{% block title %}
{{ 'title.user_editpasswd'|trans({'%id%': user.id|default('')}) }}
{% endblock %}
{% block body %}
<h1>{{ 'title.user_editpasswd'|trans({'%id%': user.id|default('')}) }}</h1>
{{ form_start(form, { method: 'PUT', action: url('app_password', {id: user.id}) }) }}
{{ form_widget(form) }}
<div class="form-group row float-sm-right">
<input type="submit" value="{{ 'action.save'|trans }}" class="btn btn-primary" />
</div>
<div>
{% if role[0] == 'ROLE_ADMIN' %}
<a href="{{ url('user_view', {id: user.id} ) }}" title="{{ 'action.back_to_view'|trans }}">
{{ 'action.back_to_view'|trans }}
</a>
{% endif %}
</div>
<div>
{% if role[0] == 'ROLE_ADMIN' %}
<a href="{{ url('user_index') }}" title="{{ 'action.index'|trans }}">
{{ 'action.index'|trans }}
</a>
{% endif %}
</div>
{{ form_end(form) }}
{% endblock %}
My app is almost finished, and that's the first and only time I have such an error.
Change argument type in controller action from User to \Symfony\Component\Security\Core\User\UserInterface and everything should work. Current user is registered in a container by this service id.
I created a custom field type class BootstrapToggleType extends AbstractType which is child of CheckboxType I also prefixed it just in case as follows
public function getBlockPrefix(): string
{
return 'bootstrap_toggle';
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$config = $form->getParent()->get($form->getName())->getConfig();
$options = $config->getOptions();
$defaultDataOptions = $this->_bootstrapToggleDataDefaults;
$form->getParent()->add($form->getName(), CheckboxType::class, array_replace_recursive($options, [
'attr' => $defaultDataOptions,
]))
;
});
}
I added custom form theme to twig.yaml form_themes: ['Form/custom_types.html.twig', 'bootstrap_4_layout.html.twig'] and created mentioned file with following code
{% block bootstrap_toggle_row %}
{% for child in form.children if not child.rendered %}
<div class="my-custom-wrapper">
<div class="form-group">
{{ form_label(child) }}
{{ form_widget(child) }}
{{ form_help(child) }}
{{ form_errors(child) }}
</div>
</div>
{% endfor %}
{% endblock %}
However the field is rendered using standard theme ignoring bootstrap_toggle_row What am I doing wrong?
The problem is basically with this part of code $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { . However I need to use such construction to create a field using name provided by FormType object $builder->add('is_published', BootstrapToggleType::class)
if I change that part of code to something like that
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addChild('testName', CheckboxType::class);
}
my custom template is rendered correctly.
added
Because I re-added another child I have to provide it's correct block prefix.
$form->getParent()->add($form->getName(), CheckboxType::class, array_replace_recursive($options, [
'attr' => $defaultDataOptions,
'block_prefix' => 'bootstrap_toggle'
]));
Then in my custom template I need to reffer directly to the form variable as a single child, not an array. So instead of looping over children
{% for child in form.children if not child.rendered %}
just direct call
{{ form_label(form) }}
{{ form_widget(form) }}
{{ form_help(form) }}
{{ form_errors(form) }}
Now it works.
I have a form with author and message fields and NotBlank() validation on both.
In twig, I do this:
{{ form_start(form) }}
{{ form_errors(form.author) }}
{{ form_label(form.author) }}
{{ form_widget(form.author) }}
{{ form_errors(form.message) }}
{{ form_label(form.message) }}
{{ form_widget(form.message) }}
{{ form_end(form) }}
If I press Save button with empty fields I EXPECT to see this:
But I get this:
Somehow the bottom error message comes from the {{ form_label(...) }} I say this, because if I comment the labels out and use static HTML for labels, the output is like on first picture.
I would prefer not to use static HTML for labels, but I don't understand where the second error messages came from.
Below my code:
Form
class TestFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('author', TextType::class, ['required' => false, 'constraints' => [new NotBlank()]])
->add('message', TextType::class, ['required' => false, 'constraints' => [new NotBlank()]])
->add('save', SubmitType::class)
;
}
}
Controller
class TestController extends Controller
{
/**
* #Route("/testing", name="test")
* #param Request $request
* #return RedirectResponse|Response
*/
public function index(Request $request)
{
$form = $this->createForm(TestFormType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
return $this->redirectToRoute('test');
}
return $this->render('test/index.html.twig', [
'form' => $form->createView(),
]);
}
}
Template
{% extends 'base.html.twig' %}
{% block title %}Hello TestController!{% endblock %}
{% block body %}
<p>This is a test...</p>
{{ form_start(form) }}
{{ form_errors(form.author) }}
{{ form_label(form.author) }}
{{ form_widget(form.author) }}
{{ form_errors(form.message) }}
{{ form_label(form.message) }}
{{ form_widget(form.message) }}
{{ form_end(form) }}
{% endblock %}
For bootstrap theme error block is integrated in label.
So you need either to remove form_errors block in your template or to override form_label block.
You can use form_row (as #Adrien suggests in commentaries) as there is no form_errors call
You've explicitly added form_errors whereas error message already rendered via form_label. either you can remove form_errors or form_label.
I try to follow this link to install and create a form with CraueFormFlowBundle
CraueFormFlowBundle tutorial
i create the file
/src/AppBundle/Form/InterventoFlow.php
namespace AppBundle/Form;
use Craue\FormFlowBundle\Form\FormFlow;
use Craue\FormFlowBundle\Form\FormFlowInterface;
class InterventoFlow extends FormFlow {
protected function loadStepsConfig() {
return array(
array(
'label' => 'wheels',
'form_type' => 'AppBundle\Form\InterventoForm',
),
array(
'label' => 'engine',
'form_type' => 'AppBundle\Form\InterventoForm',
'skip' => function($estimatedCurrentStepNumber, FormFlowInterface $flow) {
return $estimatedCurrentStepNumber > 1 && !$flow->getFormData()->canHaveEngine();
},
),
array(
'label' => 'confirmation',
),
);
}
}
after i create
/src/AppBundle/Form/InterventoForm.php
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
namespace AppBundle/Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
class InterventoForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
switch ($options['flow_step']) {
case 1:
$validValues = array(2, 4);
$builder->add('numberOfWheels', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array(
'choices' => array_combine($validValues, $validValues),
'placeholder' => '',
));
break;
case 2:
// This form type is not defined in the example.
$builder->add('engine', 'TextType', array(
'placeholder' => 'prova',
));
break;
}
}
public function getBlockPrefix() {
return 'createIntervento';
}
}
after i create the service
services:
app.form.InterventoFlow:
class: AppBundle\Form\InterventoFlow
parent: craue.form.flow
and the controller
public function interventoAction(Request $request)
{
$formData = new Validate(); // Your form data class. Has to be an object, won't work properly with an array.
$flow = $this->get('AppBundle.form.InterventoFlow'); // must match the flow's service id
$flow->bind($formData);
// form of the current step
$form = $flow->createForm();
if ($flow->isValid($form)) {
$flow->saveCurrentStepData($form);
if ($flow->nextStep()) {
// form for the next step
$form = $flow->createForm();
} else {
// flow finished
$em = $this->getDoctrine()->getManager();
$em->persist($formData);
$em->flush();
$flow->reset(); // remove step data from the session
return $this->redirect($this->generateUrl('home')); // redirect when done
}
}
return $this->render('interventi/intervento.html.twig', array(
'form' => $form->createView(),
'flow' => $flow,
));
}
and my twig file
{% extends 'base.html.twig' %}
{% block body %}
<div>
Steps:
{% include '#CraueFormFlow/FormFlow/stepList.html.twig' %}
</div>
{{ form_start(form) }}
{{ form_errors(form) }}
{% if flow.getCurrentStepNumber() == 1 %}
<div>
When selecting four wheels you have to choose the engine in the next step.<br />
{{ form_row(form.numberOfWheels) }}
</div>
{% endif %}
{{ form_rest(form) }}
{% include '#CraueFormFlow/FormFlow/buttons.html.twig' %}
{{ form_end(form) }}
{% endblock %}
{% stylesheets '#CraueFormFlowBundle/Resources/assets/css/buttons.css' %}
<link type="text/css" rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}
always give me error
Attribute "autowire" on service "app.form.intervento" cannot be inherited from "_defaults" when a "parent" is set. Move your child definitions to a separate file or define this attribute explicitly in C:\xampp\htdocs\Myprject\app/config\services.yml (which is being imported from "C:\xampp\htdocs\Myprject\app/config\config.yml").
and if i comment
parent: craue.form.flow
You have requested a non-existent service "app.form.interventoFlow".
Try to dalete _defaults section from your service.yml file.
I have a form that has two rendered controllers (forms) inside it. I want to pass a variable from the main form to the rendered. I know I can do it with something like this:
{{ render(controller(
'CompanyNameofBundle:Search:shortjq', {'orgid':entity.orgId})) }}
But I am having troubles accessing the 'orgid' which is in the main form.
my.html.twig
{% extends 'CompanyNameofBundle::base.html.twig' %}
{% block body -%}
<h1>Organization Edit</h1>
{{ form_start(edit_form, {'attr': {'novalidate': 'novalidate'}}) }}
<div class="form-group'">
<div class="col-md-2">{{ form_label(edit_form.orgName, 'Organization Name') }}</div>
<div class="col-md-4">{{ form_widget(edit_form.orgName) }}</div>
<div class="hidden">{{ form_widget(edit_form.orgId) }}</div>
<div> </div><div> </div>
</div>
<ul class="record_actions">
<li>{{ form_end(edit_form) }}</li>
<li>{{ form(delete_form) }}</li>
<li>
<a href="{{ path('org') }}">
Back to the list
</a>
</li>
</ul>
{{ render(controller(
'CompanyNameofBundle:Search:shortjq', {'orgid':entity.orgId})) }}
{% if entity.orgId is not null %}
{{ render(controller(
'CompanyNameofBundle:OrgMember:test', {'orgid':entity.orgId})) }}
{% endif %}
{% endblock %}
SearchController.php
/**
* #Route("/shortjq", name="shortjq")
* #Template()
*/
public function shortjqAction()
{
$form = $this->createForm(new JqueryType(), null, [
'action' => '',
'method' => 'POST'
]);
return array(
'form' => $form->createView(),
);
}
JqueryType.php
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setMethod('POST')
->add('name', 'text')
->add('orgid', 'integer')
->add('search', 'submit')
;
}
/**
* #return string
*/
public function getName()
{
return 'companynameofbundle_jquery';
}
To access the form widgets into the views I use this:
{{ form_widget(form.orgid) }}
If you also want to add attributes to that widget you can add them like that (for example to add a class to the widget):
{{ form_widget(form.org, { 'attr': {'class': 'the-css-class-you-want'} }) }}
Here you can search for more information Twig Template Form Function and Variable Reference