Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I display ValidatorException and required="true" of same input field in different messages elements

I took the following BalusC kickoff example and modified it a bit by adding a submit button and additional h:messages and removing the f:ajax from the h:inputSecret's (removed the f:ajax cause for some reason when I leave the first h:inputSecret it immediately displays "value is required" error for the second h:inputSecret - but the user haven't got the chance to type it in... ??? <- another future question ?:) )

OK, to make long story short:

I'm trying to figure out how can display the validation errors regarding the both password fields(that the passwords are not equal) in the global h:messages and not in the individual h:message of the password fields I do want that the required="true" will be displayed in the <h:message of each field...

But right now the validation message (thrown by my exception) and the required="true" are being displayed in the same place

Here is the code:

<h:outputLabel for="password" value="Password:" />
<h:inputSecret id="password" value="#{bean.password}" required="true">
    <f:validator validatorId="confirmPasswordValidator" />
    <f:attribute name="confirm" value="#{confirmPassword.submittedValue}" />
</h:inputSecret>
<h:message id="m_password" for="password" />

<h:outputLabel for="confirm" value="Password (again):" />
<h:inputSecret id="confirm" binding="#{confirmPassword}" required="true">
</h:inputSecret>
<h:message id="m_confirm" for="confirm" />

And additional h:commandButton with h:messages below that code :

<h:commandButton value="doSomething" action="#{myBean.myAction}">
    <f:ajax execute="password confirm" render="m_password m_confirm"></f:ajax>
</h:commandButton>
<h:messages globalOnly="true" styleClass="validation_value_required"/>
@FacesValidator("confirmPasswordValidator")
public class ConfirmPasswordValidator implements Validator {

    @Override
    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        String password = (String) value;
        String confirm = (String) component.getAttributes().get("confirm");

        if (password == null || confirm == null) {
            return; // Just ignore and let required="true" do its job.
        }

        if (!password.equals(confirm)) {
            throw new ValidatorException(new FacesMessage("Passwords are not equal."));
        }
    }

}

Also

Thanks ahead,

Solution (Thanks to BalusC)

changed

<f:attribute name="confirm" value="#{confirmPassword.submittedValue}" />

to

<f:attribute name="confirm" value="#{confirmPassword}" />

and

String confirm = (String) component.getAttributes().get("confirm");

into

UIInput confirmPasswordComponent = (UIInput) component.getAttributes().get("confirm");
String confirm = (String) confirmPasswordComponent.getSubmittedValue();

and

throw new ValidatorException(new FacesMessage("Passwords are not equal."));

into

context.addMessage(null, new FacesMessage("Passwords are not equal."));
context.validationFailed();
((UIInput) component).setValid(false);
confirmPasswordComponent.setValid(false);
return;
like image 235
Daniel Avatar asked Apr 04 '12 08:04

Daniel


1 Answers

If a Validator on a particular component throws a ValidatorException, then its FacesMessage will automatically be associated with the component on which the Validator is invoked.

You need to manually add the FacesMessage on a null client ID so that it end up in <h:messages globalOnly="true">. You also need to manually set validationFailed() on FacesContext so that JSF won't update the model values nor invoke the action. If necessary (though recommended), you also need to manually mark the components as invalid so that any appropriate listeners/tree-visitors (e.g. for highlighting) will take this into account.

if (!password.equals(confirm)) {
    context.addMessage(null, new FacesMessage("Passwords are not equal."));
    context.validationFailed();
    ((UIInput) component).setValid(false);
    confirmPasswordComponent.setValid(false); // You'd need to pass it as component instead of as its submitted value in f:attribute.
}

By the way, the OmniFaces project has an <o:validateEqual> component which should make this less tedious. See also showcase example.

like image 162
BalusC Avatar answered Nov 07 '22 02:11

BalusC