I have simple form MyForm1:
class MyForm1 extends Symfony\Component\Form\AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder->add('field1', TextType::class, [
'label' => 'Field1'
])
$builder->add('save', SubmitType::class, [
'label' => 'Save',
]);
}
}
and form MyForm2 that is inherited from MyForm1
class MyForm2 extends MyForm1
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder->add('field2', TextType::class, [
'label' => 'Field2'
])
}
}
and template
{{ form(form) }}
When I display MyForm2, save button is displayed before field2. How I can change order of elements? I know that I can remove and add this button again. Or call render function for every element in template. But I want just to set render indexes for elements. Suppose that it will take less time. Is it possible?
Thanks
In my opinion the order of your form fields is something related to the view, not the model. So you should display manually your form in your view, fields by fields.
{{ form_start(form) }}
{{ form_row(form.field1)
{{ form_row(form.field2)
{{ form_row(form.save)
{{ form_end(form) }}
class MyForm1 extends Symfony\Component\Form\AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$this->buildFields($builder, $options);
$builder->add('save', SubmitType::class, [
'label' => 'Save',
]);
}
public function buildFields(FormBuilderInterface $builder, array $options)
{
$builder->add('field1', TextType::class, [
'label' => 'Field1'
])
}
}
class MyForm2 extends MyForm1
{
// You can actually completely skip this method
/*public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
}*/
public function buildFields(FormBuilderInterface $builder, array $options)
{
parent::buildFields(FormBuilderInterface $builder, array $options);
$builder->add('field2', TextType::class, [
'label' => 'Field2'
]);
}
}
You can also add buildSubmit in the same fashion if you need.
Related
I want to embed nested collections on three levels, for the second level it works but not the last one i can't get the prototype on the last level.
I work with Symfony 6 and i use stimulus.
class FormType1 extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('field1')
->add('formType2', CollectionType::class, [
'entry_type' => FormType2::class,
'entry_options' => ['label' => false],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => FormData1::class,
]);
}
}
class FormType2 extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('field2')
->add('formType3', CollectionType::class, [
'entry_type' => FormType3::class,
'entry_options' => ['label' => false],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => FormData2::class,
]);
}
}
class FormType3 extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('field3')
->add('field4');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => FormData3::class,
]);
}
}
Here is the prototype:
<div {{ stimulus_controller('form-collection') }}
data-form-collection-index-value="{{ form.formType3|length > 0 ? form.formType3|last.vars.name + 1 : 0 }}"
data-form-collection-prototype-value="{{ form_widget(form.formType3.vars.prototype)|e('html_attr') }}" <<============
>
<ul {{ stimulus_target('form-collection', 'collectionContainer') }}></ul>
<button type="button" {{ stimulus_action('form-collection', 'addCollectionElement') }}>Add a tag</button>
</div>
In Stimulus Controller form-collection i get the prototype in this way
item.innerHTML = this.prototypeValue.replace(/__name__/g, this.indexValue);
The prototypeValue It return an empty string
I would like to know if it's possible to get the data from a non-mapped field in an embeded form.
Here is the main form :
class PlayedLifeScoreType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//->add('nom')
//->add('prenom')
// NOTE: Use form collection to allow multiple `played` forms per JoueurType
->add('playeds', CollectionType::class, [
'entry_type' => PlayedLifeType::class,
'label' => false,
])
->add('submit', SubmitType::class, [
'attr' => ['class' => 'save'],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Partie::class,
]);
}
}
And the embedded one :
class PlayedLifeType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
/*->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$form->add('joueur', null, array(
'data' => $event->getData() ?: options['joueur']
))*/
//->add('joueur')
->add('joueur', TextType::class, [
'label' => false,
'disabled' => 'true',
'attr' => ['class' => 'tinymce'],
])
->add('max')
->add('score')
->add('round', IntegerType::class,[
'mapped' => false,
'label' => 'Round',
])
;
//});
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Played::class,
'joueur' => null
]);
}
}
And want to get the data from "round". I tried like this but doesn't work :
$r = $mainForm->get('playeds')->get("round")->getData();
I get this error :
Child "round" does not exist.
Any idea ?
field "playeds" is a CollectionType.
So, for each entry, there is a 'round' value
To access this, you should do something like:
foreach ($mainForm->get('playeds') as $played) {
//you can access round here with $played->get('round')->getData()
//Or the Played object with $played->getData()
}
How i can give form's options to a subform ?
In the example below, i have the option "special" declared.
I want to access this option "special" in my subform.
My main form :
class DemandeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', TextType::class, []);
$builder->add('service_agent', ServiceType::class, [
'mapped' => false
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'special' => true
]);
}
And my subform :
class ServiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
dump($options['special']);
$builder->add('service', TextType::class, []);
$builder->add('agent', TextType::class, [
'mapped' => false
]);
}
public function configureOptions(OptionsResolver $resolver)
{
}
I answer my question.
I must declare option "special" in subform.
And this field's option is accessible in the main form.
Like that :
class DemandeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', TextType::class, []);
$builder->add('service_agent', ServiceType::class, [
'mapped' => false,
'special' => $options['special']
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'special' => true
]);
}
And in my subform :
class ServiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
dump($options['special']); // It's OK :)
$builder->add('service', TextType::class, []);
$builder->add('agent', TextType::class, [
'mapped' => false
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'special' => true
]);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('agency', EntityType::class, [
'placeholder' => "select agency",
"choice_label" => "getName",
"class" => Agency::class,
"label" => "Associée à l'agence",
"required" => false,
]);
}
How can add default choices manually for exemple :
// agencies from DB
agancy1
agency2
agency3
test // add test manually
In Symfony 2.x, I used this way to display form with custom fields
ProductType form class
public function __construct(array $aCustomFieldsList)
{
$this->aCustomFields = $aCustomFieldsList;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, [ "label" => "Name" ])
->add('price', MoneyType::class, [ "label" => "Price" ]);
foreach( $this->aCustomFields as $n=>$sCustomField )
{
$builder->add($sCustomField, TextType::class, [ "label" => {$sCustomField}"]);
}
$builder->add('save', SubmitType::class, [ "label" => "Save" ]);
}
And in the controller
$form = $this->createForm(new ProductType($aCustomFields), $product);
But in Symfony 3.x, the first argument of the method createForm() don't expect instanciated object anymore, but a string representing the class name with namespace, like this :
$form = $this->createForm(ProductType::class, $product);
What is the way to do the same as I did with Symfony 3.x ?
As Cerad suggests you can pass those options in using the options field. This was recommended even back in 2.x. You then define the option requirements for it. If you make the field required like i have then you will get errors if its not provided when you build the form.
// Form Type
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, [ "label" => "Name" ])
->add('price', MoneyType::class, [ "label" => "Price" ]);
foreach( $options['custom_field_list'] as $n=>$sCustomField )
{
$builder->add($sCustomField, TextType::class, [ "label" => {$sCustomField}]);
}
$builder->add('save', SubmitType::class, [ "label" => "Save" ]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'custom_field_list' => [],
]);
$resolver->setRequired([
'custom_field_list' => true
]);
$resolver->setAllowedTypes([
'custom_field_list'=>'array'
]);
}
Then call it in the controller:
//controller
$form = $this->createForm(ProductType::class, $product, [
'custom_field_list' => $aCustomFields
]);