How can I display ValidatorException and required="true" of same input field in different messages elements
jsf, jsf-2, validation
Solution
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.
Problem
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; ```