Showing posts with label ValueChangeListener. Show all posts
Showing posts with label ValueChangeListener. Show all posts

Wednesday, October 17, 2007

Populate child menu's

WARNING - OUTDATED CONTENT!

This article is targeted on JSF 1.2. For JSF 2.0, populating a child menu has become so much easier using <f:ajax>. See also this post for a code snippet.

Introduction

Having multiple h:selectOneMenu instances in one form which depends on each other and of which its values have to be obtained from the backing bean can drive JSF developers nuts. Especially if those are to be implemented in a form with at least one required field or if they are even required themselves. Validation errors ('Value not valid'), IllegalArgumentExceptions (at SelectItemsIterator#next()), unexpected submits (missing or wrong values), etcetera are flying around. You would almost become suicidal.

Back to top

Onchange, valueChangeListener, immediate, renderResponse and binding

To populate a child menu of which its contents is to be determined based on the value of the parent menu, you have to submit the form to the server on change of the parent menu. This can easily be achieved using the onchange attribute where you can specify some Javascript which have to be invoked when the menu is changed. Submitting the current form to the server using Javascript is simple, specifying "this.form.submit()" ought to be enough, or just submit() if you're lazy in typing.

You can use a valueChangeListener to retrieve the new value of the menu. But you of course want to prevent validation on the other required fields. This can technically be achieved by adding immediate="true" to all menu's so that all converters, validators and valuechange events of the menu's gets fired in the APPLY_REQUEST_VALUES phase instead of the PROCESS_VALIDATIONS phase and by adding the following line to the end of the valueChangeListener method:


FacesContext.getCurrentInstance().renderResponse();

This line will force a phase shift of the current phase to the RENDER_RESPONSE phase. When a valueChangeListener is invoked with immediate="true", then this line will cause the PROCESS_VALIDATIONS, UPDATE_MODEL_VALUES and INVOKE_APPLICATION being skipped, so that the other components which doesn't have immediate="true" set won't be converted, validated, applied nor invoked. Also see the former article Debug JSF lifecycle for more insights in the JSF lifecycle.

One concern is that the skipping of the UPDATE_MODEL_VALUES will also cause that the new values of the menu's which have immediate="true" set won't be set in the backing bean. This can partly be fixed by getting the new value from the ValueChangeEvent inside the valueChangeListener method and assign it to the appropriate property. But this won't work for other menu's of which the valueChangeListener isn't been invoked. This would cause problems if you select a child menu value and then select the parent menu back to null and then reselect it to same value again, the child menu which will show up again would remain the same selection instead of null while its child will not be rendered! To solve this we need to bind the menu's to the backing bean so that we can use UIInput#setValue() and UIInput#getValue() to set and get the actual values. The JSF lifecycle will set and get them in the RESTORE_VIEW and RENDER_RESPONSE phases respectively.

Back to top

Basic JSF code example

Here is a basic JSF code example which demonstrates three h:selectOneMenu components of which the listing of the next menu depends on the selection of the current menu. The next menu will be hidden until the selection of the current menu has a valid value. In this example we'll use a basic tree structure of area's (countries, cities and streets), which would make the most sense. There is also another required input field added to demonstrate the menu's working flawlessly in conjunction with other components.

One important detail to be mentioned is that the requireness of this menu group is set in a valueless h:inputHidden component instead of in the last menu. This is done so because of the fact that the last menu would not be rendered when its parent menu doesn't have a valid selection, so it would be pointless to set a required attribute on the last menu.

This example is developed and tested using JSF 1.2_05 in a Java EE 5.0 environment with a GlassFish V2 application server.

<h:form>
    <h:panelGrid columns="2">
        <h:outputText value="Choose area" />
        <h:panelGroup>
            <h:selectOneMenu
                binding="#{myBean.countryMenu}" converter="areaMenuConverter"
                onchange="this.form.submit();" valueChangeListener="#{myBean.changeCountryMenu}"
                immediate="true">
                <f:selectItem itemLabel="Please select country" />
                <f:selectItems value="#{myBean.countryItems}" />
            </h:selectOneMenu>
            <h:selectOneMenu
                binding="#{myBean.cityMenu}" converter="areaMenuConverter"
                onchange="this.form.submit();" valueChangeListener="#{myBean.changeCityMenu}"
                immediate="true" rendered="#{myBean.countryMenu.value != null}">
                <f:selectItem itemLabel="Please select city" />
                <f:selectItems value="#{myBean.cityItems}" />
            </h:selectOneMenu>
            <h:selectOneMenu
                binding="#{myBean.streetMenu}" converter="areaMenuConverter" 
                rendered="#{myBean.cityMenu.value != null}">
                <f:selectItem itemLabel="Please select street" />
                <f:selectItems value="#{myBean.streetItems}" />
            </h:selectOneMenu>
            <h:inputHidden
                required="#{myBean.streetMenu.value == null}" requiredMessage="Area is required." />
        </h:panelGroup>

        <h:outputText value="Enter input" />
        <h:inputText
            value="#{myBean.input}" 
            required="true" requiredMessage="Input is required." />

        <h:panelGroup />
        <h:commandButton value="Submit" action="#{myBean.submit}" />
    </h:panelGrid>

    <h:messages infoStyle="color: green;" errorStyle="color: red;" />
</h:form>

And here is the appropriate backing bean:

package mypackage;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.faces.application.FacesMessage;
import javax.faces.component.html.HtmlSelectOneMenu;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;

public class MyBean {

    // Init ---------------------------------------------------------------------------------------

    private static World world = new World();
    private List<SelectItem> countryItems = new ArrayList<SelectItem>();
    private List<SelectItem> cityItems = new ArrayList<SelectItem>();
    private List<SelectItem> streetItems = new ArrayList<SelectItem>();
    private HtmlSelectOneMenu countryMenu;
    private HtmlSelectOneMenu cityMenu;
    private HtmlSelectOneMenu streetMenu;
    private String input;

    {
        // Prefill country menu.
        fillAreaItems(countryItems, world.getAreas());
    }

    // Actions ------------------------------------------------------------------------------------

    public void submit() {
        // Show selection and input results as informal message.
        FacesContext.getCurrentInstance().addMessage(null,
            new FacesMessage("You have chosen: " + countryMenu.getValue() + ", "
                + cityMenu.getValue() + ", " + streetMenu.getValue()
                + " and you have entered: " + input));
    }

    // Changers -----------------------------------------------------------------------------------

    public void changeCountryMenu(ValueChangeEvent event) {
        // Get selected country.
        Country country = (Country) event.getNewValue();

        if (country != null) {
            // Fill city menu.
            fillAreaItems(cityItems, country.getAreas());
        }

        // Reset child menu's. This is only possible when using component binding.
        cityMenu.setValue(null);
        streetMenu.setValue(null);

        // Skip validation of non-immediate components and invocation of the submit() method.
        FacesContext.getCurrentInstance().renderResponse();
    }

    public void changeCityMenu(ValueChangeEvent event) {
        // Get selected city.
        City city = (City) event.getNewValue();

        if (city != null) {
            // Fill street menu.
            fillAreaItems(streetItems, city.getAreas());
        }

        // Reset child menu. This is only possible when using component binding.
        streetMenu.setValue(null);

        // Skip validation of non-immediate components and invocation of the submit() method.
        FacesContext.getCurrentInstance().renderResponse();
    }

    // Fillers ------------------------------------------------------------------------------------

    private static <A extends Area<?>> void fillAreaItems(List<SelectItem> areaItems, Set<A> areas) {
        areaItems.clear();
        for (A area : areas) {
            areaItems.add(new SelectItem(area, area.getName()));
        }
    }

    // Getters ------------------------------------------------------------------------------------

    public List<SelectItem> getCountryItems() {
        return countryItems;
    }

    public List<SelectItem> getCityItems() {
        return cityItems;
    }

    public List<SelectItem> getStreetItems() {
        return streetItems;
    }

    public HtmlSelectOneMenu getCountryMenu() {
        return countryMenu;
    }

    public HtmlSelectOneMenu getCityMenu() {
        return cityMenu;
    }

    public HtmlSelectOneMenu getStreetMenu() {
        return streetMenu;
    }

    public String getInput() {
        return input;
    }

    // Setters ------------------------------------------------------------------------------------

    public void setCountryMenu(HtmlSelectOneMenu countryMenu) {
        this.countryMenu = countryMenu;
    }

    public void setCityMenu(HtmlSelectOneMenu cityMenu) {
        this.cityMenu = cityMenu;
    }

    public void setStreetMenu(HtmlSelectOneMenu streetMenu) {
        this.streetMenu = streetMenu;
    }

    public void setInput(String input) {
        this.input = input;
    }

}

The relevant part of the faces-config.xml file look like:

<converter>
    <converter-id>areaMenuConverter</converter-id>
    <converter-class>mypackage.AreaMenuConverter</converter-class>
</converter>
<managed-bean>
    <managed-bean-name>myBean</managed-bean-name>
    <managed-bean-class>mypackage.MyBean</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

If you want to keep the bean in request scope (which is a very reasonable requirement), then you need to install Tomahawk and 'cache' the bean for the next request only using <t:saveState />.

Back to top

Menu structure

As said earlier, we're demonstrating the working of the menu's using a tree structure of area's: countries, cities and streets, because that would make the most sense. It is not necessary to take over exactly such a structure for your menu's. Just do whatever you find the best and easiest way to use, access and maintain menu items. At least I like the following relatively simple parent-child structure and the converter.

Here is the abstract class Area (please note the implementation of equals() and hashCode(), this is very important for JSF, also see Objects in h:selectOneMenu):

package mypackage;

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public abstract class Area<A extends Area<?>> {

    // Init ---------------------------------------------------------------------------------------

    private String name;
    private Set<A> areas;
    private Area<?> parent;

    // Constructors -------------------------------------------------------------------------------

    protected Area(String name, A[] areas) {
        this.name = name;
        this.areas = new TreeSet<A>(new AreaComparator<A>());
        for (A area : areas) {
            area.parent = this;
            this.areas.add(area);
        }
    }

    // Getters ------------------------------------------------------------------------------------

    public String getName() {
        return name;
    }

    public Set<A> getAreas() {
        return areas;
    }

    public Area<?> getParent() {
        return parent;
    }

    // Helpers ------------------------------------------------------------------------------------

    public boolean equals(Object other) {
        return other instanceof Area
            && this.getClass().equals(other.getClass())
            && name.equals(((Area<?>) other).name);
    }

    public int hashCode() {
        return this.getClass().hashCode() + name.hashCode();
    }
    
    public String toString() {
        return name;
    }

}

class AreaComparator<A extends Area<?>> implements Comparator<A> {

    // Invokes natural sorting on area name.
    public int compare(A a1, A a2) {
        return a1.getName().compareTo(a2.getName());
    }

}

And here is the class representing the World which is nothing more or less than a placeholder of the tree structure of area's. Note the stub constructor, it prefills the tree structure.

package mypackage;

public class World extends Area<Country> {

    // Constructors -------------------------------------------------------------------------------

    public World(String name, Country[] countries) {
        super(name, countries);
    }

    public World() {
        // Stub constructor.
        // May be replaced by DAO, configuration files, or wherever you want to store this stuff.
        this("Earth", new Country[] {
            new Country("The Netherlands", new City[] {
                new City("Haarlem", new Street[] {
                    new Street("Palamedesstraat"),
                    new Street("Vergierdeweg"),
                    new Street("Marsstraat")
                }),
                new City("Amsterdam", new Street[] {
                    new Street("Gyroscoopstraat"),
                    new Street("Albert Cuypstraat"),
                    new Street("De Boelelaan")
                }),
                new City("Almere", new Street[] {
                    new Street("Tarantellastraat"),
                    new Street("Salsastraat"),
                    new Street("Hollywoodlaan")
                })
            }),
            new Country("United States", new City[] {
                new City("New York", new Street[] {
                    new Street("Central Park West"),
                    new Street("Park Avenue"),
                    new Street("Amsterdam Avenue")
                }),
                new City("Los Angeles", new Street[] {
                    new Street("Main Street"),
                    new Street("Broadway"),
                    new Street("Olympic Boulevard")
                }),
                new City("Miami", new Street[] {
                    new Street("Miami Avenue"),
                    new Street("Biscayne Boulevard"),
                    new Street("Venetian Way")
                })
            }),
            new Country("France", new City[] {
                new City("Paris", new Street[] {
                    new Street("Avenue des Champs Elysees"),
                    new Street("Quai d'Orsay"),
                    new Street("Rue La Fayette")
                }),
                new City("Lyon", new Street[] {
                    new Street("Cours Lafayette"),
                    new Street("Quai Victor Augagneur"),
                    new Street("Rue Garibaldi")
                }),
                new City("Marseille", new Street[] {
                    new Street("Boulevard Longchamp"),
                    new Street("Rue de Rome"),
                    new Street("Cours Lieutaud")
                })
            })
        });
    }

}

The Country class:

package mypackage;

public class Country extends Area<City> {

    // Constructors -------------------------------------------------------------------------------

    public Country(String name, City[] cities) {
        super(name, cities);
    }

}

The City class:

package mypackage;

public class City extends Area<Street> {

    // Constructors -------------------------------------------------------------------------------

    public City(String name, Street[] streets) {
        super(name, streets);
    }

}

The Street class:

package mypackage;

public class Street extends Area<Area<?>> { 

    // Constructors -------------------------------------------------------------------------------

    public Street(String name) {
        super(name, new Area[0]);
    }

}

And finally the Converter to be used in menu's to convert between the Area type and String type and vice versa:

package mypackage;

import java.util.Set;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;

public class AreaMenuConverter implements Converter {

    // Init ---------------------------------------------------------------------------------------

    private static final String AREAS = "AreaMenuConverter.areas";

    // Actions ------------------------------------------------------------------------------------

    public String getAsString(FacesContext context, UIComponent component, Object value) {
        if (value != null) {
            // Cast back to Area.
            Area<?> area = (Area<?>) value;

            // Store the areas as component attribute so that they are available in getAsObject().
            // Those represents the same values as those in f:selectItems.
            component.getAttributes().put(AREAS, area.getParent().getAreas());

            // Return String representation of area.
            return area.getName();
        }

        return null; // Value is null.
    }

    @SuppressWarnings("unchecked")
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value != null) {
            // Get the areas back which were stored as component attribute in getAsString().
            Set<Area<?>> areas = (Set<Area<?>>) component.getAttributes().get(AREAS);

            // Compare name of each area with selected value.
            for (Area<?> area : areas) {
                if (area.getName().equals(value)) {
                    // Return matched area object.
                    return area;
                }
            }
        }

        return null; // Value is null or doesn't have any match.
    }

}

