I have a symfony form that contains a long list of items with checkboxes. I have successfully made knp paginator work and now the form is split into pages. However, when I move between pages I lose the checked/unchecked status of the checkboxes. I am sure that this is probably quite easy but I cannot seem to work it out. I am not very experienced with php yet.
Here is my controller:
/**
* #Route("/addquestions/{quizid}", name="addquestions")
*/
public function add(Request $request, PaginatorInterface $paginator, $quizid)
{
$repository = $this->getDoctrine()->getRepository(Quiz::class);
$quiz = $repository->find($quizid);
$repository = $this->getDoctrine()->getRepository(Question::class);
$questions = $repository->findAll();
// Paginate the results of the query
$pagination = $paginator->paginate($questions, $request->query->getInt('page', 1), 3);
$form = $this->createForm(AddQuizQuestionType::class, $quiz, ["pagination" => $pagination]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$questions = $form->get('question')->getData();
foreach ($questions as $question){
$quizquestion = new QuizQuestion();
$quizquestion->setQuestion($question);
$quiz->addQuizQuestion($quizquestion);
}
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($quiz);
$entityManager->flush();
return $this->redirectToRoute('homepage');
}
return $this->render('editquiz/addquestions.html.twig', [
'form' => $form->createView(),
'pagination' => $pagination,
]);
}
And my form - it uses the paginated query to display the items with a check box.
class AddQuizQuestionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('question', EntityType::class, [
'expanded' => true,
'multiple' => true,
'class' => Question::class,
'mapped' => false,
'choices' => $options['pagination'],
])
->add('submit', SubmitType::class, [
'label' => 'Submit',
])
->setMethod('GET')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Quiz::class,
'pagination' => null,
]);
}
}
and my template - it renders the checkboxes with images from the database.
{% extends 'base.html.twig' %}
{% block title %}CreatequizController{% endblock %}
{% block body %}
<h1>Create Quiz</h1>
{{ form_start(form) }}
{% for question in form.question %}
<h5>Question {{ question.vars.value }}</h5>
<div class="border mb-5">
<img src="{{ asset(question.vars.label) }}" alt=""/>
<div class="pl-3">
{{ form_widget(question) }}
</div>
</div>
{% endfor %}
<div class="navigation">{{ knp_pagination_render(pagination) }}</div>
{{ form_end(form) }}
{% endblock %}
I would like it so that when I jump between pages the checkboxes that I have selected remain checked.
Thank you in advance,
Martyn
Related
I've just updated to Symfony 6.2 (6.2.6 to be exact) and now, not sure why, unable to render form.
This is what debugger says:
Object of class Symfony\Component\Form\FormView could not be converted to string
In Symfony 6.2, according to the documentation, it should be possible to pass only FormInterface into render method in Controller. However, in both cases (meaning even using the createView()) method, it's unable to render the form itself. Any ideas where the problem might be?
Controller method:
#[Route(path: '/register', name: 'security-register')]
public function register(Request $request, MailUtil $util): Response
{
$form = $this->createForm(RegistrationForm::class);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$mail = new Mail();
$mail->setRecpient($form->get('email')->getData());
$mail->setTemplate("TestMail");
$util->sendMail($mail);
}
return $this->render("security/register.html.twig", ['form' => $form->createView()]);
}
Form class:
class RegistrationForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('email', EmailType::class, [
'required' => true
])
->add('password', RepeatedType::class, [
'type' => PasswordType::class,
'invalid_message' => 'register.error.password',
'required' => true,
'first_options' => ['label' => 'Password'],
'second_options' => ['label' => 'Repeat password']
])
->add('submit', SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => true,
'csrf_field_name' => '_csrf',
'csrf_token_id' => 'user_register'
]);
}
}
Twig:
{% block container %}
<div class="row">
<div class="col s6 offset-s3">
<div class="card-panel blue-grey darken-1" data-darkmode="card-panel blue-grey darken-3">
{% if not app.user %}
<h4 class="white-text center-align ">
Register
</h4>
{{ form_start(form) }}
{{ form.email }}
{{ form_end(form) }}
<div class="white-text center-align">
You are logged in as {{ app.user.userIdentifier }}<br><br><a class="waves-effect waves-light btn" href="{{ path('app_logout') }}">Logout</a>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
Tried to use new, Symfony 6.2 approach as stated in documentation: https://symfony.com/doc/current/forms.html#rendering-forms
And then tried to use the old one with createView() method.
Expected result should be rendered form, however both methods are throwing the same stacktrace.
In twig {{ form.email }} is wrong. This is probably why the error occur.
Switch to {{ form_row(form.email) }} for example
Or you can use widget and label separately.
{{ form_label(form.email) }}
{{ form_widget(form.email) }}
I have a form with a collectionType field, this field has 3 inputs and I want to align all 3 of them horizontally. Is it possible to do that in Symfony?
Also: Is it possible to choose the label for each element inside the collectionType?
Here's my form:
class WorkerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('Firstname')
->add('Lastname')
->add('tasks', CollectionType::class, [
'label' => 'Tasks',
'entry_type' => TasksType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'by_reference' => false,
'delete_empty' => true,
'attr' => [
'class' => 'collection',
],
])
;
$builder->add('save', SubmitType::class, [
'label' => 'Valider',
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => worker::class,
]);
}
}
Here's my tasks form:
class TasksType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('label')
->add('start')
->add('end')
;
}
and here's my _form.html.twig:
{% block extra_js %}
<script src="{{ asset('jquery.collection.js') }}"></script>
{% endblock %}
{% block body %}
<div class="row">
{%
form_theme form
'jquery.collection.html.twig'
'TaksTemplate.html.twig'
%}
{{ form_start(form) }}
<div class="my-custom-class-for-errors">
{{ form_errors(form) }}
</div>
<div class="col">
{{ form_row(form.Firstname, {'label': 'firstname'}) }}
</div>
<div class="col" >
{{ form_row(form.Lastname, {'label': 'Lastname'}) }}
</div>
<div class="col">
{{ form_row(form.tasks, {'label': 'tasks'}) }}
</div>
</div>
{{ form_end(form) }} </div>
{% endblock %}
{% block script %}
<script type="text/javascript">
$('.collection').collection({
'drag_drop_options': {
'placeholder': null
}
});
</script>
{% endblock %}
Also: is is possible to choose the label for each element inside the collectionType ?
No. you can't have multiple label in symfony collectionType.
I want to align all 3 of them horizontally.
Ask this again with HTML CSS tags
I have a simple form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', null, ['required' => true])
->add('doYouWant', ChoiceType::class, ['choices' => ['no' => 'no', 'yes' => 'yes']])
->add('type')
;
}
I would like the user after the selection doYouWant to "yes" to have a mandatory "type" option, so I am trying:
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($builder) {
$data = $event->getForm()->getData();
if ($data['doYouWant'] == 'yes') {
$builder->add('type', null, ['required' => true]);
}
}
);
But it does not matter...
I think the simplest way would be to add constraints to each field from your type, and then, in template, by using jquery you can toggle the visibility of your type field based on the selected value from the dropdown.
# AppBundle/Form/ExampleType.php
$builder
->add('name', null, [
'constraints' => [
new NotBlank(['message' => 'This cannot be empty']),
]
])
->add('doYouWant', ChoiceType::class, [
'placeholder' => 'Select',
'choices' => ['no' => 'No', 'yes' => 'Yes'],
'constraints' => [
new NotBlank(['message' => 'This cannot be empty']),
]
])
->add('type', EmailType::class, [
'constraints' => [
new NotBlank(['message' => 'This cannot be empty']),
new Email([
'message' => "The email '{{ value }}' is not a valid email",
])
]
])
;
I've added the type field as being of type email, just for testing purposes.
# Controller/DefaultController.php
/**
* #param Request $request
* #Route("/test", name="test")
* #return Response
*/
public function testAction(Request $request) : Response
{
$form = $this->createForm(ExampleType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
dump($form->getData());die;
}
return $this->render('default/test.html.twig', [
'form' => $form->createView(),
]);
}
# default/test.html.twig (assuming you are using bootstrap and jquery)
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
{{ form_start(form, { attr: { 'novalidate': 'novalidate' } }) }}
<div class="form-group">
{{ form_label(form.name) }}
{{ form_widget(form.name,{ attr:{ class:'form-control' } }) }}
{{ form_errors(form.name) }}
</div>
<div class="form-group">
{{ form_label(form.doYouWant) }}
{{ form_widget(form.doYouWant,{ attr:{ class:'form-control' } }) }}
{{ form_errors(form.doYouWant) }}
</div>
<div class="form-group type hidden">
{{ form_label(form.type) }}
{{ form_widget(form.type,{ attr:{ class:'form-control' } }) }}
{{ form_errors(form.type) }}
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Send</button>
</div>
{{ form_end(form) }}
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script>
$(document).ready(function(){
$('#appbundle_example_doYouWant').change(function(){
var choice = $(this).val();
if (choice == 'Yes') {
$('.type').removeClass('hidden');
} else {
$('.type').addClass('hidden');
}
});
});
</script>
{% endblock %}
You can use validation groups and put your assertions inside your entity.
And then you can choose validation groups based on the submitted data like so:
https://symfony.com/doc/current/form/data_based_validation.html
How to add assertion on entity:
class MyEntity {
/**
* #Assert\NotBlank(groups={"YourGroup"})
*/
protected $type;
}
Then on your form:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function (FormInterface $form) {
$data = $form->getData();
$want = $data->getDoYouWant();
if ($want) {
return ['YourGroup'];
}
return ['Default'];
},
));
}
I'm new to Symfony 4 and I'm trying to render a form with a ChoiceType Field with numeric choices in order to generate the exact Number of tags Chosen by the user.
This is my controller:
class ContactController extends AbstractController
{
/**
* #Route("/matrix", name="matrix")
*/
public function index(Request $request)
{
$contact = new Contact();
// i've already added some tags
$tag3 = new Tag();
$tag3->setName('tag3');
$contact->getTags()->add($tag3);
$tag4=new Tag();
$tag4->setName('ciao');
$contact->getTags()->add($tag4);
$form = $this->createForm(ContactType::class, $contact);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$contactFormData = $form->getData();
dump($contactFormData);
}
return $this->render('contact/index.html.twig', array(
//'our_form' => $form,
'form' => $form->createView(),
));
}
At this Point of my code, the form seems to be filled, I've checked with some dumps.
This is my twig
{% block body %}
<div>
{{ form_start(form) }}
{{ form_widget(form) }}
<ul class="tags" data-prototype="{{ form_widget(form.tags.vars.prototype)|e('html_attr') }}">
{% for tag in form.tags %}
<li> {{ form_row(tag.name) }}
</li>
{% endfor %}
</ul>
<input type="submit" value="Send" class="btn btn-success" />
{{ form_end(form) }}
</div>
{% endblock %}
It seems there's no visibility between this two Files, in fact, he is not able to enter into the for a loop. I've dumped some stuff and I've seen that tags have no children at this Point but it should.
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('motto')
->add('expectations', ChoiceType::class, array(
'choices' => array(
'1' => '1',
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5',
),
));
$builder->add('tags', CollectionType::class, array(
'entry_type' => TagType::class,
'entry_options' => array('label' => false),
'allow_add' => true,
'by_reference' => false,
'mapped' => false,
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}
I'm perplex about this code => $contact->getTags()->add($tag3);. It seems Tags to be an entity and Contact another one so your Contact entity should have adders/removers and/or setters/removers (event if cumulate both is not necessary).
So your entity should like:
class Contact
{
// ...
/** #var Collection */
protected $tag;
// ...
public function __construct()
{
$this->tags = new ArrayCollection();
}
// ...
public function addTag(Tag $tag)
{
$this->tags->add($tag);
}
public function removeTag(Tag $tag)
{
// ...
}
}
A good example to implement your case: How to Embed a Collection of Forms
Then i don't know what's your TagType form looks like but even if it's well develop your twig is not ok.
First form_widget(form) render the entire form
From Symfony Doc
Renders the HTML widget of a given field. If you apply this to an entire form or collection of fields, each underlying form row will be rendered.
So re-render the collection will have no effect. And even if your twig code is not te good one to render a collection.
So I'm facing this weird behavior when listing a series of checkboxes with twig
see the new page, it displays everything right, and saying that i'm saving this laboratory
img1
ok, I saved the laboratory, now went to the edit page
img2
the marked checkboxes displays at first, ruining the alphabetic order that was at the new page.
I need that the edit page become exactly like the new page, but I don't know how to organize these checkboxes on twig.
block that renders the checkboxes:
{% block _appbundle_laboratory_laboratoryExams_row %}
<div class="form-group">
{{ form_label(form) }}
<div class="col-md-10 col-md-offset-2">
<div class="app-checkbox-collection">
<p>{{ 'laboratory.field_laboratory' | trans }}</p>
{% dump(form) %}
{% for child in form %}
{% if child.exam.vars.data.type == constant('AppBundle\\Entity\\ExamLaboratory::TYPE')%}
{{ form_widget(child.permission, {
'attr' : {
'class' : 'exam-checkbox'
},
'label' : child.exam.vars.data.name
}) }}
{% endif %}
{% endfor %}
<p>{{ 'laboratory.field_image' | trans }}</p>
{% for child in form %}
{% if child.exam.vars.data.type == constant('AppBundle\\Entity\\ExamImage::TYPE')%}
{{ form_widget(child.permission, {
'attr' : {
'class' : 'exam-checkbox'
},
'label' : child.exam.vars.data.name
}) }}
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endblock %}
I don't ask at portuguese stackoverflow because no one answers symfony questions there, few users probably
EDIT:
Here is the buildForms:
Laboratory
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', null, array(
'label' => 'laboratory.name',
'attr' => array('class' => 'focus')
))
->add('leader', null, array(
'label' => 'laboratory.leader'
))
->add('city', null, array(
'label' => 'laboratory.city',
'placeholder' => 'action.select_one'
))
->add('laboratoryExams', 'collection', array(
'label' => 'laboratory.laboratoryExams',
'type' => new LaboratoryExamType(),
'allow_delete' => true,
'by_reference' => false,
))
;
}
LaboratoryExam
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('permission', null, array(
'label' => 'laboratoryexam.permission',
))
->add('exam', null, array(
'label' => 'laboratoryexam.exam',
))
;
}
You can set the order of checkboxes while building the form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('permission', 'entity', [
'class' => 'Acme\AppBundle\Entity\Parameter',
'multiple' => true,
'expanded' => true,
'query_builder'=> function(EntityRepository $repository) {
return $repository->createQueryBuilder('exam')->orderBy('exam.title', 'ASC');
},
]);
}
I would say it has nothing to do with twig.
That would be set on the form type object where the form with checkboxes is constructed.
It seems the data is populated programmatically on form creation with already selected choices being first.
If it isn't the case or you got some problem, please edit your question with formtype code and code where you get the data for the checkboxes.
thanks for the answers, i found a solution by resorting the ArrayCollection in the form.
$iterator = $form->get('laboratoryExams')->getData()->getIterator();
$iterator->uasort(function ($a, $b) {
return ($a->getExam()->getName() < $b->getExam()->getName()) ? -1 : 1;
});
$form->get('laboratoryExams')->setData(new ArrayCollection(iterator_to_array($iterator)));
IMO this should be done by default, I believe that no one would like to have the check-boxes disordered that way