Bootstrap 3: formatted checkboxes with Symfony & Twig

11,407

Solution 1

The proper way to do this is to create your own form theme. I will not write the whole thing here, but what matters to you are those:

{% block choice_widget_expanded %}
{% spaceless %}
    <div {{ block('widget_container_attributes') }} class="my-form-choices">
    {% for child in form %}
        {{ form_widget(child) }}
    {% endfor %}
    </div>
{% endspaceless %}
{% endblock choice_widget_expanded %}

{% block checkbox_widget %}
{% spaceless %}
    <div class="checkbox">
        <label for="{{ id }}"><input type="checkbox" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />{{ label|trans({}, translation_domain) }}</label>
    </div>
{% endspaceless %}
{% endblock checkbox_widget %}

I removed the label rendering from the choice_widget_expanded so I could have the checkbox inside the label tag, just like what you have in your solution. But you can basically do whatever you want in your form theme. You can also overwrite radio_widget if you want to.

If you look closely at the div around the checkboxes, I gave it the class "my-form-choices". You could give it the classes you want. It could be "col-lg-8", like you have, but for me, it makes more sense to have a generic class name, and then use mixins in your own less file. Your less file would look like this:

@import '../../../../../../vendor/twitter/bootstrap/less/bootstrap.less';

.my-form-row {
    .make-row();
}

.my-form-choices {
    .make-lg-column(8);
}

.my-form-row-label {
    .make-lg-column(4);
}

But that's up to you. You don't have to do this if you don't want to. Finally, you use your theme by starting the twig for your page like this:

{% extends 'MyOwnBundle::layout.html.twig' %}
{% form_theme form 'MyOwnBundle:Component:formtype.html.twig' %}

And you display the row simply like that (the field I used in my test is availability, yours is sample):

{{ form_row(form.availability) }}

I've tried that (with Symfony 2.4), and it works. The output is something like that:

<div class="my-form-row">
    <div class="my-form-row-label">
        <label class="required">Availability</label>
    </div>
    <div class="my-form-choices" id="myown_website_contactmessage_availability">
        <div class="checkbox">
            <label for="myown_website_contactmessage_availability_0">
                <input type="checkbox" value="morning" name="myown_website_contactmessage[availability][]" id="myown_website_contactmessage_availability_0">
                Morning
            </label>
        </div>
        <div class="checkbox">
            <label for="myown_website_contactmessage_availability_1">
                <input type="checkbox" value="afternoon" name="myown_website_contactmessage[availability][]" id="myown_website_contactmessage_availability_1">
                Afternoon
            </label>
        </div>
        <div class="checkbox">
            <label for="myown_website_contactmessage_availability_2">
                <input type="checkbox" value="evening" name="myown_website_contactmessage[availability][]" id="myown_website_contactmessage_availability_2">
                Evening
            </label>
        </div>
    </div>
</div>

Also notice that I do not have sub-rows, like you have in your solution. In fact, your solution goes beyond the question you were asking in the first place.

EDIT: here's a solution for having multiple columns

To have multiple columns, here is a quick solution. First, the form theme could be something like this:

{% block choice_widget_expanded %}
{% spaceless %}
    <div class="my-form-choices-container">
        <div {{ block('widget_container_attributes') }} class="my-form-choices">
        {% for child in form %}
            {{ form_widget(child) }}
        {% endfor %}
        </div>
    </div>
{% endspaceless %}
{% endblock choice_widget_expanded %}

{% block checkbox_widget %}
{% spaceless %}
    <div class="my-form-choice">
        <div class="checkbox">
            <label for="{{ id }}"><input type="checkbox" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />{{ label|trans({}, translation_domain) }}</label>
        </div>
    </div>
{% endspaceless %}
{% endblock checkbox_widget %}

And then your less file would look like this:

@import '../../../../../../vendor/twitter/bootstrap/less/bootstrap.less';

.my-form-row {
    .make-row();
}

.my-form-row-label {
    .make-lg-column(4);
}

.my-form-choices-container {
    .make-lg-column(8);
}

.my-form-choices {
    .make-row();
}

.my-form-choice {
    .make-md-column(6);
}