That was it! You can in fact just copypaste and run it all without any changes and then play/experiment with it further.

Back to top

Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair.

(C) October 2007, BalusC

Wednesday, September 27, 2006

Debug JSF lifecycle

Listen and debug JSF lifecycle phases

The JSF lifecycle will be explained and debugged here using the "poor man's debugging" approach with sysout's. We'll also check what happens if you add immediate="true" to the UIInput and UICommand and what happens when a ConverterException and ValidatorException will be thrown.

Well, you probably already know that the JSF lifecycle contains 6 phases:

  1. Restore view
  2. Apply request values
  3. Process validations
  4. Update model values
  5. Invoke application
  6. Render response

You can use a PhaseListener to trace the phases of the JSF lifecycle and execute some processes where required. But you can also use a "dummy" PhaseListener to debug the phases to see what is happening in which phase. Here is a basic example of such a LifeCycleListener:

Note: if you don't have a JSF playground environment setup yet, then you may find this tutorial useful as well: JSF tutorial with Eclipse and Tomcat.

package mypackage;

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

public class LifeCycleListener implements PhaseListener {

    public PhaseId getPhaseId() {
        return PhaseId.ANY_PHASE;
    }

    public void beforePhase(PhaseEvent event) {
        System.out.println("START PHASE " + event.getPhaseId());
    }

