I am using a Doctrine 2x one-to-many linking table like what is recommended in the docs
My Doctrine relationships seem to be fetching data properly, as evidenced by the fact that vars.data.myCollectionName has values,
{{ ladybug_dump( bandVacancyAssociation.bandVacancy.vars.data.genres) }}
yet when I try to access the form field children in order to use the form_row twig function my data is always an empty array
{{ ladybug_dump(edit_form.bandVacancyAssociations.children[0].children)}}
Since I do have values I can reverse engineer the symfony form structure but I would rather get it working using the built-in form widgets, any assistance would be much appreciated.
Here is the relevant code
Band
/**
* #ORM\OneToMany(targetEntity="BandVacancyAssociation", mappedBy="band", cascade={"persist"})
*/
private $bandVacancyAssociations;
BandType
$builder
->add('name')
->add('description')
->add('bandVacancyAssociations','collection',
array(
'by_reference' => true,
'type' => new BandVacancyAssociationType()
)
)
BandVacancy
/**
* #ORM\OneToMany(targetEntity="BandVacancyAssociation", mappedBy="bandVacancy",fetch="EAGER")
*/
private $bandVacancyAssociations;
/**
* #ORM\ManyToMany(targetEntity="Genre", inversedBy="bandVacancies",fetch="EAGER")
* #ORM\JoinTable(name="bandvacancy_genre",
* joinColumns={#ORM\JoinColumn(name="bandvacancy_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="genre_id", referencedColumnName="id")}
* )
*/
protected $genres;
/**
* #ORM\ManyToMany(targetEntity="Instrument", inversedBy="bandVacancies",fetch="EAGER")
* #ORM\JoinTable(name="bandvacancy_instrument",
* joinColumns={#ORM\JoinColumn(name="bandvacancy_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="instrument_id", referencedColumnName="id")}
* )
*/
protected $instruments;
BandVacancyType
$builder->add('genres', 'entity', array(
'multiple' => true,
'expanded' => true,
'class' => 'ZE\BABundle\Entity\Genre',
'property' => 'name',
))
->add('instruments', 'entity', array(
'multiple' => true,
'expanded' => true,
'class' => 'ZE\BABundle\Entity\Instrument',
'property' => 'name',
));
Band Vacancy Association
/**
* #ORM\ManyToOne(targetEntity="BandVacancy", inversedBy="bandVacancyAssociations",fetch="EAGER")
*/
private $bandVacancy;
BandVacancyAssociationType
$builder
->add('bandVacancy', 'entity', array( 'class' => 'ZE\BABundle\Entity\BandVacancy'))
;
It turns out that the one thing I was missing was this:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('bandVacancy', new BandVacancyType())
;
}
I can now loop through the values in the form.
<div class="panel">Band Vacancies
{% for bandVacancyAssociation in edit_form.bandVacancyAssociations %}
{#{{ dump(bandVacancyAssociation.vars.data.id) }}#}
<div class="panel-group" id="accordion">
<div class="panel panel-default" id="panel{{ bandVacancyAssociation.vars.data.id }}">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-target="#collapse{{ bandVacancyAssociation.vars.data.id }}"
href="#collapse{{ bandVacancyAssociation.vars.data.id }}">
{{ bandVacancyAssociation.vars.data.bandVacancy.name}}
</a>
</h4>
</div>
<div id="collapse{{ bandVacancyAssociation.vars.data.id }}" class="panel-collapse collapse in">
<div class="panel-body">
{{ form_label(bandVacancyAssociation.bandVacancy.name) }}
{{ form_widget(bandVacancyAssociation.bandVacancy.name) }}
{{ form_label(bandVacancyAssociation.bandVacancy.comment) }}
{{ form_widget(bandVacancyAssociation.bandVacancy.comment) }}
{% for genre in bandVacancyAssociation.bandVacancy.genres %}
{{ form_label(genre) }}{{ form_widget(genre) }}
{% endfor %}
{% for instrument in bandVacancyAssociation.bandVacancy.instruments %}
{{ form_label(instrument) }}{{ form_widget(instrument) }}
{% endfor %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
Thanks to whoever downvoted this question.
Related
To allow customisation of the registration form, some form fields are generated from database informations and the form in embedded into the registration form.
While each fields of the embedded form is typed, for some reason, when I render them using twig, some types like url or number are rendered as text type input.
Yet, all fields form the main form (nom, prenom, email, plainPassword) are rendered with the assigned type.
As you can see in the code fragments, I'm properly using form_widget and form_widget to render each input, thus type handling is done by Symfony.
When I dump each formView for each field, within field.vars.block_prefixes (array), I can find the type of the input as it should be.
As example, this is the content of a text input :
"block_prefixes" => array:3 [▼
0 => "form"
1 => "text"
2 => "_security_extraDataCollection_datum-30"
]
The content of a url input :
"block_prefixes" => array:4 [▼
0 => "form"
1 => "text"
2 => "url"
3 => "_security_extraDataCollection_datum-31"
]
And the content of a number input :
"block_prefixes" => array:3 [▼
0 => "form"
1 => "number"
2 => "_security_extraDataCollection_datum-33"
]
At first, I thought that was because I was using material-component-web, but even without CSS, this problem occur.
Any idea as to why url and number type are turned to text type when I render them from enbedded form?
Registration form
public function buildForm(FormBuilderInterface $builder, array $options) {
/** #var array $extraData */
$extraData=$options['extra_data'];
$builder->add('nom')
->add('prenom')
->add('email', EmailType::class)
->add('plainPassword', PasswordType::class, array(
'mapped'=>false,
'constraints'=>array(
new NotBlank(array(
'message'=>'Please enter a password',
)),
new Length(array(
'min'=>6,
'max'=>4096,
)),
),
));
if($extraData !== null && is_array($extraData) && count($extraData)) {
$builder->add('extraDataCollection', UnmappedMixedType::class, array(
'mapped'=>false,
'data'=>$extraData,
));
}
}
UnmappedMixedType form
public function buildForm(FormBuilderInterface $builder, array $options) {
/** #var array $extraData */
$extraData=$options['data'];
/** #var ExtraData $extraDatum */
foreach($extraData as $extraDatum) {
if($extraDatum->getType() == 'text') {
$builder->add('datum-'.$extraDatum->getId(), TextType::class, array(
'mapped'=>false,
'required'=>$extraDatum->getIsObligatoire(),
'label'=>$extraDatum->getLabel(),
));
} elseif($extraDatum->getType() == 'url') {
$builder->add('datum-'.$extraDatum->getId(), UrlType::class, array(
'mapped'=>false,
'required'=>$extraDatum->getIsObligatoire(),
'label'=>$extraDatum->getLabel(),
));
} elseif($extraDatum->getType() == 'number') {
$builder->add('datum-'.$extraDatum->getId(), NumberType::class, array(
'mapped'=>false,
'required'=>$extraDatum->getIsObligatoire(),
'label'=>$extraDatum->getLabel(),
));
} elseif($extraDatum->getType() == 'checkbox') {
$builder->add('datum-'.$extraDatum->getId(), CheckboxType::class, array(
'mapped'=>false,
'required'=>$extraDatum->getIsObligatoire(),
'label'=>$extraDatum->getLabel(),
));
} elseif($extraDatum->getType() == 'choice' && $extraDatum->getChoix() !== null && count($extraDatum->getChoix()) >= 1) {
$builder->add('datum-'.$extraDatum->getId(), ChoiceType::class, array(
'mapped'=>false,
'required'=>$extraDatum->getIsObligatoire(),
'label'=>$extraDatum->getLabel(),
'multiple'=>$extraDatum->getIsChoixMultipleUtilisateur(),
'choices'=>array_combine($extraDatum->getChoix(), $extraDatum->getChoix()),
));
}
}
}
Twig view
{% if form.extraDataForm is defined %}
<div class="app-auth-left-frame-extra">
<div class="app-form-container">
<div class="app-form_field-container">
{% for field in form.extraDataForm %}
{{ dump(field) }}
{% if field.vars.block_prefixes[1] == 'text' or field.vars.block_prefixes[1] == 'number' %}
<div class="mdc-text-field mdc-text-field--outlined">
{{ form_widget(field, {'attr': {'class': 'mdc-text-field__input'}}) }}
<div class="mdc-notched-outline">
<div class="mdc-notched-outline__leading"></div>
<div class="mdc-notched-outline__notch">
{{ form_label(field, null, {'label_attr': {'class': 'mdc-floating-label'}}) }}
</div>
<div class="mdc-notched-outline__trailing"></div>
</div>
</div>
{% elseif field.vars.block_prefixes[1] == 'checkbox' %}
<div class="mdc-form-field">
<div class="mdc-checkbox">
{{ form_widget(field, {'attr': {'class': 'mdc-checkbox__native-control'}}) }}
<div class="mdc-checkbox__background">
<!--suppress HtmlUnknownAttribute -->
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark-path" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59"></path>
</svg>
</div>
</div>
{{ form_label(field, null, {'label_attr': {'class': 'app-txt-light-emphasis'}}) }}
</div>
{% elseif field.vars.block_prefixes[1] == 'choice' %}
<div>{{ form_widget(field) }}</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endif %}
UrlType Field as the documentations says:
The UrlType field is a text field that prepends the submitted value with a given protocol (e.g. http://) if the submitted value doesn't already have a protocol.
If you would like an input with url type create your own Form Field Type like that:
class MyUrlType extends AbstractType {
/**
* {#inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['type'] = 'url';
}
/**
* {#inheritdoc}
*/
public function getParent()
{
return __NAMESPACE__.'\TextType';
}
}
From here https://symfony.com/doc/current/form/create_custom_field_type.html
I have the following form and Twig template:
FormType
class UserType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name','text',array('label' => 'Name', 'max_length' => 30))
->add('surname','text',array('label' => 'Sjurname', 'max_length' => 30))
....
->add('password', 'repeated', array('error_bubbling' => true,'required' => false, 'first_options' => array('label' => 'New Password'),'second_options' => array('label' => 'Repeat Password'),'type' => 'password' ,'invalid_message'=> 'Passwords must be the same.'));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Bundle\Entity\User',
));
}
/**
* #return string
*/
public function getName()
{
return 'user';
}
}
Twig
....
{{form_start(form, {'method':'Put','attr':{'data-toggle':"validator", 'role':"form" ,'novalidate':'novalidate', 'autocomplete':'off'}})}}
{{ form_errors(form) }}
<div class="form-group has-feedback">
{{ form_label(form.password, 'New Password:',{'label_attr':{'class': 'col-md-5'}}) }}
<div class="col-md-7">
{{ form_widget(form.password,{'attr':{'class': 'form-control', 'type': 'password','data-error': 'You must enter a minimum of 6 characters', 'placeholder': 'Min 6 characters', 'data-minlength':"6", 'required':'true' }}) }}
<span class="glyphicon form-control-feedback" aria-hidden="true"></span>
<div class="help-block with-errors"></div>
</div>
</div>
{{ form_row(form.submit,{'attr':{'class': 'btn btn-primary pull-right' }}) }}
<div class="hidden">
{{ form_rest(form) }}
</div>
{{ form_end(form) }}
...
The problem I have is that the input is not created. The label is displayed. I have tried using form.password.first and form.password.second but neither is the input (only the labels). With the other fields I do not have that problem, the only thing in this case all the others are hidden in a div. What could be the problem?
I appreciate your help.
Take a look at this. it is for symfony 2.8/3+ but you can adapt it to your needs.
FormType
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
'first_options' => [
'label' => 'registration.password',
],
'second_options' => [
'label' => 'registration.retype.password',
],
]) ...
twig
<div>
{{ form_label(form.plainPassword.first) }}
{{ form_widget(form.plainPassword.first) }}
{{ form_errors(form.plainPassword.first) }}
</div>
<div>
{{ form_label(form.plainPassword.second) }}
{{ form_widget(form.plainPassword.second) }}
{{ form_errors(form.plainPassword.second) }}
</div>
Do you have a version of xDebug installed? You could place a breakpoint on the return section of the Controller and have a look inside the children element of the form. This could tell you what the names of the elements are.
**PS, I would have commented this but I don't have enough rep.
I have an issue that I haven't been able to solve. I have 2 entities:
<?php
namespace ...\Entity;
// ...
/**
* Pregunta
*
* #ORM\Table(name="pregunta", indexes={#ORM\Index(name="fk_respuesta_tipo_respuesta1_idx", columns={"tipo_respuesta_id"}))})
* #ORM\Entity
*/
class Pregunta {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="...\Entity\Respuesta", mappedBy="pregunta")
*/
private $respuesta;
public function __construct() {
$this->tipoPrueba = new \Doctrine\Common\Collections\ArrayCollection();
$this->respuesta = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add respuesta
*
* #param ...\Entity\Respuesta $respuesta
* #return Pregunta
*/
public function addRespuesta(...\Entity\Respuesta $respuesta) {
$this->respuesta[] = $respuesta;
return $this;
}
/**
* Remove respuesta
*
* #param ...\Entity\Respuesta $respuesta
*/
public function removeRespuesta(...\Entity\Respuesta $respuesta) {
$this->respuesta->removeElement($respuesta);
}
/**
* Get respuesta
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getRespuesta() {
return $this->respuesta;
}
function setRespuesta(\Doctrine\Common\Collections\Collection $respuesta) {
$this->respuesta = $respuesta;
}
}
Then I have the Respuesta entity:
<?php
class Respuesta {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="texto_respuesta", type="text", nullable=false)
*/
private $textoRespuesta;
// ...
I have a PreguntaType which has its fields and a collection of RespuestaType:
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('titulo', 'textarea', array("label" => "Enunciado: ", "required" => true, "attr" => array('class' => 'form-control')))
->add('numeroPagina', 'integer', array("label" => "Página: ", "required" => true, "attr" => array('class' => 'form-control')))
->add('areaConocimiento', 'entity', array('class' => 'UciBaseDatosBundle:AreaConocimiento', 'required' => false, 'attr' => array('style' => 'width: 100%')))
->add('trianguloTalento', 'entity', array('class' => 'UciBaseDatosBundle:TrianguloTalento', 'required' => false, 'attr' => array('style' => 'width: 100%')))
->add('capitulo', 'entity', array('class' => 'UciBaseDatosBundle:Capitulo', 'required' => false, 'attr' => array('style' => 'width: 100%')))
->add('grupoProcesos', 'entity', array('class' => 'UciBaseDatosBundle:GrupoProcesos', 'required' => false, 'attr' => array('style' => 'width: 100%')))
->add('tipoPrueba', 'entity', array('class' => 'UciBaseDatosBundle:TipoPrueba', 'expanded' => true, 'multiple' => true, 'required' => false, 'attr' => array('style' => 'width: 100%')))
->add('libro', 'entity', array('class' => 'UciBaseDatosBundle:Libro', 'required' => false, 'attr' => array('style' => 'width: 100%')))
->add('respuesta', 'collection', array(
'type' => new RespuestaType(),
'prototype' => true,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'label' => ' '
));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'Uci\Bundle\BaseDatosBundle\Entity\Pregunta'
));
}
However, when I debug my form submission, if i set my collection to 'required' => true, it throws this error An invalid form control with name='...[respuesta][Respuesta0][RespuestaField]' is not focusable. On the other hand, If I set it to 'required' => false, my collections' fields are always empty.
This is my TWIG file:
<form action="{{ path('uci_administrador_registrarPregunta', { 'idTipoRespuesta': tipoRespuesta.id }) }}" name="formulario" method="POST" enctype="multipart/form-data">
<h3 class="thin text-center">Registrar una nueva pregunta {{ tipoRespuesta.nombre }}</h3>
<p class="text-center text-muted">{{ tipoRespuesta.explicacion }}</p>
<hr>
{% if error %}
<div style="color:red">{{ error }}</div>
{% endif %}
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.titulo) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.numeroPagina) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.areaConocimiento) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.capitulo) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.grupoProcesos) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.trianguloTalento) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.tipoPrueba) }}
</div>
</div>
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(form.libro) }}
</div>
</div>
<br>
<hr>
<h3>Respuestas</h3><br>
<div class="respuestas" data-prototype="{{ form_widget(form.respuesta.vars.prototype)|e }}">
{# iterate over each existing tag and render its only field: name #}
{% for respuesta in form.respuesta %}
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(respuesta.correcta) }}
</div>
</div>
{% endfor %}
</div>
<br><br>
<div class="row">
<div class="col-lg-8">
</div>
<div class="col-lg-4 text-right">
<button class="btn btn-action" type="submit">Registrar</button>
</div>
</div>
{{ form_rest(form) }}
</form>
I use some javascript to add my collection forms:
var $collectionHolder;
// setup an "add a tag" link
var $addTagLink = $('Añadir respuesta');
var $newLinkLi = $('<div></div>').append($addTagLink);
function addTagForm($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, 'Respuesta' + index);
// increase the index with one for the next item
$collectionHolder.data('index', $collectionHolder.find(':input').length);
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormLi = $('<div style="background-color:#F6F6F6; border-radius:10px;padding: 25px;border: 5px solid #003c70;margin: 5px;"></div><br>').append(newForm);
$newLinkLi.before($newFormLi);
}
document.ready = function () {
// Get the ul that holds the collection of tags
$collectionHolder = $('div.respuestas');
// add the "add a tag" anchor and li to the tags ul
$collectionHolder.append($newLinkLi);
// 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);
$addTagLink.on('click', function (e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new tag form (see next code block)
addTagForm($collectionHolder, $newLinkLi);
});
// ...
}
I would really appreciate any help.
Thanks.
My problem was in my javascript. I don´t know why my previous code didn't work but I changed and it worked. My javascript is the following:
var collectionHolder = $('#respuestas');
var prototype = collectionHolder.attr('data-prototype');
var form = prototype.replace(/__name__/g, collectionHolder.children().length); //importante
var removeFormA = $('Borrar');
var newLi = $('<li></li>');
newLi.append(form);
newLi.append(removeFormA);
collectionHolder.append(newLi);
And this is part of my TWIG file:
<ul id="respuestas" data-prototype="{{ form_widget(form.respuesta.vars.prototype)|e }}">
{% for respuesta in form.respuesta %}
<li> {{ form_row(respuesta) }}</li>
{% endfor %}
</ul>
I hope it helps someone else.
Regards.
Are you trying to hide some fields in your form ? It looks like some fields are required but not actually displayed in the page, which prevents the browser from validating the form. Refer to that answer : https://stackoverflow.com/a/28340579/4114297
I wonder why you're only rendering respuesta.correcta in the loop. You might want to render the while respuesta item :
<div class="respuestas" data-prototype="{{ form_widget(form.respuesta.vars.prototype)|e }}">
{# iterate over each existing tag and render its only field: name #}
{% for respuesta in form.respuesta %}
<div class="row top-margin">
<div class="cols-xs-12 col-sm-10 col-md-8 col-lg-8">
{{ form_row(respuesta) }} {# <- Here #}
</div>
</div>
{% endfor %}
</div>
If you need some fields to be hidden and/or pre-filled, you could do that RespuestaType
I want to customize all multiple selects of my application like that :
<select class="selectpicker show-tick" data-size="auto">
...
</select>
How should I do that?
EDIT for ncrocfer
This is my build form method:
This is not complete, lack some stuff...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('misc')
->add('url')
->add('attachment', 'file')
->add('time_estimated')
->add('started_at')
->add('finished_at')
->add('default')
->add('deadline')
->add('priority', 'entity', array(
'class' => 'LanCrmBundle:TaskPriority',
'property' => 'title',
'multiple' => false
))
->add('project', 'entity', array(
'class' => 'LanCrmBundle:Project',
'property' => 'title',
'multiple' => false
))
->add('category', 'entity', array(
'class' => 'LanCrmBundle:TaskCategory',
'property' => 'title',
'multiple' => false
))
->add('user', 'entity', array(
'class' => 'LanSecurityBundle:User',
'property' => 'username',
'multiple' => false
))
->add('products', 'entity', array(
'class' => 'LanCrmBundle:Product',
'property' => 'title',
'multiple' => true
))
;
}
Use the attr attribute in your form buider :
<?php
namespace Foo\BarBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class FooType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('foo', 'choice', array(
'choices' => array(0 => 'Option 1', 1 => 'Option 2'),
'attr' => array('class' => 'selectpicker show-tick', 'data-size' => 'auto'),
))
;
}
}
Edit
If you have a lot of forms like you said in your comment, you can use the form customization.
Create a new template file :
{# src/Foo/BarBundle/Resources/views/Form/fields.html.twig #}
{% block choice_widget_collapsed %}
{% spaceless %}
<select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %} class="selectpicker show-tick" data-size="auto">
{% if empty_value is not none %}
<option value="">{{ empty_value|trans({}, translation_domain) }}</option>
{% endif %}
{% if preferred_choices|length > 0 %}
{% set options = preferred_choices %}
{{ block('choice_widget_options') }}
{% if choices|length > 0 and separator is not none %}
<option disabled="disabled">{{ separator }}</option>
{% endif %}
{% endif %}
{% set options = choices %}
{{ block('choice_widget_options') }}
</select>
{% endspaceless %}
{% endblock choice_widget_collapsed %}
Then import your template inside your application configuration :
# app/config/config.yml
twig:
form:
resources:
- 'FooBarBundle:Form:fields.html.twig'
The change will be effective on all your select.
Use attr attribute in FormBuilder:
'attr' => array('class'=>'selectpicker show-tick', 'data-size'=>'auto')
I have an interesting problem. I couldn't find any solution in stackoverflow and also google. I have an Entity User and User have some metas. So I created a UserMeta Entity and also UserMetaValue. In user form there is lots of tabs. And I used these metas in the tabs. Some of them in first tabs, some of them in other tabs. And all tabs have their own form. When I bind a form on active tab, active tab metas update, others changed to NULL.
StudentPersonalType.php
namespace ATL\UserBundle\Form\Type;
use ATL\CommonBundle\Utils\Shortcut;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormBuilderInterface;
class StudentPersonalType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add("first_name", null, array(
"label" => "İsim",
"required" => true,
"attr" => array(
"class" => "span10"
)
))->add("last_name", null, array(
"label" => "Soyisim",
"required" => true,
"attr" => array(
"class" => "span10"
)
))->add("username", null, array(
"label" => "Öğrenci Numarası",
"required" => true,
"attr" => array(
"class" => "span10"
)
))->add("email", null, array(
"label" => "Email",
"required" => true,
"attr" => array(
"class" => "span10"
)
))->add('metas', 'collection', array(
'label' => "Metas",
'type' => new UserMetaType()
));
}
public function getName(){
return "personal";
}
public function setDefaultOptions(OptionsResolverInterface $resolver){
$resolver->setDefaults(array(
"data_class" => "ATL\UserBundle\Entity\User"
));
}
}
StudentEducationType.php
namespace ATL\UserBundle\Form\Type;
use ATL\CommonBundle\Utils\Shortcut;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormBuilderInterface;
class StudentEducationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('metas', 'collection', array(
'label' => "Metas",
'type' => new UserMetaType(),
'by_reference' => false
));
}
public function getName(){
return "education";
}
public function setDefaultOptions(OptionsResolverInterface $resolver){
$resolver->setDefaults(array(
"data_class" => "ATL\UserBundle\Entity\User"
));
}
}
And twig
<div id="personal-info" class="tab-pane row-fluid active">
<form style="margin:20px 0 0 0;" class="ajaxForm form-horizontal form-row-seperated" action="{{ formAction }}">
{{ form_row(form.first_name) }}
{{ form_row(form.last_name) }}
{{ form_row(form.email) }}
{{ form_row(form.username) }}
{% for meta in form.metas %}
{% if meta.value.vars.label in formValues.personal %}
{{ form_widget(meta) }}
{% endif %}
{% endfor %}
{{ form_row(form._token) }}
<div class="form-actions" style="margin-bottom:0;">
<button class="btn blue" type="submit"><i class="icon-ok"></i> Kaydet</button>
</div>
</form>
</div>
<div id="education-info" class="tab-pane row-fluid">
<form style="margin:20px 0 0 0;" class="ajaxForm form-horizontal form-row-seperated" action="{{ formAction }}">
{% for meta in educationForm.metas %}
{% if meta.value.vars.label in formValues.education %}
{{ form_widget(meta) }}
{% endif %}
{% endfor %}
{{ form_row(educationForm._token) }}
<div class="form-actions" style="margin-bottom:0;">
<button class="btn blue" type="submit"><i class="icon-ok"></i> Kaydet</button>
</div>
</form>
</div>
I filter collection fields by check it in twig file with IF statement.
I ask my question again, How could I use metas in different form in same page without affecting the others?
You have done half the job of filtering out the irrelevant fields for rendering. But you also need to filter out the irrelevant fields for form binding.
When you bind the request to the form it is expecting values for every UserMeta entity because there is a field for all of them in the UserMetaType collection. You'll need to remove all the UserMetaType forms which don't correspond to one of your submitted values. It's probably best to this with a FormEvents::PRE_BIND listener.
You can see a simpler example of this at Form: Avoid setting null to non submitted field. It will be slightly more complex for you, because you'll have to iterate through the collection of UserMetaType forms and remove the ones which you don't want bound.