Collection of form embedded is showing the whole form insetead of a single field - forms

I'm following the official documentation example but I can't make it to work properly.
Summarizing, I have material and items_budget table. Both of them are related as oneToMany and manyToOne, by the id and material fields:
Material.orm.yml:
oneToMany:
itemsBudget:
targetEntity: ItemsBudget
mappedBy: material
ItemsBudget.orm.yml:
material:
targetEntity: Material
inversedBy: itemsBudget
joinColumn:
name: material
referencedColumnName: id
Here, the ItemsBudgetType where I set the material field as a collection:
$builder->add('budget', 'entity', array(
'class' => 'PanelBundle:Budget',
'attr' => array(
'class' => 'form-control',
),
))
->add('material', 'collection', array(
'type' => new MaterialType(),
'allow_add' => true
))
->add('quantity', 'number', array(
'attr' => array(
'class' => 'form-control',
),
))
->add('price', 'money', array(
'attr' => array(
'class' => 'form-control',
),
));
Just for information, the MaterialType:
$builder->add('name', 'text', array(
'attr' => array(
'class' => 'form-control',
),
))
->add('description', 'text', array(
'attr' => array(
'class' => 'form-control',
),
))
->add('current_quantity', 'text', array(
'attr' => array(
'class' => 'form-control',
'required' => false
),
))
->add('price', 'money', array(
'attr' => array(
'class' => 'form-control',
),
));
Here the index.html.twig of my ItemsBudget view:
<strong>Materiais</strong>
<div class="form-group">
<ul class="materials" data-prototype="{{ form_widget(form.material.vars.prototype)|e }}">
{% for material in form.material %}
<li>{{ form_widget(form.material.vars.prototype.name) }}</li>
{% endfor %}
</ul>
</div>
In the view, I have tried also as it is in the example: {{ form_row(material.name) }}, but it still shows the whole Material form.
And where I'm calling them, in the ItemsBudgetController:
public function addAction(Request $request)
{
$form = $this->createForm(new ItemsBudgetType(), new ItemsBudget());
$manager = $this->getDoctrine()->getManager();
if ($request->getMethod() == 'POST') {
$form->handleRequest($request);
if ($form->isValid()) {
$ItemsBudgetEntity = $form->getData();
$manager->persist($ItemsBudgetEntity);
$manager->flush();
$BudgetEntity = $form->get('budget')->getData();
$BudgetEntity->addItemsBudget($ItemsBudgetEntity);
$manager->persist($BudgetEntity);
$manager->flush();
$this->addFlash('success', 'Materiais para o orçamento adicionados');
return $this->redirect($this->generateUrl('panel_budgets'));
}
}
return $this->render('PanelBundle:ItemsBudget:index.html.twig', array(
'form' => $form->createView(),
'budgets' => $manager->getRepository('PanelBundle:Budget')->findAll()
));
}
The JavaScript is the same from the example too:
function addMaterial($collectionHolder, $newLinkLi) {
var prototype = $collectionHolder.data('prototype');
var index = $collectionHolder.data('index');
var newForm = prototype.replace(/__name__/g, index);
$collectionHolder.data('index', index + 1);
var $newFormLi = $('<li></li>').append(newForm);
$newLinkLi.before($newFormLi);
}
var $collectionHolder;
var addMaterialLink = $('Mais');
var $newLinkLi = $('<li></li>').append(addMaterialLink);
jQuery(document).ready(function () {
$collectionHolder = $('ul.materials');
$collectionHolder.append($newLinkLi);
$collectionHolder.data('index', $collectionHolder.find(':input').length);
addMaterialLink.on('click', function (e) {
e.preventDefault();
addMaterial($collectionHolder, $newLinkLi);
});
});
This is the issue: instead of showing only the name field from Material form, it is showing the whole form every time I click the "Mais". Am I missing something?

The javascript takes the content of the data-prototype attribute and appends it to the html of the form. The attribute in your template contains {{ form_widget(form.material.vars.prototype)|e }} which is the html prototype of the form.
Try:
<ul class="materials" data-prototype="{{ form_widget(form.material.vars.prototype.name) }}">

Related

Symfony - two forms activate separately