    public void afterPhase(PhaseEvent event) {
        System.out.println("END PHASE " + event.getPhaseId());
    }

}

Add the following lines to the faces-config.xml to activate the LifeCycleListener.

<lifecycle>
    <phase-listener>mypackage.LifeCycleListener</phase-listener>
</lifecycle>

This produces like the following in the system output:

START PHASE RESTORE_VIEW 1
END PHASE RESTORE_VIEW 1
START PHASE APPLY_REQUEST_VALUES 2
END PHASE APPLY_REQUEST_VALUES 2
START PHASE PROCESS_VALIDATIONS 3
END PHASE PROCESS_VALIDATIONS 3
START PHASE UPDATE_MODEL_VALUES 4
END PHASE UPDATE_MODEL_VALUES 4
START PHASE INVOKE_APPLICATION 5
END PHASE INVOKE_APPLICATION 5
START PHASE RENDER_RESPONSE 6
END PHASE RENDER_RESPONSE 6

Back to top

Basic debug example

To trace all phases of the JSF lifecycle, here is some sample code which represents simple JSF form with a "dummy" converter and validator and the appropriate backing bean with all component bindings. The code sample can be used to give us more insights into the phases of the JSF lifecycle, to understand it and to learn about it. Please note that component bindings are rarely used in real world this way. They are in this code sample just there to track and log component set/get actions by JSF. In real world code you should not have the need for them.

