Pass a variable to a parent form in twig - forms

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) }}

Related

How to call a Stencil in swift with different variable name?

I have 2 stencil templates, which pretty much the same except the variable name is different:
template #1
{% for p in anArray %}
{% if p.property1 = "abc" %}
// some logic
{% endif %}
{% endfor %}
template #2
{% for a in aDifferentNameArray %}
{% if a.property1 = "abc" %}
// same logic as template 1
{% endif %}
{% endfor %}
I think it will be cleaner if I can refactor this into a template and have template #1 and #2 call this new template
{% if ??.property1 = "abc" %}
// same logic as template 1
{% endif %}
But problem is in template #1, the variable is p where as in template #2, the variable is a.
So what can i do to call the new template with template #1 & #2 with different variable name?
This is exactly the use case for the include tag. Using this tag, you can include the contents of another template, and also pass it a context of your choice.
Move some logic to a separate template. Let's call that file some logic.stencil. In there, wherever you have used p.propertyX, you should change it to just propertyX, since you are giving it p/a as the context.
some logic.stencil:
{% if property1 = "abc" %}
{# same logic as template 1, except all the "p." are deleted. #}
{% endif %}
In template #1, do:
{% for p in anArray %}
{% include "some logic.stencil" p %} {# passing "p" as the context #}
{% endfor %}
In template #2, do:
{% for a in aDifferentNameArray %}
{% include "some logic.stencil" a %} {# passing "a" as the context #}
{% endfor %}

How to check if form has errors in twig?

Beside the form field specific error messages directly attached to the form field I would like to display a message above the form that the form contains errors.
How can I check in a Symfony3 twig template if a form has errors? There used to be something like this in Symfony2:
{% if form.get('errors') is not empty %}
<div class="error">Your form has errors. Please check fields below.</div>
{% endif %}
But this doesn't work in Symfony3. Any ideas? (form.vars.errors doesn't work.)
Use form.vars.errors:
{% if form.vars.errors is not empty %}
{# ... #}
{% endif %}
Attention! Note that this just evaluatues to true, if your root form has errors (or if child forms have errors and allow bubbling the error up to the root form). If regular child elements of your form have errors, this will not evaluate to empty!
So the valid variable is probably of more suitable:
{% if not form.vars.valid %}
{# ... errors ! #}
{% endif %}
With symfony 3.4 it's not possible through form.vars.errors anymore except if you have error_bubbling = true and form compound = false which is unlikely.
You can either use a dirty code like this :
{% set errors = false %}
{% for child in form.children %}
{% if child.vars.errors is defined and child.vars.errors|length %}
{% set errors = true %}
{% endif %}
{% endfor %}
{% if errors %}
[...]
{% endif %}
If you are trying to build a login form with AuthenticationUtils, use a code like this in controller :
//Get the login error if there is one
if ($error = $authenticationUtils->getLastAuthenticationError()) {
//Add error message to mail field
$form->get('mail')->addError(new FormError($error->getMessageKey()));
}
//Last username entered by the user
if ($lastUsername = $authenticationUtils->getLastUsername()) {
$form->get('mail')->setData($lastUsername);
}
//Render view
return $this->render('#ExampleBundle/template.html.twig', array('form' => $form->createView(), 'error' => $error));
And use a simple code like this in twig template :
{% if error %}
[...]
{% endif %}

Global form attribute in Symfony 2 form

I'm using Symfony 2.4. What I am trying to do is to set form attributes on the form level which are accessible to each individual widget.
I have a custom form theme which can render the form slightly differently if it is a horizontal or vertical layout. I'd like to be able to specify { 'horizontal': true } as an attribute on the form, then be able to read it while I'm reading each widget.
Is it possible? I want to avoid needing to do this on every widget.
{{ form_row(form.person.province, {horizontal:true}) }}
{{ form_row(form.person.postalCode, {horizontal:true}) }}
{{ form_row(form.person.phone, {horizontal:true}) }}
couldn't you achieve that in your own form theme?
{% block form_row %}
{% spaceless %}
{% if horizontal|default(true) %} {# or something along these lines #}
...
{% else %}
...
{% endif %}
{% endspaceless %}
{% endblock form_row %}

Symfony2 Theming form generated by embedded controller action

I am generating the form in embedded controller action. And now i have faced the following problem. The Form Theming is not working in that case.
So what i have:
The tempalte "page.html.twig"
{% block content %}
{% render 'MyBundle:Widget:index' %}
{% endblock %}
The indexAction() creates the form and rendering another template "form.html.twig" which is normally renders a form using form_row, form_rest and so on.
So, now i am trying to customize form theming, and here is my problem.
When i put the code
{% form_theme form _self %}
in the page.html.twig, i got an error the the form variable does not exists. And its correct, the form var is created later in the embedded controller.
But when i put the theming code in embedded template "form.html.twig", i got another error "Variable "compound" does not exist"
{% block form_label %}
{% spaceless %}
{% if not compound %}
{% set label_attr = label_attr|merge({'for': id}) %}
{% endif %}
{% if required %}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %}
{% endif %}
{% if label is empty %}
{% set label = name|humanize %}
{% endif %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %} {% if attr.tooltip is defined %}title="{{ attr.tooltip }}"{% endif %}>{{ label|trans({}, translation_domain) }}{% if required %}<span>*</span>{% endif %}</label>
{% endspaceless %}
{% endblock form_label %}
This part of code was copied from this file https://github.com/symfony/symfony/blob/2.1/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
So tried someone to do something like this?
Answering my question myself.
It was a small sentence in Symfony2 docs http://symfony.com/doc/current/book/forms.html
This {% form_theme form _self %} functionality will only work if your template extends another. If your template does not, you must point form_theme to a separate template.
So there are two solutions to solve this problem:
move form theme code to the separate file and include it in a embedded template using
{% form_theme form with 'fields.html.twig' %}
leave the form theme code in the same template, where the form will be generated, but extend the template from some "form.html.twig" empty template.
I have only done the second way, and its works, but I am sure the first one will work as well.

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.