I wrote a function that has two forms that are activate separately, persisting same Entity. First is activated for input field and second for random generated field.
But when I click submit it activates both forms one after other. If used conditions to prevent that but I seems that it doesn't work.
My code:
$id = $request->get('id');
$user = $this->container->get('account')->getUserRepository()->find($id);
$form1 = $this->createFormBuilder()
->add('password', PasswordType::class, array(
'label' => 'Enter New Password',
'attr' => ['class'=>'form-control']))
->add('save', SubmitType::class, array(
'label' => 'Send', 'attr' => ['class' => 'btn btn-primary action-save']
))
->getForm();
$form2 = $this->createFormBuilder()
->add('password', PasswordType::class, array(
'label' => 'Generate New Password',
'disabled'=> true,
'attr' => ['class'=>'form-control']))
->add('save', SubmitType::class, array(
'label' => 'Send',
'attr' => ['class' => 'btn btn-primary action-save']
))
->getForm();
$form1->handleRequest($request);
if($form1->isSubmitted() && $form1->isValid()) {
$this->addFlash(
'notice',
'You successfully changed the password!'
);
$data = $form1->getData();
$new_password = $data['password'];
$encoder = $this->container->get('security.encoder_factory')->getEncoder($user);
$new_pwd_encoded = $encoder->encodePassword($new_password);
$oneTimePsw = '';
$user->setPassword($new_pwd_encoded);
$manager = $this->getDoctrine()->getManager();
$manager->flush();
}
$form2->handleRequest($request);
if($form2->isSubmitted() && $form2->isValid()) {
$this->addFlash(
'notice',
'Password is successfully generated!'
);
$data = $form2->getData();
$new_password = $data['password'];);
$new = $this->get('member.account')->generateRandomPassword();
$oneTimePsw = '';
$user->setPassword($new);
$manager = $this->getDoctrine()->getManager();
$manager->flush();
}
return $this->render('#AdminTemplates/admin/reset_password.html.twig', array(
'form1' => $form1->createView(),
'form2' => $form2->createView()
));
My twig
<div id="setPassword" style="display:none;">
{{ form_start(form1) }}
{{ form_end(form1) }}
</div>
<div id="generatePassword" style="display:none;">
{{ form_start(form2) }}
{{ form_end(form2) }}
</div>
I think your problem is a simple if else anidation problem, when i had the same issue this is what i did:
$id = $request->get('id');
$user = $this->container->get('account')->getUserRepository()->find($id);
$form1 = $this->createFormBuilder()
->add('password', PasswordType::class, array(
'label' => 'Enter New Password',
'attr' => ['class'=>'form-control']))
->add('save', SubmitType::class, array(
'label' => 'Send', 'attr' => ['class' => 'btn btn-primary action-save']
))
->getForm();
$form2 = $this->createFormBuilder()
->add('password', PasswordType::class, array(
'label' => 'Generate New Password',
'disabled'=> true,
'attr' => ['class'=>'form-control']))
->add('save', SubmitType::class, array(
'label' => 'Send',
'attr' => ['class' => 'btn btn-primary action-save']
))
->getForm();
$form1->handleRequest($request);
$form2->handleRequest($request);
if($form1->isSubmitted() && $form1->isValid()) {
$this->addFlash(
'notice',
'You successfully changed the password!'
);
$data = $form1->getData();
$new_password = $data['password'];
$encoder = $this->container->get('security.encoder_factory')->getEncoder($user);
$new_pwd_encoded = $encoder->encodePassword($new_password);
$oneTimePsw = '';
$user->setPassword($new_pwd_encoded);
$manager = $this->getDoctrine()->getManager();
$manager->flush();
}else
if($form2->isSubmitted() && $form2->isValid()) {
$this->addFlash(
'notice',
'Password is successfully generated!'
);
$data = $form2->getData();
$new_password = $data['password'];);
$new = $this->get('member.account')->generateRandomPassword();
$oneTimePsw = '';
$user->setPassword($new);
$manager = $this->getDoctrine()->getManager();
$manager->flush();
}
return $this->render('#AdminTemplates/admin/reset_password.html.twig', array(
'form1' => $form1->createView(),
'form2' => $form2->createView()
));
This should work, because a user can't submit 2 forms at once. I also had a problem with the isValid() method so also try for testing taking it down.

Symfony form not valid and not submittable