Here's the form, just put it in its entirety in the body of your test.jsp file (note: at time of writing, JSF 1.2 is used, but if you're using JSF 2.x, you can of course also put this in a test.xhtml file):

<h:form>
    <h:inputText
        binding="#{myBean.inputComponent}"
        value="#{myBean.inputValue}"
        valueChangeListener="#{myBean.inputChanged}">
        <f:converter converterId="myConverter" />
        <f:validator validatorId="myValidator" />
    </h:inputText>
    <h:commandButton
        value="submit"
        action="#{myBean.action}" />
    <h:outputText
        binding="#{myBean.outputComponent}"
        value="#{myBean.outputValue}" />
    <h:messages />
</h:form>

Here's the dummy converter: MyConverter.java

package mypackage;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;

public class MyConverter implements Converter {

    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        System.out.println("MyConverter getAsObject: " + value);
        return value;
    }

    public String getAsString(FacesContext context, UIComponent component, Object value) {
        System.out.println("MyConverter getAsString: " + value);
        return (String) value;
    }

}

Here's the dummy validator: MyValidator.java

package mypackage;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

public class MyValidator implements Validator {

    public void validate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException
    {
        System.out.println("MyValidator validate: " + value);
    }

}

Here's the backing bean: MyBean.java

package mypackage;

import javax.faces.component.html.HtmlInputText;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.event.ValueChangeEvent;

