JSF 2.0: Validate equality of 2 InputSecret Fields (confirm password) without writing Code?

StackOverflow https://stackoverflow.com/questions/2909021

  •  04-10-2019
  •  | 
  •  

Pergunta

I'm developing a pure JavaEE6 application with JSF 2.0 and Glassfish. My JSF implementation is Primefaces (beside Mojarra provided by Glassfish).

I want to verify if the values of 2 password fields in a JSF form are equal. With Seam, there is the neat component <s:validateEquality for="pw1"/>. I want do to the same without Seam, just using JSF (or maybe a component of a JSF library). Until now i only saw examples which validate the form with a custom validator. But i would like to compare the fields without writing Java code or Javascript code. Is that possible?

This what it looks like with Seam:

...
<h:inputSecret id="passwort" value="#{personHome.instance.password}" 
    redisplay="true" required="true">
  <f:validateLength minimum="8"/>
  <a:support event="onblur" reRender="passwortField" bypassUpdates="true" ajaxSingle="true" />
</h:inputSecret>
...    
<h:inputSecret id="passwort2" required="true" redisplay="true">
  <!-- find the JSF2.0-equivalent to this tag: -->
  <s:validateEquality for="passwort"/>
  <a:support event="onblur" reRender="passwort2Field" bypassUpdates="true" ajaxSingle="true" />
</h:inputSecret>
...
Foi útil?

Solução 5

This is the way i finally did it, which i like cause it's short and easy. The only problem is that it's not really re-usable, but as i only need this in one case, i rather save some LOCs and do it this way. Snippet from my view:

<h:inputSecret id="password" value="#{personHome.person.password}">
  <f:ajax event="blur" render="passwordError" />
</h:inputSecret> 
<h:message for="password" errorClass="invalid" id="passwordError" />

<h:inputSecret id="password2" validator="#{personHome.validateSamePassword}">
  <f:ajax event="blur" render="password2Error" />
</h:inputSecret> 
<h:message for="password2" errorClass="invalid" id="password2Error" />

My Backing Bean (just the important part):

@Named @ConversationScoped
public class PersonHome {
  private Person person;

  public Person getPerson() {
    if (person == null) return new Person();
    else return person;
  }

