Symfony2 multiple Entities of same class in one Form - php

I want to render a form which has multiple Entities of same Class.
I will display 2 fields, Price(type=text) and Enabled(type=checkbox).
I don't know how many I will have of those entities, so form will have to get them dynamically.
I have tried to do the following
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('price', 'text', array(
'label' => 'Price',
'required' => true
))
->add('enabled','checkbox',array(
'label' => 'Use this currency',
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Osiris\Entity\Pricing',
'csrf_protection' => false
));
}
public function getName()
{
return 'pricingtype';
}
And in my Controller I have created my form like this:
$pricingForm = $this->createFormBuilder($prices)
->add('items','collection',array(
'required' => false,
'prototype' => true,
'type' => new PricingType(),
))
->getForm()
;
In my twig I do:
{% for price in form_pricing %}
<h2>Price</h2>
<div class="row">{{ form_widget(price) }}</div>
{% endfor %}
However it comes only with h2 Prices and empty div with class=row. I feel like I am half way there, but I've no idea how to move on.
If someone knows how to get fields on submit as well, I will really appreciate it.

I found the solution,
the way I was creating the form in Controller was wrong!
I had to do the following:
$pricingForm = $this->createFormBuilder(array('prices'=>$prices))
->add('prices','collection',array(
'required' => true,
'allow_add' => true,
'type' => new PricingType(),
))
->getForm()
;
"allow_add => true" is necessary when working with collection, otherwise it will NOT add any of PricingType collection of entities to the form.
Then, because the form is built inside the controller "$this->createFormBuilder(array('prices'=>$prices))" , $prices array must be passed as an array with array keyname same as the one used in "->add('prices','collection',array(...)" , which is 'prices' so Symfony will know what to bind where. $prices is an array of Pricing objects array(0 => new Pricing()).
In my PricingType I have:
class PricingType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('price', 'text', array(
'label' => false,
'required' => true
))
->add('enabled','checkbox',array(
'label' => 'Use this currency',
))
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'XXX\XXX\Entity\Pricing',
'csrf_protection' => false
));
}
public function getName()
{
return 'pricingtype';
}
}
Here I need to have control over label attribute. I could not find the way for it( if anyone knows please post how to). I override my twig template as follows:
On the top we need next line of code:
{% form_theme form_pricing _self %}
Then override row and widget as follows (it was a nightmare to debug):
{% block _form_prices_entry_row %}
{% spaceless %}
{{ form_widget(form) }}
{% endspaceless %}
{% endblock %}
{% block _form_prices_entry_widget %}
{% spaceless %}
{{ form_row(form.price, { 'label' : form.vars.value.getCurrency().getTitle() } ) }}
{{ form_row(form.enabled) }}
{% endspaceless %}
{% endblock %}
In the body then, render form elements as follows:
{% for price in form_pricing.prices %}
<div class="price-row">{{ form_row(price) }}</div>
{% endfor %}
I really hope this will help you guyz. It was a real nightmare to debug especially the twig file, I did it thanks to my clever colleague.

I think you have missed a for loop in your twig file
Check this example:
{# store the prototype on the data-prototype attribute #}
<ul id="email-fields-list" data-prototype="{{ form_widget(form.emails.vars.prototype)|e }}">
{% for emailField in form.emails %}
<li>
{{ form_errors(emailField) }}
{{ form_widget(emailField) }}
</li>
{% endfor %}
</ul>
See the loop, I think that you need to add in your twig file.
In addition to loops you need to add JavaScript also.
Check this link:
http://symfony.com/doc/current/reference/forms/types/collection.html#adding-and-removing-items
Check the complete code. It will help you out to generate multiple entity forms from a single entity class using collection field type.

Related

Align multiple form inputs horizontally

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

How to keep form data when browsing paginated output?

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

Symfony not Render CollectionType

I create a form for a collection type of data in a separated from my controller
its my controller
/**
* #Security("has_role('ROLE_USER')")
* #Route("/phonebook/add", name="add")
*/
public function addPerson()
{
$person = new PhoneBookP();
$form = $this->createForm(PersoanlBookType::class, $person);
return $this->render(
'default/add.html.twig',
array('form' => $form->createView())
);
}
and its my form
->add('emails', CollectionType::class, array(
'entry_type' => EmailType::class,
'allow_add' => true,
'prototype' => true,
'allow_delete' => true,
'attr' => [
'class' => "emails-collection",
],
))
and my twig is
{% block body %}
{{ form(form) }}
{% endblock %}
it has no error and work in any common field (like NumberType,..) but not render CollectionType in my output. I using Symfony 4. whats my wrong?
in your rendering form you need to do this:
$person = new PhoneBookP();
//addEmail() is the arraycollection in your PhoneBookP Entity
//to be able to load the form, you need to add one from your arraycollection
$person->addEmail(new Email());
$form = $this->createForm(PersoanlBookType::class, $person);
The tags form_start and form_end only render the <form>-tags. To render the rest of the form elements call the form-twig-helper inside the <form>-tags too:
{{ form_start(form) }}
{{form(form)}}
<button type="submit">Register!</button>
{{ form_end(form) }}

Symfony 4 Form Collection with prototypes

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.

How to control marked checkbox order in twig

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

Categories