Bootstrap radio buttons and checkboxes in columns, with contextual text fields

We recently did a project with the following requirements:

  • Touch-friendly interface (translation: big touch targets);
  • Bootstrap;
  • Radio and checkboxes need to highlight when selected;
  • When a radio button or checkbox is selected, sometimes a little additional information is required.

See the demo to get a clear idea of what I’m talking about.

To accomplish this, we did several things:

  1. Used Bootstrap “radio-inline” and “checkbox-inline” classes;
  2. Wrapped each radio/checkbox in a label, styled the label to look like a touch target, and set it so clicking on the label selected the radio/checkbox.
  3. Created a “column” class to make the labels form columns;
  4. Used jQuery to add a “checked” class to each label when its radio/checkbox is selected.
  5. Created an “additional-info-wrap” class to associate a contextual text field with any radio/checkbox we needed.

Sounds complicated, but it’s really not. The actual code is pretty simple.

HTML

<form class="form-horizontal">
        <div class="form-group">
              <label class="col-md-2 control-label" for="Checkboxes">Checkboxes</label>  

              <div class="col-md-10 columns"> 
                    <label class="checkbox-inline" for="Checkboxes_Apple">
                      <input type="checkbox" name="Checkboxes" id="Checkboxes_Apple" value="Apple">
                      Apple
                    </label> 
                    <label class="checkbox-inline" for="Checkboxes_Orange">
                      <input type="checkbox" name="Checkboxes" id="Checkboxes_Orange" value="Orange">
                      Orange
                    </label> 
                    <label class="checkbox-inline" for="Checkboxes_Bananas">
                      <input type="checkbox" name="Checkboxes" id="Checkboxes_Bananas" value="Bananas">
                      Banana
                    </label>
                    <label class="checkbox-inline" for="Checkboxes_Kumquats">
                      <input type="checkbox" name="Checkboxes" id="Checkboxes_Kumquats" value="Kumquats">
                      Kumquat
                    </label> 
                    <span class="additional-info-wrap">
                        <label class="checkbox-inline" for="Checkboxes_Grape">
                          <input type="checkbox" name="Checkboxes" id="Checkboxes_Grape" value="Grape">
                          Grape
                        </label>
                        <div class="additional-info hide">
                              <input type="text" id="CheckboxesNameOfGrape" name="CheckboxesNameOfGrape" placeholder="Name of Grape" class="form-control" disabled="">
                        </div>
                    </span>
                    <span class="additional-info-wrap">
                        <label class="checkbox-inline" for="Checkboxes_Other">
                          <input type="checkbox" name="Checkboxes" id="Checkboxes_Other" value="Other">
                          Other
                        </label>
                        <div class="additional-info hide">
                              <input type="text" id="CheckboxesOther" name="CheckboxesOther" placeholder="Describe" class="form-control" disabled="">
                        </div>
                    </span>
                </div>
            </div>
        <div class="form-group">
              <label class="col-md-2 control-label" for="Radios">Radio buttons</label>  

              <div class="col-md-10 columns"> 
                    <label class="radio-inline" for="Radios_Apple">
                      <input type="radio" name="Radios" id="Radios_Apple" value="Apple">
                      Apple
                    </label> 
                    <label class="radio-inline" for="Radios_Orange">
                      <input type="radio" name="Radios" id="Radios_Orange" value="Orange">
                      Orange
                    </label> 
                    <label class="radio-inline" for="Radios_Bananas">
                      <input type="radio" name="Radios" id="Radios_Bananas" value="Bananas">
                      Banana
                    </label>
                    <label class="radio-inline" for="Radios_Kumquats">
                      <input type="radio" name="Radios" id="Radios_Kumquats" value="Kumquats">
                      Kumquat
                    </label> 
                    <span class="additional-info-wrap">
                        <label class="radio-inline" for="Radios_Grape">
                          <input type="radio" name="Radios" id="Radios_Grape" value="Grape">
                          Grape
                        </label>
                        <div class="additional-info hide">
                              <input type="text" id="RadiosNameOfGrape" name="RadiosNameOfGrape" placeholder="Name of Grape" class="form-control" disabled="">
                        </div>
                    </span>
                    <span class="additional-info-wrap">
                        <label class="radio-inline" for="Radios_Other">
                          <input type="radio" name="Radios" id="Radios_Other" value="Other">
                          Other
                        </label>
                        <div class="additional-info hide">
                              <input type="text" id="RadiosOther" name="RadiosOther" placeholder="Describe" class="form-control" disabled="">
                        </div>
                    </span>
                </div>
            </div>
    </form>