public class MyBean {

    // Init ----------------------------------------------------------------------------------------

    private HtmlInputText inputComponent;
    private String inputValue;
    private HtmlOutputText outputComponent;
    private String outputValue;

    // Constructors -------------------------------------------------------------------------------

    public MyBean() {
        log("constructed");
    }

    // Actions ------------------------------------------------------------------------------------

    public void inputChanged(ValueChangeEvent event) {
        log(event.getOldValue() + " to " + event.getNewValue());
    }

    public void action() {
        outputValue = inputValue;
        log("succes");
    }

    // Getters/setters ----------------------------------------------------------------------------

    public HtmlInputText getInputComponent() {
        log(inputComponent);
        return inputComponent;
    }

    public void setInputComponent(HtmlInputText inputComponent) {
        log(inputComponent);
        this.inputComponent = inputComponent;
    }

    public String getInputValue() {
        log(inputValue);
        return inputValue;
    }

    public void setInputValue(String inputValue) {
        log(inputValue);
        this.inputValue = inputValue;
    }

    public HtmlOutputText getOutputComponent() {
        log(outputComponent);
        return outputComponent;
    }

    public void setOutputComponent(HtmlOutputText outputComponent) {
        log(outputComponent);
        this.outputComponent = outputComponent;
    }

    public String getOutputValue() {
        log(outputValue);
        return outputValue;
    }

    // Helpers ------------------------------------------------------------------------------------

    private void log(Object object) {
        String methodName = Thread.currentThread().getStackTrace()[2].getMethodName();
        System.out.println("MyBean " + methodName + ": " + object);
    }
}

The minimal faces configuration: faces-config.xml

<converter>
    <converter-id>myConverter</converter-id>
    <converter-class>mypackage.MyConverter</converter-class>
</converter>
<validator>
    <validator-id>myValidator</validator-id>
    <validator-class>mypackage.MyValidator</validator-class>
</validator>
<managed-bean>
    <managed-bean-name>myBean</managed-bean-name>
    <managed-bean-class>mypackage.MyBean</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
</managed-bean>

Note: at the time of writing, JSF 1.2 was the standard and JSF 2.0 didn't exist yet. If you're however using JSF 2.x, then you could of course also instead use the @FacesConverter("myConverter"), @FacesValidator("myValidator") and @ManagedBean @RequestScoped annotations respectively.

Back to top

The first call

The first call in a freshly started webapplication with a fresh session should output at least:

START PHASE RESTORE_VIEW 1
END PHASE RESTORE_VIEW 1
START PHASE RENDER_RESPONSE 6
MyBean <init>: constructed
MyBean getInputComponent: null
MyBean setInputComponent: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean getInputValue: null
MyBean getOutputComponent: null
MyBean setOutputComponent: javax.faces.component.html.HtmlOutputText@13bbca56
MyBean getOutputValue: null
END PHASE RENDER_RESPONSE 6

1. Restore view.
As the session is fresh, there's no means of any UIViewRoot to restore, so nothing to see here.

2. Apply request values.
This phase is skipped because there is no form submit.

3. Process validations.
This phase is skipped because there is no form submit.

4. Update model values.
This phase is skipped because there is no form submit.

5. Invoke application.
This phase is skipped because there is no form submit.

6. Render response.
The bean is constructed. Behind the scenes a new UIViewRoot is created and stored in the session. If the component binding getters returns precreated components (precreated in e.g. the constructor) and not null, then those will be used, otherwise JSF will create new components. The components will be stored in the UIViewRoot and the bounded components are set in the component bindings. The values to be shown are retrieved from the value binding getters in the backing bean. If the values aren't set yet, they defaults to null. The component bindings are not required by the way. Only use them if you actually need the component in the backing bean for other means than getting/setting the value. In this article they are included just to demonstrate what all happens in the lifecycle.

Back to top

The form submit

The form submit with the value "test" entered should output at least:

START PHASE RESTORE_VIEW 1
MyBean <init>: constructed
MyBean setInputComponent: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputComponent: javax.faces.component.html.HtmlOutputText@13bbca56
END PHASE RESTORE_VIEW 1
START PHASE APPLY_REQUEST_VALUES 2
END PHASE APPLY_REQUEST_VALUES 2
START PHASE PROCESS_VALIDATIONS 3
MyConverter getAsObject: test
MyValidator validate: test
MyBean getInputValue: null
MyBean inputChanged: null to test
END PHASE PROCESS_VALIDATIONS 3
START PHASE UPDATE_MODEL_VALUES 4
MyBean setInputValue: test
END PHASE UPDATE_MODEL_VALUES 4
START PHASE INVOKE_APPLICATION 5
MyBean action: succes
END PHASE INVOKE_APPLICATION 5
START PHASE RENDER_RESPONSE 6
MyBean getInputValue: test
MyConverter getAsString: test
MyBean getOutputValue: test
END PHASE RENDER_RESPONSE 6

