Defining custom twig form block for errors rendering - symfony

I'm trying to define a specific new block for form field errors rendering, keeping form_errors unchanged for common errors rendering.
# Twig Configuration
twig:
debug: %kernel.debug%
strict_variables: %kernel.debug%
form:
resources:
- 'ApplicationMyBundle:Main:form/customFormTheme.html.twig'
In customFormTheme.html.twig I overwrite a few blocks copied from form_div_layout.html.twig plus I added the folloowing new one.
{% block field_errors %}{% spaceless %}
{% if errors|length > 0 %}
<ul class="errors">
{% for error in errors %}
{% if error.messageTemplate|length %}
<li class="error">{{ error.messageTemplate|trans(error.messageParameters, 'validators') }}</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
{% endspaceless %}{% endblock %}
Then I expect to be able to use this block in my views like this :
<div>
{{ form_label(form.message, 'message.label'|trans({},'contact')|raw ) }}
{{ form_widget(form.message, {attr: {maxlength:1000, size:1000, rows:8}}) }}
{{ field_errors(form.message) }}
</div>
but I receive the following error :
The function "field_errors" does not exist. Did you mean "form_errors"
I also tried by naming my block text_errors or textarea_errors mentioned here but I haven't been luckier.
Any idea ?

Actually it works by defining the block text_errors or textarea_errors only and still use {{ form_errors(field.name) }} in your template. If a block named after the type of your field exists (according to form field types) it will be used instead of form_errors.
!! But you can't use directly {{ text_errors(field.name) }} in your twig template !!
The same way you can have a custom row for a specific type like this
{% block textarea_row %}{% spaceless %}
<div class="textarea l-field {{ (form_errors(form)?'error':'') }}">
{{ form_label(form) }}
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
{% endspaceless %}{% endblock textarea_row %}
and use it in your template as follow :
{# message has textarea field type #}
{{ form_row(form.message, {
label: 'message.label'|trans({},'contact')|raw ,
attr: {maxlength:1000, size:1000, rows:8}})
}}
You can also pass many custom parameters by using the object attr{}
{% block form_row %}
{% spaceless %}
<div class="form-field {{ (form_errors(form)?'error':'') }}">
{{ form_label(form) }}
{{ form_widget(form) }}
{{ dump(attr) }}
{% if attr.help is defined and not attr.help == '' %}<p class="form-help">{{ attr.help }}</p>{% endif %}
{{ form_errors(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}
and use it like this
{{ form_row(form.message, {
label: 'message.label'|trans({},'contact')|raw ,
attr: {
maxlength:1000, size:1000, rows:8,
help: 'password.help'|trans({})|raw
}
})
}}

Related

symfony's form_errors displays list item instead of just text

[ Symfony 4 ]
I've this template code in Symfony:
{{ form_widget(registrationForm.username, {'attr': {'class': 'form-control'}}) }}
{{ form_errors(registrationForm.username) }}
Instead of just displaying error text, it's generating a list item like this:
<ul><li> Username already exists </li></ul>
How to not have it generate this list item and just get the text?
I guess it is correct behavior, cause you can have multiple errors for one field for example "Username is too short" and "Field Username contains inappropriate characters", but to get only first error you can use:
{{ form_errors(registrationForm.username|first) }}
Or you can customize your form_errors rendering, first create file for form_errors, for example your_form/custom_form_errors.html.twig :
{% block form_errors %}
{% spaceless %}
<div class="error">{{ errors|first }}</div>
{% endspaceless %}
{% endblock %}
And after that include it to your view file:
{% form_theme form 'your_form/custom_form_errors.html.twig' %}
...
{{ form_errors(registrationForm.username) }}
just to extend #Andrii Filenko 's answer. You can modify the output the form_errors or any other form twig function pretty easily. it's called custom theming in Symfony.
Consider this:
// templates/register.html.twig
{% extends "base.html.twig" %}
{% form_theme registrationForm _self %}
{% block form_errors %}
{% spaceless %}
{% if errors|length > 0 %}
<ul class="changed list">
{% for error in errors %}
<li>{{ error.message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endspaceless %}
{% endblock form_errors %}
{% block body %}{% endblock %}
Output:
<ul class="changed list"><li> Username already exists </li></ul>

twig customizing form_row with form_theme

I want to remove 'div' from form_row block using form_theme like this:
{% form_theme feedback_form _self %}
{% block form_row %}
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
{% endblock %}
{{ form_start(feedback_form) }}
{{ form_row(feedback_form.name) }}
{{ form_row(feedback_form.email) }}
{{ form_row(feedback_form.subject) }}
<button type="submit">Send</button>
{{ form_end(feedback_form) }}
just like it is said in Doc https://symfony.com/doc/2.8/form/form_customization.html#customizing-the-form-row
The trouble is that twig tries to render the {% block form_row %} right in that place of template, where it is, and the result is
Variable "form" does not exist.
because I don't have variable 'form' in this template.
The template is rendered by separate action in the footer of page, and it doesn't extend anything.
Ok, I found the solution while was writing the question.
Putting the code of form_row block to the separate file solves the problem.
{# feedback_form.html.twig #}
{% form_theme feedback_form 'fields.html.twig' %}
{{ form_start(feedback_form) }}
{{ form_row(feedback_form.name) }}
{{ form_row(feedback_form.email) }}
{{ form_row(feedback_form.subject) }}
<button type="submit">Send</button>
{{ form_end(feedback_form) }}
{# fields.html.twig #}
{% block form_row %}
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
{% endblock %}
Extending another template helps too.
{# base.html.twig #}
{% block content %}
{% endblock %}
{# feedback_form.html.twig #}
{% extends 'base.html.twig' %}
{% form_theme feedback_form _self %}
{% block form_row %}
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
{% endblock %}
{% block content %}
{{ form_start(feedback_form) }}
{{ form_row(feedback_form.name) }}
{{ form_row(feedback_form.email) }}
{{ form_row(feedback_form.subject) }}
<button type="submit">Send</button>
{{ form_end(feedback_form) }}
{% endblock %}

rendering forms and widget in Symfony2

I am developing a website and I would like to personalized how the forms are rendered.
My purpose is for each field I should achieve this structure
<div class="form-group">
<label for=":id">:LABEL</label>
<input type=":type" class="form-control" id=":id" />
</div>
In case of checkbox, the input must to appear first and without class atribute
The options for doing this are listed in Symfony Form Theming documentation.
In your form theme Twig file (normally app/Resources/views/form.html.twig) you should add something like this:
{% block form_row %}
{% spaceless %}
<div class="form-group">
{{ form_label(form) }}
{{ form_errors(form) }} {# Can remove this unless you want inline errors #}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}
{% block form_widget_simple %}
{% spaceless %}
{% set type = type|default('text') %}
{% set attr = attr|merge({'class': (attr.class|default('') ~ ' form-control')|trim}) %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{% endspaceless %}
{% endblock form_widget_simple %}
The Ryan answer has needed to add one more line like this:
{% form_theme form _self %}
{% block form_row %}
{% spaceless %}
<div class="form-group">
{{ form_label(form) }}
{{ form_errors(form) }} {# Can remove this unless you want inline errors #}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}
{% block form_widget_simple %}
{% spaceless %}
{% set type = type|default('text') %}
{% set attr = attr|merge({'class': (attr.class|default('') ~ ' form-control')|trim}) %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{% endspaceless %}
{% endblock form_widget_simple %}
as per symfony2 documentation How to customize Form Rendering the current template in which u want to make change , u will have to write the line
{% form_theme form _self %}
in the starting of any block overriding ..

How to set a css class of the widget type with Symfony2

Is there a way to apply a specific class on the root div of an input depending on the input type ?
ex.:
<div class="radio-input">
<input...
....
</div>
I tried to overload "fields.html.twig" with this :
{% block form_row %}
{% spaceless %}
{% set type = type|default('text') %}
<div class="{{ type }}-input">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}
But "type" is always equal to "text".
Not a particularly nice solution but it seems block_prefixes is passed to block form_row. Block_prefixes is an array of the form field types in order of inheritance so a text field called first name would have a block_prefixes array like:
block_prefixes = array("form","field","text","first_name")
Which means you could potentially get the second from last array element and use that:
{% block form_row %}
{% spaceless %}
<div class="{{ (block_prefixes|slice((block_prefixes|length - 2),1))|first }}-input">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}
I'm sure there is a nicer way than this though I just don't have it to mind.
P.s type is undefined which is why its always text for you as you are setting it to default to text

How to add a css class on a "form_row" in twig template

I would like to know how I can add a css class on a "{{ form_row() }}" in twig. For the moment, I have this code :
{{ form_row(form.username, {'label' : "Login", 'attr': {'class': 'loginForm'}}) }}
... But the CSS class "loginForm" isn't used in the HTML code.
Thank you :) !
If you want the common class for the form_row (it means one class for form_label, form_widget and form_errors), you should customize a field_row block.
This article explains how to customize form fields: How to customize Form Rendering.
There are some methods to do this.
For example I'm using Method 2 (How to customize Form Rendering: Method 2):
{% extends 'form_div_layout.html.twig' %}
{% block field_row %}
{% spaceless %}
{% set class='' %}
{% if attr.class is defined %}
{% set class = 'class="' ~ attr.class ~ '"' %}
{% endif %}
<div {{ class }} >
{{ form_label(form, label|default(null)) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock field_row %}
My answer is very similar to nni6's but it allows you to pass through the entire attr array.
My HTML structure is for Twitter Bootstrap but you can have whatever you want. This example also places an error class on the wrapper div if there are any errors - this part is not required but is useful if you use Bootstrap:
{% extends 'form_div_layout.html.twig' %}
{% block field_row %}
{% spaceless %}
{% set field_row_classes = ['control-group'] %}
{% if errors|length > 0 %}
{% set field_row_classes = field_row_classes|merge(['error']) %}
{% endif %}
<div class="{{ field_row_classes|join(' ') }}">
{{ form_label(form, label|default(null)) }}
<div class="controls">
{{ form_widget(form, { 'attr' : attr|default({}) }) }}
{{ form_errors(form) }}
{% if help is defined %}
<p class="help-block">{{ help }}</p>
{% endif %}
</div>
</div>
{% endspaceless %}
{% endblock field_row %}
The difference here is that I'm calling form_widget with the attr array (if it was specified, defaults to {})
Now you can set up your form as normal but pass through a custom class:
{{ form_row(myentity.myproperty, { 'label' : 'mylabel', 'attr' : { 'class' : 'myclass' }}) }}
This post need an update !
Since Symfony 4.3, the row_attr option permits to add attributes (and so, some class). Let's check those links :
Here :
https://symfony.com/blog/new-in-symfony-4-3-more-form-improvements#row-attributes-in-form-themes
or more specifically : https://symfony.com/doc/current/reference/forms/types/form.html#row-attr
I use this code to configure bootstrap in symfony
{% block field_row %}
{% spaceless %}
<div class="control-group {% if errors|length > 0 %}error{% endif %}">
{{ form_label(form, label, { 'attr': { 'class': 'control-label' } }) }}
<div class="controls">
{{ form_widget(form, { 'attr' : attr|default({}) }) }}
{{ form_errors(form) }}
</div>
</div>
{% endspaceless %}
{% endblock field_row %}
The easiest way to accomplish this without overwriting blocks is this:
{% set attr = {'class' : form.myElement.vars.attr.class ~ ' addedClasses...', 'style' : form.myElement.vars.attr.style ~ '; addedStyles...'} %}
{{- form_row(form.myElement, {'attr' : attr}) -}}

Resources