Weblog

Implementing dependent select boxes in JSF

Some time ago Tom Hofte wrote a post on implementing dependent select boxes using ajax in struts. In this post i’ll show you how to do it with JSF. But i’ll do it the easy way, using ADF Faces. ADF Faces is pretty powerful, you don’t have to handcode any javascript to create dependent select boxes.

Here’s a screenshot of the page i’m going to create. The page contains two select boxes, the first contains a couple of countries, the second contains names of cities. The idea is that when you select a country, the second combobox will be automatically populated with a list of cities belonging to the country you selected.

ADF Faces dependent dropdown

The sourcecode of the JSF page:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<%@ page contentType="text/html;charset=windows-1252"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://xmlns.oracle.com/adf/faces" prefix="af"%>
<%@ taglib uri="http://xmlns.oracle.com/adf/faces/html" prefix="afh"%>
<f:view>
  <afh:html>
    <afh:head title="Select country and city">
      <meta http-equiv="Content-Type"
            content="text/html; charset=windows-1252"/>
    </afh:head>
    <afh:body>
      <h:form>
        <af:messages/>
        <af:selectOneChoice
            label="Country" value="#{page1BackingBean.country}"
            id="countryDropDown"
            binding="#{page1BackingBean.countryBinding}"
            autoSubmit="true"
            valueChangeListener="#{page1BackingBean.countryChangeListener}">
          <f:selectItems value="#{page1BackingBean.countrySelectItems}"/>
        </af:selectOneChoice>
        <af:selectOneChoice
            label="City" value="#{page1BackingBean.city}"
            id="citiesDropDown" binding="#{page1BackingBean.cityBinding}"
            partialTriggers="countryDropDown" autoSubmit="true">
          <f:selectItems value="#{page1BackingBean.citySelectItems}"/>
        </af:selectOneChoice>
        <af:outputText
         value="country: #{page1BackingBean.country}, city: #{page1BackingBean.city}"
         partialTriggers="countryDropDown citiesDropDown"/>
      </h:form>
    </afh:body>
  </afh:html>
</f:view>

When you select a country, the server is immediately informed of your choice. This is caused by the autoSubmit attribute on the first selectOneChoice tag. On the server the valueChangeListener for country is invoked, which populates the second dropdown box with the correct list of cities. Now the second combobox needs to be redrawn to display the new cities. You handle this with the partialTriggers on the second selectOneChoice. The partialTrigger attribute indicates that the component needs to be updated when the component with id countryDropDown has changed. I’ve done the same for the outputText component, to make sure the text also displays the latest values in the comboboxes.

The source code for the backing bean:

import java.util.ArrayList;
import java.util.Collection;

import javax.faces.component.UISelectItems;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;

import oracle.adf.view.faces.component.core.input.CoreSelectOneChoice;

public class Page1BackingBean {
    //values
    String country;
    String city;
    SelectItem[] countrySelectItems;
    SelectItem[] citySelectItems;

    //bindings
    private CoreSelectOneChoice countryBinding;
    private CoreSelectOneChoice cityBinding;

    // data
    SelectItem[] countries =
        new SelectItem[] { new SelectItem("Netherlands"), new SelectItem("Belgium") };
    SelectItem[] nlCities =
        new SelectItem[] { new SelectItem("Amsterdam"), new SelectItem("The Hague") };
    SelectItem[] beCities =
        new SelectItem[] { new SelectItem("Gent"), new SelectItem("Brugge") };

    public Page1BackingBean() {
        countrySelectItems = countries;
        citySelectItems = null;
    }

    public void setCountry(String country) { this.country = country; }
    public String getCountry() { return country; }
    public void setCity(String city) { this.city = city; }
    public String getCity() {return city;}

    public void setCountryBinding(CoreSelectOneChoice countryBinding) {
        this.countryBinding = countryBinding;
    }

    public CoreSelectOneChoice getCountryBinding() {
        return countryBinding;
    }

    public void setCityBinding(CoreSelectOneChoice cityBinding) {
        this.cityBinding = cityBinding;
    }

    public CoreSelectOneChoice getCityBinding() {
        return cityBinding;
    }

    public SelectItem[] getCountrySelectItems() {
        return countrySelectItems;
    }

    public SelectItem[] getCitySelectItems() {
        System.err.println("getCitySelectItems: ");
        if (countryBinding.getValue() != null) {
            if (countryBinding.getValue().equals("Netherlands")) {
                citySelectItems = nlCities;
            } else if (countryBinding.getValue().equals("Belgium")) {
                citySelectItems = beCities;
            }
        }
        return citySelectItems;
    }

    public void countryChangeListener(ValueChangeEvent evt) {
        cityBinding.setValue(null);
    }

}

Two methods are of interest here. The getCitySelectItems method returns the correct list of cities depending on the value of country. The countryChangeListener resets the city combobox when a new country has been selected. For this countryChangeListener to function correctly, the backing bean needs to be created in the session scope:

<?xml version="1.0" encoding="windows-1252"?>
<!DOCTYPE faces-config PUBLIC
  "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">
<faces-config xmlns="http://java.sun.com/JSF/Configuration">
  <application>
    <default-render-kit-id>oracle.adf.core</default-render-kit-id>
  </application>
  <managed-bean>
    <managed-bean-name>page1BackingBean</managed-bean-name>
    <managed-bean-class>Page1BackingBean</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