1. Restore view.
The bean is constructed. The UIViewRoot is restored from session and the bounded components are set in the component bindings.

2. Apply request values.
Nothing to see here. Behind the scenes the submitted form values are obtained as request parameters and set in the relevant components in the UIViewRoot, for example inputComponent.setSubmittedValue("test").

3. Process validations.
The submitted values are passed through the converter getAsObject() method and validated by the validator. If the conversion and validation succeeds, then the initial input value will be retrieved from the value binding getter and behind the scenes the inputComponent.setValue(submittedValue) and inputComponent.setSubmittedValue(null) will be executed. If the retrieved initial input value differs from the submitted value, then the valueChangeListener method will be invoked.

4. Update model values.
The converted and validated values will now be set in the value binding setters of the backing bean. E.g. myBean.setInputValue(inputComponent.getValue()).

5. Invoke application.
The real processing of the form submission happens here.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean. If a converter is definied, then the value will be passed through the converter getAsString() method and the result will be shown in the form.

Back to top

Add immediate="true" to UIInput only

Extend the h:inputText in the test.jsp with immediate="true":

    ...
    <h:inputText
        binding="#{myBean.inputComponent}"
        value="#{myBean.inputValue}"
        valueChangeListener="#{myBean.inputChanged}"
        immediate="true">
    ...

The form submit with the value "test" entered should output at least:

START PHASE RESTORE_VIEW 1
MyBean <init>: constructed
MyBean setInputComponent: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputComponent: javax.faces.component.html.HtmlOutputText@13bbca56
END PHASE RESTORE_VIEW 1
START PHASE APPLY_REQUEST_VALUES 2
MyConverter getAsObject: test
MyValidator validate: test
MyBean getInputValue: null
MyBean inputChanged: null to test
END PHASE APPLY_REQUEST_VALUES 2
START PHASE PROCESS_VALIDATIONS 3
END PHASE PROCESS_VALIDATIONS 3
START PHASE UPDATE_MODEL_VALUES 4
MyBean setInputValue: test
END PHASE UPDATE_MODEL_VALUES 4
START PHASE INVOKE_APPLICATION 5
MyBean action: succes
END PHASE INVOKE_APPLICATION 5
START PHASE RENDER_RESPONSE 6
MyBean getInputValue: test
MyConverter getAsString: test
MyBean getOutputValue: test
END PHASE RENDER_RESPONSE 6

1. Restore view.
The bean is constructed. The UIViewRoot is restored from session and the bounded components are set in the component bindings.

2. Apply request values.
Behind the scenes the submitted form values are obtained as request parameters and set in the relevant components in the UIViewRoot, for example inputComponent.setSubmittedValue("test"). The submitted values are immediately passed through the converter getAsObject() method and validated by the validator. If the conversion and validation succeeds, then the initial input value will be retrieved from the value binding getter and behind the scenes the inputComponent.setValue(submittedValue) and inputComponent.setSubmittedValue(null) will be executed. If the retrieved initial input value differs from the submitted value, then the valueChangeListener method will be invoked. This all happens in this phase instead of the Process validations phase due to the immediate="true" in the h:inputText.

3. Process validations.
Nothing to see here. The conversion and validation is already processed in the Apply request values phase, before the values being put in the components. This is due to the immediate="true" in the h:inputText.

4. Update model values.
The converted and validated values will now be set in the value binding setters of the backing bean. E.g. myBean.setInputValue(inputComponent.getValue()).

5. Invoke application.
The real processing of the form submission happens here.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean. If a converter is definied, then the value will be passed through the converter getAsString() method and the result will be shown in the form.

Note for other components without immediate: any other UIInput components inside the same form which don't have immediate="true" set will just continue the lifecycle as usual.

Back to top

Add immediate="true" to UICommand only