Nothing too fancy here. Each radio/checkbox is wrapped in a label with a “for” attribute applied; this selects the radio/checkbox when its label is clicked.

Each group of labels is wrapped in a div with the class “column”.

The radios/checkboxes that have an extra text field are wrapped in a span with the class “additional-info-wrap”; the extra text field itself is in a div with the class “additional-info”, and with the Bootstrap “hide” class attached so it’s not displayed.

CSS

label.radio-inline, label.checkbox-inline {
  background-color: #dcdfd4;
  cursor: pointer;
  font-weight: 400;
  margin-bottom: 10px !important;
  margin-right: 2%;
  margin-left:0;
  padding: 10px 10px 10px 30px;
}
label.radio-inline.checked, label.checkbox-inline.checked {
  background-color: #266c8e;
  color: #fff !important;
  text-shadow: 1px 1px 2px #000 !important;
}
.checkbox-inline + .checkbox-inline, .radio-inline + .radio-inline {
  margin-left: 0;
}
.columns label.radio-inline, .columns label.checkbox-inline {
  min-width: 190px;
  vertical-align: top;
  width: 30%;
}
.additional-info-wrap {
  display: inline-block;
  margin: 0 2% 0 0;
  min-width: 190px;
  position: relative;
  vertical-align: top;
  width: 30%;
}
.additional-info-wrap label.checkbox-inline, .additional-info-wrap label.radio-inline {
  width: 100% !important;
}
.additional-info-wrap .additional-info {
  background-color: #266c8e;
  clear: both;
  color: #fff !important;
  margin-top: -10px;
  padding: 0 10px 10px;
  text-shadow: 1px 1px 2px #000 !important;
  width: 100%;
}

Again, fairly straightforward.

The first style turns the labels into touch targets, by giving them some padding and a gray background color.

The second style turns the labels blue, with white text, when the “checked” class is added.

The third style cancels some base Bootstrap margins that mess up our column layout.

The fourth style creates the columns, by setting a width and margin for each label. They stack up in three columns until the page gets too narrow, and then they stack in two columns.

The last three styles all handle the contextual text field.

“additional-info-wrap” is set to mimic the other labels,

The radio/checkbox inside “additional-info-wrap” is set to “width:100%” so it fills out the entire width.

Finally, “additional-info” wraps the contextual text field and gives it some padding, margin and the same background color as the labels.

JAVASCRIPT

$(document).ready(function() {

    //When checkboxes/radios checked/unchecked, toggle background color
    $('.form-group').on('click','input[type=radio]',function() {
        $(this).closest('.form-group').find('.radio-inline, .radio').removeClass('checked');
        $(this).closest('.radio-inline, .radio').addClass('checked');
    });
    $('.form-group').on('click','input[type=checkbox]',function() {
        $(this).closest('.checkbox-inline, .checkbox').toggleClass('checked');
    });

    //Show additional info text box when relevant checkbox checked
    $('.additional-info-wrap input[type=checkbox]').click(function() {
        if($(this).is(':checked')) {
            $(this).closest('.additional-info-wrap').find('.additional-info').removeClass('hide').find('input,select').removeAttr('disabled');
        }
        else {
            $(this).closest('.additional-info-wrap').find('.additional-info').addClass('hide').find('input,select').val('').attr('disabled','disabled');
        }
    });

    //Show additional info text box when relevant radio checked
    $('input[type=radio]').click(function() {
        $(this).closest('.form-group').find('.additional-info-wrap .additional-info').addClass('hide').find('input,select').val('').attr('disabled','disabled');
        if($(this).closest('.additional-info-wrap').length > 0) {
            $(this).closest('.additional-info-wrap').find('.additional-info').removeClass('hide').find('input,select').removeAttr('disabled');
        }        
    });
});

The first event listener adds/removes the “checked” class from a label when it is clicked. For checkboxes, it’s a simple toggle. For radios, it has to remove the “checked” class from all the other radios in the group before adding it to the selected radio.

The last two event listeners show/hide a contextual text field, if present, by removing/adding the Bootstrap “hide” class from the related “additional-info” div. You’ll see that the code is slightly different for radios vs. checkboxes, again because in a radio group you have to account for all the radios, while checkboxes can be treated individually.

 

Drag and drop sorting of table rows in priority order

A common design pattern is making choices from a list, and then adding those choices to a table of results.

