Symfony Forms with Bootstrap 3 : Rendering a help block

It’s a pretty common need to show a help text with a form filed. Where we’ll tell the user about purpose of this field or some validation rules. Although Twitter Bootstrap already have a `help-block` class, Symfony Form component don’t have built-in support for rendering this kind of help block.

In this post I’ll share a simple trick that I use with Symfony Forms to render help block under form fields. Here is a sample output.

bootstrap-help-block

Let’s render a simple Symfony Form with bootstrap markup first. Then we’ll add a help text with a field. The good news is, 2 ready made form themes are shipped with recent (from 2.6) Symfony versions to render Forms with Twitter Bootstrap 3 markup.

If you are using an older version of Symfony, you can copy the new themes from here.

Before starting, I am assuming that, you have included bootstrap css in your layout. Also assuming  you have basic knowledge about creating and rendering Symfony Forms. If my assumption is not correct, please go through the following resources quickly.

Step 1. Create a simple Symfony form.

To make a quick example, I am creating it in controller.

  1. /**
  2.  * @Route("/test/form/help", name="test_form_help")
  3.  */
  4. public function sampleFormAction()
  5. {
  6.     $form = $this->createFormBuilder()
  7.                  ->add('task', 'text')
  8.                  ->add('dueDate', 'date')
  9.                  ->getForm();
  10.  
  11.     return $this->render('AppBundle:Test:form_help.html.twig', ['form' => $form->createView()]);
  12. }

We’ll work on task field in this post. But, for the dueDate field, if you want to use a datepicker instead of 3 dropdowns, please check the next post.

Step 2. Render the form in our view file with bootstrap theme.

We’ll use the theme for horizontal bootstrap form. Here is how to do it.

  1. // AppBundle:Test:form_help.html.twig
  2. {% form_theme form 'bootstrap_3_horizontal_layout.html.twig' %}
  3.  
  4. {{ form_start(form) }}
  5.     {{ form_widget(form) }}
  6.  
  7.     <div class="form-group">
  8.         <div class="col-sm-offset-2 col-sm-10">
  9.             <button type="submit" class="btn btn-default">Create Task</button>
  10.         </div>
  11.     </div>
  12. {{ form_end(form) }}

Now if we refresh the page we should see a pretty twitter bootstrap form. That means we are ready to talk about the trick.

Step 3. Set the help text to form field as an attribute

Let’s modify the task field and add a custom attribute “data-help“. Until we complete 5th step, it will be printed just as an attribute of text field, nothing else.

  1. //->add('task', 'text')
  2. ->add('task', 'text', ['attr' => ['data-help'  => 'Task name should be short and actionable']])

Step 4. Create a new form theme by extending current one

Let’s create a new form theme by extending current theme bootstrap_3_horizontal_layout.html.twig. And we’ll then do our magic in this theme file. Create a file bootstrap_3_cool.html.twig in /app/Resources/views/Form/ directory. Just copy and pest the following content in this file –

  1. {% extends 'bootstrap_3_horizontal_layout.html.twig' %}
  2.  
  3. {% block form_help -%}
  4.     {% for attrname, attrvalue in attr %}
  5.         {% if attrname == 'data-help' %}
  6.             <span class="help-block">{{ attrvalue|trans }}</span>
  7.         {% endif %}
  8.     {% endfor %}
  9. {%- endblock form_help %}
  10.  
  11. {% block form_errors -%}
  12.     {{ parent() }}
  13.     {{- block('form_help') -}}
  14. {%- endblock form_errors %}

Step 5. Use this theme for rendering our form

