JSF : Immediate attribute, ValueChangeListner

As we know JSF has 6 life cycle phases. But sometimes you want value change events or action events to fire at the beginning of the life cycle to bypass validation for one or more components or have validation in apply request phase itself.

The immediate attribute can be used to achieve the following effects:

  • Allow a commandLink or commandButton to navigate the user to another page without processing any data currently in input fields of the current screen. In particular, this allows navigation to occur even when there are currently validation errors. A “cancel” button typically falls into this category.
  • Allow a commandLink or commandButton to trigger back-end logic while ignoring validation for some of the fields on the screen. This is a more general version of the item above.
  • Make one or more input components “high priority” for validation, so that if any of these are invalid then validation is not performed for any “low-priority” input components in the same page. This can reduce the number of error messages shown.

Lets take an simple registration example which have 2 pages. First pages just has the link for the user to go to actula registraton page which has 1 input field and a cancel and submit button. We want that on click of cancel button the user should be taken back to the first page and on click of submit button values must be submitted. We would be listening to all the phases by implementing a phase listener. Also, we have implemented a dummy validator to check in which phase validation happens.

RegistrationImmediateAttrForm.java:


package mynotes.jsf.bean;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
@ManagedBean(name="registrationImmediateAttrForm")
@SessionScoped
public class RegistrationImmediateAttrForm {

private String name;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

RegistrationImmediateAttrAction.java:


package mynotes.jsf.actionBean;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;

import mynotes.jsf.bean.RegistrationImmediateAttrForm;
@ManagedBean(name="registrationImmediateAttrAction")
public class RegistrationImmediateAttrAction {

@ManagedProperty(value="#{registrationImmediateAttrForm}")
RegistrationImmediateAttrForm aRegistrationImmediateAttrForm;
public String initializeRegistration(){
System.out.println("Inside initializeRegistration");
return "registration";
}

public String register(){
System.out.println("Inside register");
System.out.println("Name=>"+aRegistrationImmediateAttrForm.getName());

return "registration";
}

public String cancel(){
System.out.println("Inside cancel");
return "preRegistration";
}

public RegistrationImmediateAttrForm getaRegistrationImmediateAttrForm() {
return aRegistrationImmediateAttrForm;
}

public void setaRegistrationImmediateAttrForm(
RegistrationImmediateAttrForm aRegistrationImmediateAttrForm) {
this.aRegistrationImmediateAttrForm = aRegistrationImmediateAttrForm;
}
}

Notice in cancel() methos we are navigating back to preRegistration page.

DummyValidator.java:


package mynotes.jsf.util;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
@FacesValidator("dummyValidator")
public class DummyValidator implements Validator{

public void validate(FacesContext context, UIComponent component, Object value)
throws ValidatorException {
System.out.println("DummyValidator validate: " +" component=>"+component.getId()+" value=>"+ value);

}}

LifeCycleListener.java:


package mynotes.jsf.util;

import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

public class LifeCycleListener implements PhaseListener{

private static final long serialVersionUID = 1L;

public void afterPhase(PhaseEvent pe) {
System.out.println("After Phase: " + pe.getPhaseId().toString() + " invoked.");
}

public void beforePhase(PhaseEvent pe) {
System.out.println("Before Phase: " + pe.getPhaseId().toString() + " invoked.");
}
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}

}

Add the following in your faces-config.xml to register this life cycle listener to jsf.


preRegistration.xhtml



<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Immediate Attribute</title>
</head>
<h:body>
<h3>Registration Disclaimer</h3>
<h:form>
<h:outputText value="Please continue with the regstration by following link"></h:outputText>
<br/>
<h:commandLink action="#{registrationImmediateAttrAction.initializeRegistration}" value="Register"></h:commandLink>
</h:form>
</h:body>
</html>

registration.xhtml


<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Immediate Attribute</title>
</head>
<h:body>
<h3>Immediate Attribute Registration Page</h3>
<h:form>
<table>
<tr>
<td><h:outputLabel for="name" value="Name: "></h:outputLabel></td>
<td><h:inputText value="#{registrationImmediateAttrForm.name}"
id="name" label="Name" required="true" >
<f:validator validatorId="dummyValidator" />
</h:inputText> <h:message for="name"></h:message></td>
</tr>
<tr>
<td><h:commandButton value="Cancel"
action="#{registrationImmediateAttrAction.cancel}"
></h:commandButton></td>
<td><h:commandButton value="Register"
action="#{registrationImmediateAttrAction.register}"></h:commandButton></td>
</tr>
</table>
</h:form>
</h:body>
</html>

Notice that required="true" for tha name field. Lets test and monitor the logs simultaneously:

immediate_preRegistration

Console logs :