In this solution, you change the number of columns by changing the size of the columns. The cells should stack neatly. In this case, it is better to compile less (I won't explain that here). I have tried this solution, and it works well. The advantage of using less in this case, is that you can use the same form theme on multiple pages, and change the number of columns just by having a different "less" file for each of your pages.

Solution 2

You need to access to the form vars which contain the information about the checkboxes:

form.sample.vars.form.children

now you can loop through the checkboxes and access the values:

{% for children in form.sample.vars.form.children %}
  <div class="checkbox>
    <label>
      <input type="checkbox" name="{{ children.vars.full_name }}" id="{{ children.vars.id }}" value="{{ children.vars.value }}"{% if children.vars.data %} checked="checked"{% endif %} />
      {% if children.vars.label is defined %}
        {{ children.vars.label|trans }}
      {% else %}
        {{ children.vars.value|capitalize|trans }}
      {% endif %}
    </label>
  </div>
{% endfor %}

I place this code in a Twig template and add some logic to create rows with variable columns, so it look like this:

{% set cols = columns|default(1) %}
{% set i = 1 %}
{% for children in childrens %}
  {% if i == 1 %}<div class="row">{% endif %}
  <div class="col-lg-{{ 12/cols }} col-md-{{ 12/cols }}">
    <div class="checkbox {{ class|default('') }}">
      <label>
        <input type="checkbox" name="{{ children.vars.full_name }}" id="{{ children.vars.id }}" value="{{ children.vars.value }}"{% if children.vars.data %} checked="checked"{% endif %} />
        {% if children.vars.label is defined %}
          {{ children.vars.label|trans }}
        {% else %}
          {{ children.vars.value|capitalize|trans }}
        {% endif %}
      </label>
    </div>
  </div>
  {% set i = i+1 %}
  {% if i > cols %}
    </div>
    {% set i = 1 %}
  {% endif %}
{% endfor %}
{% if i != 1 %}</div>{% endif %}

now you can include this checkbox template where you need it:

<div class="row">
  <div class="col-lg-4">
    {{ form_label(form.sample) }}
  </div>
  <div class="col-lg-8">
    {% include 'checkbox.twig' with {childrens:form.sample.vars.form.children, columns:2} only %}
  </div>
</div>

the example above will return well formatted Bootstrap 3 output with checkboxes in two columns like this:

<div class="row">
  <div class="col-lg-4">
    <label class="required">Veranstalter</label>
  </div>
  <div class="col-lg-8">
    <div class="row">
  <div class="col-lg-6 col-md-6">
    <div class="checkbox ">
      <label><input type="checkbox" name="form[sample][]" id="form_sample_0" value="DUMMY">Dummy</label>
    </div>
  </div>
  <div class="col-lg-6 col-md-6">
    <div class="checkbox ">
      <label><input type="checkbox" name="form[sample][]" id="form_sample_1" value="DUMMY_1">Dummy 1</label>
    </div>
  </div>
</div>
<div class="row">
  <div class="col-lg-6 col-md-6">
    <div class="checkbox ">
      <label><input type="checkbox" name="form[sample][]" id="form_sample_2" value="DUMMY_2">Dummy 2</label>
    </div>
  </div>
</div>  

It's easy to use and will also work for radio buttons - just replace the class "checkbox" with "radio".

Share:
11,407

Related videos on Youtube

Ralf Hertsch
Author by

Ralf Hertsch

I'm staying hungry and never stop learning and searching for fresh ideas. I started programming in the seventies , since the last ten years developing Open Source Webapplications with PHP and MySQL.

Updated on June 21, 2022

Comments

  • Ralf Hertsch
    Ralf Hertsch almost 2 years

    How to create a formatted group of checkboxes as described in the Bootstrap documentation using the Symfony Form Factory and Twig?

    Using something like

    <div class="row">
      <div class="col-lg-4">
        {{ form_label(form.sample) }}
      </div>
      <div class="col-lg-8">
        {{ form_widget(form.sample) }}
      </div>
    </div>
    

    will not result in the needed output:

    a) each checkbox within a Bootstrap 3 structure like:

    <div class="radio">
      <label>
        <input type="radio" ... />Option one
      </label>
    </div>
    

    b) in addition the ability to output the checkboxes in two or more columns if needed:

    <div class="row">
      <div class="col-lg-6 col-md-6">
        <div class="radio"> ... </div>
      </div>
      <div class="col-lg-6 col-md-6">
        <div class="radio"> ... </div>
      </div>
    </div>
    
  • BENARD Patrick
    BENARD Patrick over 10 years
    I don't understand, you asked and answered your question at same time ?
  • Ralf Hertsch
    Ralf Hertsch over 10 years
  • Jean-François Beauchef
    Jean-François Beauchef over 10 years
    This answer received an upvote, and it is a very good effort. But it is not the best solution. The proper way to do this is through form theming, not including another twig. Also, this solution lets the user specify the number of checkboxes on each row. And while this is interesting, it wasn't part of the question.
  • Ralf Hertsch
    Ralf Hertsch over 10 years
    Thank you! Until now I had never used form theming, so this solution was not in my head. This seems to be the better way to general adapt the form factory output to Bootstrap 3 - I will have a closer look to it within next time.