Often the table will use some sort of Javascript plugin — Footables, Datatables, etc. — to provide sorting and searching functionality.

But what if you want the results in a table, and the only extra you need is a quick way to sort the table into a prioritized list? And you want to do it using a touch-friendly drag/drop interface?

Here’s a simple way to do that, using jQuery and jQuery UI. Check out the demo to see it in action.

HTML

First, we build a table with some data in it:

<table class="table" id="diagnosis_list">
    <thead>
        <tr><th>Priority</th><th>Name</th><th>Favorite fruit</th><th>Vegetarian?</th><th>&nbsp;</th></tr>
    </thead>
    <tbody>
        <tr><td class='priority'>1</td><td>George Washington</td><td>Apple</td><td>N</td><td><a class='btn btn-delete btn-danger'>Delete</a></td></tr>
        <tr><td class='priority'>2</td><td>John Adams</td><td>Pear</td><td>Y</td><td><a class='btn btn-delete btn-danger'>Delete</a></td></tr>
        <tr><td class='priority'>3</td><td>Thomas Jefferson</td><td>Banana</td><td>Y</td><td><a class='btn btn-delete btn-danger'>Delete</a></td></tr>
        <tr><td class='priority'>4</td><td>Ben Franklin</td><td>Kumquat</td><td>N</td><td><a class='btn btn-delete btn-danger'>Delete</a></td></tr>
        <tr><td class='priority'>5</td><td>Alexander Hamilton</td><td>Red grapes</td><td>N</td><td><a class='btn btn-delete btn-danger'>Delete</a></td></tr>
    </tbody>
</table>

It’s just a normal table, with an ID and explicit <thead> and <tbody> sections. This lets us make the <tbody> sortable, while leaving the <thead> alone. Also note that the first <td> in each row has the class “priority”. This is where our javascript will write each row’s priority number.

CSS

The demo includes basic Bootstrap styles for overall look and feel. So we only require a little bit of custom CSS to make the interactions more enjoyable.

.ui-sortable tr {
    cursor:pointer;
}    
.ui-sortable tr:hover {
    background:rgba(244,251,17,0.45);
}

When hovering over a draggable table row, this CSS displays the hand cursor and turns the background of the row a transparent yellow.

Javascript

To get the sortable behavior, we call in the jQuery UI Javascript file. Note that we don’t call in the jQuery UI CSS: we want the functionality, not the styling.

Then we add the following code:

$(document).ready(function() {
    //Helper function to keep table row from collapsing when being sorted
    var fixHelperModified = function(e, tr) {
        var $originals = tr.children();
        var $helper = tr.clone();
        $helper.children().each(function(index)
        {
          $(this).width($originals.eq(index).width())
        });
        return $helper;
    };

    //Make diagnosis table sortable
    $("#diagnosis_list tbody").sortable({
        helper: fixHelperModified,
        stop: function(event,ui) {renumber_table('#diagnosis_list')}
    }).disableSelection();

    //Delete button in table rows
    $('table').on('click','.btn-delete',function() {
        tableID = '#' + $(this).closest('table').attr('id');
        r = confirm('Delete this item?');
        if(r) {
            $(this).closest('tr').remove();
            renumber_table(tableID);
            }
    });
});

//Renumber table rows
function renumber_table(tableID) {
    $(tableID + " tr").each(function() {
        count = $(this).parent().children().index($(this)) + 1;
        $(this).find('.priority').html(count);
    });
}

This is the core of the demo, so let’s go through it in detail.

    //Make diagnosis table sortable
    $("#diagnosis_list tbody").sortable({
        helper: fixHelperModified,
        stop: function(event,ui) {renumber_table('#diagnosis_list')}
    }).disableSelection();

This calls the jQuery UI “sortable” method on the <tbody> element of the table. It includes a helper function called fixHelperModified. Once the sort is finished, it calls the renumber_table() function.

What is the point of fixHelperModified? I’m so glad you asked:

    //Helper function to keep table row from collapsing when being sorted
    var fixHelperModified = function(e, tr) {
        var $originals = tr.children();
        var $helper = tr.clone();
        $helper.children().each(function(index)
        {
          $(this).width($originals.eq(index).width())
        });
        return $helper;
    };

The best way to see what this function does is to execute the sortable call without it. By default, any table row you drag will collapse down to minimum size, leaving you with a strange looking table:

bad_sortable_table

