Tuesday, December 11, 2007

Validator for multiple fields

Notice

The JSF utility library OmniFaces has several useful multi-field validators such as <o:validateEqual>, which may end up to be easier than homegrowing one. See also the javadoc of the base class ValidateMultipleFields.

Introduction

Validators in JSF are nice. They, however, have its shortcomings. They will by default validate only one field at once. There is no standard way to attach one validator to multiple fields. Although there are some situations where you want this kind of functionality. For example validating the password confirmation field, validating the range of two numeric or date values (e.g. the one have to be lesser than the other), validating correctness of the three separate day, month and year fields, etcetera.

The cleanest solution would be to create a custom component which renders two or more components and use a specific validator for that, but that would involve more work. The easiest solution in this particular case is to attach the validator to the first component of the group (components are rendered, validated, converted and updated in the same order as you define them in the JSF view) and pass the other component(s) as unique f:attribute facet(s) along the first component. Then in the validator you can get the desired component(s) using UIComponent#getAttributes().

Back to top

Basic example

This example demonstrates a basic registration form with one username field and two password fields. The value of the second password field should equal to the value of the first password field before the action method may be invoked. The stuff is tested in a Java EE 5.0 environment with Tomcat 6.0 with Servlet 2.5, JSP 2.1 and JSF 1.2_07 (currently called Mojarra by the way!).

Here is the relevant JSF code. Note the binding="#{confirm}" of the second password field. It binds the component to the view and makes it available elsewhere by #{confirm}. This is an instance of the UIInput class which has a getSubmittedValue() method to get the submitted value. Note the f:attribute of the first password field, its value should point to the component of the second password field #{confirm}. Also note that the second password field doesn't have any value bound to the backing bean as this is unnecessary in this specific case.

<h:form id="register">
    <h:panelGrid columns="3">
        <h:outputLabel for="username" value="Username" />
        <h:inputText id="username" value="#{myBean.username}" required="true" />
        <h:message for="username" style="color: red;" />

        <h:outputLabel for="password" value="Password" />
        <h:inputSecret id="password" value="#{myBean.password}" required="true">
            <f:validator validatorId="passwordValidator" />
            <f:attribute name="confirm" value="#{confirm}" />
        </h:inputSecret>
        <h:message for="password" style="color: red;" />

        <h:outputLabel for="confirm" value="Confirm password" />
        <h:inputSecret id="confirm" binding="#{confirm}" required="true" />
        <h:message for="confirm" style="color: red;" />

        <h:panelGroup />
        <h:commandButton value="Register" action="#{myBean.register}" />
        <h:message for="register" style="color: green;" />
    </h:panelGrid>
</h:form>

And now the validator code:

package mypackage;

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 PasswordValidator implements Validator {

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

    public void validate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException
    {
        // Cast the value of the entered password to String.
        String password = (String) value;

        // Obtain the component and submitted value of the confirm password component.
        UIInput confirmComponent = (UIInput) component.getAttributes().get("confirm");
        String confirm = confirmComponent.getSubmittedValue();

        // Check if they both are filled in.
        if (password == null || password.isEmpty() || confirm == null || confirm.isEmpty()) {
            return; // Let required="true" do its job.
        }

        // Compare the password with the confirm password.
        if (!password.equals(confirm)) {
            confirmComponent.setValid(false); // So that it's marked invalid.
            throw new ValidatorException(new FacesMessage("Passwords are not equal."));
        }

        // You can even validate the minimum password length here and throw accordingly.
        // Or, if you're smart, calculate the password strength and throw accordingly ;)
    }

}

The appropriate test backing bean look like:

package mypackage;

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

public class MyBean {

    // Init ---------------------------------------------------------------------------------------

    private String username;
    private String password;

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

    public void register() {

        // Just for debug. Don't do this in real! Hash the password, save to DB and forget it ;)
        System.out.println("Username: " + username);
        System.out.println("Password: " + password);

        // Show succes message.
        FacesContext.getCurrentInstance().addMessage("register", new FacesMessage("Succes!"));
    }

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

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

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

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

Finally the relevant part of the faces-config.xml:


    <validator>
        <validator-id>passwordValidator</validator-id>
        <validator-class>mypackage.PasswordValidator</validator-class>
    </validator>
    
