Pushing an option in a nested form with Symfony 2 - forms

I'm working on a form using select boxes. These boxes stand for scales which defer among countries.
Therefore, I created a service which generates custom values to feed the select box according to the user's preferences.This works fine with a first level form:
Controller code:
$form = $this->createForm(new formType, $entity, array(
// Getting the service
'myScales' => $this->get('myBnd.scales'),
// Getting user's scale preference
'scalesLocale' => $this->get('security.context')->getToken()->getUser()->getScaleLng(),
));
Then, I got all I need in the formType to display the customized select:
public function buildForm(FormBuilder $builder, array $options) {
$scaleSelect = array();
// Here is a custom code using $options['myScales'] and $options[scalesLocale']
// This builds the relevant $scaleSelect
// ....
$builder
->add('scale', 'choice', array(
'choices' => $scaleSelect
))
->add('subscale', 'collection', array(
'type' => new subType,
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
))
Then I need to define the nested subType. It also requires a customed select box. How can I send it the variable $scaleSelect in order to generate the (same) appropriate select box ?

quickest option that crosses my mind:
->add('subscale', 'collection', array(
'type' => new Subtype($scaleSelect),
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
))
then in your Subtype class:
function __construct($choices) {
$this->choices = $choices;
}
after that you can access your passed choices in buildform with $this->choices.
Hope it helps.

Related

SYMFONY FormTypeCollection with various fields

In my project I would like to be able to add a Collection on a Form. I thought about the FormTypeCollection. But the thing is, I need something like that:
A "New" button, at the end of the form and everytime you click on the new, a "mini-form" is added and you have the three input to fill: "name,text,link". I would like it to be stored in the database as artists = [name,text,link] for example. I have no idea how to do that. i don't want to add an Entity Artist because I just need this for display and I don't need it to be stored as an Entity on the database.
My code right now is like that:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Contenu')
->add('published', CheckboxType::class, ['required' => false, 'label' => 'Publier'])
->add('title', TextType::class, ['required' => true, 'label' => 'Titre'])
->add('marketingEtiquette', TextType::class, ['required' => false, 'label' => 'Etiquette Marketing'])
->add('textLink', TextType::class, ['required' => true, 'label' => 'Texte du lien'])
->add('shoppingLink', TextType::class, ['required' => true, 'label' => 'Lien'])
->add('media', ElFinderType::class, array(
'label' => 'Photo',
'instance' => 'form',
'enable' => true,
'required' => true,
'attr' => array('class' => 'form-control')
)
)
->add('position',ChoiceType::class, array(
'label' => 'Position dans la page',
'choices' => array(
'Bloc Artistes' => 'artists',
'Bloc haut de page' => 'top',
'Bloc bas de page' => 'bottom'
)
))
->add('artists',CollectionType::class,array(
'label' => 'Les artistes',
'allow_add' => true,
))
->end();
}
I don't know how to add 3 fields to the field artists and generate them on the add button click. i don't even know if it's possible actually. I also don't know what should be the type 'artists' in the database.
EDIT:
I would have like to do something similar to that, so I don't need to create an Entity nor a FormType:
->add('artists',CollectionType::class,array(
'entry_type' => TextType::class ,
'entry_options' => [
'artistName' => TextType::class,
'artistText' => TextType::class,
'artistLink' => TextType::class,
],
'label' => 'Les artistes',
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'by_reference' => false
))
But it's not working so I guess I can't. Error:
The current field `artists` is not linked to an admin. Please create one for the target entity : ``
EDIT 2:
I created my ArtistFormType:
class ArtistFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('artistName', TextType::class, array(
'label' => 'Nom de l\'artiste'
))
->add('artistText', TextType::class, array(
'label' => 'Texte sous l\'artiste'
))
->add('artistLink', TextType::class, array(
'label' => 'Lien vers l\'artiste'
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => null,
]);
}
}
I called it like that:
->add('artists',CollectionType::class,array(
'entry_type' => ArtistFormType::class,
'label' => 'Les artistes',
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'by_reference' => false
))
But I get the same error:
The current field `artists` is not linked to an admin. Please create one for the target entity : ``
EDIT : This solution is limited to Symfony\Component\Form\Extension\Core\Type\CollectionType, it might be different with the one from Sonata Admin.
You can find some information on this in the documentation.
The "easiest" way to do that, is to create another form (ArtistFormType for example) with the fields you wish to add.
Then in your parent form you add the CollectionType :
$formMapper
// ...
->add('artists', CollectionType::class, [
'entry_type' => ArtistFormType::class,
'label' => 'Artists',
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'by_reference' => false
])
;
Then if you already have some artists in your entity it will render the ArtistFormType once for each artist.
If you want to add new artists it is a bit trickier and requires some Javascript.
First, render the collection inside an <ul></ul> tag :
<ul class="artists">
{{ form_row(form.artists) }}
</ul>
Following this jsfiddle :
Find the collection and add an "add artist" button to it
<script>
var $addArtistLink = $('Add an artist');
var $newLinkLi = $('<li></li>').append($addArtistLink);
jQuery(document).ready(function() {
// Get the ul that holds the collection of artists
var $collectionHolder = $('ul.artists');
// add the "add a tag" anchor and li to the tags ul
$collectionHolder.append($newLinkLi);
// ...
}
</script>
Add the "click" event with some dom manipulations to add new artists :
jQuery(document).ready(function() {
// ...
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
$addArtistLink.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new artist form
addArtistForm($collectionHolder, $newLinkLi);
});
});
function addArtistForm($collectionHolder, $newLinkLi) {
// Get the data-prototype explained earlier
var prototype = $collectionHolder.data('prototype');
// get the new index
var index = $collectionHolder.data('index');
// Replace '$$name$$' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, index);
// increase the index with one for the next item
$collectionHolder.data('index', index + 1);
// Display the form in the page in an li, before the "Add an artist" link li
var $newFormLi = $('<li></li>').append(newForm);
// also add a remove button
$newFormLi.append('x');
$newLinkLi.before($newFormLi);
// handle the removal
$('.remove-artist').click(function(e) {
e.preventDefault();
$(this).parent().remove();
return false;
});
}
Then it should work fine. Of course some changes might be done to fit your project (field names, add the remove button on existing artists, ...).

