I have a page with forms in hiddens modals.
Forms come from the same entity with different ids.
Displaying the modal works. For each modal I want to display, datas are ok inside inputs.
The issue is : when I submit the form, every forms (hidden included) are persisted.
//EditServiceType.php
namespace Guillaume\PartnerManagerBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class EditServiceType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', null, array(
'label_attr' => array('class' => 'col-form-label'),
'attr' => array('class' => 'form-control')
))
->add('type')
->add('version')
->add('connection_type', ChoiceType::class, array(
'choices' => array(
'Connected' => 2,
'Local' => 1,
)));
}/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Guillaume\PartnerManagerBundle\Entity\Service'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'edit_guillaume_partnermanagerbundle_service';
}
}
Part of my Controller
$formEdits = [];
$forms = [];
$serviceFormEdits = [];
foreach($services as $service) {
$serviceFormEdits[$service->getId()] = $em->getRepository('GuillaumePartnerManagerBundle:Service')->find($service);
$forms[$service->getId()] = $this->createForm('Guillaume\PartnerManagerBundle\Form\EditServiceType', $serviceFormEdits[$service->getId()]);
$formEdits[$service->getId()] = $forms[$service->getId()]->createView();
$forms[$service->getId()]->handleRequest($request);
if ($forms[$service->getId()]->isSubmitted() && $forms[$service->getId()]->isValid()) {
$em->persist($serviceFormEdits[$service->getId()]);
$em->flush();
$this->addFlash('notice', 'Service has been edited');
}
}
}
//(...)
return $this->render('GuillaumePartnerManagerBundle:customers:customer.html.twig', [
'formEdits' => $formEdits,
]);
Part of my twig (the modal)
{% for result in results %}
{% form_theme formEdits[result.service.id] 'bootstrap_4_horizontal_layout.html.twig' %}
<div class="modal fade" id="editEnvironment-{{ result.service.id }}" tabindex="-1" role="dialog"
aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="exampleModalLongTitle">{{ 'Edit Environment'|trans }}</h2>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
{{ form_start(formEdits[result.service.id]) }}
<div class="form-group row">
{{ form_widget(formEdits[result.service.id]) }}
</div>
<div class="modal-footer row">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" value="cancel" class="btn btn-secondary" data-dismiss="modal">{{ 'Cancel'|trans }}</button>
<button type="submit" value="save" class="btn btn-primary">{{ 'Validate'|trans }}</button>
</div>
</div>
{{ form_end(formEdits[result.service.id]) }}
</div>
</div>
</div>
</div>
{% endfor %}
Finally I figured it out.
adding 'id' in options resolver
$forms[$service->getId()] = $this->createForm('Guillaume\PartnerManagerBundle\Form\EditServiceType', $serviceFormEdits[$service->getId()], ['id' => $service->getId()]);
adding input hidden in formType
->add('id', HiddenType::class, array(
'data' => $options['id'],
'mapped' => false,
))
and I use this id when submit is valid
$postData = $request->request->get('edit_guillaume_partnermanagerbundle_service');
$postId = $postData['id'];
$forms[$postId]->handleRequest($request);
if ($forms[$postId]->isSubmitted() && $forms[$postId]->isValid()) {
$em->persist($serviceFormEdits[$postId]);
$em->flush($serviceFormEdits[$postId]);
//(...)
Related
I've 4 entities Company, Network, Customer and Process.
I don't know why I use fucntion create and edit don't save process in my DB !
Cutomers Netwoks and Compny this saved well.
Perhaps I forget somethings.
Some one can help please.
Company Form `
<?php
namespace App\Form;
use App\Entity\Company;
use App\Entity\Process;
use App\Form\NetworkType;
use App\Form\CustomerType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class CompanyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class, [
'label' => 'Nom',
'required' => 'true',
'attr' => [
'class' => 'mt-1',
],
'label_attr' => [
'class' => 'mt-1'
],
'constraints' => [
new NotBlank([
'message' => 'Ce champ ne peut-être vide'
])
]
])
->add('phone', TelType::class, [
'required' => 'true',
'label' => 'Téléphone',
'attr' => [
'class' => 'mt-1',
'placeholder' => 'xx xx xx xx xx'
],
'label_attr' => [
'class' => 'mt-1'
],
'constraints' => [
new NotBlank([
'message' => 'Ce champ ne peut-être vide'
])
]
])
->add('location', TextType::class, [
'label' => 'Ville',
'required' => 'true',
'attr' => [
'class' => 'mt-1'
],
'label_attr' => [
'class' => 'mt-1'
],
'constraints' => [
new NotBlank([
'message' => 'Ce champ ne peut-être vide'
])
]
])
->add('customers', CollectionType::class, [
'entry_type' => CustomerType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'label' => false
])
->add('networks', CollectionType::class, [
'entry_type' => NetworkType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'label' => false
])
->add('processes', EntityType::class, [
'class' => Process::class,
// 'entry_options' => ['label' => false],
// 'allow_add' => true,
// 'allow_delete' => true,
// 'by_reference' => false,
'choice_label' => 'software',
'label' => 'process',
'multiple' => true,
// 'expanded' => true
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Company::class,
]);
}
}
Company View
{% extends 'base.html.twig' %}
{% block title %}
{{company.name}}
{% endblock %}
{% block body %}
<nav class="navbar navbar-expand bg-light navbar-light sticky-top px-4 py-0">
{% include "navigation/_nav-horizontal.html.twig" %}
</nav>
<div class="container-fluid pt-4 px-4">
<div class="row row-cols-1 row-cols-md-3 g-4">
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title text-center">Process</h5>
{% for process in company.processes %}
{# {% for key in process.software %} #}
<p class="card-text">{{process.software}}</p>
{# {% endfor %} #}
{% endfor %}
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title text-center">Architecture réseau</h5>
{% for network in company.networks %}
<h6>{{network.title}}</h6>
<p class="card-text">{{network.content}}</p>
{% if network.attachment %}
<p class="card-text">{{network.attachment}}</p>
{% endif %}
{% endfor %}
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title text-center">Contacts</h5>
{% for customer in company.customers %}
<!-- Button trigger modal -->
<button type="button" class="btn btn-outline-info" data-bs-toggle="modal" data-bs-target="#{{customer.firstname}}">
{{customer.firstname}}
</button>
<!-- Modal -->
<div class="modal fade" id="{{customer.firstname}}" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">{{customer.firstname}}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="my-3">{{customer.firstname}}</div>
<div class="my-3">{{customer.lastname}}</div>
<div class="my-3">{{customer.mobile}}</div>
<div class="my-3">{{customer.email}}</div>
<div>
<a class="btn btn-primary" href="#" role="button">Modifier</a>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% include "partiel/_footer.html.twig" %}
{% endblock %}
Company Controller
<?php
namespace App\Controller;
use App\Entity\Company;
use App\Form\CompanyType;
use App\Repository\CompanyRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class CompanyController extends AbstractController
{
#[Route('/company', name: 'company', methods: ['GET'])]
public function index(CompanyRepository $repo, PaginatorInterface $paginator, Request $request): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$companies = $repo->findAll();
$companies = $paginator->paginate(
$companies, /* query NOT result */
$request->query->getInt('page', 1), /*page number*/
15 /*limit per page*/
);
return $this->render('company/index.html.twig', [
'companies' => $companies,
]);
}
#[Route('/company/add', name: 'company_add', methods: ['GET', 'POST'])]
#[Route('/company/edit/{slug}', name: 'company_edit', methods: ['GET', 'POST'])]
public function addOrEdit(Company $company = null, Request $request, EntityManagerInterface $em): Response
{
$user = $this->getUser();
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$message = 'Société modifié avec succès';
$messageType = 'info';
if (is_null($company)) {
$company = new Company();
$message = 'Société ajouté avec succès';
$messageType = 'success';
}
$form = $this->createForm(CompanyType::class, $company);
$form->handleRequest($request);
// dd($form);
if ($form->isSubmitted() && $form->isValid()) {
// dd($form);
// Ajout de Contacts
foreach ($company->getCustomers() as $customer) {
$customer->setIsActive(true);
}
$company->setIsActive(true);
// dd($company);
$em->persist($company->setUser($user));
$em->flush();
$this->addFlash($messageType, $message);
return $this->redirectToRoute('company');
}
return $this->renderForm('company/add_or_edit.html.twig', [
'company' => $company,
'form' => $form,
]);
}
#[Route('/company/{slug}', name: 'company_show', methods: ['GET'])]
public function show(Company $company): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
if (!$company) {
throw $this->createNotFoundException('La société est introuvable');
}
// dd($company);
return $this->render('company/show.html.twig', [
'company' => $company,
]);
}
#[Route('/company/status/{slug}', name: 'company_status', methods: ['GET'])]
public function status(Company $company, ManagerRegistry $doctrine): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
if (!$company) {
throw $this->createNotFoundException('La société est introuvable');
}
$oldStatus = $company->getIsActive();
if ($oldStatus === true) {
$newStatus = false;
} else {
$newStatus = true;
}
$company->setIsActive($newStatus);
$em = $doctrine->getManager();
$em->flush();
return $this->redirectToRoute('company');
}
}
`
Thank's indeed
I try to get a add or edit Company but I can't get and save processes
I want to generate a list of form for all entries in a table (just two fields) with just a save and a delete button.
Here is the screen : https://i.imgur.com/4hW48Bw.png
Here is the form part :
templates\item\brand\_brandForm.html.twig
{{ form_start(formView) }}
<div class="row justify-content-md-center">
<div class="col col-lg-auto">
#
<br>
{{brandId}}
</div>
<div class="col col-lg-3">
{{ form_row(formView.fullname) }}
</div>
<div class="col col-lg-3">
{{ form_row(formView.icon) }}
</div>
<div class="col col-lg-3 align-self-end">
<button class="btn btn-primary" type="submit" name="update_button" value="{{brandId}}">
<i class="fas fa-save"></i>
</button>
<button class="btn btn-danger" type="submit" name="delete_button" value="{{brandId}}">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</div>
{{ form_end(formView) }}
Here is the view :
templates\item\brand\listForm.html.twig
{% extends 'base.html.twig' %}
{% block title %}Create a brand
{% endblock %}
{% block body %}
<h1>Brand list form</h1>
{% for form in forms %}
{{form | raw}}
{% endfor %}
{% endblock %}
Here is the FormType :
class BrandType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('fullname')
->add('icon');
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Brand::class,
]);
}
}
And finally here is the controller part :
public function editableList(EntityManagerInterface $em, Request $request, BrandRepository $brandRepository)
{
$formHtml = [];
$brands = $brandRepository->findAll();
foreach ($brands as $brand) {
$form = $this->createForm(BrandType::class, $brand);
$form->handleRequest($request);
if ($form->isSubmitted()) {
dd($brand);
}
$formHtml[] = $this->renderView('item/brand/_brandForm.html.twig', [
'formView' => $form->createView(),
'brandId' => $brand->getId(),
]);
}
return $this->render('item/brand/listForm.html.twig', [
'forms' => $formHtml,
]);
}
Forms are correctly generated but when I submit one of them it returns a entity with the correct submitted data but with the wrong ID (the first one returned by the database).
I tried to figure out how to pass the ID to the POST request but I'm stuck because I can't set ID on the submitted entity. Maybe I'm on the wrong way, but I would be sure that I'm not missing an option to achieve my need like that.
Any suggestion will be welcome ;)
Finally I found the solution.
The trick was simply to create named form with ID as name.
I use the factory to do that.
Here is the fixed controller :
/**
* #Route("admin/item/brand/editable-list", name="admin_item_brand_editable-list")
*/
public function editableList(FormFactoryInterface $formFactoryInterface, EntityManagerInterface $em, Request $request, BrandRepository $brandRepository)
{
$formHtml = [];
$brands = $brandRepository->findAll();
foreach ($brands as $brand) {
$form = $formFactoryInterface->createNamedBuilder($brand->getId(), BrandType::class, $brand)->getForm();
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->get('saveButton')->isClicked()) {
$em->flush();
$formHtml[] = $this->renderView('item/brand/_brandForm.html.twig', [
'formView' => $form->createView(),
'brandId' => $brand->getId(),
]);
} elseif ($form->get('deleteButton')->isClicked()) {
$em->remove($brand);
$em->flush();
} else {
throw new ErrorException('un bouton doit être clické');
}
} else {
$formHtml[] = $this->renderView('item/brand/_brandForm.html.twig', [
'formView' => $form->createView(),
'brandId' => $brand->getId(),
]);
}
}
return $this->render('item/brand/listForm.html.twig', [
'forms' => $formHtml,
]);
}
I try to render the collection type prototype I create with an embed collection form.
I read the documentation on Symonfy, Github, ... but don't succeed to understand the way to use the block to render the form.
Here I have a form (RegistrationFormType) based on the User entity which has an embed collection form of Adress (AdressFormType).
I succeed to add the button and generate new adress on the form but I would like to have my prototype with the same layout as the one of my first adress (retrieved from DB).
Could you help me to understand what I do wrong please?
Here my RegistrationFormType
<?php
namespace App\Form;
use App\Entity\User;
//use Doctrine\DBAL\Types\TextType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Form\CallbackTransformer;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('roles',ChoiceType::class,[
'choices'=>[
'Specialist'=>'Role_Specialist',
'Parent'=>'Role_Parent',
'Center'=>'Role_Center',],
'label'=>"Je m'inscris en tant que"])
->add('name')
->add('firstname',TextType::class, [
'label' => 'Firstname',
'row_attr' => [
'id' => 'firstname'
],])
->add('email')
->add('phone',TelType::class)
->add('NISS',TextType::class, [
'label' => 'NISS',
'row_attr' => [
'id' => 'NISS'
]
,
'required'=>'false',
])
->add('BCE',TextType::class, [
'label' => 'BCE',
'row_attr' => [
'id' => 'BCE'
],
'required'=>'false'])
->add('agreeTerms', CheckboxType::class, [
'mapped' => false,
'constraints' => [
new IsTrue([
'message' => 'You should agree to our terms.',
]),
],
])
->add('plainPassword', PasswordType::class, [
// instead of being set onto the object directly,
// this is read and encoded in the controller
'mapped' => false,
'attr' => ['autocomplete' => 'new-password'],
'constraints' => [
new NotBlank([
'message' => 'Please enter a password',
]),
new Length([
'min' => 6,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
]),
],
])
//imbrication de adress dans le formulaire user afin de retrouver toutes les adresses qui lui sont référées
->add('adress',CollectionType::class,[
'entry_type' => AdressType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'block_name' => 'adress'
])
;
// Data transformer
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
function ($rolesArray) {
// transform the array to a string
return count($rolesArray)? $rolesArray[0]: null;
},
function ($rolesString) {
// transform the string back to an array
return [$rolesString];
}
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
AdressType
<?php
namespace App\Form;
use App\Entity\Adress;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AdressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type',ChoiceType::class,[
'choices' =>[
'Adresse Principale' => 'Principale',
'Adresse Secondaire' => 'Secondaire'
]
])
->add('housenumber',TextType::class,[
'label'=>'N°'
])
->add('additional_info',TextType::class,[
'label'=>'Apt/Etage/...',
'required'=>false
])
->add('street', TextType::class, [
'label' => 'Rue',
'row_attr' => [
'id' => 'Adress2'
],])
->add('postalcode')
->add('city')
->add('region')
->add('country')
->add('latitude', HiddenType::class)
->add('longitude', HiddenType::class)
// ->add('users')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Adress::class,
]);
}
}
The twig template
{% extends 'base.html.twig' %}
{% block title %}Register{% endblock %}
{% block body %}
{% for flashError in app.flashes('verify_email_error') %}
<div class="alert alert-danger" role="alert">{{ flashError }}</div>
{% endfor %}
<div class="container">
{{ form_start(registrationForm) }}
<div class="form-group">{{ form_row(registrationForm.roles) }}</div>
<div class="form-row">
<div class="form-group col-md-6">{{ form_row(registrationForm.name) }}</div>
<div class="form-group col-md-6">{{ form_row(registrationForm.firstname) }}</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">{{ form_row(registrationForm.email) }}</div>
<div class="form-group col-md-6">{{ form_row(registrationForm.plainPassword, {
label: 'Password'
}) }}</div>
</div>
<div class="form-row">
<div class="form-group col-md-4">{{ form_row(registrationForm.phone) }}</div>
<div class="form-group col-md-4">{{ form_row(registrationForm.NISS, {label: 'NISS'}) }}</div>
<div class="form-group col-md-4">{{ form_row(registrationForm.BCE, {label: 'BCE'}) }}</div>
</div>
{# #TODO formatter pour qu'on differencie bien les adresses entre elles par block. Cf block en dessous#}
<h3>Adresses</h3>
{% block _registration_form_adress_entry_widget %}
<div class="adress" data-prototype="{{ form_widget(registrationForm.adress.vars.prototype)|e('html_attr') }}">
{% for adress in registrationForm.adress %}
<div class="form-group col-md-4">{{ form_row(adress.type) }}</div>
<div class="form-row">
<div class="form-group col-md-4">{{ form_row(adress.street) }}</div>
<div class="form-group col-md-4">{{ form_row(adress.housenumber) }}</div>
<div class="form-group col-md-4">{{ form_row(adress.additional_info) }}</div>
</div>
<div class="form-row">
<div class="form-group col-md-3">{{ form_row(adress.postalcode) }}</div>
<div class="form-group col-md-3">{{ form_row(adress.city) }}</div>
<div class="form-group col-md-3">{{ form_row(adress.region) }}</div>
<div class="form-group col-md-3">{{ form_row(adress.country) }}</div>
</div>
{% endfor %}
</div>
<button type="button" class="add_item_link" data-collection-holder-class="adress">Add an adress</button>
{{ form_end(registrationForm) }}
{% endblock %}
</div>
<script>
const addFormToCollection = (e) => {
const collectionHolder = document.querySelector('.' + e.currentTarget.dataset.collectionHolderClass);
const item = document.createElement('li');
item.innerHTML = collectionHolder
.dataset
.prototype
.replace(
/__name__/g,
collectionHolder.dataset.index
);
collectionHolder.appendChild(item);
collectionHolder.dataset.index++;
};
document
.querySelectorAll('.add_item_link')
.forEach(btn => btn.addEventListener("click", addFormToCollection));
</script>
{% endblock %}
{#{% block registration_form_adress_entry_row %}#}
{# <div class="form-row">#}
{# <div class="form-group col-md-4">{{ form_row(adress.street) }}</div>#}
{# <div class="form-group col-md-4">{{ form_row(adress.housenumber) }}</div>#}
{# <div class="form-group col-md-4">{{ form_row(adress.additional_info) }}</div>#}
{# </div>#}
{# <div class="form-row">#}
{# <div class="form-group col-md-3">{{ form_row(adress.postalcode) }}</div>#}
{# <div class="form-group col-md-3">{{ form_row(adress.city) }}</div>#}
{# <div class="form-group col-md-3">{{ form_row(adress.region) }}</div>#}
{# <div class="form-group col-md-3">{{ form_row(adress.country) }}</div>#}
{# </div>#}
{#{% endblock %}#}
The code rendering
Thank you :)
You can define a macro that will render both the existing subfields of the form and the prototype string.
{% import _self as formMacros %}
{% macro address(item) %}
... your code formatting the address subform ...
{% endmacro %}
So this line in twig:
<div class="adress" data-prototype="{{ form_widget(registrationForm.adress.vars.prototype)|e('html_attr') }}">
would be
<div class="adress" data-prototype="{{ formMacros.address(registrationForm.adress.vars.prototype)|e('html_attr') }}">
and same thing for the existing fields.
In symfony3 i have base view and inside this one im rendering other controller (this controller render form (return view with form - form is method POST)) when i try to use this one on the page object Request-isMethod() always return GET how to fix it.
Part of base view: (main controller show welcome page and render inside this one)
<li><a>{{ render(controller('CommonUserBundle:Login:login')) }}</a></li>
Form login type
class LoginType extends AbstractType
{
public function getName()
{
return 'login_form';
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->
add('username', TextType::class, array(
'label' => 'Użytkownik'
))
->add('password', PasswordType::class, array(
'label' => 'Hasło'
))
->add('remember_me', CheckboxType::class, array(
'label' => 'Zapamiętaj mnie'
))
->add('save', SubmitType::class, array(
'label' => 'Zaloguj'
));
}
}
Controller that render the form above
/**
* #Route("/", name="login")
*
* #Template
*/
public function loginAction(Request $Request)
{
$Session = $this->get('session');
if($Request->attributes->has(Security::AUTHENTICATION_ERROR)){
$loginError = $Request->attributes->get(Security::AUTHENTICATION_ERROR);
} else {
$loginError = $Session->remove(Security::AUTHENTICATION_ERROR);
}
$loginForm = $this->createForm(LoginType::class, array(
'username' => $userName = $Session->get(Security::LAST_USERNAME)
));
return array(
'loginError' => $loginError,
'loginForm' => $loginForm->createView()
);
}
Twig of above
<button type="button" class="btn btn-success btn-sm" data-toggle="modal" data-target="#loginForm">
Zaloguj
</button>
<div class="modal fade" id="loginForm" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">Logowanie</h4>
{% if loginError is defined and loginError is not null %}
<div class="">
Blad i chuj: {{ loginError.message }}
</div>
{% endif %}
</div>
<div class="modal-body">
{% form_theme loginForm 'bootstrap_3_layout.html.twig' %}
{{ form(loginForm, {'action': path('_check_path')}) }}
</div>
<div class="modal-footer">
{{ render(controller('CommonUserBundle:Login:rememberPassword')) }}
<button type="button" class="btn btn-default" data-dismiss="modal">Zamknij</button>
</div>
</div>
</div>
</div>
So as i said, i render this form on other twig tempalte and after i click SUBMIT my Request->getMethod is GET. In html all is ok method="post", when i create other Route only to this form his own (not rendered) its working ok. How to change it to work ok?
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