Symfony2 collection of entities, add new from select - symfony

I am starting with Symfony2, excuse me if my question is very easy but:
I have 2 entities:
News (id, title, tags)
Tag (id, news)
I have relations many-to-many
Sample code of News:
/**
* #ORM\ManyToMany(targetEntity="Tag", inversedBy="news", cascade={"persist", "remove"}))
* #ORM\JoinTable(name="news_tags")
*/
private $tags;
Sample code of Tag:
/**
* #ORM\ManyToMany(targetEntity="News", mappedBy="tags")
*/
private $news;
I have a TagType to adding tags to DB, just simple
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('name', 'text');
}
NewsType
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('title', 'text', array(
'attr'=>array(
'maxlength'=>60
)
))
->add('tags', 'collection', array(
'type'=>new TagType(),
'allow_add'=>true,
'allow_delete'=>true,
'by_reference'=>false,
))
;
}
Sample of News form HTML:
<table class="tags table table-hover">
<thead>
<tr>
<th>name</th>
<th>action</th>
</tr>
</thead>
<tbody data-prototype="{% filter escape %}{% include 'XAdminBundle:Form:news_tags_prototype.html.twig' with {'form': form.tags.vars.prototype} %}{% endfilter %}">
{% for tag in form.tags %}
<tr>
<td>
{{ tag.vars.data.name }}
{{ form_widget(tag.name, {'attr': {'class': 'hidden'} }) }}
</td>
<td class="action">
</td>
</tr>
{% endfor %}
</tbody>
</table>
news_tags_prototype.html.twig
<tr>
<td>{{ form_widget(form.name, {'attr': { 'class': 'form-control' } }) }}</td>
<td class="action"></td>
</tr>
In this case when I click Add Tag, I've got a text input and I can add completely new tag, but the only one I want after click Add Tag get a select box with names of Tag entitiess from DB.
How to do this throw js and prototype (suggested by http://symfony.com/doc/current/cookbook/form/form_collections.html).
Thanks for your help!

In TagType, you should go with something like:
$builder->add('name', 'entity', array(
'class' => 'SomeBundle:Tag'));
Which will ensure that the prototype renders as a dropdown instead of a text element. Hope that helps.
Edit: I just realized that you probably need your existing TagType in order to add the tag to the database in the first place. In that case, create a new file called TagDropdownType with my suggested change above and then in your NewsType, change:
->add('tags', 'collection', array(
'type'=>new TagType(),
'allow_add'=>true,
'allow_delete'=>true,
'by_reference'=>false,
))
to:
->add('tags', 'collection', array(
'type'=>new TagDropdownType(),
'allow_add'=>true,
'allow_delete'=>true,
'by_reference'=>false,
))

Related

Symfony - Form with multiple entity objects

I'm running Symfony 3.4 LTS and I have an entity Attribute:
<?php
class Attribute
{
private $id;
private $code;
private $value;
private $active;
// My getters and setters
Below the database table:
I would like to get, in a form, all the rows with code == 'productstatus'. I tried:
<?php
$attributes = $this->getDoctrine()->getRepository(Attribute::class)->findBy(['code' => 'productstatus']);
// $attributes contains array with 3 objects 'Attribute'
$form = $this->createFormBuilder($attributes);
$form->add('value', TextType::class);
$output = $form->getForm()->createView();
If I dump() the $output var in Twig:
... I'm unable to make a loop to display the 3 fields with values.
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
Result :
My goal is to allow the user to edit all the values of a specific attributes in the same form (or multiple forms, but in the same page). I already tried with CollectionType without success.
I found a solution: create 2 nested forms and use CollectionType. Hope it helps.
<?php
// Controller
$attributes = $this->getDoctrine()->getRepository(Attribute::class)->findBy(['code' => 'productstatus']);
$form = $this->createForm(AttributeForm::class, ['attributes' => $attributes]);
// AttributeForm
$builder
->add('attributes', CollectionType::class, [
'entry_type' => AttributeValueForm::class,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
]);
// AttributeValueForm
$builder
->add('value', TextType::class, ['label' => false, 'required' => false])
->add('active', CheckboxType::class, ['label' => false, 'required' => false])
// Twig view
{{ form_start(form) }}
{% for attribute in form.attributes.children %}
<tr>
<td>{{ form_widget(attribute.value) }}</td>
<td>{{ form_widget(attribute.active) }}</td>
</tr>
{% endfor %}
{{ form_end(form) }}
You an use an if statement in the your AttributeType like in the example below:
$builder
->add('entree', EntityType::class, array('class'=>'cantineBundle:plat',
'choice_label'=>function($plat){
if($plat->getType()=='Entree'&&$plat->getStatus()=="non reserve"){
return $plat->getNomPlat();
}
},
'multiple'=>false)
);

Symfony Forms - How to Change a CollectionTypes Items labels

This is my first question on stackoverflow; until now I just looked for answers for my issues and found them. But now it seems that I have one that nobody or at least no one here stumbled across.
About my problem:
In my Class FieldType extends AbstractType I want to change the labels of CollectionType items.
This works for all contained items, but I want to set the labels individually:
$translations = $builder->create("translations", CollectionType::class, array(
"label" => "Translations",
"type" => TranslationType::class,
"entry_options" => array(
'label' => "THIS IS A TEST"
)
));
Here I add new types to the CollectionType and try to set each item's label:
foreach ($this->field->getTranslations() as $translation) {
/** #var Language $language */
$iso2 = strtolower($translation->getLanguage()->getIso2());
$translation = $builder->create("translation_{$iso2}", TranslationType::class, array(
'label' => $iso2,
)
);
$translations->add($translation);
}
$builder->add($translations);
But they are not displayed in the template; I suppose that here the indices of the Collection are shown (0,1,...). (see Rendered Translations FormView)
This is my TranslationType:
class TranslationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add("label");
$builder->add("placeholder");
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Translation::class,
));
}
}
I got here by searching for exactly the same question.
The 0,1,... probably are the keys of ArrayCollection of your entity. But that doesn't help.
The methods below are not pretty, but they should work. I'll be watching this thread for better suggestions...
Method 1
In your FormType class, add:
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
...
/**
* this gets called in the final stage before rendering the form
*/
public function finishView(FormView $view, FormInterface $form, array $options)
{
foreach ($view['translations']->children as $childView)
{
// here you have access to all child form vars, assigned entities, etc
$childView->vars['label'] = ''; // set label here
}
}
Method 2:
Just customize the template, as per http://symfony.com/doc/current/cookbook/form/form_customization.html
Instead of {{ form_label() }}, use something else :)
As I said, it is not pretty, but hope it helps.
Thanks for the answer #Karolis. Just in case this might be interesting for someone else. I was also facing this issue trying to customize the label of individual collection items, and I was trying for a while to do it through twig like so:
{% for key, child in successStoryForm.content.children %}
<div id="successStory_content">
<div class="form-group">
<div id="{{ child.vars.id }}">
<div class="form-group">
<label class="control-label required" for="{{ child.vars.id }}_text">{{ contentHeaders[key] }}</label>
<textarea id="{{ child.vars.id }}_text" name="{{ child.vars.full_name }}[text]" required="required" class="form-control" placeholder="<p>Dein Text</p>"><p>Dein Text_2</textarea>
<span class="help-block">
<ul class="list-unstyled">
<li>
<span class="glyphicon glyphicon-exclamation-sign"></span>
{{ dump(child.vars.errors) }}
</li>
</ul>
</span>
</div>
</div>
</div>
</div>
{% endfor %}
This actually rendered the label correctly, however, I was not able to render the errors properly. They were always empty.
In my main formtype I add this collection using:
->add('content', CollectionType::class, [
'entry_type' => SuccessStoryContentType::class,
'required' => true,
'entry_options' => [
'label' => false,
],
])
The SuccessStoryContentType is a simple form type with some validation (left out here)
$builder
->add('text', TextareaType::class);
To get this to work I had to dig a little deeper into the form views children like so:
foreach ($view['content']->children as $key => $childView)
{
$childView->vars['form']->children['text']->vars['label'] = $label;
}

