If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeCheckout 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!
... 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:
... 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 |
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
:
{% 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
:
... 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.
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
:
{% 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:
{% 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:
{% 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:
{% 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:
{% 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.
Hey Yehor!
That looks correct! I wonder if the error is actually occurring because you have a form_row()
on some other fields and - since you're not passing iconClass
to that form row, it's causing an error.
Try doing something like this:
{% if iconClass is defined %}
{{ iconClass }}
{% endif %}
That should avoid a "variable is undefined" error if you don't pass this variable.
Cheers!
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?
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
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.
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!
// composer.json
{
"require": {
"php": "^7.1.3",
"symfony/symfony": "3.4.*", // v3.4.49
"doctrine/orm": "^2.5", // 2.7.5
"doctrine/doctrine-bundle": "^1.6", // 1.12.13
"doctrine/doctrine-cache-bundle": "^1.2", // 1.4.0
"symfony/swiftmailer-bundle": "^2.3", // v2.6.7
"symfony/monolog-bundle": "^2.8", // v2.12.1
"symfony/polyfill-apcu": "^1.0", // v1.23.0
"sensio/distribution-bundle": "^5.0", // v5.0.25
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.29
"incenteev/composer-parameter-handler": "^2.0", // v2.1.4
"composer/package-versions-deprecated": "^1.11", // 1.11.99.4
"knplabs/knp-markdown-bundle": "^1.4", // 1.9.0
"doctrine/doctrine-migrations-bundle": "^1.1", // v1.3.2
"stof/doctrine-extensions-bundle": "^1.2" // v1.3.0
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.7
"symfony/phpunit-bridge": "^3.0", // v3.4.47
"nelmio/alice": "^2.1", // v2.3.6
"doctrine/doctrine-fixtures-bundle": "^2.3" // v2.4.1
}
}
Hey, can anybody tell me is there a way to pass direct unconventional params to form_row
{{ form_row(form.fullName, {'iconClass': 'cat'}) }}
{{ form_row(form.fullName, {'iconClass': 'dog'}) }}
and then inside row just read?
I see iconClass in dump() while debug
{% block form_row %}
{{ dump() }}
but when I try to access it
{{ iconClass }}
I have an error that variable is undefined. Very strange behavior
{% endblock %}
I use symfony 4.4.