JSF doesn't support cross-field validation, is there a workaround?
Solution 1:
The easiest custom approach I've seen and used as far is to create a <h:inputHidden>
field with a <f:validator>
wherein you reference all involved components as <f:attribute>
. If you declare it before the to-be-validated components, then you can obtain the submitted values inside the validator by UIInput#getSubmittedValue()
.
E.g.
<h:form>
<h:inputHidden id="foo" value="true">
<f:validator validatorId="fooValidator" />
<f:attribute name="input1" value="#{input1}" />
<f:attribute name="input2" value="#{input2}" />
<f:attribute name="input3" value="#{input3}" />
</h:inputHidden>
<h:inputText binding="#{input1}" value="#{bean.input1}" />
<h:inputText binding="#{input2}" value="#{bean.input2}" />
<h:inputText binding="#{input3}" value="#{bean.input3}" />
<h:commandButton value="submit" action="#{bean.submit}" />
<h:message for="foo" />
</h:form>
(please note the value="true"
on the hidden input; the actual value actually doesn't matter, but keep in mind that the validator won't necessarily be fired when it's null or empty, depending on the JSF version and configuration)
with
@FacesValidator(value="fooValidator")
public class FooValidator implements Validator {
@Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
UIInput input1 = (UIInput) component.getAttributes().get("input1");
UIInput input2 = (UIInput) component.getAttributes().get("input2");
UIInput input3 = (UIInput) component.getAttributes().get("input3");
// ...
Object value1 = input1.getSubmittedValue();
Object value2 = input2.getSubmittedValue();
Object value3 = input3.getSubmittedValue();
// ...
}
}
If you declare the <h:inputHidden>
after the to-be-validated components, then the values of the involved components are already converted and validated and you should obtain them by UIInput#getValue()
or maybe UIInput#getLocalValue()
(in case the UIInput
isn't isValid()
) instead.
See also:
- Validator for multiple fields (JSF 1.2 targeted)
Alternatively, you can use 3rd party tags/components for that. RichFaces for example has a <rich:graphValidator>
tag for this, Seam3 has a <s:validateForm>
for this, and OmniFaces has several standard <o:validateXxx>
components for this which are all showcased here. OmniFaces uses a component based approach whereby the job is done in UIComponent#processValidators()
. It also allows customizing it in such way so that the above can be achieved as below:
<h:form>
<o:validateMultiple id="foo" components="input1 input2 input3" validator="#{fooValidator}" />
<h:inputText id="input1" value="#{bean.input1}" />
<h:inputText id="input2" value="#{bean.input2}" />
<h:inputText id="input3" value="#{bean.input3}" />
<h:commandButton value="submit" action="#{bean.submit}" />
<h:message for="foo" />
</h:form>
with
@ManagedBean
@RequestScoped
public class FooValidator implements MultiFieldValidator {
@Override
public boolean validateValues(FacesContext context, List<UIInput> components, List<Object> values) {
// ...
}
}
The only difference is that it returns a boolean
and that the message should be specified as message
attribute in <o:validateMultiple>
.
Solution 2:
Apache ExtVal was not mentioned here.
There are some cross validations in it (among other validations which might be useful):
https://cwiki.apache.org/confluence/display/EXTVAL/Property+Validation+Usage#PropertyValidationUsage-CrossValidation