Provide default data in collection child form

I have a nested form with prototype feature in Symfony 2. Here is the parent form which contains the collection:
$builder
->add('rooms', 'collection', array(
'type' => new RoomForm(),
'allow_add' => true,
'allow_delete' => true,
'data' => array(new RoomForm()),
))
As you can see, no data_class is defined. After the form submission $form->getData() correctly return associative array.
RoomForm is a simple form class composed by two fields:
$builder
->add(
$builder->create('dateAvailabilityStart', 'text', array(
'label' => 'label.from'
)))
->add(
$builder->create('dateAvailabilityEnd', 'text', array(
'label' => 'label.until'
)))
I would like find a way to populate my collection with existing RoomForm (for edit mode) and associate data in correct fields.
Any ideas?
You could do it from within your controller. Given that above form type is named as RoomFormCollection you could do something like this:
// This should be an array
$rooms = ... // Either from database or...
$form = $this->createForm(new RoomFormCollection(), array(
'rooms' => rooms
));
Another thing, 'data' => array(new RoomForm()), is not valid. RoomForm as its name suggests is a form type, not data struct. You should remove it...
Hope this helps...

Symfony form creates new object and create first one-to-many object

I have an entity for support tickets: SupportTicket(). I also have an entry for replies to each ticket: SupportEntry(). I setup a one-to-many relationship between SupportTicket() and SupportEntry().
Now what I'm trying to do is build my form so that it creates the initial SupportTicket and then inserts the first SupportEntry, all in the same form. I've been messing around with my code for a while, only half-understanding what I'm doing, but this is where I'm at right now:
// My controller, creating the form
$supportTicket = new SupportTicket();
$form = $this->createFormBuilder($supportTicket)
->add('subject', 'text', array(
'label' => 'Subject'
))
->add('jobNumber', 'text', array(
'label' => 'Job Number'
))
->add('supportGroup', 'entity', array(
'label' => 'Group',
'class' => 'ShawmutClientBundle:SupportGroup',
'property' => 'name',
'multiple' => true,
'expanded' => true
))
// ->add('supportEntries', new SupportEntryType())
->add('supportEntries', new SupportEntryType())
->add('Save', 'submit')
->getForm();
My attempt at the custom form type
<?php
namespace Shawmut\ClientBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SupportEntryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('comment', 'textarea');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Shawmut\ClientBundle\Entity\SupportEntry',
));
}
public function getName()
{
return 'SupportEntryType';
}
}
The form does have the comment box that I've pulled in from the form type, but when I try to submit the form, I get this error:
Neither the property "supportEntries" nor one of the methods "setSupportEntries()", "_set()" or "_call()" exist and have public access in class "Me\MyBundle\Entity\SupportTicket".
And yeah, that makes sense. It should be the addSupportEntries() method which is there. So how do I tell the form builder to use addSupportEntries instead of setSupportEntries?
Thanks in advance
Give the collection form type a go.
->add(
'supportEntries',
'collection',
array(
'type' => new SupportEntryType(),
'label' => 'Support Entries',
'error_bubbling' => true,
'cascade_validation' => true,
)
)
If you are using the collection form type, and the textarea is not showing, add:
'allow_add' => true
to the properties array().
The code would look something like this:
->add(
'supportEntries',
'collection',
array(
'type' => new SupportEntryType(),
'label' => 'Support Entries',
'error_bubbling' => true,
'allow_add' => true
'cascade_validation' => true,
)
)
To show the widget, assuming you are using twig:
{{ form_widget(form.supportEntries.vars.prototype.comment) }}
For saving the support entry, depending on how you built your entities, you might need to make some extra modifications.
The documentation should help you get it right:
How to Embed a Collection of Forms

