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"> <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>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"?
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"/> <f:attribute name="nextButton" value="#{nextButton}"/> </p:inputText> <p:commandButton id="nextButton" actionListener="#{controller.onNext}" update="personForm" binding="#{nextButton}"/> </h:form>The custom validator looks like:
@FacesValidator(value = "requiredFieldValidator") 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")); } }How is your approach? Leave your comment down below!
Comments
Post a Comment