-
Available since OmniFaces 1.0
The <o:validator>
is a taghandler that extends the standard <f:validator>
tag family with support for deferred value expressions in all attributes. In other words, the validator attributes are not evaluated anymore on a per view build time basis, but just on every access like as with UI components and bean properties. This has among others the advantage that they can be evaluated on a per-iteration basis inside an iterating component, and that they can be set on a custom validator without needing to explicitly register it in a tagfile.
Usage
When you specify for example the standard <f:validateLongRange>
by validatorId="jakarta.faces.LongRange"
, then you'll be able to use all its attributes such as minimum
and maximum
as per its documentation, but then with the possibility to supply deferred value expressions.
<o:validator validatorId="jakarta.faces.LongRange" minimum="#{item.minimum}" maximum="#{item.maximum}" />
The validator ID of all standard Faces validators can be found in their javadocs. First go to the javadoc of the class of interest, then go to VALIDATOR_ID
in its field summary and finally click the Constant Field Values link to see the value.
It is also possible to specify the validator message on a per-validator basis using the message
attribute. Any "{0}" placeholder in the message will be substituted with the label of the referenced input component. Note that this attribute is ignored when the parent component has already validatorMessage
specified.
<o:validator validatorId="jakarta.faces.LongRange" minimum="#{item.minimum}" maximum="#{item.maximum}"
message="Please enter between #{item.minimum} and #{item.maximum} characters" />
JSF 2.3 compatibility
The <o:validator>
is currently not compatible with validators which are managed via the managed=true
attribute set on the FacesValidator
annotation, at least not when using Mojarra. Internally, the converters are wrapped in another instance which doesn't have the needed setter methods specified. In order to get them to work with <o:validator>
, the managed=true
attribute needs to be removed, so that OmniFaces ValidatorManager
will automatically manage them.
Demo
In the below first demo, it was the intent to use <f:validateLongRange>
as follows:
<f:validateLongRange minimum="#{question.minimum}" maximum="#{question.maximum}" />
But this fails hard as the minimum and maximum values are set during view build time instead of during processing the validation.
The #{question} is not available during view build time and thus both minimum and maximum will default to 0
(for a background explanation, check this Stack Overflow answer).
It works as intuitively expected with the <o:validator>
as it allows a render time evaluation of attributes.
For another related use case, checkout the
<o:converter>
showcase page.
Further, the <o:validator>
also allows setting the validation message on a per-validator basis
by the message
attribute. This is not possible with <f:validator>
and the
validatorMessage
attribute of the parent input component applies to all validators.
Note that the message
attribute of <o:validator>
has no effect when the
validatorMessage
attribute of the parent input component has already been set.
With f:validateLongRange in each row - fails!
With o:validator in each row - works!
Set validator message on a per-validator basis
<h3>With f:validateLongRange in each row - fails!</h3>
<h:form>
<h:dataTable value="#{validatorBean.questions}" var="question">
<h:column>
Enter value between #{question.minimum} and #{question.maximum}
</h:column>
<h:column>
<h:inputText id="input" value="#{question.answer}" required="true" requiredMessage="Fill out the value!">
<f:validateLongRange minimum="#{question.minimum}" maximum="#{question.maximum}" />
</h:inputText>
</h:column>
<h:column>
<h:message for="input" />
</h:column>
</h:dataTable>
<h:commandButton value="Submit">
<f:ajax execute="@form" render="@form" />
</h:commandButton>
<h:outputText value="OK!" rendered="#{facesContext.postback and not facesContext.validationFailed}" />
</h:form>
<hr />
<h3>With o:validator in each row - works!</h3>
<h:form>
<h:dataTable value="#{validatorBean.questions}" var="question">
<h:column>
Enter value between #{question.minimum} and #{question.maximum}
</h:column>
<h:column>
<h:inputText id="input" value="#{question.answer}" required="true" requiredMessage="Fill out the value!">
<o:validator validatorId="jakarta.faces.LongRange" minimum="#{question.minimum}" maximum="#{question.maximum}" message="Invalid value!" />
</h:inputText>
</h:column>
<h:column>
<h:message for="input" />
</h:column>
</h:dataTable>
<h:commandButton value="Submit">
<f:ajax execute="@form" render="@form" />
</h:commandButton>
<h:outputText value="OK!" rendered="#{facesContext.postback and not facesContext.validationFailed}" />
</h:form>
<hr />
<h3>Set validator message on a per-validator basis</h3>
<h:form>
<h:messages />
<h:dataTable binding="#{table}" value="#{validatorBean.entities}" var="entity">
<h:column>
<c:set var="label" value="Input #{table.rowIndex + 1}" />
<o:outputLabel for="input" value="#{label}" />
</h:column>
<h:column>
<h:inputText id="input" value="#{entity.value}" required="true" requiredMessage="#{label} is required">
<o:validator validatorId="jakarta.faces.Length" minimum="5" message="{0} must be at least 5 chars" />
<o:validator validatorId="jakarta.faces.RegularExpression" pattern="\\d+" message="{0} must contain digits only" /><!-- Fails in Apache EL (Tomcat/TomEE/etc), use \d+ instead. -->
</h:inputText>
</h:column>
</h:dataTable>
<h:commandButton value="Submit">
<f:ajax execute="@form" render="@form" />
</h:commandButton>
<h:outputText value="OK!" rendered="#{facesContext.postback and not facesContext.validationFailed}" />
</h:form>
package org.omnifaces.showcase.taghandlers;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import jakarta.annotation.PostConstruct;
import jakarta.inject.Named;
import org.omnifaces.cdi.ViewScoped;
import org.omnifaces.showcase.model.ExampleEntity;
@Named
@ViewScoped
public class ValidatorBean implements Serializable {
private static final long serialVersionUID = 1L;
private List<Question> questions;
private List<ExampleEntity> entities;
@PostConstruct
public void init() {
questions = Arrays.asList(new Question(1, 9), new Question(5, 10), new Question(3, 7));
entities = Arrays.asList(new ExampleEntity(), new ExampleEntity(), new ExampleEntity());
}
public List<Question> getQuestions() {
return questions;
}
public List<ExampleEntity> getEntities() {
return entities;
}
public static class Question {
private Integer answer;
private Integer minimum;
private Integer maximum;
public Question(Integer minimum, Integer maximum) {
this.minimum = minimum;
this.maximum = maximum;
}
public Integer getAnswer() {
return answer;
}
public void setAnswer(Integer answer) {
this.answer = answer;
}
public Integer getMinimum() {
return minimum;
}
public void setMinimum(Integer minimum) {
this.minimum = minimum;
}
public Integer getMaximum() {
return maximum;
}
public void setMaximum(Integer maximum) {
this.maximum = maximum;
}
}
}
package org.omnifaces.showcase.model;
import java.io.Serializable;
public class ExampleEntity implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String value;
public ExampleEntity() {
//
}
public ExampleEntity(Long id, String value) {
this.id = id;
this.value = value;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public boolean equals(Object other) {
return (other instanceof ExampleEntity) && (id != null)
? id.equals(((ExampleEntity) other).id)
: (other == this);
}
@Override
public int hashCode() {
return (id != null)
? (this.getClass().hashCode() + id.hashCode())
: super.hashCode();
}
@Override
public String toString() {
return String.format("ExampleEntity[%d, %s]", id, value);
}
}