Symfony2 Multiple forms send by one submit button - forms

I need help. I've created view, where I'm creating multiple forms for one type of object. Now i want to save the forms, all with one button and then persist those objects to database.
Here is the controller:
/**
* #Route("/project/{project_id}/string/{id}/edit/", name="StringEdit")
* #Template()
*/
public function editAction($project_id, $id, Request $request)
{
$string = $this->getDoctrine()->getRepository('DomestosTranslatingBundle:String')->find($id);
$translations = $this->getDoctrine()->getRepository('DomestosTranslatingBundle:Translation')->findByString($string);
//$form = $this->createForm(new TranslationType(), $translation);
//$form->handleRequest($request);
$forms = array();
foreach($translations as $translation){
$form = $this->createForm(new TranslationType, $translation);
$form = $form->createView();
$forms[] = $form;
}
return $this->render('DomestosTranslatingBundle:String:edit.html.twig', array(
'forms' => $forms,
'string' => $string,
));
}
And the view:
{% extends "::base.html.twig" %}
{% block title %}Edit translations{% endblock %}
{% block body %}
Code: {{string.code}}
<p>
<table>
{% for keylang,lang in string.project.lang %}
{% for key,form in forms %}
{% if key == keylang %}
<tr>
<td>{{lang.title}}</td>
<td>{{form_widget(form.text)}}</td>
</tr>
{% endif %}
{% endfor %}
{% endfor %}
</table>
<p>
{% endblock %}

No, you can't do this. Only one form could be submitted at one time.
Instead of using an array of forms, you could create one form and Embed a Collection of Forms.

Related

Symfony & Easy Admin : How to apply easyadmin template to custom form

I created a custom form for and integrated it in easyadmin. the forms is displayed, filled and action is working, but the templating is not good :
Here is my Twig :
{% extends "#EasyAdmin/page/content.html.twig" %}
{% form_theme form with easyadmin_config('design.form_theme') only %}
{% block body_id 'easyadmin-edit-User-1' %}
{% block body_class 'edit edit-user' %}
{% block content_title %}
<h1 class="title">Edit Account</h1>
{% endblock %}
{% block content_footer_wrapper '' %}
{% block main %}
{% block entity_form %}
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit" class="btn btn-primary">Update</button>
{{ form_end(form) }}
{% endblock entity_form %}
{% endblock %}
{% block body_javascript %}
{{ include('#EasyAdmin/default/includes/_select2_widget.html.twig') }}
{% endblock %}
and my controller :
class UserController extends EasyAdminController
{
public function editaccountAction(UserInterface $loggedUser, Request $request) {
$repository = $this->getDoctrine()->getRepository(User::class);
$id = $loggedUser->getId();
$entity = $repository->find($id);
$form = $this->createForm(UserType::class, $entity);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** #var Article $article */
$entity= $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
$this->addFlash('success', 'Account Saved');
return $this->redirectToRoute('easyadmin');
}
return $this->render('user/editaccs.html.twig', [
'form' => $form->createView(),
]);
}
}
How can I have the same form presentation as edit?
Before
{{ form_start(form) }}
add
{% form_theme form '#EasyAdmin/form/bootstrap_4.html.twig' %}
https://symfony.com/doc/current/form/form_themes.html#applying-themes-to-single-forms
UPD:
In EasyAdmin 4.3 theme would be
{% form_theme form '#EasyAdmin/crud/form_theme.html.twig' %}

Pass a variable to a parent form in twig