    <managed-bean>
        <managed-bean-name>myBean</managed-bean-name>
        <managed-bean-class>mypackage.MyBean</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
    </managed-bean>

That's all, folks!

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) December 2007, BalusC

47 comments:

dbccos said...

How would you modify this example to support the case when you don't know the ID of the second field? For example, in my situation I have an editable datatable (just as described in one of your other posts) in which I wish to validate one of the input fields using another of the input fields in the same row. I've tried using

<h:inputText value="#{dataItem.attrib1}" required="#{detail.required}">

<f:attribute name="aParam" value="#{dataItem.someAttrib}"/>
<f:validator validatorId="myValidatorId"/>
</h:inputText>

where dataItem is defined earlier as:
<h:dataTable value="#{myBean.dataList}" var="dataItem">

Lavnish said...

in above code if the user changes the password and no the confirm password ... then the validation wont be called .. right ??

so ... one needs to add validation rules to both the inputText...

now if user modifies both password and confirmPassword then we will get two error messages not one ...

however what we need here is that user may modify either one or both of the password and there should be just one message displayed how do we support that ??

BalusC said...

If your actual problem is that you have 2 identical messages if the user doesn't fill in the both password fields, then just change the 'required' attribute of the confirm password field as follows:

required="#{!empty param['register:password']}"

Øyvind said...

Why not just use Tomahawks standard validator for this (validateEqual)?
http://www.developersbook.com/jsf/myfaces/tomahawk-tag-reference/tomahawk-validateEqual.php
This also loosens up some of the tight coupling in this example

Bauke said...

Because of 1) I didn't knew such a tag exist and 2) validateEqual is only useful if you want to validate 2 fields on equality, not if you want to validate multiple fields for some other reasons.

Øyvind said...

Ok, good reasons I guess. I didn't mean to trample on your solution. Actually, I learned quite a bit from it..

Let me just add that if you use the tomahawk validateEqual validator you can also define localized error messages to your form by entering them in your message bundle file with something like
org.apache.myfaces.Equal.INVALID=*
org.apache.myfaces.Equal.INVALID_detail=Passwords don't match

James said...

Thanks a lot!!!! This works perfectly.

Does anyone know of a way to validate that a series of fields are not null? I have 6 checkboxes that I need to make sure the user selects at least one of them

Bauke said...

Use h:selectManyCheckbox with required="true".

James said...

I have a situation where I need to perform a simple validation check between a select field and an inputtext. The problem is, the second field is the input box and that is the field that I need to be sure has a value if the select field has a value. I can't throw the validation on the select field since the input field will always have null the first pass through and I can't place the validation on the input field since it will not be thrown if the field is left blank. Any ideas on how to do this?

Janusz said...

Hiho this worked very well for me.
I searched a long time how to do this with only the jsf reference implementation.

Nice piece of code thanks a lot

Dac Lan Khanh said...

Thanks a lot BalusC !

Your code is very helpful !

chamika deshan said...

Thank You!
Great idea...

Amit Rangra said...

How do we write a JSF custom validator tag ???

deinte said...

Hi.

I have seen this tutorial, but I can't see how to attach to my page so it validate my fields before I submit the form.

let say we have this scenario.

2 or more hselectonemeny and 1 or more listbox..

I create a validator to control that the value in hselectonemenu1 < hselectonemenu2 and there is at least one element selected in listbox or something like that.

In my page how I call the validator???

Any comment??

Thanks

deinte said...

Hi.

After try several times I have my validator working ok, but I would like to know how to face the next thing:

Let's say I have to h:selectManyListBox related each other. One of them MUST be selected and the other UNSELECTED, so how can I access the values of the other component when I'm validating the other.

Example:
h:selectManyListbox01 validator="#{bean.validateSelectManylistbox01}"
h:selectManyListbox02 validator="#{bean.validateSelectManylistbox02}"

If I'm validating the selectManyListbox01 and nothing is selected I need to know if something in selectManyListbox02 is selected and the opposite.

I hope I have explain correctly

Any comment?
Thanks

BalusC said...

Get the submitted value by UIInput#getSubmittedValue(). This will only work if its value isn't been processed yet during the validations phase.

deinte said...

Hi.

Sorry, but I didn't understand. Could you please be more specific?

Thanks

Nodles said...

