A use case
I have a form which has requirements as follow:- There are some mandatory fields.
- Validation is triggered when changing value on each field.
- A button "Next" is enable only when all fields are entered. It turns to disabled if any field is empty.
My first approach
I defined a variable "isDisableNext" at a backend bean "Controller" for dynamically disabling/enabling the "Next" button by performing event "onValueChange", but, it had a problem:<h:form id="personForm">Due to JSF lifecyle, the application code of Ajax "onValueChange" (at phase Invoke Application) is never invoked when validation failed. How could we update the value "isDisableNext"?
<p:outputLabel value="First Name" for="firstName"/>
<p:inputText id="firstName" value="#{person.firstName}"
required="true">
<p:ajax event="change" listener="#{controller.onValueChange}" update="nextButton"/>
</p:inputText>
<p:outputLabel value="Last Name" for="lastName"/>
<p:inputText id="lastName" value="#{person.lastName}"
required="true">
<p:ajax event="change" listener="#{controller.onValueChange}" update="nextButton"/>
</p:inputText>
<p:commandButton id="nextButton" actionListener="#{controller.onNext}" update="personForm" disabled="#{controller.isDisabledNext}"/>
</h:form>
src: http://docs.oracle.com/javaee/5/tutorial/doc/bnaqq.html
My new approach: I don't try to update the backend bean at phase Invoke Application anymore but use custom validator
What is it? And, why is it possible?- At phase Process Validation, it always calls my application code even when validation failed
- I handle enabling/disabling the button with JSF component tree instead of a backend bean.
The previous implementation turns to the following:
- Don't use required="true" because it won't invoke customer validators when a field's submitted value is empty. Then I need to add the "*" manually with "span class="ui-outputlabel-rfi".
- Use custom validator with "f:validator"
- Pass component button "Next" with "f:attribute" and "binding"
<h:form id="personForm">
<p:outputLabel value="First Name" for="firstName">
<span class="ui-outputlabel-rfi">*</span>
</p:outputLabel>
<p:inputText id="firstName" value="#{person.firstName}">
<p:ajax event="change" listener="#{controller.onValueChange}" update="nextButton"/>
<f:validator validatorId="requiredFieldValidator"/>
<f:attribute name="nextButton" value="#{nextButton}"/>
</p:inputText>
<p:outputLabel value="Last Name" for="lastName">
<span class="ui-outputlabel-rfi">*</span>
</p:outputLabel>
<p:inputText id="lastName" value="#{person.lastName}">
<p:ajax event="change" listener="#{controller.onValueChange}" update="nextButton"/>
<f:validator validatorId="requiredFieldValidator"/>The custom validator looks like:
<f:attribute name="nextButton" value="#{nextButton}"/>
</p:inputText>
<p:commandButton id="nextButton" actionListener="#{controller.onNext}" update="personForm" binding="#{nextButton}"/>
</h:form>
@FacesValidator(value = "requiredFieldValidator")How is your approach? Leave your comment down below!
public class RequiredFieldValidator implements Validator{
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
CommandButton nextButtonUi = (CommandButton) component.getAttributes().get("nextButton");
Map<String, String> requestParameterMap = context.getExternalContext().getRequestParameterMap();
String firstName = requestParameterMap.get(getClientId("firstName"));
String lastName = requestParameterMap.get(getClientId("lastName"));
if(StringUtils.isEmpty(firstName) || StringUtils.isEmpty(lastName)) {
nextButtonUi.setDisabled(true);
}else {
nextButtonUi.setDisabled(false);
}
RequestContext.getCurrentInstance().update(getClientId("nextButton"));
}
}