Welcome

Customizing JSF Required Field Messages per Component Instance

JSF is nice that it provides some useful built-in features to validate entered values or if provided any required one, but it fails at providing enough mechanism to let developers customize those features at the level of each component instance. This is a common drawback of JSF specification in general.

We specify that user input is required for an input text, textarea or select listbox with setting its required field to true, and JSF checks if any user input is entered during validation phase. If user hadn?t provided any input for the component, component becomes invalid and an ugly error message is raised via FacesContext.addMessage(), saying something like ?Input required for this field…?. There is not possible to customize that error message by no means, except localizing it in your preffered language, specific to each component instance. For example, it would be nice to have a message for a component that gets username information, saying ?You must provide a valid username information right here…?.

Rick Hightower has a nice blog, seeking solution to this problem. Unfortunately, there is no proper way to customize required messages, but hacking methods. Rick employs a PhaseListener that comes into scene after validation phase. It simply searches faces messages, look for any matches with faces? default required error message text. When it finds one, it strips off clientId of component, then looks for a better message from message bundle with clientId key. Finally, default summary and detail message texts are replaced with custom ones.

We have employed a similar hack in our project, too. Major deviation from Rick?s solution is that, we do the required field validation ourselves, before validation phase. We also employed a message key to indicate custom required message in the bundle with f:attribute within components, whose required messages are wanted to be customized. Our PhaseListener traverses over the whole UIComponent tree and looks which UIInput components have required fields with value true. Then it checks if user provided any input. If input is not provided, component is marked as invalid and a faces error message is created. It first searches for custom required error message text, and if finds one it creates faces error message with custom error message text, specific to that UIComponent/UIInput instance. Unless it finds any custom message text key, it then uses default faces required message text. Following is its source code;

import java.util.Iterator;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import org.apache.commons.lang.StringUtils;

public class RequiredFieldValidatorPhaseListener implements PhaseListener {
    private final static String REQUIRED_FIELD_ATTRIBUTE_KEY = "requiredMsgKey";

    private ResourceBundle resourceBundle;

    public void afterPhase(PhaseEvent event) {    }

    public void beforePhase(PhaseEvent event) {
        FacesContext facesContext = event.getFacesContext();
        UIViewRoot viewRoot = facesContext.getViewRoot();
        initializeResourceBundle(facesContext);
        doCustomRequiredFieldValidation(facesContext,viewRoot);
    }

    public PhaseId getPhaseId() {
        return PhaseId.PROCESS_VALIDATIONS;
    }

    private void initializeResourceBundle(FacesContext facesContext) {
        String messageBundle = facesContext.getApplication().getMessageBundle();
        Locale locale = facesContext.getApplication().getDefaultLocale();
        resourceBundle = ResourceBundle.getBundle(messageBundle,locale);
    }

    private void doCustomRequiredFieldValidation(FacesContext facesContext, UIComponent component) {

        if(component instanceof UIInput) {
            UIInput inputComponent = (UIInput)component;
            if(inputComponent.isRequired()) {
               boolean valid = validateValue(inputComponent);

               if(!valid){
                   inputComponent.setValid(false);
                   addErrorMessage(facesContext,inputComponent);
                   facesContext.renderResponse();
               }
            }
        }  

       
        for (Iterator iter = component.getChildren().iterator(); iter.hasNext();) {
            UIComponent childComponent = (UIComponent) iter.next();
            doCustomRequiredFieldValidation(facesContext,childComponent);
        }
    }

    private String getRequiredFieldMsgKey(UIInput inputComponent) {
        String msgKey = (String)inputComponent.getAttributes().get(REQUIRED_FIELD_ATTRIBUTE_KEY);
        return msgKey;
    }

    private boolean  validateValue(UIInput inputComponent) {
        boolean valid = true;

        Object submittedValue = inputComponent.getSubmittedValue();

        if(submittedValue == null) {
            valid = false;
        } else if(submittedValue instanceof String) {
            valid = !StringUtils.isEmpty((String)submittedValue);
        }
        return valid;
    }

    private void addErrorMessage(FacesContext facesContext, UIInput component) {
        String msgKey = getRequiredFieldMsgKey(component);

        String summary = getValue("summary." + msgKey);

        if(summary == null) {

            summary = resourceBundle.getString("javax.faces.component.UIInput.REQUIRED");
        }

        String detail  = getValue("detail." + msgKey);

        FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_FATAL,summary,detail);
        facesContext.addMessage(component.getClientId(facesContext),facesMessage);
    }

    private String getValue(String key) {
        try {
            return resourceBundle.getString(key);
        } catch (RuntimeException e) {
            return null;
        }
    }
}

Finally, let’s look at inside a jsf page, and how it is employed for UIInput type components;

<h:inputText id="txtName" required="true" >
            <f:attribute name="requiredMsgKey" value="msgNameRequired" />
</h:inputText>

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.