The last thing is using this theme for rendering our form. Do it just by changing the form theme name in view file –

  1. {# form_theme form 'bootstrap_3_horizontal_layout.html.twig' #}
  2. {% form_theme form ':Form:bootstrap_3_cool.html.twig' %}

Done! Now if we refresh the form, we’ll see the help text below form field exactly like the screenshot above. 🙂

So, how is it working?

Few more lines to explain the trick we just did. Let’s go through the lines of bootstrap_3_cool.html.twig

  • Line 1: Extending the bootstrap_3_horizontal_layout.html.twig layout. So this file will use all the the rendering blocks unless we redefine.
  • Line 3-9: We are defining a new block named form_help. Here we are checking if there is an attribute defined with name “data-help”. If found, we are printing it’s value in a span with class help-block. This is Bootstrap’s style of printing help text.
  • Line 11-14: Redefining form_errors block. Here we are printing parents content as usual and then calling form_help block that we just defined above.

So, every time a form_row is being rendered, it’s printing form_error below form_widget. And at that point our new block form_help is being called and he is printing our help block. If you are interested in digging more, please check the full source of parent bootstrap 3 layout theme.

11 Comments

  1. There is a small problem, the custom attribute “help“ is also added as html attribute

    to remove that add

    {%- if attrname == ‘help’ %}
    {%- elseif attrname in [‘placeholder’, ‘title’] -%}

    to block widget_attributes

    {%- block widget_attributes -%}
    id=”{{ id }}” name=”{{ full_name }}”
    {%- if disabled %} disabled=”disabled”{% endif -%}
    {%- if required %} required=”required”{% endif -%}
    {%- for attrname, attrvalue in attr -%}
    {{- ” ” -}}
    {%- if attrname == ‘help’ %}
    {%- elseif attrname in [‘placeholder’, ‘title’] -%}
    {{- attrname }}=”{{ attrvalue|trans({}, translation_domain) }}”
    {%- elseif attrvalue is sameas(true) -%}
    {{- attrname }}=”{{ attrname }}”
    {%- elseif attrvalue is not sameas(false) -%}
    {{- attrname }}=”{{ attrvalue }}”
    {%- endif -%}
    {%- endfor -%}
    {%- endblock widget_attributes -%}

  2. Thx buddy, you saved my day! But I’ve also one suggestion to make. In your solution the custom attribute is rendered within the input field. Therefore your should consider to use a global data-* attribute like “data-help” to have your page validate correctly.

  3. Interesting. So, in order to render one line of HTML, you need to extend Symfony’s base functionality, push a file, reference it, update supporting code and your set?

    1. Please don’t get it wrong! To render “one line of HTML”, you can just put that line where you need it. For example, to render bootstrap style help text under a filed, you can simply do it in Twig –

      {{ form_row(form.field) }}
      The help message text
      

      And you’re done!

      BTW, this post is actually discussing about making this kind help text rendering more sophisticated. If you setup it this way, just a single line of {{ form_widget(form) }} may render your form containing 20 fields and their help texts at appropriate place with expected design. And, this type of abstraction is a MUST DO if you need to work with complex requirement like rendering dynamic forms designed by users.

      Thanks a lot for commenting and sharing your impression. 🙂

  4. Line 6 of bootstrap_3_cool.html.twig file could be very dangerous. Another possible XSS vulnerability comes from abuse of the |raw filter. Whenever the data piped to this filter comes from a user input, a Cross-Site Scripting attack is possible.

    You don’t have to use this filter. So, first step, you MUST remove it 😉

    In a second step, you could replace it by a trans filter. It could be a good practice to 😉

    Line 6 becomes :
    {{ attrvalue|trans }}

    1. Thanks Alexandre for mentioning very important points!
      If the form is built using user input, then raw filter must not be used here. But I guess it’s not harmful when rendering only predefined FormTypes. However, I am removing it as someone may not notice this point and fall in vulnerability.
      And, adding trans filter is also very good suggestion. Thanks again!

  5. Nice solution, thank you!

    Instead of looping over the attr array, you could test if data-help is defined:

    {% if attribute(attr, ‘data-help’) is defined %}
    {{ attr[‘data-help’]|trans }}
    {% endif %}

    This could save some runtime if many attributes are set.

    And maybe another optimization in step 5. Instead of changing the form_theme change the config.yml:

    twig:
    ….
    form_themes:
    – bootstrap_3_horizontal_layout.html.twig
    – ‘@AppBundle/Resources/views/form/bootstrap_3_cool.html.twig’

    With this config you do not need to change any template

Leave a Comment

Your email address will not be published. Required fields are marked *