Before Phase: RESTORE_VIEW 1 invoked.
After Phase: RESTORE_VIEW 1 invoked.
Before Phase: RENDER_RESPONSE 6 invoked.
After Phase: RENDER_RESPONSE 6 invoked.

Clicking Register

immediate_Registration

 

Console logs :


Before Phase: RESTORE_VIEW 1 invoked.
After Phase: RESTORE_VIEW 1 invoked.
Before Phase: APPLY_REQUEST_VALUES 2 invoked.
After Phase: APPLY_REQUEST_VALUES 2 invoked.
Before Phase: PROCESS_VALIDATIONS 3 invoked.
After Phase: PROCESS_VALIDATIONS 3 invoked.
Before Phase: UPDATE_MODEL_VALUES 4 invoked.
After Phase: UPDATE_MODEL_VALUES 4 invoked.
Before Phase: INVOKE_APPLICATION 5 invoked.
Inside initializeRegistration
After Phase: INVOKE_APPLICATION 5 invoked.
Before Phase: RENDER_RESPONSE 6 invoked.
After Phase: RENDER_RESPONSE 6 invoked.

Clicking 'Cancel' button:

immediate_preRegistration_cancel

Console logs :


Before Phase: RESTORE_VIEW 1 invoked.
After Phase: RESTORE_VIEW 1 invoked.
Before Phase: APPLY_REQUEST_VALUES 2 invoked.
After Phase: APPLY_REQUEST_VALUES 2 invoked.
Before Phase: PROCESS_VALIDATIONS 3 invoked.
After Phase: PROCESS_VALIDATIONS 3 invoked.
Before Phase: RENDER_RESPONSE 6 invoked.
After Phase: RENDER_RESPONSE 6 invoked.

Notice that the JSF now throws a error. Also, its the required="true" error, our dummyvalidator didnt get a chance to run because value was null. Adding immediate="true" in cancel button and running again.

Console logs :


Before Phase: RESTORE_VIEW 1 invoked.
After Phase: RESTORE_VIEW 1 invoked.
Before Phase: APPLY_REQUEST_VALUES 2 invoked.
Inside cancel
After Phase: APPLY_REQUEST_VALUES 2 invoked.
Before Phase: RENDER_RESPONSE 6 invoked.
After Phase: RENDER_RESPONSE 6 invoked.

Now the page is back to the preRegister page. Notice the method is called in Apply Request phase itself, and no validaton phase occurs.

Now lets add immediate="true" in the input field and click 'Register' and monitor the logs:

Console logs :


Before Phase: RESTORE_VIEW 1 invoked.
After Phase: RESTORE_VIEW 1 invoked.
Before Phase: APPLY_REQUEST_VALUES 2 invoked.
DummyValidator validate: component=>name value=>Test
After Phase: APPLY_REQUEST_VALUES 2 invoked.
Before Phase: PROCESS_VALIDATIONS 3 invoked.
After Phase: PROCESS_VALIDATIONS 3 invoked.
Before Phase: UPDATE_MODEL_VALUES 4 invoked.
After Phase: UPDATE_MODEL_VALUES 4 invoked.
Before Phase: INVOKE_APPLICATION 5 invoked.
Inside register
Name=>Test
After Phase: INVOKE_APPLICATION 5 invoked.
Before Phase: RENDER_RESPONSE 6 invoked.
After Phase: RENDER_RESPONSE 6 invoked.

Notice that the validator ran in Apply Request phase and not the validation phase. This could be useful some value to be setted in model before itself. Like while implementing a valueChangeListner.

Sometimes Components in Web application are dependent on each other. To keep dependent component in sync, it is possible to change the dependent component on change of independent component. For example, on selecting a country from dropdown, values in city dropdown changes. We intened that this change happens without actually submitting other values. This can be achieve by valueChangeListener. It can be implemented in 2 ways:

  • <f:valueChangeListener> tag - where we have seperate class which implements valueChangeListner interface. This eaxmple is based in this.
  • valueChangeListener attribute inside input components - where we write a method which return void and passes ValueChangeEvent as input.

RegistrationValueChangeListnerForm.java


package mynotes.jsf.bean;

import java.util.LinkedHashMap;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean(name="registrationValueChangeListnerForm")
@SessionScoped
public class RegistrationValueChangeListnerForm {

private Map<String,String> countries;
private Map<String,String> cities =new LinkedHashMap<String,String>();
private String name;
private String country;
private String city;
@PostConstruct
public void init(){
countries = new LinkedHashMap<String,String>();
countries.put("India", "ind");//label, value
countries.put("United States", "us");
}

public Map<String, String> getCities() {
return cities;
}
public void setCities(Map<String, String> cities) {
this.cities = cities;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}

public Map<String, String> getCountries() {
return countries;
}
public void setCountries(Map<String, String> countries) {
this.countries = countries;
}

}

