DigitalJoel

2009/07/07

JSF Tri State Checkbox

Filed under: facelets, java, jquery, JSF — digitaljoel @ 5:05 am

In my work with JSF I found that I needed a tri-state checkbox.  In my case, the checkbox represented whether or not a decorator was added to all selected objects, no selected objects, or some of the selected objects.  This corresponded to checked, unchecked, or partially checked.

When I was looking for something that would fit my needs I found a Javascript implementation at Shams’ Blog which is a bit of a different use case than what I was looking for, but has some good points and useful images.

I created the following facelet file that manages state transitions and image management for the JSF tri state checkbox.  My control doesn’t handle the nested selection like you’ll see in Sham’s blog, but it fills the needs stated above.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:c="http://java.sun.com/jstl/core" >

<!--
  An input component that is represented as a checkbox with three states.  Checked, Unchecked, and Intermediate.

  Required attributes:
    checkedValue - the value that should be set for this component when the checkbox is checked
    uncheckedValue - the value that should be set for this component when the checkbox is unchecked
    intermediateValue - the value that should be set for this component when the checkbox is in the intermediate state.
    value - bean property to hold the value.  Should evaluate to checkedValue, uncheckedValue, or intermediateValue the first time.
    controller - controller that implements the value change listener.
    key - key value to pass as an attribute of the control that contains the checkbox value.
    valueChangeListener - change listener for value of this checkbox component.
 -->

<ui:composition>

  <script type="text/javascript">
    function setCheckValue( img, next, newVal )
    {
      img.css('display', 'none');
      img.siblings('[id$='+next+']').css('display','inline');
      img.siblings('[id$=triStateCheck]').val(newVal);
    }
  </script>

  <h:graphicImage
    id="trueValue"
    url="/images/ico_checked.gif"
    style="display: #{value == checkedValue ? 'inline' : 'none'}"
    onmouseover="jQuery(this).attr('src','/images/ico_checked_highlighted.gif')"
    onmouseout="jQuery(this).attr('src','/images/ico_checked.gif')"
    onclick="setCheckValue( jQuery(this), 'falseValue', '#{uncheckedValue}')"
    rendered="#{(empty rendered) ? true : rendered}" />
  <h:graphicImage
    id="falseValue"
    style="display: #{value == uncheckedValue ? 'inline' : 'none'}"
    onmouseover="jQuery(this).attr( 'src', '/images/ico_unchecked_highlighted.gif' )"
    onmouseout="jQuery(this).attr('src', '/images/ico_unchecked.gif')"
    url="/images/ico_unchecked.gif"
    onclick="setCheckValue( jQuery(this),
        '#{value == intermediateValue ? 'intermediateValue' : 'trueValue'}',
        '#{value == intermediateValue ? intermediateValue : checkedValue}')"
    rendered="#{(empty rendered) ? true : rendered}" />
  <h:graphicImage
    id="intermediateValue"
    style="display: #{value == intermediateValue ? 'inline' : 'none'}"
    onmouseover="jQuery(this).attr( 'src', '/images/ico_intermediate_highlighted.gif' )"
    onmouseout="jQuery(this).attr('src', '/images/ico_intermediate.gif')"
    url="/images/ico_intermediate.gif"
    onclick="setCheckValue( jQuery(this), 'trueValue', '#{checkedValue}')"
    rendered="#{(empty rendered) ? true : rendered}" />

  <h:inputHidden
    id="triStateCheck"
    value="#{value}"
    valueChangeListener="#{controller&#91;valueChangeListener&#93;}">
    <f:attribute name="key" value="#{key}" />
  </h:inputHidden>

</ui:composition>
</html>

The control starts with a very simple script that uses jquery.

    function setCheckValue( img, next, newVal )
    {
      img.css('display', 'none');
      img.siblings('[id$='+next+']').css('display','inline');
      img.siblings('[id$=triStateCheck]').val(newVal);
    }

The script takes three parameters. The current image, the next image, and the new value. It then hides the current image, shows the new image, and sets the value of the control to the new value.

Each image in the control has a couple of Javascript event handlers. One to show the highlighted version of the current image on mouseover, and another to show the regular version on mouse out. Finally, a third to transition to the next state and set the value on click. You’ll notice that in the falseValue image, the onclick is a bit longer than the others.

    onclick="setCheckValue( jQuery(this),
        '#{value == intermediateValue ? 'intermediateValue' : 'trueValue'}',
        '#{value == intermediateValue ? intermediateValue : checkedValue}')"

This is because if the value doesn’t start at intermediate, then we don’t ever want to hit the intermediate value in transitions. For instance, if the value starts at true or false, the checkbox will function just like a normal checkbox, toggling only between true and false. If the checkbox starts at the intermediate value, then it will go from intermediate to true, then to false, then back to intermediate on subsequent clicks. If this is not the desired behavior, then the conditional statements in the onclick can be removed so that it always goes to intermediate.

The final interesting part of the control is the hidden field that holds the value of the checkbox.

  <h:inputHidden
    id="triStateCheck"
    value="#{value}"
    valueChangeListener="#{controller&#91;valueChangeListener&#93;}">
    <f:attribute name="key" value="#{key}" />
  </h:inputHidden>

This inputHidden control contains the current value of the checkbox. The value is set initially, and then changed through javascript when the checkboxes are clicked. When debugging, it can be helpful to change the inputHidden to a simple input text control so you can inspect it and see the value change. The control will call a value change listener which can use the key attribute to determine which entity the checkbox belongs to. This is valuable when you are stamping the checkboxes ( as in a ui:repeat ) so you know which entity in the collection had the checkbox changed.

In order to use the control, simply save the text to a file and include a pointer to it in your JSF taglib definition. You can modify the image paths for your needs and set the other attributes as they are commented at the top of the file.

Suggestions on how to improve this control? I’d love to hear them.

Create a free website or blog at WordPress.com.