thx, have already learnt a lot with your helpful blog. Now I've a question concerning validation in a datatable build with ice:columns-tags enclosing just one ice:inputText-Tag. Have columns- and raw-Data-Models and build a table with several rows/columns. Now I have to validate the entries of two different cells together, means f.i. cell 1: start-time, cell 2: end-time--> validate that value of cell 1 is before value of cell 2. The problem for me is to specifie the cells, cause I only have one ice:inputText -field in my jsf-file: tried to use findComponent() with modified clientId() but failed, have read the invokeOnComponent-approach on the hookom's-blog (http://weblogs.java.net/blog/jhook/archive/2006/02/new_feature_for.html) but didn't really understood it. Could you give me some advise, please, here in the blog or by mail. Thx a lot for every hint. Here's some code to illustrate:
/ice:dataTable id="table" ...>


/ice:columns id="columns" value="#{myBean.columnsModel}" var="table">
/ice:inputText id="in" value="#{myBean.cellValue}" >
/f:validator validatorId="weekValidator" />
//ice:inputText>


//ice:columns>
//ice:dataTable>

(with /=<, (cause:"Tag is not allowed..." )

Nodles said...

probably you'll need my eMail: here it is Seldon-X(ad)web.de

Ing Walter P. Rodriguez S. said...

Hi Balusc,


I have an editable datatable (just as described in one of your other posts) in which I wish to validate one of the input fields using another of the input fields in the same row. I've tried using

>h:dataTable id="tableDS" binding="#{ctllisDSolicitud.dataTable}" value="#{ctllisDSolicitud.dataList}"
var="dataItem"

>h:inputText id="precio" value="#{dataItem.precio}" rendered="#{ctllisDSolicitud.editModeRow}" required="#{!empty param['ds:crudDS:tableDS:save']}" styleClass="input" size="10" >
>f:validateLength minimum="0" maximum="10" />
>val:commonsValidator type="required" arg="Monto" server="true" client="false"/>
>f:validator validatorId="validarpresupDisponible" />
>f:attribute name="idproy" value="ds:crudDS:idproyecto" />
>f:attribute name="idper" value="ds:crudDS:idperiodo" />
>f:attribute name="idpar" value="ds:crudDS:tableDS:0:partida" />
>/h:inputText>

When I run it i receive an exception like this:

Loading validation rules file from /org/apache/shale/validator/validator-rules.xml
Loading validation rules file from /WEB-INF/custom-rules.xml
validarpresupDisponible.validate: ds:crudDS:idproyecto 1
validarpresupDisponible.validate: ds:crudDS:idperiodo 4
executePhase(PROCESS_VALIDATIONS 3,com.sun.faces.context.FacesContextImpl@1f88c39) threw exception

Waiting for your reply
Thanks alot for sharing the knowledge.

Jim said...

Thanks! This example works great! I did have to change one thing with JSF 1.2 - I have to use UIInput.getSubmittedValue(), not getValue() in order to get the values for comparison for some reason :( But this was a really rich post -- I learned a lot looking through the code here, so definitely thanks!

BalusC said...

You need the getSubmittedValue() whenever you want to get the value of a field which isn't converted/validated yet. The fields are converted/validated in the sequence as they appear in the form.

waparosi said...

Thanks a lot BalusC! This example works great! I have an editable datatable, in which I wish to validate one of the input fields using another of the input fields in the same row. Any ideas on how to do this?

Waiting for your reply
Thanks alot for sharing the knowledge.

mcahornsirup said...

I totally agree. Currently, it seems like this is the way to go. But in general, using the id is somehow ugly : )

Frank said...

Hi BalusC,

First let me thank for the Code. However, I have a question.

What happens when user corrects the password he/she entered in password field to match the password in confirm field? I believe the validator will not be called and the error displayed on confirm field stays there, until the user clicks into confirm field and mouse out.

How to handle such a scenario?

BalusC said...

Not true. The validation will only be fired when you submit the form to the server. The JSF validation is entirely server side. If you want client side validation, look in the Javascript/Ajax corner.

lafual said...

You didn't seem to answer the comparison of 2 values in a datatable.

As the fields are handled "in order" - I store the fields and on the validator of the last field in the row I do the actual comparison


so here goes ...

JSP

h:datatable value="{#mybean.anArray}" var="x"
h:inputText validator="{#mybean.validateA}" value="{x.valA
h:inputText validator="{#mybean.validateB}" value="{x.valB}"
h:datatable

Java

Date first, second;

public void validateA(FacesContext arg1, IUComponent arg2, Object arg3) {
first = (Date) arg3;
}

public void validateB(FacesContext arg1, IUComponent arg2, Object arg3) {
second = (Date) arg3;
if (! second.after(first)) {
// make faces message
throw new ValidatorException(msg)
}
}


Sorry if this not syntactially accurate, my development PC and internet PC are seperate and I am typing from memory.

Pawel Duda said...

Hi BalusC! I came across a following problem. Your validation works perfect unless you attach a rich:message to the component that should be passed with f:attribute. The error only happens when I wrap rich:message with my own Facelet component (to add some custom look and feel) and put it in a separate JAR like here:

http://thomaswabner.wordpress.com/2008/06/25/reuse-facelets-xhtml-files-and-taglibs-from-jar-archives/

What happens is that the validator fires, but then findComponent() returns a null object. It seems it is not there in the JSF component tree.

When I moved the wrapped message component back from a separate JAR directly into my web project, things work just fine.

Do you have any clue why that could happen?

GPR said...

Tks for the example. But I need validate whatever one of some fields. Its required that the user type one of them. How do i can do this?

BalusC said...

Let the required attribute evaluate 'true' if none of the other two fields is submitted.

Sergio Martin said...

Your code is very nice, but it´s not working for me :-(
When I do:
String passwordId = (String) component.getAttributes().get("passwordId");
I literally get passwordId="registerform:password" (in the Validator).
How can I get the real value, and avoid getting the string with registerform:password?

Thanks a lot for your help!!
Sergio

BalusC said...

You need to change the f:attribute accordingly. Check in the generated HTML output what the final client ID of the password element is and specify that in the f:attribute.

Sergio Martin said...

Yes, I´ve done it.
My code is like this







and my form name is "registerform"
Thanks again

Sergio Martin said...

Oh, it seems I cannot paste my code :-(

Anyway, my code is exactly like yours, but in the server side it doesn´t retrieve the value of the attribute but it literally retrieves "register:password".

Thanks once more!

Aram said...

Hi BalusC,

Thank you for the Code. But, I have a question.
I want during checking the error will be shown not only on confirm field, but also on password field and if I change password value in such a way that it will equal to confirm value, both errors will disappear.
Can you give me an advice how can I do that or may be some link?

Chetan said...

Great dude your code is working excellent with icefaces...

Zorgan said...

I am writing an application with seam 2.2 facelets and richfaces.
I tried your solution but it didn't work for me because of a tricky reason :
in my xhtml file i DO declare an id for my inputText :
h:inputText id="clientName" value="#{clientManager.clientName}"

but in the generated HTML i do not find the id attribute !!!
It looks like : input type="text" name="formName:clientTable:j_id50fsp"

do you know a workaround for this ??

shivanthi said...

Hi Balusc,
how to set the condition the text field want to allow only integer. also the text field should allow greater than integer 20.
plz send the code for that.thanks

Sonal said...

Thank you ! Its working for me & solve lots of big issue............

Tommy Maurice Kacaribu said...

simple and easy to understand...thx so much BalusC.

~nikhilopedia~ said...

This helped me when I was just about to give up on JSF validation and switch to Javascript validation instead! Cheers!

Bill Schneider said...

Thanks! I like this approach because you don't have to reference component ID's in the managed bean.

Two important clarifications of note:

1. #{component.value} references the UIInput's getValue() method, not a managed bean property. (In the Process Validations phase, setters haven't been called yet, and may not get called.)

2. It's important for the validation to be on the LAST component in the group, otherwise the getValue method will not return the correct value. UIInput.value will not be set if any validations have failed already.

Bill Schneider said...

Another observation: if one of the injected inputs is optional and gets converted to a non-string type (date, number etc.) then a null won't get injected into the f:attribute because it hasn't propagated to the component.value yet. You can use component.localValue instead as a workaround.

See:
http://java.net/jira/browse/JAVASERVERFACES-2264

Ishaan said...

How to validate strictly only checkbox to be selected among many checkboxes in jsf

Sameet Kumar Bhole said...

can we provide multiple validator message for a single component. OR can we print p:message from bean class using FacesMessage??

Yi SHU said...
This comment has been removed by the author.
Yi SHU said...
This comment has been removed by the author.