  public void validateSamePassword(context:FacesContext, toValidate:UIComponent, value:Object) {
    String confirmPassword = (String)value;
    if (!confirmPassword.equals(person.getPassword()) {
      FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Passwords do not match!", "Passwords do not match!")
      throw new Validatorexception(message);
    }
  }

Outras dicas

The Seam3 Faces module will support "Cross-field form validation" in it's imminent Alpha3 release. This is your best bet for a minimal code solution, see this blog for a howto.

Alternatively I've done this programmatically by using the f:attribute tag to pass the clientId of another form field to a custom validator, then using the UIComponent passed into the custom validator to access the other filed by id.

Here's the facelet file:

<h:outputLabel value="Enter your email address" rendered="#{!cc.attrs.registration.subRegistration}" />
<h:inputText label="Email" id="textEmail1" value="#{cc.attrs.registration.email}" rendered="#{!cc.attrs.registration.subRegistration}" required="true" maxlength="128" size="35"></h:inputText>
<h:message for="textEmail1" rendered="#{!cc.attrs.registration.subRegistration}"></h:message>

<h:outputLabel value="Re-enter your email address confirmation:" rendered="#{!cc.attrs.registration.subRegistration and cc.attrs.duplicateEmailRequired}" />
<h:inputText label="Email repeat" id="textEmail2" rendered="#{!cc.attrs.registration.subRegistration and cc.attrs.duplicateEmailRequired}" maxlength="64" size="35">
    <f:validator validatorId="duplicateFieldValidator" />
    <f:attribute name="field1Id" value="#{component.parent.parent.clientId}:textEmail1" />
</h:inputText>
<h:message for="textEmail2" rendered="#{!cc.attrs.registration.subRegistration and cc.attrs.duplicateEmailRequired}"></h:message>

Here's the validator class:

package ca.triumf.mis.trevents.jsf.validator;

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

@FacesValidator(value="duplicateFieldValidator")
public class DuplicateFieldValidator implements Validator {

@Override
public void validate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException {
    // Obtain the client ID of the first field from f:attribute.
    System.out.println(component.getFamily());
    String field1Id = (String) component.getAttributes().get("field1Id");

    // Find the actual JSF component for the client ID.
    UIInput textInput = (UIInput) context.getViewRoot().findComponent(field1Id);
    if (textInput == null)
        throw new IllegalArgumentException(String.format("Unable to find component with id %s",field1Id));
    // Get its value, the entered text of the first field.
    String field1 = (String) textInput.getValue();

    // Cast the value of the entered text of the second field back to String.
    String confirm = (String) value;

    // Check if the first text is actually entered and compare it with second text.
    if (field1 != null && field1.length() != 0 && !field1.equals(confirm)) {
        throw new ValidatorException(new FacesMessage("E-mail addresses are not equal."));
    }
}
}

You may use Primefaces tag in this very simple way:

<p:password id="password" value="#{bean.password}" match="repeated_password" />

<p:password id="repeated_password" value="#{bean.password}" />

I had to use a mixture of both answers to succeed.

I used ifischers short solution but my bean password field was null.

So I used the lines from Brian Leathem to get the UIInput from the context:

public void passwordValidator(FacesContext context, UIComponent toValidate, Object value) {

    UIInput passwordField = (UIInput) context.getViewRoot().findComponent("registerForm:password");
    if (passwordField == null)
        throw new IllegalArgumentException(String.format("Unable to find component."));
    String password = (String) passwordField.getValue();
    String confirmPassword = (String) value;
    if (!confirmPassword.equals(password)) {
        FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Passwords do not match!", "Passwords do not match!");
        throw new ValidatorException(message);
    }
}

If you're using JSF utility library OmniFaces, then you could use <o:validateEqual>. It also allows setting a custom message. The showcase has a live example demonstrating the common usecase of validating the password confirmation. You don't even need ajax to update the model before invoking the validator (as your own approach does).

Here's the minimum necessary code:

<h:inputSecret id="password" value="#{personHome.person.password}" />
<h:message for="password" />

<h:inputSecret id="password2" />
<h:message for="password2" />

<o:validateEqual components="password password2" 
    message="Passwords do not match!" showMessageFor="password2" />

No Java code needed.

You can do it easily with Apache MyFaces ExtVal.

Edited: Before reading, please, consider that this solution works perfectly, and the answer is from July 2012, so please don't down vote me just because you did not like it. The world changed and now we have better components and solutions.

Without solution, I was forced to do the validation in a ugly way (not recommended). At least it works till I found better solution.

In the method that returns the action, I check both values, in case of different values, I add error messages on context and return null to the navigation handler.

package com.jsf.beans.user;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.component.html.HtmlInputSecret;

import org.apache.commons.lang.StringUtils;

import com.pichler.jsf.beans.base.JsfViewBean;

 @ManagedBean(name = "changePassword")
 @RequestScoped
 public class ChangePassword extends JsfViewBean {
private HtmlInputSecret inputSecret1, inputSecret2;

/**
 * @return the inputSecret1
 */
public HtmlInputSecret getInputSecret1() {
    return inputSecret1;
}

/**
 * @param inputSecret1
 *            the inputSecret1 to set
 */
public void setInputSecret1(HtmlInputSecret inputSecret1) {
    this.inputSecret1 = inputSecret1;
}

/**
 * @return the inputSecret2
 */
public HtmlInputSecret getInputSecret2() {
    return inputSecret2;
}

/**
 * @param inputSecret2
 *            the inputSecret2 to set
 */
public void setInputSecret2(HtmlInputSecret inputSecret2) {
    this.inputSecret2 = inputSecret2;
}

private String password1, password2;

public String alterar() {
    if (!StringUtils.equals(password1, password2)) {
        addErrorMessage(inputSecret1.getClientId(),
                "As senhas não coincidem");
        addErrorMessage(inputSecret2.getClientId(),
                "As senhas não coincidem");
        return null;
    }
    return null;
}

/**
 * @return the password1
 */
public String getPassword1() {
    return password1;
}

/**
 * @param password1
 *            the password1 to set
 */
public void setPassword1(String password1) {
    this.password1 = password1;
}

/**
 * @return the password2
 */
public String getPassword2() {
    return password2;
}

/**
 * @param password2
 *            the password2 to set
 */
public void setPassword2(String password2) {
    this.password2 = password2;
}

}

*JsfViewBean is just a class that has some common methods, as "addMessages".

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top