Symfony2 customize form choice labels

I want my choice labels rendered unescaped
here my FormType code:
$builder
->add('banner', new BannerType())
->add('runtime', 'entity', array(
'class' => 'AdvertisingBundle:Runtime',
'expanded' => true,
'property' => 'label'
))
here the label property:
public function getLabel()
{
return "<div class labelLeft>".$this->description.
"</div><div class labelLeft>".$this->hint."</div>";
}
I want the label rendered raw, which form_theme fragment I need to override?
here my solution:
<table>
{% for option in form.runtime %}
<tr>
<td class="choiceColumn">
{{ form_widget(option) }}
</td>
{{ option.vars.label |raw}}
</tr>
{% endfor %}
</table>

accessing other properties in twig of an entity field type

I'm trying to display an entity field type expanded and with multiple set to true. However, I want to display the other properties of the entity. This is similar to Symfony2 : accessing entity fields in Twig with an entity field type but that solution hasn't worked for me. I'm getting the error: Item "code" for "" does not exist.
How can I access the other properties of the entity (color in this case)?
Here is what I have so far:
$builder->add('colors', 'entity', array(
'class' => 'PrismPortalCommonBundle:Color',
'property' => 'code',
'expanded' => true,
'multiple' => true,
));
and in the twig template:
{% for color in form.colors %}
<tr>
<td>{{ form_widget(color) }}</td>
<td>{{ color.vars.data.code }}</td>
</tr>
{% endfor %}
In Symfony 2.5 - you can accomplish this by accessing the data from each choice using the child's index value.
In the form builder - as you might expect:
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Generate form
$builder
->add('child', 'entity', array(
'class' => 'MyBundle:Child',
'label' => 'Children',
'property' => 'any_property_for_label',
'expanded' => true,
'multiple' => true
));
}
In the Twig template:
{{ form_start(form) }}
{% for child in form.child %}
{% set index = child.vars.value %}{# get array index #}
{% set entity = form.child.vars.choices[index].data %}{# get entity object #}
<tr>
<td>{{ form_widget(child) }}</td>{# render checkbox #}
<td>{{ entity.name }}</td>
<td>{{ entity.email }}</td>
<td>{{ entity.otherProperty }}</td>
</tr>
{% endfor %}
{{ form_end(form) }}

Creating Repeative Fields inside my Form Symfony2

I am working on a college project where I want to take the attendance of all the students. I have created a model with 3 fields i,e date, present (boolean) and student_id. Now when I try to generate a form out of it, it will display me only these 3 fields. However I want all the students of the class. So I created a loop for students and created an array of attendance objects. Now I am stuck, I don't know how I can pass them to my TWIG file, and I am also confused if it ist the right way to do this. Here is my Model and Controller code
FORM
namespace College\StudentBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class StudentAttendanceType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('date')
->add('present')
;
}
public function getName()
{
return 'college_studentbundle_studentattendancetype';
}
}
CONTROLLER
public function takeAttendanceAction($Department_Id)
{
$students = $this->getDoctrine()
->getRepository('CollegeStudentBundle:Student')
->findAll($Department_Id);
foreach($students as $key => $student){
$attendance[$key] = new StudentAttendance();
$form[$key] = $this->createForm(new StudentAttendanceType(), $attendance[$key]);
}
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()
->getEntityManager();
$em->persist($attendance);
$em->flush();
return $this->redirect($this->generateUrl('CollegeStudentBundle_index', array('id' => $Department_Id)));
}
}
return $this->render('CollegeStudentBundle:StudentAttendance:take-attendance.html.twig', array(
'form' => $form->createView(), 'department' => $Department_Id, 'students' => $students,
));
}
How can I render the form in a way that it will display me all the students with a separate checkbox ?
HTML.TWIG
{% block body %}
<form action="{{ path('CollegeStudentBundle_take_attendance',{'id':department} ) }}" method="post" {{ form_enctype(form) }} name="acadimics-form" id="acadimics-form" >
{{ form_errors(form) }}
{{ form_row(forms[0].date) }}
<table id="mytabs" border="1" cellpadding="5" cellspacing="2" width="100%" >
<tr>
<th> Enrolment No. </th>
<th> Student's Name </th>
<th> Present </th>
</tr>
{% for student in students %}
<tr>
<td> {{ student.enrolmentNo }} </td>
<td> {{ student.firstname }} {{ student.lastname }} </td>
<td> {{ form_row(form.present) }} </td>
</tr>
{% endfor %}
{{ form_rest(form) }}
</table>
<input type="submit" value="Take Attendance" />
</form>
</div>
{% endblock %}
Not tested. But there should be a little modification.
The loop in the controller:
foreach($students as $key => $student){
$attendance[$key] = new StudentAttendance();
$form[$key] = $this->createForm(new StudentAttendanceType(), $attendance[$key])->createView();
}
return the array:
return $this->render('CollegeStudentBundle:StudentAttendance:take-attendance.html.twig', array(
'form' => $form, 'department' => $Department_Id, 'students' => $students,
));
In the template:
{% for sform in form %}
{{ form_widget(sform) }}
{% endfor %}
I think you may be on the wrong track. S2 already deals with arrays out of the box.
Read through:
http://symfony.com/doc/current/cookbook/form/form_collections.html
http://symfony.com/doc/current/reference/forms/types/collection.html
You basically want to create a master ListofStudents form and then imbed a StudentAttendence form. You can then pass your array of students to the master form and all the array processing will happen magically.
In your ListOfStudents form you will have something like:
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('students', 'collection', array('type' => new StudentAttendenceType()));
You return the variable students to the template and you're calling student.firstname in twig, it should be students.firstname and you should keep your 'form' => $form->createView() in the return of your controller.
I think it should work better, hope this help!

Resources