The helper function maintains the full width of the row, avoiding a weird visual experience while dragging. Thanks to Brian Grinstead for making this JSFiddle that illustrates the problem and a couple of different solutions, as originally raised on Stack Overflow.

When dragging is done, it triggers the renumber_table() function:

//Renumber table rows
function renumber_table(tableID) {
    $(tableID + " tr").each(function() {
        count = $(this).parent().children().index($(this)) + 1;
        $(this).find('.priority').html(count);
    });
}

This puts all the table rows into a jQuery object, then uses jQuery’s .each() method to loop through each row and renumber them based on their position in the table. “count” is the index of each row; the code adds 1 to this because indexes start at 0, and we want to start at one. Then it replaces the content of the “priority” <td> with count.

Bonus: Delete a table row

You may have noticed I skipped a code block:

    //Delete button in table rows
    $('table').on('click','.btn-delete',function() {
        tableID = '#' + $(this).closest('table').attr('id');
        r = confirm('Delete this item?');
        if(r) {
            $(this).closest('tr').remove();
            renumber_table(tableID);
            }
    });

As the note says, this code is triggered when a row’s “Delete” button is clicked. It does several things:

  1. Gets the ID of the table;
  2. Shows a confirmation dialog asking if you really want to delete the row;
  3. If you confirm the deletion, it finds the <tr> tag for the row the button is in, deletes the row, and then calls the renumber_table() function to, uh, renumber the table. This last is why it grabbed the ID of the table in Step 1; it needs to pass the ID to the renumbering function.

Clickable labels that highlight when selected

I’ve been designing a touch-friendly form for one of our clients. One thing we wanted to do was make checkboxes and radio buttons easier to tap, and clearly show what has been selected. A little bit of CSS and JavaScript did the trick. Check out the demo to see it in action.

This example uses Bootstrap classes, so you can drop these snippets straight into your own Bootstrap-based site and it should work just fine.

HTML

    <form id="testForm" class="form-horizontal" method="post">
    <div class="form-group">
      <div class="col-md-4">
        <label class="radio" for="option-0">
          <input name="options" id="option-0" value="1" type="radio">
          Option #1
        </label>
        <label class="radio" for="option-1">
          <input name="options" id="option-1" value="2" type="radio">
          Option #2
        </label>
        <label class="radio" for="option-2">
          <input name="options" id="option-2" value="3" type="radio">
          Option #3
        </label>
      </div>
    </div>
    <div class="form-group">
      <div class="col-md-4">
        <label class="checkbox" for="cb-option-0">
          <input name="checkboxes" id="cb-option-0" value="1" type="checkbox">
          Option #1
        </label>
        <label class="checkbox" for="cb-option-1">
          <input name="checkboxes" id="cb-option-1" value="2" type="checkbox">
          Option #2
        </label>
        <label class="checkbox" for="cb-option-2">
          <input name="checkboxes" id="cb-option-2" value="3" type="checkbox">
          Option #3
        </label>
      </div>
    </div>
    </form>

Pretty straightforward. Just standard Bootstrap form-groups, with the radios and checkboxes wrapped in label tags with appropriate “checkbox” and “radio” classes.

CSS

label.radio-inline,
label.checkbox-inline,
label.radio,
label.checkbox {
    margin-right:2%;
    cursor:pointer;
    font-weight:400;
    padding:10px 10px 10px 30px;
    background-color:#dcdfd4;
    margin-bottom:10px!important
}
label.radio-inline.checked,
label.checkbox-inline.checked,
label.radio.checked,
label.checkbox.checked {
    background-color:#266c8e;
    color:#fff!important;
    text-shadow:#000 1px 1px 2px!important
}

Again, fairly straightforward: We give all the labels a gray background and some padding. If we add the class “checked”, we use a blue background with white text.

JAVASCRIPT

//When checkboxes/radios checked/unchecked, toggle background color
$('.form-group').on('click','input[type=radio]',function() {
    $(this).closest('.form-group').find('.radio-inline, .radio').removeClass('checked');
    $(this).closest('.radio-inline, .radio').addClass('checked');
});
$('.form-group').on('click','input[type=checkbox]',function() {
    $(this).closest('.checkbox-inline, .checkbox').toggleClass('checked');
});

The Javascript ties it all together.

When a radio button is clicked, it finds the parent form-group and then from there looks for any radio buttons. It removes any existing “checked” classes from the radio buttons, then adds the “checked” class to the label for the radio button that was just clicked.

When a checkbox is clicked, the code is simpler: it just finds the label for that checkbox and toggles the “checked” class on it.