I'm trying to create an embedded form in symfony2 that will consist of a Contact form that will have embedded a ContactPhonenumber form to add phone numbers.
I have followed the instructions in Symfony2 documentation about embedding forms but I can't find a way to show at least an input field for phonenumbers. If I do it without errors, I only get back a label for the embedded phonenumber form.
How can I show an input there?
My code is:
My two entity form classes:
class ContactType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('contactType')
->add('title')
->add('name')
->add('givenName')
->add('middleName')
/*->add(
'contactPhonenumbers',
'collection',
array( 'type' => new ContactPhonenumberType($options) )
)
*/
->add('contactPhonenumbers', new ContactPhonenumberType($options) )
;
}
//.......
//More functions
//.......
}
class ContactPhonenumberType extends AbstractType
{
public function __construct(array $options=null) {
$this->options = $options;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('position')
->add('number')
->add(
$builder->create(
'contact',
'searchable',
array(
'data_class' => '....Bundle\Entity\Contact',
'class' => '....Bundle:Contact',
'dataroute' => '...._contact_searchable',
'classlistview' => new ....Bundle\Component\Listview\ContactList
)
)->addViewTransformer(
new NameToIdTransformer(
$options[ 'em' ],
$options[ 'request' ],
$this,
array(),
'....Bundle:Contact'
)
)
)
->add(
$builder->create(
'phonenumberType',
'searchable',
array(
'data_class' => '....Bundle\Entity\PhonenumberType',
'class' => '.....Bundle:PhonenumberType',
'dataroute' => '...._phonenumbertype_searchable',
'classlistview' => new ....\Component\Listview\PhonenumberTypeList
)
)->addViewTransformer(
new NameToIdTransformer(
$options[ 'em' ],
$options[ 'request' ],
$this,
array(),
'...Bundle:PhonenumberType'
)
)
)
;
}
}
And my function on controller
public function newAction( Request $request )
{
$em = $this->getDoctrine()->getManager();
$entity = new Contact();
// $number = new ArrayCollection();
// $number[] = new ContactPhonenumber();
// $entity->setContactPhonenumbers( $number );
$form = $this->createCreateForm( $entity );
return $this->render(
'...Bundle:Contact:new.html.twig',
array(
'entity' => $entity,
'form' => $form->createView()
)
);
}
And the form in twig:
<div class="widget-body">
{{ form_start( form, { 'attr': { 'class': 'form-horizontal', 'role': 'form', 'novalidate': 'novalidate' } } ) }}
{{ form_errors( form ) }}
{{ form_row( form.contactType ) }}
{{ form_row( form.title ) }}
{{ form_row( form.name ) }}
{{ form_row( form.givenName ) }}
{{ form_row( form.middleName ) }}
------>
{{ form_row(form.contactPhonenumbers) }}
<--------
{{ form_row( form.salutation ) }}
{{ form_row( form.address ) }}
{{ form_row( form.superiorContact ) }}
<div class="form-group">
<hr>
</div>
<div class="form-group">
<div class="col-sm-2"></div>
<div class="col-sm-10">
{{ form_widget( form.submit ) }}
{{ form_widget( form.cancel ) }}
</div>
</div>
{{ form_row( form._token ) }}
{{ form_end( form, { 'render_rest': false } ) }}
Related
I have a simple form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', null, ['required' => true])
->add('doYouWant', ChoiceType::class, ['choices' => ['no' => 'no', 'yes' => 'yes']])
->add('type')
;
}
I would like the user after the selection doYouWant to "yes" to have a mandatory "type" option, so I am trying:
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($builder) {
$data = $event->getForm()->getData();
if ($data['doYouWant'] == 'yes') {
$builder->add('type', null, ['required' => true]);
}
}
);
But it does not matter...
I think the simplest way would be to add constraints to each field from your type, and then, in template, by using jquery you can toggle the visibility of your type field based on the selected value from the dropdown.
# AppBundle/Form/ExampleType.php
$builder
->add('name', null, [
'constraints' => [
new NotBlank(['message' => 'This cannot be empty']),
]
])
->add('doYouWant', ChoiceType::class, [
'placeholder' => 'Select',
'choices' => ['no' => 'No', 'yes' => 'Yes'],
'constraints' => [
new NotBlank(['message' => 'This cannot be empty']),
]
])
->add('type', EmailType::class, [
'constraints' => [
new NotBlank(['message' => 'This cannot be empty']),
new Email([
'message' => "The email '{{ value }}' is not a valid email",
])
]
])
;
I've added the type field as being of type email, just for testing purposes.
# Controller/DefaultController.php
/**
* #param Request $request
* #Route("/test", name="test")
* #return Response
*/
public function testAction(Request $request) : Response
{
$form = $this->createForm(ExampleType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
dump($form->getData());die;
}
return $this->render('default/test.html.twig', [
'form' => $form->createView(),
]);
}
# default/test.html.twig (assuming you are using bootstrap and jquery)
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
{{ form_start(form, { attr: { 'novalidate': 'novalidate' } }) }}
<div class="form-group">
{{ form_label(form.name) }}
{{ form_widget(form.name,{ attr:{ class:'form-control' } }) }}
{{ form_errors(form.name) }}
</div>
<div class="form-group">
{{ form_label(form.doYouWant) }}
{{ form_widget(form.doYouWant,{ attr:{ class:'form-control' } }) }}
{{ form_errors(form.doYouWant) }}
</div>
<div class="form-group type hidden">
{{ form_label(form.type) }}
{{ form_widget(form.type,{ attr:{ class:'form-control' } }) }}
{{ form_errors(form.type) }}
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Send</button>
</div>
{{ form_end(form) }}
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script>
$(document).ready(function(){
$('#appbundle_example_doYouWant').change(function(){
var choice = $(this).val();
if (choice == 'Yes') {
$('.type').removeClass('hidden');
} else {
$('.type').addClass('hidden');
}
});
});
</script>
{% endblock %}
You can use validation groups and put your assertions inside your entity.
And then you can choose validation groups based on the submitted data like so:
https://symfony.com/doc/current/form/data_based_validation.html
How to add assertion on entity:
class MyEntity {
/**
* #Assert\NotBlank(groups={"YourGroup"})
*/
protected $type;
}
Then on your form:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function (FormInterface $form) {
$data = $form->getData();
$want = $data->getDoYouWant();
if ($want) {
return ['YourGroup'];
}
return ['Default'];
},
));
}
I have following form in Symfony 2.8:
$form = $this->createFormBuilder()
->add('name', 'text')
->add('email', 'text')
->add('phone', 'text')
->add($this->createFormBuilder()
->create('address', 'form', array('virtual' => true))
->add('street', 'text')
->add('city', 'text')
->add('zip', 'text')
)
->getForm();
And I would like to dynamically add addresses in JS. I can add by CollectionType single input, as per following documentation: https://symfony.com/doc/current/reference/forms/types/collection.html
But I would like to add whole subform address. So I would like to achieve following HTML result:
<input name="form[address][0][street]" />
<input name="form[address][0][city]" />
<input name="form[address][0][zip]" />
not
<input name="form[address][street][0]" />
<input name="form[address][city][0]" />
<input name="form[address][zip][0]" />
Can anybody help? Thanks!
thanks to the comments I solved in in following way:
class Address
{
private $street;
private $city;
public function getStreet()
{
return $this->street;
}
public function setStreet($street)
{
$this->street = $street;
}
public function getCity()
{
return $this->city;
}
public function setCity($city)
{
$this->street = $city;
}
}
class AddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('street', 'text')
->add('city', 'text');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Address::class,
));
}
}
then in controller:
$data = array(
"addresses" => array(
0 => new Address())
);
// $data is to show first empty subform
$form = $this->createFormBuilder($data)
->add('name', 'text')
->add('email', 'text')
->add('phone', 'text')
->add('addresses',
CollectionType::class,
array(
'entry_type' => AddressType::class,
'empty_data' => true
))
->getForm();
and the twig template looks like this:
{{ form_start(form) }}
{{ form_label(form.name) }}
{{ form_widget(form.name) }}
{{ form_label(form.email) }}
{{ form_widget(form.email) }}
{% for address in form.addresses %}
{{ form_widget(address) }}
{% endfor %}
{{ form_end(form) }}
I have created a form to invite new users. The form has no problem submitting and being handled correctly, until isValid() method returns errors after a submission. When this happens, the page is re-rendered correctly with the appropriate errors being shown. Unfortunately, the submit button then becomes un-responsive: It is still styled correctly (the style changes when it is disabled via jQuery, so that doesn't seem to be the problem), the correct submission URL still appears at the bottom-left of the navigator, but nothing happens when it is clicked.
The UserInviteType form building class:
class UserInviteType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('userBackground', ChoiceType::class, array(
'choices' => array(
'Professeur' => 'professor',
'Étudiant' => 'student',
'Employé de laboratoire' => 'labEmployee',
'Employé administratif' => 'adminEmployee',
'Autre' => 'other'
)))
->add('firstName', TextType::class)
->add('lastName', TextType::class)
->add('email', EmailType::class)
->add('misc', TextType::class, array(
'required' => false,
))
->add('level', EntityType::class, array(
'required' => false,
'class' => 'AspProfessorProfileBundle:Level',
'choice_label' => 'value',
'multiple' => false,
))
->add('canModify', CollectionType::class, array(
'entry_type' => CanModifyInitType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => true,
))
->add('save', SubmitType::class, array(
'disabled' => 'false',
))
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Asp\UserBundle\Entity\UserInvite',
'validation_groups' => array(
'Asp\UserBundle\Entity\UserInvite',
'determineValidationGroups'
),
));
}
}
The controller which handles page rendering and form submission:
public function usersAction(Request $request)
{
$user = new UserInvite();
$form = $this->get('form.factory')->create(UserInviteType::class, $user);
/* retreive all users - for user list rendering*/
$userManager = $this->get('fos_user.user_manager');
$users = $userManager->findUsers();
if ($request->isMethod('POST') && $form->handleRequest($request)->isValid()) {
$em = $this->getDoctrine()->getManager();
$data = $form->getData();
if ($data->getUserBackground() == 'professor' || $data->getUserBackground() == 'adminEmployee' || $data->getUserBackground() == 'other') {
$canMod = $user->getCanModify();
foreach ($canMod as $cM) {
$user->removeCanModify($cM);
}
}
$em->persist($user);
$em->flush();
$request->getSession()->getFlashBag()->add('notice', 'Invitation envoyé à '.$user->getFirstName().' '.$user->getLastName().'('.$user->getEmail().').');
return $this->redirectToRoute('asp_core_admin_users');
}
return $this->render('AspCoreBundle:Admin:users.html.twig', array(
'users' => $users,
'form' => $form->createView()
));
}
The Twig view rendering of the form:
<div class="user-init-form-container">
{{ form_start(form, {'attr': {'class': 'form-horizontal'}}) }}
{{ form_errors(form) }}
{# ....... Declare other form elements ....... #}
<div class="form-group">
<div class="col-sm-offset-3 col-sm-8 col-lg-offset-2 col-lg-9">
{{ form_widget(form.save, { 'id': 'submit_button', 'label': 'Inviter', 'attr': {'class': 'btn btn-primary'}}) }}
</div>
</div>
{{ form_end(form) }}
</div>
I do not do any jQuery interaction with the submit button directly (except when I wanted to see the disabled button styling to confirm this wasn't the problem).
I have looked around quite a bit and can't seem to find any topic anywhere discussing this. Hope one of you can spot what I am doing wrong, because I am really stumped right now!
Thank you.
Alex S.
Try to specify form action ?
{{ form_start(form, {'attr': {'id': 'myId'}, 'action': path('my_route')}) }}
Other methods if you prefer http://symfony.com/doc/current/form/action_method.html
I Figured it out.
It was due to a hidden field which was in-properly rendered after error generation. This hidden field suddenly became required and would prevent the submission of the form.
Thank you.
Alex
I have this form in my symfony application:
namespace MyNamespace\EntityBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class OrganizationType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// profession checkboxes imbrication
->add('professions', 'collection', array(
'type' => new ProfessionType(),
'allow_add' => true,// if unrecognized items are submitted to the collection, they will be added as new items
'allow_delete' => false,
'by_reference' => false, //in order that the adders are called.
'mapped' => true,
))
->add('name')
->add('siret')
->add('corporation')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyNamespace\EntityBundle\Entity\Organization',
'csrf_protection' => true,
'csrf_field_name' => '_token_',
// a unique key to help generate the secret token
'intention' => 'organization_stuff',
));
}
/**
* #return string
*/
public function getName()
{
return 'organization';
}
}
And this how I render the form in my twig view:
<div>
{{ form_start(form, {'action': path('path_action'), 'method': 'POST'}) }}
{{ form_errors(form) }}
{{ form_row(form.professions.vars.prototype) }}
{{ form_row(form.name) }}
{{ form_row(form.siret) }}
{{ form_row(form.corporation) }}
{{ form_end(form) }}
</div>
It renders me this in my html view on my browser:
As you can see I have a required label named __name__label__ (at the top of the form) and the embedded form label Professions above the submit button.
How can I fix that, or customize this behavior ?
Note: in my twig if I only use {{ form_row(form.professions) }}, my professionType does not display the fields.
This is the code of ProfessionType.php :
$builder
->add('production', 'checkbox', array('required' => false ))
->add('transport', 'checkbox', array('required' => false ))
->add('pumping', 'checkbox', array('required' => false ))
;
I think you are having those labels because you have used the default view format predefined by symfony you need to customize it , the other reason is that you have displayed the embedded form prototype, you need to set this prototype as data type attribute :
<ul class="tags" data-prototype="{{ form_widget(form.tags.vars.prototype)|e }}">
...
See http://symfony.com/doc/current/cookbook/form/form_collections.html
I have an entity "ticket" and I would like to create a form with many of this entity.
So I created a FormType like this :
class TicketType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('quantity');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
));
}
public function getName()
{
return 'event';
}
}
And here my controller :
$clientTickets = array();
for ($i = 0; $i < 5; $i++) {
clientTickets[] = new Ticket();
}
$formTickets = $this->createForm(new TicketType(), array('tickets' => $clientTickets));
if ($request->getMethod() == 'POST') {
$formTickets->handleRequest($request);
for ($i=0; $i < $formTickets.size(); $i++) {
$em->persist($ticket[$i]);
}
}
And finally my twig :
{{ form_start(form, {'attr': {'class': 'form-horizontal', 'id': 'msform', 'name': 'myform', 'method': 'POST' }}) }}
{% for clientTicket in form.tickets %}
{{ form_widget(form.quantity) }}
{% endfor %}
{{ form_rest(form) }}
{{ form_end(form) }}
When I go on the page, I get this error :
Method "tickets" for object "Symfony\Component\Form\FormView" does not
exist in YourBundle:folder:view.html.twi at line
Why did I do wrong ? Have you got a solution ?
You may have to do something like this:
Let's have this form type:
class TicketType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder - > add('field1', 'text');
$builder - > add('field2', 'text');
$builder - > add('field3', 'text');
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver - > setDefaults(array(
'data_class' = > null,
));
}
public function getName() {
return 'event';
}
}
Then in your controller you will need to have something like this:
public function testAction( )
{
$clientTickets = array(
'tickets' => array(
array(
'field1' => 'aaa',
'field2' => 'aaa',
'field3' => 'aaa'
),
array(
'field1' => 'bbb',
'field2' => 'bbb',
'field3' => 'bbb'
),
array(
'field1' => 'ccc',
'field2' => 'ccc',
'field3' => 'ccc'
)
)
);
$formTickets = $this->createFormBuilder( $clientTickets )
->add( 'tickets', 'collection', array( 'type' => new TicketType() ) )
->getForm();
if ($formTickets->handleRequest( $this->getRequest() )->isValid()) {
$data = $formTickets->getData();
var_dump( $data );
}
return array(
'form' => $formTickets->createView()
);
}
And your twig will look something like this:
{{ form_start(form) }}
<ul>
{% for clientTicket in form.tickets %}
<li>{{ form_row(clientTicket.field1) }} {{ form_row(clientTicket.field2) }} {{ form_row(clientTicket.field3) }}</li>
{% endfor %}
</ul>
{{ form_rest(form) }}
{{ form_end(form) }}
I think this code is self explanatory. Hope it helps
UPDATE:
I think you need to do something like this:
{% for i in 0..form|length %}
{{ form[i].method }}
{% endfor %}