Here is a trick that allows you to perform JSF-based BeanValidation depending on the action. It will be supported by next SpringFuse version (3.0.69)
Note: We are using Spring and SpringWebFlow, but this should be easy to adapt it to a pure Java EE application.
The challenge when developing JSF applications involving multi-page form is to provide a usable navigation between the pages.
By usable navigation we mean:
a user should be able to navigate freely from one page to another without loosing the data entered in the input fields, even if the entered data is invalid, so that the user can come back to it later.
for certain actions (for example 'save' action) the validation should be enforced.
The solution should be easy to develop and maintain. For example, we have tried to implement the a trick that Mr Ed Burns suggests, without any success.
JSF 2.0 supports BeanValidation, which is extremely convenient. We should leverage it.
We leverage the binding attribute of the JSF <f:validateBean/> tag.
As the documentation says, this attribute value is a ValueExpression that evaluates to an object that implements javax.faces.validate.BeanValidator
Unsurprisingly BeanValidator implements the validate method below from the Validator interface:
public void validate(FacesContext context,
UIComponent component,
Object value) throws ValidatorException;
We just have to extend BeanValidator and override the validate method, here is the code:
package com.jaxio.demo.web.component;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.BeanValidator;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* Lenient bean validator which disable validation in certain cases
* in order to let the user navigate to sub view without loosing the
* data entered in input field.
*/
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class LenientBeanValidator extends BeanValidator {
@Override
public void validate(FacesContext context, UIComponent component, Object value) {
if (doValidation(context)) {
super.validate(context, component, value);
}
// simply skip it...
}
private boolean doValidation(FacesContext context) {
// main page (action that saves the main entity)
if (context.getExternalContext().getRequestParameterValuesMap().containsKey("form:save")) {
return true;
}
// sub page (action that binds the entered data to an associated entity)
if (context.getExternalContext().getRequestParameterValuesMap().containsKey("form:ok")) {
return true;
}
return false;
}
}
Of course the code above may be improved to fit your need and your security requirements.
Then, to use it from your view, you simply do as follow:
<h:form id="form">
<f:validateBean disabled="true">
<!-- ... skip for clarity ... -->
<p:inputText id="email" label="${msg.account_email}" value="#{account.email}" maxlength="80" >
<f:validateBean binding="#{lenientBeanValidator}"/>
</p:inputText>
<!-- 'save' (validation is enforced) -->
<p:commandButton id="save" action="save" ajax="true" update="messages" process="@form"
styleClass="aria-save-button default"
image="ui-icon-disk"
value="#{msg.menu_save}"
rendered="#{not subPage}" />
<!-- 'ok' (validation is enforced) -->
<p:commandButton id="ok" action="ok" process="@form" ajax="true" update="messages"
styleClass="default"
image="ui-icon-check"
value="${msg.submit}"
rendered="#{subPage}" />
<!-- 'otherCase' (validation is NOT enforced) -->
<p:commandButton id="navigateInSubPage" action="navigateInSubPage" process="@form" ajax="false"
styleClass="default"
image="ui-icon-check"
value="${msg.submit}"
/>
</f:validateBean>
</h:form>
If you have any remark on this solution, please share :)
Enjoy!
The SpringFuse/Jaxio team.