I need to pass a variable to a parent form in twig:
{# This block is called for the children and the parent #}
{% block form_rows %}
{% for child in form %}
{% if child.vars.foo == 'bar' %}
{% set form.vars = form.vars|merge({'key': 'value'}) %}
{% endif %}
{# Parent check children key var #}
{% if child.vars.key %}
{# do something #}
{% endif %}
{# Will call children form_rows block recursively #}
{{ form_row(child) }}
{% endfor %}
{% endblock form_rows %}
This of course don't work because you cannot set a variable like this and form is an object not an array.
I couldn't find any solution until now.
Ok, the whole thing is a bad idea, but it should be possible since Twig v1.2 using the attribute function. For that you need a setter Method in you form object, eg:
class YourFormClass {
public $vars;
/* your class code comes here */
public function setVars($newVars) {
$this->vars = $newVars;
}
}
Assuming that the twig form variable is an instance of YourFormClass you can now use the attribute function like this:
{% set newVal = form.vars|merge({'key': 'value'}) %}
{{ attribute(form, 'setVars', [newVal]) }}
{{ dump(form.vars) }}

Render custom entity type field in form (Symfony)

I got stuck while trying to customize the rendering of a specific field in my form.
It looks like this:
$builder->add('players', 'entity', array(
'class' => 'Acme\Bundle\Entity\Player',
'expanded' => true,
'multiple' => true,
'required' => false,
));
The form itself is beeing rendered with a simple:
{% block form_content %}
{% form_theme form 'AcmeBundle:Form:fields_child.html.twig' %}
{{ form_widget(form) }}
{% endblock %}
Now inside fields_child.html.twig i'm extending from another form template but there is nothing special there.
My HTML looks like this:
Players:
- [checkbox-input] 1
Where 1 equals the id of the only player in the database. However instead of rendering the ID im trying to render his picture and full name after the checkbox.
I have tried many combinations of the form theming to override it but failed each time.
Could someone post the twig block to render what i want here?
Thanks
You have to create custom form field type together with custom widget template for it.
http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html
I recently ran into this problem (was a little bit different situation. I needed to show products as table with checkboxes..), Form's child data always returned null value that's why I ended up with this (dirty:)) solution:
Controller action:
...
$productRepository = $entityManager->getRepository('VendorMyBundle:Product');
$products = [];
$formChildren = $productListForm->createView()->children;
foreach ($formChildren['products'] as $formProduct) {
$formProductId = $formProduct->vars['value'];
$productEntity = $productRepository->find($formProductId);
$products[$formProductId] = $productEntity;
}
...
return $this->render('TEMPLATE', [
'productListForm' => $productListForm->createView(),
'products' => $products,
]);
Template:
...
{% for productForm in productListForm.products %}
{% set id = productForm.vars.value %}
<tr>
<td class="check">
{{ form_widget(productForm) }}
</td>
<td class="photo">
{% if products[id].getImages().isEmpty() == false %}
{% set productImage = products[id].getImages().first() %}
<img src="{{ productImage.getWebPath() | imagine_filter('st_product_cabinet_thumbnail') }}" />
{% else %}
<span class="no-image">No image</span>
{% endif %}
</td>
<td class="title">
{{ products[id].getName() }}
</td>
<td class="status">
{{ products[id].getStatusName(products[id].getStatus()) }}
</td>
<td class="price">
<ul>
{% for productPrice in products[id].getPrices() %}
<li>{{ productPrice.getValue() ~ ' ' ~ productPrice.getCurrencyCode() }}</li>
{% endfor %}
</ul>
</td>
</tr>
{% endfor %}
...

Customize form field rendering

I would like to customize the rendering of a form field in the edit page from sonata admin bundle to include an applet that uses the text content of a field.
I know that I have to edit the configureFormFields function in the admin class, but I need to know 3 things:
What is the syntax to provide a field form template
Where to put the template file ( which directory )
What the template have to looks like.
Found a solution
What i have done is:
Created a field type, lets call it myfieldType in myCompany\myBundle\Form\Type\myfieldType.php
namespace myCompany\myBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class myfieldType extends AbstractType
{
public function getParent()
{
return 'text';
}
public function getName()
{
return 'myfield';
}
}
Registered the Type in app/config/services.yml
myCompany.myBundle.form.type.myfield:
class: myCompany\myBundle\Form\Type\myfieldType
tags:
- { name: form.type, alias: myfield }
In my myentityAdmin class,
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('myfieldname', 'myfield')
...
}
and
public function getFormTheme() {
return array('myCompanymyBundle:Admin:myfield_edit.html.twig');
}
and the template :
{# src/mycompany/myBundle/Resources/views/Form/myfield_edit.html.twig #}
{% block myfield_widget %}
{% spaceless %}
{{ block('textarea_widget') }}
{% endspaceless %}
{% endblock %}
And now i can access the form field value by the twig variable "value" !
So easy... when you got it.
user1254498's solution won't work unless the block name prefix matches the name of the form type. At least with the last version of sonata admin bundle (2.2.12). In this case:
{# src/mycompany/myBundle/Resources/views/Form/myfield_edit.html.twig #}
{% block myfield_widget %}
{% spaceless %}
{{ block('textarea_widget') }}
{% endspaceless %}
{% endblock %}
And, regarding getFormTheme(), you shoud return also the parent theme, otherwise you may break the whole style...
public function getFormTheme()
{
return array_merge(
parent::getFormTheme(), array(
'mycompanyBundle:Form:myfield_edit.html.twig')
);
}
Also, you can access the admin service in the twig template with the variable sonata_admin.admim.
In your services.yml file you define the template for your edit Action:
app.admin.product:
class: AppBundle\Admin\ProductAdmin
arguments: [~, AppBundle\Entity\Product, AppBundle:Admin\Product]
tags:
- {name: sonata.admin, manager_type: orm, group: Products, label: Products}
calls:
- [ setTemplate, [edit, AppBundle:Product:edit.html.twig]]
In that template you can then override templates for fields in your form:
{% extends 'SonataAdminBundle:CRUD:base_edit.html.twig' %}
{% form_theme form.selectall 'AppBundle:Form:selectall.html.twig' %}
{% form_theme form.Country 'AppBundle:Form:country.html.twig' %}
Then my template looks like that:
{% block form_row %}
<div class="form-group">
{{ form_label(form) }}
{% set c = 0 %}
{% for i in form %}
{% set c = c+1 %}
{% if (c == 1) %}
<div style="float: left; width: 20%;">
{% endif%}
{{ form_row(i) }}
{% if ((c == 60) or (form|length == loop.index)) %}
</div>
{% set c = 0 %}
{% endif%}
{% endfor %}
</div>
{% endblock form_row %}
In this case, my countries check boxes appear in column of 60 elements, not in one column with the whole list of elements.
Hope this is helpful to someone else.

Symfony 2 custom form field type: how to add javascript and css only once?

I want to use javascript in custom Symfony 2 form field type extension. So, I have Twig extension template like this:
{% block some_widget %}
<input ... />
<script src="some.js"></script>
<link href="some.css" />
{% endblock %}
But I want to have these script and link tags only once in my HTML, ideally in head tag, without modifing base template. I tried to extend Twig blocks, but I have no access to action template blocks inside form template. Or maybe something like this:
{# widget tempate #}
{% block some_widget %}
<input ... />
{{ use_javascript('some.js') }}
{{ use_css('some.css') }}
{% endblock %}
{# main action template #}
...
<head>
{{ dump_javascripts() }}
{{ dump_css() }}
</head>
...
How to do this with Symfony 2 Forms + Twig?
P.S. Sorry for my bad english.
I had to write a self contained form widget that requires javascript, I was able to achieve what you are trying to do through the event_dispatcher listening on the kernel.response to append the javascript at the end of the Symfony\Component\HttpFoundation\Response. Here's a snippet of my form type :
<?php
namespace AcmeBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
class AcmeFileType extends AbstractType{
private $twig;
private $dispatcher;
public function __construct(\Twig_Environment $twig, EventDispatcherInterface $dispatcher){
$this->twig = $twig;
$this->dispatcher = $dispatcher;
}
public function buildView(FormView $view, FormInterface $form, array $options){
$javascriptContent = $this->twig->render('AcmeBundle:Form:AcmeFileType.js.twig', array());
$this->dispatcher->addListener('kernel.response', function($event) use ($javascriptContent) {
$response = $event->getResponse();
$content = $response->getContent();
// finding position of </body> tag to add content before the end of the tag
$pos = strripos($content, '</body>');
$content = substr($content, 0, $pos).$javascriptContent.substr($content, $pos);
$response->setContent($content);
$event->setResponse($response);
});
}
...
When you define your form type in your services.yml it looks like this :
acme.form.acme_file_type:
class: AcmeBundle\Form\AcmeFileType
arguments:
- #twig
- #event_dispatcher
tags:
- { name: form.type, alias: acmefile }
So now, everytime you build a form with acmefile the javascript will be appended to the <body>. This solution does not prevent the javascript from being present multiple time though, but you should easily be able to improve this to suit your needs.
You can also play around with the $response object to modify the headers instead if you wish.
The best way is to provide the separate template with css & scripts loading.
With the comments in readme so only thing developer will must do is to
{% block stylesheets %}
{{ parent() }}
include "#MyBestBundle/Resources/view/styles.html.twig"
{% endblock %}
or try to intercept the form rendering with DI and add the assets. But it more difficult to do , if possible to implement.
My way of doing was by creating a custom twig extension where I add JS to a buffer and during form rendering and later dump it at the of my layout.
Something like this:
<?php
namespace AppBundle\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class WysiwygExtension extends AbstractExtension
{
/**
* #var array
*
* A pool of elements IDs for Wysiwyg binding.
*/
private $wysiwygElements = [];
/**
* {#inheritdoc}
*/
public function getFunctions()
{
return array(
new TwigFunction('addWysiwygBinding', [$this, 'addWysiwygBinding']),
new TwigFunction('popWysiwygBindings', [$this, 'popWysiwygBindings']),
);
}
public function addWysiwygBinding(string $id): void
{
$this->wysiwyglements[] = $id;
}
public function popWysiwygBindings(): array
{
$elements = array_unique($this->wysiwygElements);
$this->wysiwygElements = [];
return $elements;
}
}
Then form-fields.html.twig:
...
{% block wysiwyg_widget %}
{% apply spaceless %}
{{ form_widget(form) }}
{% do addWysiwygBinding(id) %}
{% endapply %}
{% endblock %}
...
Then layout.html.twig:
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
...
{% set ids = popWysiwygBindings() %}
{% if ids is not empty %}
{% javascripts
'bundles/admin/plugins/wysiwyg_1.js'
'bundles/admin/plugins/wysiwyg_2.js'
%}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
{% endif %}
{% for id in ids %}
{{ include('_wysiwyg.html.twig', { id: id }) }}
{% endfor %}
</body>
</html>
This is how I use it. Hope it's what you're looking for.
base.html.twig
<head>
{% block stylesheets %}
css...
{% endblock %}
</head>
foo.html.twig
{% extends '::base.html.twig' %}
{% block stylesheets %}
{{ parent() }}
css that you need in foo.html.twig
{% endblock %}
I found some 'dirty' method used by many peoples in other situations.
We check the loading of script on client side. In case we have a zlkladr.js file, that have a global object 'zlkladr'
{% block our_widget %}
{% spaceless %}
...
<script>
// We must load the script only once, even if many widgets on form
if ( !window.zlkladr ) {
document.write('<script src="{{ asset('bundles/kladr/js/zlkladr.js') }}"></sc'+'ript>');
}
</script>
{% endspaceless %}
{% endblock %}