When trying to submit my form, it is not working. The button is not doing anything at all. So I tried to dump something withing my:
if ($form->isValid() && $form->isSubmitted())
And realized that it's not even entering the if statement, so I assume that there is something wrong with my form. But I can't yet figure out what it would be, so I would be glad if anyone could help me!
/**
* #Route("/document/bulkdeactivate", name="documentBundle_document_bulkDeactivate")
* #Template()
*/
public function bulkDeactivateAction(Request $request) {
$em = $this->getDoctrine()->getManager();
$selected_documents = $request->request->all();
$form = $this->createForm(DocumentDeactivationType::class);
$form->handleRequest($request);
if ($form->isValid() && $form->isSubmitted()) {
foreach($selected_documents as $document) {
$documentR = json_decode(json_encode($document), true);
dump($documentR);
for($i=0; $i<count($documentR); $i++){
$doc = $em->getRepository('DocumentBundle:Document')->findOneById($documentR[$i]);
dump($doc);
$doc->setActive(false);
$em->persist($doc);
$em->flush();
}
}
$this->addFlash(
'success',
'The document has been deactivated!'
);
return $this->redirectToRoute('documentBundle_document_list');
}
return $this->render('DocumentBundle:Panels:ActivationPanel.html.twig', array(
'form' => $form->createView(),
));
}
my Form Type:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('documentlist', EntityType::class, array(
'class' => 'DocumentBundle:Document',
'choice_label' => 'name',
'required' => false,
'multiple' => true,
'expanded' => false,
'placeholder' => "Select Documents",
'label' => 'label.document_list',
))
->add('submit', SubmitType::class, array(
'label' => 'Submit',
'attr' => array(
'class' => 'btn btn btn-default',
),
));
}
And the form part of my twig template:
{% block content %}
{{ form_start(form) }}
{{ form(form.documentlist) }}
{{ form(form.submit) }}
{{ form_end(form) }}
{% endblock content %}
There must be some issues with the form, do you have any advice for my?
Actually there are several issues:
you cannot add a submit button that is not bound to a Symfony form in your case because Symfony will not identify it properly (for lack of a name that has to match SF's automatic input names generation)
In your formBuilder put:
$builder->add('submit', SubmitType::class);
your form has an incorrect name ("form") that conflicts with Twig's form() function, you have to change it,
you might have an issue (not tested, just a guess) regarding the many calls made to Twig's form() because this function dumps the whole form content, to manually dump each part use
Your {% block content %} should have this:
{{ form_widget(form.your_widget_name, attrs) }}
I eventually solved my issue:
1. as pointed out by #NaeiKinDus, I needed a Submit button that actually belonged to my form, plus I changed my form name to a custom deactivationForm
{% block content %}
{{ form(deactivationForm) }}
{% endblock content %}
the buildForm method:
$builder
->add('documentlist', EntityType::class, array(
'class' => 'DocumentBundle:Document',
'choice_label' => 'name',
'required' => false,
'multiple' => true,
'expanded' => false,
'placeholder' => "Select Documents",
'label' => 'label.document_list',
'translation_domain' => 'Documents',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('d')
->where('d.active = FALSE');
},
))
->add('submit', SubmitType::class, array(
'label' => 'Submit',
'attr' => array(
'class' => 'btn btn btn-default',
),
));
then in my controller:
/**
* #Route("/document/bulkdeactivate", name="documentBundle_document_bulkDeactivate")
* #Template()
*/
public function bulkDeactivateAction(Request $request) {
/*
* GET DOCUMENT FROM DB
*/
$em = $this->getDoctrine()->getManager();
$selected_documents = $request->request->all();
$deactivationForm = $this->createForm(DocumentDeactivationType::class);
$deactivationForm->handleRequest($request);
if ($deactivationForm->isValid() && $deactivationForm->isSubmitted()) {
foreach($selected_documents as $document) {
$documentR = json_decode(json_encode($document), true);
dump(count($documentR['documentlist']));
for($i=0; $i<count($documentR['documentlist']); $i++){
$doc = $em->getRepository('DocumentBundle:Document')->findOneById($documentR['documentlist'][$i]);
dump($documentR['documentlist'][$i]);
$doc->setActive(true);
$em->persist($doc);
$em->flush();
}
}
$this->addFlash(
'success',
'The selected document(s) have been deactivated!'
);
return $this->redirectToRoute('documentBundle_document_list');
}
return $this->render('DocumentBundle:Panels:ActivationPanel.html.twig', array(
'deactivationForm' => $deactivationForm->createView(),
));
}
I tried to access the wrong array positions (I didn't realize that the decoding of the ajax data sent me 3 array positions but I only wanted to access the first 'documentlist' and then get the document IDs from that one.

Form Collection with choice field

I have a form to modify an entity which has some children entities. I use a form collection to do so. When I edit this entity, the children collection should appear but it doesn't. The children collection is composed of 2 choice fields and 1 integer field. The integer field is well rendered with the right data but choice fields ask to select an option whereas it should show Matiere and Colle of children entities.
In the code, Colle entity is a child of Matiere Entity. I'm using MaterializeCSS as a framework.
Here's my code :
Child Form :
$builder
->add('matiere', EntityType::class, [
'class' => 'PACESColleBundle:Matiere',
'attr' => ['class'=> 'matiere'],
'choice_label' => 'name',
'label' => false,
'required' => false,
'placeholder' => 'Choisissez une matière',
'mapped' => false])
->add('colleEnfant', EntityType::class, [
'class' => 'PACESColleBundle:Colle',
'attr' => ['class' => 'colles'],
'choice_label' => 'nom',
'label' => false,
'group_by' => 'matiere',
'required' => true,
'placeholder' => 'choose.colle'])
->add('ordre', IntegerType::class,[
'attr'=>['class'=>'ordre'],
'required' => true,
'label' => false]);
Parent Form :
$builder->add('nom', TextType::class,['label' => 'Nom de la colle'])
->add('collesEnfants', CollectionType::class,
['label' => false,
'entry_type' => SousColleFormType::class,
'required' => true,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false]);
View :
<table id="tableau" class="creneaux"
data-prototype="{{ form_widget(form.collesEnfants.vars.prototype)|e }}">
<thead>
<tr>
<th>Matière</th>
<th>Colle</th>
<th>Ordre</th>
<th>Supprimer</th>
</tr>
</thead>
<tbody>
{% for colle in form.collesEnfants %}
<tr>
<td>{{ form_row(colle.matiere) }}</td>
<td>{{ form_row(colle.colleEnfant) }}</td>
<td>{{ form_row(colle.ordre) }}</td>
<td><i class="material-icons">delete</i></td>
</tr>
{% endfor %}
</tbody>
</table>
<script>
$(document).ready(function() {
$('.matiere').material_select();
$('.colles').material_select()
});
</script>
You could add a query in your entity type and check if the field 'nom' exist in your entity.
$builder->add('colleEnfant', EntityType::class, [
'class' => 'PACESColleBundle:Colle',
'attr' => ['class' => 'colles'],
'choice_label' => 'nom',
'label' => false,
'required' => true,
'placeholder' => 'choose.colle',
'query_builder' => function(ColleRepository $repository) {
$qb = $repository->createQueryBuilder('c');
// the function returns a QueryBuilder object
return $qb->groupBy('c.matiere')->orderBy('c.nom', 'ASC');
}
])
After you need to add an event listener who detect the select modification.
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$matiere = $this->em->getRepository('PACESColleBundle:Matiere')->find($data['matiere']);
$form = $event->getForm();
$form->add('colleEnfant', EntityType::class, [
'class' => 'PACESColleBundle:Colle',
'attr' => ['class' => 'colles'],
'choice_label' => 'nom',
'label' => false,
'required' => true,
'placeholder' => 'choose.colle',
'query_builder' => function(ColleRepository $repository) use ($matiere) {
$qb = $repository->createQueryBuilder('c');
// the function returns a QueryBuilder object
return $qb
->where('c.matiere = :matiere')
->setParameter('matiere', $matiere)
->orderBy('c.nom', 'ASC');
}
]);
});
I hope this going to help you.
I ended up doing it with JQuery.
Script :
$(document).ready(function() {
{% for colleEnfant in collesEnfants %}
$('#paces_colle_colle_ajoutsupercolle_collesEnfants_{{ loop.index0 }}_matiere option[value="{{ colleEnfant.matiere }}"]').prop('selected', true);
$('#paces_colle_colle_ajoutsupercolle_collesEnfants_{{ loop.index0 }}_colleEnfant option[value="{{ colleEnfant.colle }}"]').prop('selected', true);
{% endfor %}
$('.matiere').material_select();
$('.colles').material_select();
}
Controller :
$collesEnfantsForm = [];
foreach ($colle->getCollesEnfants() as $colleEnfant) {
$collesEnfantsForm[] = ['matiere' => $colleEnfant->getMatiere()->getId(), 'colle' => $colleEnfant->getId()];
}
it is because ArrayChoiceList::getValuesForChoices() does comparison for objects by reference. As result if you have not same instance of object in data and in list of choices that it won't be selected. Solution is to use "choice_value" options for ChoiceType field. eg.
$builder->add('type', ChoiceType::class, [
'choices' => FeeType::getTypes(),
'choice_label' => 'name',
'required' => true,
'choice_value' => static function (?FeeType $feeType) {
return $feeType ? $feeType->getId() : '';
}
])

Symfony2 - NotBlank constraint not working on EntityType

I have two EntityType fields in my form and both has NotBlank constraint assigned to them.
Now, I have this issue that NotBlank constraint is not working only on one field with multiple => true set on.
$builder
->add('preferredCountries', EntityType::class, array(
'required' => false,
'class' => 'IndexBundle:Country',
'property' => 'name',
'empty_value' => 'Choose',
'multiple' => true,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.name != :name')
->orderBy('c.name', 'ASC')
->setParameter('name', 'Other');
},
'constraints' => array(
new NotBlank(array(
'message' => 'blank!!!',
)),
)
))
->add('internshipProgram', EntityType::class, array(
'required' => false,
'class' => 'IndexBundle:InternshipProgram',
'property' => 'name',
'empty_value' => 'Choose',
'constraints' => array(
new NotBlank(array(
'message' => 'blank!!!',
)),
)
))
In this case when I submit empty values, field internshipProgram get an error, and prefferedCountries not.
Form display:
<div class="form-group col-xs-12">
{{ form_label(current_internship_form.preferredCountries, 'Preferred countries', { 'label_attr': {'class': 'label-text'} }) }}
{{ form_widget(current_internship_form.preferredCountries) }}
<span class="error text-danger small">{{ form_errors(current_internship_form.preferredCountries) }}</span>
</div>
<div class="form-group col-xs-12">
{{ form_label(current_internship_form.internshipProgram, 'What type of training agreement will you have?', { 'label_attr': {'class': 'label-text'} }) }}
{{ form_widget(current_internship_form.internshipProgram, { 'id': 'internship_program', 'attr': {'class': 'form-control '}}) }}
<span class="error text-danger small">{{ form_errors(current_internship_form.internshipProgram) }}</span>
</div>
Is there a mistake in my code or is it somehow related to multiple choice selection? Has anyone had similar issue and know how to solve it?
You can't use NotBlank constraint on EntityType with multiple set to true. As the array will never be null. You should try using the count constraint like this:
$builder
->add('preferredCountries', EntityType::class, array(
'required' => false,
'class' => 'IndexBundle:Country',
'property' => 'name',
'empty_value' => 'Choose',
'multiple' => true,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.name != :name')
->orderBy('c.name', 'ASC')
->setParameter('name', 'Other');
},
'constraints' => array(
new Count(array(
'min' => 1,
'minMessage' => "Should not be blank"
))
)
))
...
Also you can specify at Entity level,
/**
* #Count(min = 1, minMessage = "At least one branch must be selected")
*/
protected $multiCheckBox;

Symfony form output

I render a form in my application:
{{ form_widget(form.weeks) }}
The options come from a entity query builder.
I want to check in view if($entitiy->getBooked()){ echo disabled } on each option. (so they can not select booked options).
But how can I do that?
if I do that manual the {{ form_rest(form) }} will put a new select on the bottom.
formBuilder:
$builder->add( 'weeks', 'entity', array(
'class' => 'Mitch\NameBundle\Entity\CaravanRow',
'property' => 'line',
'query_builder' => function(EntityRepository $er ) use ( $caravan ) {
return $er->createQueryBuilder('w')
->orderBy('w.dateFrom', 'ASC')
->where('w.caravan = :caravan' )
->andWhere('w.visible = 1')
->setParameter( 'caravan', $caravan );
},
'attr' => array(
'multiple' => true,
'size' => 5,
'style' => 'width: 415px;'
),
));