Skip to main content

Implementing a Cascading DropDown with JSF 2

Posted by lamine_ba on May 12, 2011 at 11:42 AM PDT

The Beginning

The term " Cascading DropDown" means a dynamic dependent list boxes that allow a “child” list box to refresh when a selection is made in a “parent” list box. It is a recurrent problem in the software space and it has only one solution but sadly several implementations of it that are depending on the tools that you are using and their limitations which can force you to invent ways that shouldn't be invented.This blog entry is all about how to implement a cascading dropdown using the JSF framework and today we have the simple requirement to display a list of countries and to update a list of cities once a country is selected. Having now the scenario, I think we can start to present the actors :

The Entities

  1. public class Country {
  2.  
  3.         private Long id;
  4.         private String name;   
  5.        
  6. }
  7.  
  8. public class City {
  9.  
  10.         private Long id;
  11.         private String name;
  12.         private Country country;
  13.        
  14. }

The DAO [Provide your own implementation]

  1. public interface Dao {
  2.  
  3. public List<Country> getCountries();
  4.        
  5. public List<City> getCities(Long country_id);
  6.  
  7. }

The Managed Bean

  1. @ManagedBean
  2. public class Bean {
  3.  
  4.         private Long country_id=0L;  // select the first option in the combo
  5.         private Long city_id=0L;    // select the first option in the combo
  6.         private @Inject Dao dao;
  7.        
  8.         public List<Country> getCountries() {
  9.                
  10.                 return dao.getCountries();
  11.                        
  12.         }
  13.        
  14.         public List<City> getCities() {
  15.                
  16.                 return dao.getCities(country_id);
  17.                        
  18.         }
  19.        
  20. ..................................................................
  21.  
  22. }

The Facelets view

  1. <html xmlns="http://www.w3.org/1999/xhtml"
  2. xmlns:f="http://java.sun.com/jsf/core"
  3. xmlns:h="http://java.sun.com/jsf/html">
  4.  
  5. <h:head>
  6.  
  7. <h:outputScript name="jsf.js" library="javax.faces"/>
  8.  
  9. </h:head>
  10.  
  11. <body>
  12.  
  13. <h:form>
  14.  
  15. <h:selectOneMenu value="#{bean.country_id}" id="countries">
  16.         <f:selectItem itemLabel="-- Select a Country -- " itemValue="0"/>  
  17.         <f:selectItems value="#{bean.countries}" var="country" itemValue="#{country.id}" itemLabel="#{country.name}"/>
  18.         <f:ajax event="change"  render="cities" />
  19. </h:selectOneMenu>  
  20.  
  21.  <h:selectOneMenu value="#{bean.city_id}" id="cities">
  22.         <f:selectItem itemLabel="-- Select a City -- " itemValue="0"/>    
  23.         <f:selectItems value="#{bean.cities}" var="city" itemValue="#{city.id}" itemLabel="#{city.name}"/>
  24.  </h:selectOneMenu>
  25.  
  26.  
  27. </h:form>
  28.  
  29. </body>
  30.  
  31. </html>

The End

And that's all. We have created a cascading dropdown without using a programmatic approach. We select a country in the first combo and automatically an ajax request is fired behind the scenes to update the other combo. This feature is really a powerful one until the idea to add a commandButton in your form to make a postback and save things, comes to you.

  1. <html xmlns="http://www.w3.org/1999/xhtml"
  2. xmlns:f="http://java.sun.com/jsf/core"
  3. xmlns:h="http://java.sun.com/jsf/html">
  4.  
  5. <h:head>
  6.  
  7. <h:outputScript name="jsf.js" library="javax.faces"/>
  8.  
  9. </h:head>
  10.  
  11. <body>
  12.  
  13. <h:form>
  14.  
  15. <h:selectOneMenu value="#{bean.country_id}" id="countries">
  16.         <f:selectItem itemLabel="-- Select a Country -- " itemValue="0"/>  
  17.         <f:selectItems value="#{bean.countries}" var="country" itemValue="#{country.id}" itemLabel="#{country.name}"/>
  18.         <f:ajax event="change"  render="cities" />
  19. </h:selectOneMenu>  
  20.  
  21.  <h:selectOneMenu value="#{bean.city_id}" id="cities">
  22.         <f:selectItem itemLabel="-- Select a City -- " itemValue="0"/>    
  23.         <f:selectItems value="#{bean.cities}" var="city" itemValue="#{city.id}" itemLabel="#{city.name}"/>
  24.  </h:selectOneMenu>
  25.  
  26. <h:commandButton action="#{bean.save}"  value="Save">
  27.  
  28. </h:form>
  29.  
  30. </body>
  31.  
  32. </html>

And boum! you get a validation error. Sadly, the combo displaying your cities is saying that the value you have picked in its list is not a valid one because it is not anymore in its list". Isn't it a craziness statement?. After some long and hard hours debugging the jsf.js file and looking at the tree printed in the console by my PhaseListener, I came accross no rationale idea. Everything was fine. The partial view processing and rendering were done perfectly and the state of the combo was updated and saved. And suddenly when I was about to loose hope, comes this ironical idea : "Hey put the managed bean in a higher scope".

  1. @ManagedBean
  2. @ViewScoped
  3. public class Bean {
  4.  
  5.   public String save() {
  6.  
  7.     return "next";
  8.  
  9.   }
  10. }

And definitely that was the solution and it is not ironical at all once you understand how the value of an UISelectOne is validated and what it does mean ' to put a managed bean in the request scope'. There is a big security concern to take into account when validating the value of an UISelectOne. This security concern is summarized into this simple question : How to prevent the client side to insert values that were not valid choices? No don't worry, the JSF framework is not asking you to provide this answer. This validation has been made as transparent as to make another call of your getCities() method for a regeneration of the data model in order to do a value comparison.

  1.  public List<City> getCities() {
  2.        
  3.      return dao.getCities(country_id);
  4.  
  5.  }

But wait a minute, the bean is created at each request and the Validation Phase is before the Update Model Values Phase. What JSF will get when running getCities() at this point of time? Just an empty list because the bean can't remember the country_id. And that is why we get this validation error ("The value is not valid") and that is why we have to put the bean in a higher scope. If we put the bean in the ViewScope, it will be removed as soon as we move to another view. Definitely the right place to put it. But wait a minute, isn't JSF calling my data access logic two times? A first call at the render phase and another call at the validation phase. That is really a big performance concern to deal with when having my data stored in a database. Then how to prevent that? Three solutions come to my mind:

  • The EG must find another way to validate the value without a regeneration of the data model
  • If the EG can't, I want to have a way to validate the value myself through a light query
  • Again if the EG can't, It is better to write your managed bean like this and let it go

 

  1. @ManagedBean
  2. @ViewScoped
  3. public class Bean {
  4.  
  5.     private Long country_id=0L;  // select the first option in the combo
  6.     private Long city_id=0L;    // select the first option in the combo
  7.     private @Inject Dao dao;
  8.     private List<Country> countries;  
  9.     private List<City> cities;
  10.  
  11.     public List<Country> getCountries() {
  12.        
  13.         if(countries==null)
  14.             countries=dao.getCountries();
  15.         return countries;
  16.     }
  17.    
  18.     public List<City> getCities() {
  19.        
  20.         if(cities==null)
  21.            cities=dao.getCities(country_id);
  22.         return cities;
  23.     }
  24.  
  25.     public void setCountry_id(Long country_id) {
  26.  
  27.            this.country_id=country_id;
  28.            cities=null;
  29.      }
  30.    
  31. }
AttachmentSize
menu1.PNG12.28 KB
menu2.PNG5.51 KB
Related Topics >>