We are using selectOneMenu of jsf that requires a Map with label and value. Notice that we are initializing countries atrribute in @PostConstruct.

RegistrationValueChangeListnerAction.java


package mynotes.jsf.actionBean;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;

import mynotes.jsf.bean.RegistrationValueChangeListnerForm;
@ManagedBean(name="registrationValueChangeListnerAction")
public class RegistrationValueChangeListnerAction {

@ManagedProperty(value="#{registrationValueChangeListnerForm}")
RegistrationValueChangeListnerForm aRegistrationValueChangeListnerForm;

public RegistrationValueChangeListnerForm getaRegistrationValueChangeListnerForm() {
return aRegistrationValueChangeListnerForm;
}

public void setaRegistrationValueChangeListnerForm(
RegistrationValueChangeListnerForm aRegistrationValueChangeListnerForm) {
this.aRegistrationValueChangeListnerForm = aRegistrationValueChangeListnerForm;
}

public String submit(){
System.out.println("Inside Submit");
return "register";
}

}

CountryValueChangeListner.java:


package mynotes.jsf.util;

import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ValueChangeEvent;
import javax.faces.event.ValueChangeListener;

import mynotes.jsf.bean.RegistrationValueChangeListnerForm;

public class CountryValueChangeListner implements ValueChangeListener{

public void processValueChange(ValueChangeEvent event)
throws AbortProcessingException {
System.out.println("CountryValueChangeListner processValueChange method");

String oldValue = (String) event.getOldValue(); // obtain previous selected value
String newValue = (String) event.getNewValue(); // obtain current selected value

System.out.println("oldValue=>" + oldValue);
System.out.println("newValue=>" + newValue);
RegistrationValueChangeListnerForm form = (RegistrationValueChangeListnerForm) FacesContext.getCurrentInstance().
getExternalContext().getSessionMap().get("registrationValueChangeListnerForm");
if ("us".equalsIgnoreCase(newValue)) {
form.getCities().clear();
form.getCities().put("New York", "nyk");
form.getCities().put("Washington D.C.", "dc");
} if ("ind".equalsIgnoreCase(newValue)) {
form.getCities().clear();
form.getCities().put("Mumbai", "mum");
form.getCities().put("Pune", "pun");
}
FacesContext.getCurrentInstance().renderResponse();

}

}

registration.xhtml:


<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Value Change Listner</title>
</head>
<h:body>
<h3>Value Change Listner Registration Page</h3>
<h:form>
<table>
<tr>
<td><h:outputLabel for="name" value="Name: "></h:outputLabel></td>
<td><h:inputText value="#{registrationValueChangeListnerForm.name}"
id="name" label="Name" required="true">
</h:inputText> <h:message for="name"></h:message></td>

</tr>
<tr>
<td><h:outputLabel for="country" value="Country: "></h:outputLabel></td>
<td><h:selectOneMenu value="#{registrationValueChangeListnerForm.country}"
id="country" immediate="true" onchange="submit()">
<f:valueChangeListener type="mynotes.jsf.util.CountryValueChangeListner"/>
<f:selectItems value="#{registrationValueChangeListnerForm.countries}"/>
</h:selectOneMenu></td>

</tr>
<tr>
<td><h:outputLabel for="city" value="City: "></h:outputLabel></td>
<td><h:selectOneMenu value="#{registrationValueChangeListnerForm.city}"
id="city">
<f:selectItems value="#{registrationValueChangeListnerForm.cities}"/>
</h:selectOneMenu></td>

</tr>
<tr>
<td colspan="2"><h:commandButton value="Submit"
action="#{registrationValueChangeListnerAction.submit}"></h:commandButton></td>
</tr>
</table>
</h:form>
</h:body>
</html>

valueChangeListner_Registration1

Console logs:


Before Phase: RESTORE_VIEW 1 invoked.
After Phase: RESTORE_VIEW 1 invoked.
Before Phase: RENDER_RESPONSE 6 invoked.
After Phase: RENDER_RESPONSE 6 invoked.

Changing country

valueChangeListner_Registration2

Console logs:


Before Phase: RESTORE_VIEW 1 invoked.
After Phase: RESTORE_VIEW 1 invoked.
Before Phase: APPLY_REQUEST_VALUES 2 invoked.
CountryValueChangeListner processValueChange method
oldValue=>null
newValue=>us
After Phase: APPLY_REQUEST_VALUES 2 invoked.
Before Phase: RENDER_RESPONSE 6 invoked.
After Phase: RENDER_RESPONSE 6 invoked.

Notice that no validation error was thrown for the 'name' field.

Also, just for records for values in selectOneMenu can also be done by Hardcoded value in "f:selectItem" tag or by an Object array and put it into "f:selectItems" tag, then represent the value with "var" attribute.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: