Buy

Form Theming: Add an Error Icon

Checkout Bootstrap's form documentation. Under validation, they have a cool feature: when your field has an error, you can add a cute icon. I want a cute icon! To get it, we just need to add a has-feedback class to the div around the entire field and add the icon itself.

Right now, each field is surrounded by a div with a form-group class. How can we also add a has-feedback class to this? Answer: override the block that's responsible for rendering the row part of every field. In other words, the form_row block.

In form_div_layout.html.twig, search for the form_row block. There it is!

372 lines vendor/symfony/symfony/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
... lines 1 - 243
{%- block form_row -%}
<div>
{{- form_label(form) -}}
{{- form_errors(form) -}}
{{- form_widget(form) -}}
</div>
{%- endblock form_row -%}
... lines 251 - 372

But, we might be overriding this in the bootstrap theme - so check there too. Yep, we are: and this is where the form-group class comes from:

246 lines vendor/symfony/symfony/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig
... lines 1 - 184
{% block form_row -%}
<div class="form-group{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">
{{- form_label(form) -}}
{{- form_widget(form) -}}
{{- form_errors(form) -}}
</div>
{%- endblock form_row %}
... lines 192 - 246

Overriding a Block

Ok! So... how can we override this? Very simple. First, copy the block. Second, go to your templates directory and create a new file called _formTheme.html.twig. The name of this isn't file is not important. And just so we know when this is working, add a class: cool-class:

8 lines app/Resources/views/_formTheme.html.twig
{% block form_row -%}
<div class="form-group cool-class{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">
{{- form_label(form) -}}
{{- form_widget(form) -}}
{{- form_errors(form) -}}
</div>
{%- endblock form_row %}

Finally, we need to point Symfony to this new form theme template. And we already know where to do this: right inside config.yml. After the bootstrap template, add a new line with _formTheme.html.twig:

75 lines app/config/config.yml
... lines 1 - 36
# Twig Configuration
twig:
... lines 39 - 42
form_themes:
- bootstrap_3_layout.html.twig
- _formTheme.html.twig
... lines 46 - 75

Because this is after bootstrap, its blocks will override those from bootstrap. Oh, and even though we don't have it explicitly listed here, Symfony always uses form_div_layout.html.twig as the fallback file.

Ok, go back, and refresh! Inspect any form element. There it is! Our block is now being used.

Using Variables in Blocks

And here's where things get really interesting. We need to add a class to the div, but only if this field has a validation error. Well check this out: this block is already using a few variables, like compound, force_error and valid:

8 lines app/Resources/views/_formTheme.html.twig
{% block form_row -%}
<div class="form-group cool-class{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">
... lines 3 - 5
</div>
{%- endblock form_row %}

But, where are those coming from? And what other stuff can we use?

It turns out that these are the same form variables that we can override from the main, _form.html.twig template. Once you're inside of a form theme block, these become local variables.

To see this in action, call dump() with no arguments:

9 lines app/Resources/views/_formTheme.html.twig
{% block form_row -%}
{{ dump() }}
<div class="form-group cool-class{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">
... lines 4 - 6
</div>
{%- endblock form_row %}

This will print all the variables we can use.

Refresh the page. Ah, now we have a big dump before every single field: revealing all of the variables we have access to. And it doesn't matter which block you're overriding: you always have access to this same, big group of variables. We can use these to only add that has-feedback class if there is an error.

Remove the dump. Then, set a new variable called showErrorIcon. Copy all of the logic from the if statement below that controls whether or not the has-error class is added and paste it here:

12 lines app/Resources/views/_formTheme.html.twig
{% block form_row -%}
{% set showErrorIcon = (not compound or force_error|default(false)) and not valid %}
... lines 3 - 10
{%- endblock form_row %}

The most important variable is valid: if this is false, the field failed validation. Don't worry about the compound variable - we'll talk about that soon.

Next, at the end of the div, use an inline if statement so that if showErrorIcon is true, we add the has-feedback class:

12 lines app/Resources/views/_formTheme.html.twig
{% block form_row -%}
{% set showErrorIcon = (not compound or force_error|default(false)) and not valid %}
<div class="form-group {% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}{{ showErrorIcon ? ' has-feedback' : '' }}">
... lines 4 - 9
</div>
{%- endblock form_row %}

Then, to add the icon, add that same if statement after printing the widget. Add a span with the necessary classes to make this an icon:

12 lines app/Resources/views/_formTheme.html.twig
{% block form_row -%}
{% set showErrorIcon = (not compound or force_error|default(false)) and not valid %}
<div class="form-group {% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}{{ showErrorIcon ? ' has-feedback' : '' }}">
{{- form_label(form) -}}
{{- form_widget(form) -}}
{% if showErrorIcon %}
<span class="glyphicon glyphicon-remove form-control-feedback" aria-hidden="true"></span>
{% endif %}
{{- form_errors(form) -}}
</div>
{%- endblock form_row %}

Ok, time to try it. Refresh! There's nothing yet, but there also aren't any validation errors. Empty the name field and submit. Our beautiful "X"!

But now, set the Subfamily field to "Select a Subfamily" and submit. Ok, the drop-down looks a little funny - the "X" is on top of the arrow. In fact, the Bootstrap docs warn you about this: this icon should only be added to text fields. And other fields, like checkboxes, will look even worse!

So, it's time to get a little smarter, and only add the cute icon to text fields.

Leave a comment!

  • 2017-09-08 Diego Aguiar

    Hey Bananaapple

    Yeah, you have to do that if your form theme does not live at the root of "app/Resources/views"

    Have a nice day

  • 2017-09-08 Bananaapple

    When adding the _formTheme.html.twig to config.yml I had to set a relative path to the file from the views folder so admin/genus/_formTheme.html.twig

    I am using Symfony 3.1.4 so just the version downloaded from this tutorial.

    What am I doing wrong / different to cause this change in behaviour?

  • 2017-08-30 Diego Aguiar

    Hey Nikolay!

    This is happening because you are choosing the placeholder option, which is not a SubFamily, and since your setSubFamily() method does not allow null to be passed, it explodes. So, you have two choices, allow passing null or validate the SubFamily field.

    Cheers!

  • 2017-08-30 Nikolay

    I have some problem.

    In EDIT form, if I'm choice option 'Select subfamily' (placeholder) in list of Subfamily, Symfony will show error:

    Type error: Argument 1 passed to AppBundle\Entity\Genus::setSubFamily() must be an instance of AppBundle\Entity\SubFamily, null given, called in D:\OpenServer\domains\aquanote.local\vendor\symfony\symfony\src\Symfony\Component\PropertyAccess\PropertyAccessor.php on line 636

    In ADD form all work fine - correctly handles the wrong choice and displays under SELECT field the error message.