Extend the h:commandButton in the test.jsp with immediate="true" (don't forget to remove from h:inputText):

    ...
    <h:commandButton
        value="submit"
        action="#{myBean.action}"
        immediate="true" />
    ...

The form submit with the value "test" entered should output at least:

START PHASE RESTORE_VIEW 1
MyBean <init>: constructed
MyBean setInputComponent: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputComponent: javax.faces.component.html.HtmlOutputText@13bbca56
END PHASE RESTORE_VIEW 1
START PHASE APPLY_REQUEST_VALUES 2
MyBean action: succes
END PHASE APPLY_REQUEST_VALUES 2
START PHASE RENDER_RESPONSE 6
MyBean getOutputValue: null
END PHASE RENDER_RESPONSE 6

1. Restore view.
The bean is constructed. The UIViewRoot is restored from session and the bounded components are set in the component bindings.

2. Apply request values.
The real processing of the form submission happens here. This happens in this phase instead of the Invoke application phase due to the immediate="true" in the h:commandButton. The UIInput components which don't have immediate="true" set will not be converted, validated nor updated, but behind the scenes the inputComponent.setSubmittedValue(submittedValue) will be executed before the action() method will be executed.

3. Process validations.
This phase is skipped due to the immediate="true" in the h:commandButton.

4. Update model values.
This phase is skipped due to the immediate="true" in the h:commandButton.

5. Invoke application.
This phase is skipped due to the immediate="true" in the h:commandButton.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean, except for the UIInput components which don't have immediate="true" set. Behind the scenes those will be retrieved from the components in the UIViewRoot, e.g. inputComponent.getSubmittedValue().

Note for all components without immediate: as the Update model values phase is skipped, the value bindings aren't been set and the value binding getters will return null. But the values are still available as submitted value of the relevant components in the UIViewRoot. In this case you can retrieve the non-converted and non-validated input value from inputComponent.getSubmittedValue() in the action() method. You could even change it using inputComponent.setSubmittedValue(newValue) in the action() method.

Back to top

Add immediate="true" to UIInput and UICommand

Extend the h:inputText as well as the h:commandButton in the test.jsp with immediate="true":

    ...
    <h:inputText
        binding="#{myBean.inputComponent}"
        value="#{myBean.inputValue}"
        valueChangeListener="#{myBean.inputChanged}"
        immediate="true">
    ...
    <h:commandButton
        value="submit"
        action="#{myBean.action}"
        immediate="true" />
    ...

The form submit with the value "test" entered should output at least:

START PHASE RESTORE_VIEW 1
MyBean <init>: constructed
MyBean setInputComponent: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputComponent: javax.faces.component.html.HtmlOutputText@13bbca56
END PHASE RESTORE_VIEW 1
START PHASE APPLY_REQUEST_VALUES 2
MyConverter getAsObject: test
MyValidator validate: test
MyBean getInputValue: null
MyBean inputChanged: null to test
MyBean action: succes
END PHASE APPLY_REQUEST_VALUES 2
START PHASE RENDER_RESPONSE 6
MyConverter getAsString: test
MyBean getOutputValue: null
END PHASE RENDER_RESPONSE 6

1. Restore view.
The bean is constructed. The UIViewRoot is restored from session and the bounded components are set in the component bindings.

2. Apply request values.
Behind the scenes the submitted form values are obtained as request parameters and set in the relevant components in the UIViewRoot, for example inputComponent.setSubmittedValue("test"). The submitted values are immediately passed through the converter getAsObject() method and validated by the validator. If the conversion and validation succeeds, then the initial input value will be retrieved from the value binding getter and behind the scenes the inputComponent.setValue(submittedValue) and inputComponent.setSubmittedValue(null) will be executed. If the retrieved initial input value differs from the submitted value, then the valueChangeListener method will be invoked. This all happens in this phase instead of the Process validations phase due to the immediate="true" in the h:inputText. Finally the real processing of the form submission happens here. This happens in this phase instead of the Invoke application phase due to the immediate="true" in the h:commandButton.

3. Process validations.
This phase is skipped due to the immediate="true" in the h:commandButton.

4. Update model values.
This phase is skipped due to the immediate="true" in the h:commandButton.

5. Invoke application.
This phase is skipped due to the immediate="true" in the h:commandButton.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean. If a converter is definied, then the value will be passed through the converter getAsString() method and the result will be shown in the form.

Note for all components with immediate: as the Update model values phase is skipped, the value bindings aren't been set and the value binding getters will return null. But the values are still available by the relevant components in the UIViewRoot. In this case you can retrieve the input value from inputComponent.getValue() in the action() method. The new input value is also available by the ValueChangeEvent in the inputChanged() method. You could even change it using inputComponent.setValue(newValue) in the action() method.

Note for other components without immediate: any other UIInput components inside the same form which don't have immediate="true" set will not be converted, validated nor updated, but behind the scenes the inputComponent.setSubmittedValue(submittedValue) will be executed before the action() method will be executed. You can retrieve the non-converted and non-validated input value from inputComponent.getSubmittedValue() in the action() method. You could even change it using inputComponent.setSubmittedValue(newValue) in the action() method.

Back to top

Conversion error

Let's see what happens if a conversion error will occur. Change the getAsObject() method of MyConverter.java as follows (and remove the immediate="true" from the test.jsp file):

package mypackage;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

public class MyConverter implements Converter {

    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        System.out.println("MyConverter getAsObject: " + value);
        throw new ConverterException("Conversion failed.");
    }

    ...
}

The form submit with the value "test" entered should output at least:

