A Three State Checkbox built with jQuery

December 3, 2012

As part of a complex app I recently built, I needed a checkbox that had three states, instead of the usual two. I was using the checkbox to set options for multiple items, where the items might previously have had different options selected. I needed a state for the checkbox that told the application not to make any changes to that item unless the user explicitly selected checked or unchecked.

The solution I came up with (which was of course based on various ideas I found while googling the problem) was to use the disabled property of the checkbox to define the third state. The trick here was to have a checkbox in the disabled state that could still be clicked on to enable it. This was done by placing a span over the checkbox that would intercept the click. When it was clicked, it would enable the checkbox, and hide itself. The checbox change event was augmented to disable the checkbox and show the mask when the checkbox was clicked and it was already checked. This set up a three-stage loop where clicking on the checkbox would take it from disabled to unselected to selected to disabled.

Implementation

This was a relatively straightforward implementation once I understood the concept of applying the mask. There is a bit of jQuery based javaScript code and a few lines of css required to make this work.

HTML

First, when defining the checkbox, it must be wrapped in the masking span. If you were placing the checkbox in a table cell, the span needs to be completely inside the cell. For example:

...
<span style="position: relative;">
    <input id="demoCheck" class="tri-check" type="checkbox" disabled="disabled" />
</span></pre>
<div id="demoCheck-masq" class="checkbox_masq"></div>
<pre><span style="position: relative;">
  </span>

I set disabled to “true” as I wanted that to be the default state of the checkbox. The class and ID for the checkbox and for the mask are all important for how you access and style the checkbox.

Style

There are two styles that I use for this implementation. The mask style sets up the overlay that is used for the disabled state.

.checkbox_masq {
  position:   absolute;
  left:       0;
  right:      0;
  top:        0;
  bottom:     0;
  cursor:     pointer;
}

The tri-check style just sets the pointer. I’m not sure this is even needed, as the style should be the pointer for the cursor when the checkbox is enabled, anyway.

.tri-check {
  cursor:     pointer;
}

JavaScript

The scripts are also relatively straightforward. I have one function that walks through the page and sets the callbacks for both the mask and for the checkbox itself.

(function($) {
  $.fn.setTriCheckCallback = function() {
    $(".tri-check").each( function(i) {
      var boxid=$(this).prop("id");
      $("#" + boxid + "-masq").on("click",function() {
        $(this).hide();
        $("#" + boxid).prop("disabled",false);
      });
      $(this).on("click", function() {
        if ( $(this).prop("checked") == false) {
          $(this).prop("disabled",true);
          $("#" + boxid + "-masq").show();
        };
      });
    });
  }
})(jQuery);

The “each” function walks through the page and grabs every object which is classed as “tri-check”. It figures out the name of the mask by appending “-masq” to the check box ID. This is why the naming above was important. For the mask, the function sets up a “click” callback which hides the mask and enables the checkbox. Then, the function sets up a “click” callback on the checkbox itself, which checks to see if the checkbox is now checked, and if it isn’t, it disables the checkbox and shows the mask.

I call this as a function because my app has many checkboxes that are set up this way. I generate them, and then I call this function once to set up the call backs.

This is a relatively simple mechanism, but as I couldn’t find a good document on it to bookmark, I wrote this for reference purposes.