</faces-config>

I’m actually not too happy about the session scope. It means that you’ll get very weird effects when you open this page in two windows or two tabs. I’m a big user of browser tabs. For example, when i use our bug database, i’ll often open multiple “edit bug” pages in different tabs at the same time. This is probably not a thing you should do in a JSF application.

Share and Enjoy:
  • del.icio.us
  • Google Bookmarks
  • DZone
  • LinkedIn
  • SphereIt
  • StumbleUpon
  • Technorati

16 Responses to “Implementing dependent select boxes in JSF”

  1. shrgh Says:

    I am using ADF selectOneChoice and populating textfields etc. based on the selection in the SelectOneChoice. But it is not possible for me to create my BackingBean in session – it needs to be request scope. Is there any way of doing thi? Pls help.

  2. Andrej Koelewijn Says:

    Populating the textfields should work with request scope. This problem is making valueChangeListeners work, i think this requires session scope. But as long as you’re not using the valueChangeListener, request scope might work.

  3. shrgh Says:

    I am using a ValueChangeListener method – but is there an alternative to this? How else can we generate an action based on a change in the SelectOneChoice

  4. shrgh Says:

    is there any way to access the request from the constructor of the backing bean?

  5. Przemek Says:

    It works only if size of city list is always the same. What in other case???

  6. Kolley Says:

    Hi, great stuff, I was wondering how you would do this if the drop downs were in a table. How would you set the partialTriggers when the Id would change?

    Thanks!

  7. Pritesh Says:

    Hello Sir,
    Thanks a lot for such a code. It was useful.

  8. Eder Says:

    hi
    as I can insert the values of the selectOneChoice in the database

  9. Minal Says:

    Hi, I tried the above ex.. but my countryBinding.getValue() is coming null in ublic SelectItem[] getCitySelectItems(). What can be the reason for it.

    Please reply me soon on my mail Id,….
    minalk@acrotechnologies.com
    Thanks in advance

  10. bharath Says:

    hai,
    no items is displayed in city select drop down list box when i choose country in the country drop down list box.
    can u help me

    iam using netbeans6.0

  11. Ashu Says:

    The code provided in this site is useful. It can be understand eaisly. It works fine.

    Regrads
    Aswini

  12. Vin Says:

    Hi All,
    I’m developing an application that needed dependent select boxes like this. I’m using NetBeans 6.5 + JSF 1.2. My question is how to include ADF lib and use it in NB. Is there a better way do implement dependent Select boxes in the NB? Thank you.

  13. zhou Says:

    The backing bean should NOT be set as session scope. Set as request scope. You can bind the value of the selectOneChoice (country & city) to session variables instead of the local variable in the backing bean. The items in the combo box of the city are defined in the backing bean, but the content is depending on the current value of country selection in the session. I writed dependency components all the time for 2.5 years. It works and I use JDeveloper 10.1.3.0

  14. gaurav Says:

    there is the problem in the above code i have
    when we select the netherlands and not go to the city again select the other then the same city it show it is not showing the other city
    but if i select the city and again select the brazil then it show the brazil city
    my code is

  15. gaurav Says:

    Insert title here

    my bean is

    package UserBean;

    import javax.faces.event.ValueChangeEvent;
    import javax.faces.model.SelectItem;

    import org.apache.myfaces.trinidad.component.core.input.CoreSelectOneChoice;

    public class UserBean {

    private String city;

    private String country;

    SelectItem[] countrySelectItems;

    SelectItem[] citySelectItems;
    private CoreSelectOneChoice countryBinding;

    private CoreSelectOneChoice cityBinding;

    SelectItem[] countries =

    new SelectItem[] { new SelectItem(“India”), new SelectItem(“Belgium”) };

    SelectItem[] nlCities =

    new SelectItem[] { new SelectItem(“delhi”), new SelectItem(“noida”) };

    SelectItem[] beCities =

    new SelectItem[] { new SelectItem(“Gent”), new SelectItem(“Brugge”) };

    public UserBean() {

    countrySelectItems = countries;

    citySelectItems = null;

    }

    public void setCountry(String country) {
    this.country = country;
    }

    public String getCountry() {
    return country;
    }

    public void setCity(String city) {
    this.city = city;
    }

    public String getCity() {
    return city;
    }

    public void setCountryBinding(CoreSelectOneChoice countryBinding) {

    this.countryBinding = countryBinding;

    }

    public CoreSelectOneChoice getCountryBinding() {

    return countryBinding;

    }

    public void setCityBinding(CoreSelectOneChoice cityBinding) {

    this.cityBinding = cityBinding;

    }

    public CoreSelectOneChoice getCityBinding() {

    return cityBinding;

    }

    public SelectItem[] getCountrySelectItems() {

    return countrySelectItems;

    }

    public SelectItem[] getCitySelectItems() {

    if (countryBinding.getValue() != null) {

    if (countryBinding.getValue().equals(“India”)) {

    citySelectItems = nlCities;

    } else if (countryBinding.getValue().equals(“Belgium”)) {

    citySelectItems = beCities;

    }

    }

    return citySelectItems;

    }

    public void countryChangeListener(ValueChangeEvent evt) {

    cityBinding.setValue(null);

    }

    }
    and the face-config.xml is

    page1BackingBean
    UserBean.UserBean
    request

  16. gaurav Says:

    can any one help me

Leave a Reply

Technology