START PHASE RESTORE_VIEW 1
MyBean <init>: constructed
MyBean setInputComponent: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputComponent: javax.faces.component.html.HtmlOutputText@13bbca56
END PHASE RESTORE_VIEW 1
START PHASE APPLY_REQUEST_VALUES 2
END PHASE APPLY_REQUEST_VALUES 2
START PHASE PROCESS_VALIDATIONS 3
MyConverter getAsObject: test
END PHASE PROCESS_VALIDATIONS 3
START PHASE RENDER_RESPONSE 6
MyBean getOutputValue: null
END PHASE RENDER_RESPONSE 6

1. Restore view.
The bean is constructed. The UIViewRoot is restored from session and the bounded components are set in the component bindings.

2. Apply request values.
Nothing to see here. Behind the scenes the submitted form values are obtained as request parameters and set in the relevant components in the UIViewRoot, for example inputComponent.setSubmittedValue("test").

3. Process validations.
The submitted values are passed through the converter getAsObject() method, where a ConverterException is thrown. The validator and the valueChangeListener are bypassed. Also the inputComponent.setValue(submittedValue) won't take place. The lifecycle will proceed to the Render response phase immediately.

4. Update model values.
This phase is skipped due to the ConverterException.

5. Invoke application.
This phase is skipped due to the ConverterException.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean, expecting the values for which a ConverterException has occurred. Behind the scenes those will be retrieved from the components in the UIViewRoot, e.g. inputComponent.getSubmittedValue().

Note: any other UIInput components inside the same form which didn't throw a ConverterException will also skip the update model values and invoke application phases.

Back to top

Validation error

Let's see what happens if a validation error will occur. Change the validate() method of MyValidator.java as follows (and remove the immediate="true" from the test.jsp file and revert MyConverter.java back to original):

package mypackage;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

public class MyValidator implements Validator {

    public void validate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException
    {
        System.out.println("MyValidator validate: " + value);
        throw new ValidatorException(new FacesMessage("Validation failed."));
    }

}

The form submit with the value "test" entered should output at least:

START PHASE RESTORE_VIEW 1
MyBean <init>: constructed
MyBean setInputComponent: javax.faces.component.html.HtmlInputText@2a9fca57
MyBean setOutputComponent: javax.faces.component.html.HtmlOutputText@13bbca56
END PHASE RESTORE_VIEW 1
START PHASE APPLY_REQUEST_VALUES 2
END PHASE APPLY_REQUEST_VALUES 2
START PHASE PROCESS_VALIDATIONS 3
MyConverter getAsObject: test
MyValidator validate: test
END PHASE PROCESS_VALIDATIONS 3
START PHASE RENDER_RESPONSE 6
MyBean getOutputValue: null
END PHASE RENDER_RESPONSE 6

1. Restore view.
The bean is constructed. The UIViewRoot is restored from session and the bounded components are set in the component bindings.

2. Apply request values.
Nothing to see here. Behind the scenes the submitted form values are obtained as request parameters and set in the relevant components in the UIViewRoot, for example inputComponent.setSubmittedValue("test").

3. Process validations.
The values are retrieved as objects from the components, passed through the converter getAsObject() method and validated by the validator, where a ValidatorException is thrown. The valueChangeListener is bypassed. Also the inputComponent.setValue(submittedValue) won't take place. The lifecycle will proceed to the Render response phase immediately.

4. Update model values.
This phase is skipped due to the ValidatorException.

5. Invoke application.
This phase is skipped due to the ValidatorException.

6. Render response.
The values to be shown are retrieved from the value binding getters in the backing bean, except of the values for which a ValidatorException has occurred. Behind the scenes those will be retrieved from the components in the UIViewRoot, e.g. inputComponent.getSubmittedValue().

Note: any other UIInput components inside the same form which didn't throw a ValidatorException will also skip the update model values and invoke application phases.

Back to top

Okay, when should I use the immediate attribute?

If it isn't entirely clear yet, here's a summary, complete with real world use examples when they may be beneficial:

  • If set in UIInput(s) only, the job which is normally invoked during process validations phase will be taken place in apply request values phase instead. Use this to prioritize validation for the UIInput component(s) in question. When validation/conversion fails for any of them, the non-immediate components won't be validated/converted.
  • If set in UICommand only, the apply request values phase until with update model values phases will be skipped for any of the UIInput component(s). Use this to skip the entire processing of the form. E.g. "Cancel" or "Back" button.
  • If set in both UIInput and UICommand components, the job which is normally invoked during the apply request values phase until with update model values phases will be skipped for any of the UIInput component(s) which does not have this attribute set. Use this to skip the processing of the entire form except for certain fields (with immediate). E.g. "Password forgotten" button in a login form with a required and immediate username field and a required but non-immediate password field.
Back to top

Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair.

(C) September 2006, BalusC