Sunday, September 21, 2008

Validate required checkbox

Introduction

The required attribute of a h:selectBooleanCheckbox is a bit non-intuitive. If you want to require the user to check the desired checkbox, you would assume that setting the required attribute to true ought to be sufficient.

But it is not. As for every other UIInput component the default required="true" validator would only check if the value is actually filled and been sent to the server side, i.e. the value is not null nor empty. In case of a h:selectBooleanCheckbox, which accepts Boolean or boolean properties only, JSF EL will coerce the unchecked value to Boolean.FALSE during apply request values phase, right before validations phase. This value is not null nor empty! Thus, the required attribute of the h:selectBooleanCheckbox is fairly pointless. It would always pass the validation and thus never display the desired required message in case of an unchecked checkbox.

Back to top

RequiredCheckboxValidator

To solve this non-intuitive behaviour (I am still not sure if this is a bug or a feature; the coercion of a null or empty Boolean property to Boolean.FALSE instead of null might be a bug, but this is not the case when you used boolean; after all I would consider it as an unwanted feature which should be better documented), best what you can do is to create your own javax.faces.validator.Validator implementation specific for a h:selectBooleanCheckbox of which a checked value is required. It is relatively simple, just check if the provided value parameter equals Boolean.FALSE and handle accordingly.

package mypackage;

import java.text.MessageFormat;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

public class RequiredCheckboxValidator implements Validator {

    public void validate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException
    {
        if (value.equals(Boolean.FALSE)) {
            String requiredMessage = ((UIInput) component).getRequiredMessage();

            if (requiredMessage == null) {
                Object label = component.getAttributes().get("label");
                if (label == null || (label instanceof String && ((String) label).length() == 0)) {
                    label = component.getValueExpression("label");
                }
                if (label == null) {
                    label = component.getClientId(context);
                }
                requiredMessage = MessageFormat.format(UIInput.REQUIRED_MESSAGE_ID, label);
            }

            throw new ValidatorException(
                new FacesMessage(FacesMessage.SEVERITY_ERROR, requiredMessage, requiredMessage));
        }
    }

}

Note that this validator checks if the developer has set any requiredMessage attribute so that it uses its value instead of the JSF default required message.

Here is how you should define it in the faces-config.xml:

<validator>
    <validator-id>requiredCheckboxValidator</validator-id>
    <validator-class>mypackage.RequiredCheckboxValidator</validator-class>
</validator>

That's it! Attach this validator to the h:selectBooleanCheckbox using f:validator.

Back to top

Basic demonstration example

Here is a sample form. It represents a kind of an agreement form which requires the checkbox being checked before submit. This is not an uncommon functional requirement.

The stuff is developed and tested in a Java EE 5.0 environment with Tomcat 6.0.18 with Servlet 2.5, JSP 2.1 and JSF 1.2_09.

<h:form>
    <h:outputLabel for="agree" value="Do you agree with me?" />
    <h:selectBooleanCheckbox id="agree" value="#{myBean.agree}" requiredMessage="You must agree!">
        <f:validator validatorId="requiredCheckboxValidator" />
    </h:selectBooleanCheckbox>
    <h:commandButton value="Submit" action="#{myBean.submit}" />
    <h:messages infoStyle="color: green;" errorStyle="color: red;" />
</h:form>

The appropriate test backing bean (request scoped) look like:

package mypackage;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

public class MyBean {

    // Properties ---------------------------------------------------------------------------------

    private Boolean agree;

    // Actions ------------------------------------------------------------------------------------

    public void submit() {
        FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("You agreed with me!"));
    }

    // Getters ------------------------------------------------------------------------------------

    public Boolean getAgree() {
        return agree;
    }

    // Setters ------------------------------------------------------------------------------------

    public void setAgree(Boolean agree) {
        this.agree = agree;
    }

}

Now, when you submit the form with an checked checkbox, JSF will just proceed with form processing, otherwise it will display the desired required message!

Back to top

Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair.

(C) September 2008, BalusC