Symfony2 form populating entitiy based on other entity

I'm using Symfony2 form component to create and modify forms. I'm currently loading all model entities as choices, but I need to get only the Models related to the selected (posted) value from manufacturer.
How can I achieve this?
class VehicleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('manufacturer', 'entity', array(
'label' => 'Brand',
'attr' => array('class' => 'input-block-level'),
'class' => 'BalslevCarBundle:Manufacturers',
'property' => 'short_name',
'empty_value' => 'All',
'required' => false
));
$builder->add('model', 'entity', array(
'label' => 'Model',
'class' => 'BalslevCarBundle:Models',
'property' => 'model',
'empty_value' => 'All',
'required' => false
));
}
public function getName()
{
return 'search';
}
}
First of all, you have to create "dynamically" your choices based onto user selection.
There are two ways, strictly coupled with your application behaviour, that leads (in both cases) at the same "core" code; the difference is only the way you call it.
So, let's explain the two possibilities:
Select manufacturer "statically" and don't change it over the time of request: use data retrieved from your business logic
Select manufacturer "dinamically" and can change them over the time of request: replace your form at every request; new form will be provided by business logic
So, the HEART of your question is: how can I retrieve only associated entities?
Answer
Into your VehicleType.php (where entity, in this case, is VehicleType ?)
private $manufacturerId;
public function __construct($manufacturerId)
{
$this->manufacturerId = $manufacturerId;
}
public function BuildForm(FormBuilderInterface $builder)
{
$manufacturerId = $this->manufacturerId;
$manufacturerQuery = function(EntityRepository $repo) use ($manufacturerId ){
return $repo->createQueryBuilder('m')
->where('m.id = :id')
->setParameter('id',$manufacturerId );};
$builder->add('manufacturer', 'entity', array(
'label' => 'Brand',
'attr' => array('class' => 'input-block-level'),
'class' => 'BalslevCarBundle:Manufacturers',
'property' => 'short_name',
'empty_value' => 'All',
'required' => false,
'query_builder' => $manufacturerQuery;
));
$builder->add('model', 'entity', array(
'label' => 'Brand',
'attr' => array('class' => 'input-block-level'),
'class' => 'BalslevCarBundle:Manufacturers',
'property' => 'short_name',
'empty_value' => 'All',
'required' => false,
'query_builder' => $modelQuery;
));
}
As you can see, in this way we use a custom repository to get only entities that are suitable for our needs. Obviously I am writing my DQL query without knowing your DB structure: it's your task and responsibility to adapt it to your real situation.
Moreover, you have to create another query for model (called $modelQuery in my answer) to retrieve correct models based on selected $id

How to implement the event listener to a radio button on Symfony2?

This is my form
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('nombreBD','text', array( 'required' => true, 'label' => 'Nombre Base de Datos: '))
->add('Servidor', 'choice', array(
'choices' => array('1' => 'Si', '0' => 'No'),
'required' => true,
'multiple' => false,
'expanded' => true,
'label' => 'Servidor Existente?'
))
->add('ServidorBD','entity',
array ('class' => 'MonseWebBundle:ServidoresBD',
'multiple' => true,
'required' => true,
'label' => 'Servidor de Base de Datos: ',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')
->orderBy('u.url', 'ASC');
},
))
;
}
and what i'm trying to do is if the User chooses "No" in the radio button, the "ServidorBD" entity shouldn't display and it should display instead another form (loading it dynamically or redirecting the user to another url) to add a new one.
Since i'm new to Symfony2, i don't quite understand how to attach the eventlistener to the "radio button" nor how to display another bit of form instead of the "ServidorBD" when this happens.
PLEASE HELP! T-T
What you want to do is build your form dynamically according to datas you will bind on your form.
In Symfony 2.0.x or 2.1.x, the form component is not able to to alter the form structure after binding datas on it. That will be done in Symfony 2.2.
See this issue: https://github.com/symfony/symfony/issues/3767
So, currently, you can't use the form event listener to archive this use case.