Issue Details (XML | Word | Printable)

Key: JAVASERVERFACES_SPEC_PUBLIC-987
Type: Improvement Improvement
Status: Reopened Reopened
Priority: Major Major
Assignee: Unassigned
Reporter: Ed Burns
Votes: 0
Watchers: 1
Operations

If you were logged in you would be able to see more operations.
javaserverfaces-spec-public

Solve "Cascading Dropdown Problem"

Created: 25/Apr/11 09:28 AM   Updated: 17/Dec/13 03:25 PM
Component/s: Facelets/VDL
Affects Version/s: 1.1, 1.2, 2.0, 2.1
Fix Version/s: 2.3

Time Tracking:
Not Specified

File Attachments: 1. Text File 20110510-i_spec_987.patch (263 kB) 10/May/11 03:06 PM - Ed Burns


Status Whiteboard:

size_large impotance_large

Tags:
Participants: Ed Burns, i_oss, Jakob Korherr and lamine_ba


 Description  « Hide

>>>>> On Sat, 23 Apr 2011 18:41:42 +0000, Jason Bonifay said:

JB> 1) the classic "cascading drop down problem" - this is a scenario
JB> where the first dropdown is dynamically populated with values and
JB> the selection available in the second dropdown is dependent on the
JB> value selected in the first dropdown (e.g.: select a country then
JB> the list of cities are dynamically populated). Important to note
JB> here - if dropdown #1 has static values this problem obviously
JB> trivial, the challenge arises from a dynamically defined list being
JB> dependent on the selected value from a parent list the is also
JB> dynamically defined. ASP.NET solves this (and other problems) by
JB> defining several "page lifecycle functions" akin to JSF but let's
JB> you easily override functions in the backing classes (i.e.:
JB> "page_init", "page_load" etc) to programmatically control where to
JB> populate each dropdown. Remember - You can't paint the values in
JB> dropdown #2 until you can retrieve the selected index from dropdown
JB> #1 - but you can't retrieve the selected index until you first fill
JB> the options so the framework can then bind the selected index.



lamine_ba added a comment - 25/Apr/11 12:10 PM - edited

One of the solutions to the "Cascading Dropdown problem " is to make the UISelect components aware of each other by adding a parent-child relations. If a selection occurs in the parent (change event), the child is update automatically. The JSF framework will send an Ajax call to invoke a method in the managed bean like for example : load_childs(parent_id ). By parent_id, I mean the value selected in the parent combo, the id of a country for example to invoke a method like load_cities(country_id ) for example. The selected value is the only thing a developer needs in order to run successfully his data access logic. A developer must provide in the UI the method to be called for the partial update. The JSF framework will manage transparently the interaction (display,update,remove,disable) with a client side approach. no UI binding in your managed beans thus making your application scalable.
one selection and multiple selection must be managed.


lamine_ba added a comment - 27/Apr/11 10:38 AM

JB> Agreed. This seems to be the way that other frameworks (and the manual work-around I’ve implemented) are solving it and it seems to work quite well

MLB> Yes it is my opinion, the idea has already been tested and it works quite well. So, let's implement it..


i_oss added a comment - 27/Apr/11 05:05 PM

Sorry I don't quite understand the problem, isn't the setter of <h:selectXXX value="#{my.selectionSetter}"/> the method you are looking for? if you change the items of dropdown #2 in the value-setter of #1, it should just work the way described above.

So I am not sure, which lifecylce-phase is missing here? Maybe if someone could point out at which time in the lifecycle "page_init" and "page_load" would take place?


lamine_ba added a comment - 28/Apr/11 06:18 AM

we are talking about doing something like this : http://www.coderanch.com/t/510988/JSF/java/ajax-update-selectOneMenu.

"page_init" and "page_load" functions belong to ASP.NET lifecycle.

our problem is to update dynamically a dropdown based on a selection made on another dropdown (change event). and the solution should be something like this.

<h:selectOneMenu value="#{bean.country}" id="countries">  
        <f:selectItems value="#{bean.countries}" />  
        <f:ajax event="change" render="cities"  />  
    </h:selectOneMenu>  


   <h:selectOneMenu value="#{bean.city}" id="cities">  
        <f:selectItems value="#{bean.cities}" />  
    </h:selectOneMenu>
@ManagedBean
  public class Bean {
   
  private Long country;
  private Long city;

  ............................................

  }

i_oss added a comment - 29/Apr/11 05:26 AM - edited

so it just should be:

(apart from Long probably not being very userfriendly in the rendered page )

public void setCountry(Long country) {
    // put checks for changes here, if it is a long running operation...
    this.cities = whateverToGetTheCitiesFor(country);
    this.country = country;
}

Or am I missing something here?

Still I'd like to know where page_init and page_load would be added to the lifecycle?


lamine_ba added a comment - 29/Apr/11 10:58 AM

i_oss> (apart from Long probably not being very userfriendly in the rendered page )

You can choose whatever you want. I often use the Long type for my id and my primary key.

public class Country {

   @Id
   private Long id; // choose whatever you want, Long,Integer,String,....
   private String name;
}

i_oss>

public void setCountry(Long country) {
    // put checks for changes here, if it is a long running operation...
    this.cities = whateverToGetTheCitiesFor(country);
    this.country = country;
}

And you are saving three states which can be a good thing when you have the need to cache your data in order to prevent multiple time access

@ManagedBean
  public class Bean {
   
  private Long country;             //1
  private Long city;                //2
  private List<SelectItem> cities;  //3
  ............................................

  }

and you have created two methods

public List<SelectItem> whateverToGetTheCitiesFor(Long id country) {
}

public List<SelectItem> getCities() {   
   return cities;
}

which could be just one method

public List<SelectItem> getCities() {   
   // call your DAO here
}

Your solution is not my solution and that is the reason why I have just created a skeleton. The only thing I need myself is to be able to know the id of the country and the id of the city that have been selected. ( value="#{bean.country}", value="#{bean.city}").

i_oss> Or am I missing something here?

Yes, you are really missing something here. We are talking about ASP.net, not about JSF

JB>ASP.NET solves this (and other problems) by
JB> defining several "page lifecycle functions" akin to JSF but let's
JB> you easily override functions in the backing classes (i.e.:
JB> "page_init", "page_load" etc) to programmatically control where to
JB> populate each dropdown. Remember - You can't paint the values in
JB> dropdown #2 until you can retrieve the selected index from dropdown
JB> #1 - but you can't retrieve the selected index until you first fill
JB> the options so the framework can then bind the selected index.

This issue is not really an issue because you can create a working solution with JSF in 5 minutes. We were just unaware that we could realize it without using a programmatic approach. Its only merit is that it helps us find some ideas in how to ease the job and as soon as we have finished to describe them, it will be closed.


lamine_ba added a comment - 01/May/11 09:06 AM - edited
  • Cascading Dropdown implementation with JSF 2.0

  • The entities
public class Country {

	private Long id;
	private String name;	
	
}

public class City {

	private Long id;
	private String name;
	private Country country;
	
}
  • The DAO
public interface Dao {

public List<Country> getCountries();
	
public List<City> getCities(Long country_id);

}
  • The Managed Bean
@ManagedBean
public class Bean {

	private Long country_id=new Long(0);  // select the first option in the combo 
	private Long city_id=new Long(0);    // select the first option in the combo
	private @Inject Dao dao;
	
	public List<SelectItem> getCountries() {
		
		List<SelectItem> items=new ArrayList<SelectItem>();
		for(Country country:dao.getCountries())
			items.add(new SelectItem(country.getId(),country.getName()));
		return items;
	}
	
	public List<SelectItem> getCities() {
		
		List<SelectItem> items=new ArrayList<SelectItem>();
		for(City city: dao.getCities(country_id))
			items.add(new SelectItem(city.getId(),city.getName()));
		return items;
	}
	
	-----------------------------------------------------------------

}
  • The Facelets view
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">

<h:head>

<h:outputScript name="jsf.js" library="javax.faces"/> 

</h:head>

<body>

<h:form>

<h:selectOneMenu value="#{bean.country_id}" id="countries">
        <f:selectItem itemLabel="-- Select a Country -- " itemValue="0"/>  
        <f:selectItems value="#{bean.countries}" />  
        <f:ajax event="change"  render="cities" /> 
</h:selectOneMenu>  

 <h:selectOneMenu value="#{bean.city_id}" id="cities">
        <f:selectItem itemLabel="-- Select a City -- " itemValue="0"/>    
        <f:selectItems value="#{bean.cities}" /> 
 </h:selectOneMenu>
 

</h:form>

</body>

</html>
  • The Conclusion

And That's all. we have created a cascading dropdown without using a programmatic approach like this ugly one. (http://www.juurlink.org/2011/02/dynamic-dependent-list-boxes/). 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.

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">

<h:head>

<h:outputScript name="jsf.js" library="javax.faces"/> 

</h:head>

<body>

<h:form>

<h:selectOneMenu value="#{bean.country_id}" id="countries">
        <f:selectItem itemLabel="-- Select a Country -- " itemValue="0"/>  
        <f:selectItems value="#{bean.countries}" />  
        <f:ajax event="change"  render="cities" /> 
</h:selectOneMenu>  

 <h:selectOneMenu value="#{bean.city_id}" id="cities">
        <f:selectItem itemLabel="-- Select a City -- " itemValue="0"/>    
        <f:selectItems value="#{bean.cities}" /> 
 </h:selectOneMenu>
 
<h:commandButton action="#{bean.save}"  value="Save">

</h:form>

</body>

</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 this same value 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 the session scope".

@ManagedBean
@SessionScoped
public class Bean {

  public String save() {

    ----------------------

  }
}

and definitely that was the solution.......


lamine_ba added a comment - 01/May/11 09:33 AM - edited
  • Cascading Dropdown Implementation with JSF 2.2

  • The @SelectionModel annotation

creating SelectItem objects is a recurrent and tedious task for a developer. We must automate it. The developer will save time and we will gain in performance because we have a double iteration, one for creating a list of SelectItem objects and another iteration for creating from this same list another list of UISelectItem components. With this annotation, we can cut the first.

@SelectionModel(value="id",label="name")
on a method returning a list or a Collection of Country means virtually

new SelectItem(country.getId(),country.getName())
for each country.

@ManagedBean
@SessionScoped
public class Bean {

	
        @SelectionModel(value="id",label="name")
	public List<Country> getCountries() {
	  
           return dao.getCountries();	
	}
	
        @SelectionModel(value="id",label="name")
	public List<City> getCities() {
		
	   return  dao.getCities(country_id))
	
	}
	
	-----------------------------------------------------------------

}

If we bring a convention on how to get the value and the label on objects, we can make this annotation optional which will be again for us a gain of time and clarity.

@ManagedBean
@SessionScoped
public class Bean {

	
 	public List<Country> getCountries() {
	  
           return dao.getCountries();	
	}
	
 
	public List<City> getCities() {
		
	   return  dao.getCities(country_id))
	
	}
	
	-----------------------------------------------------------------

}

Ed Burns added a comment - 10/May/11 09:33 AM

In progress prototype


i_oss added a comment - 10/May/11 11:08 AM

The @SelectionModel is a nice idea.
At the moment you can avoid using SelectItems already by:
1) A Converter, and a sensible toString method on your beans (so this is the current "convention" for not using SelectItems and/or Annotations)
Drawbacks: This can't be tuned on a per <h:select...> basis, so the labels would always be the same and the value would be the full bean
2) Use itemValue and itemLabel on the <f:selectItems...>
Drawbacks: The template developer then has to know what value is expected, and how to build the label from the bean.

In case we create @SelectionModel, I think value and label should be EL with a implicit varname for the items in the list
for example: @SelectionModel(value="#{item.id}", label="#{item.firstname} #{item.lastname}")


lamine_ba added a comment - 10/May/11 12:24 PM - edited

Hi Imre. Yes The @SelectionModel is a nice idea to avoid using SelectItems. I had this idea because I was unaware that this annotation already exists in JSF 2.0 through another representation more cleaner, more simpler and more transparent.

2) Use itemValue and itemLabel on the <f:selectItems...>

public List<Country> getCountries() {
        return dao.getCountries();
}
<f:selectItems value="#{bean.countries}" 
var="country" itemValue="#{country.id}" itemLabel="#{country.name}" />

I prefer now this way which makes my annotation obsolete and there is no way to get an annotation through the Expression Language. But It would be wonderful to have such feature in the EL api.

2) Use itemValue and itemLabel on the <f:selectItems...>
Drawbacks: The template developer then has to know what value is expected, and how to build the label from the bean.

Yes I agree and that is why we want to bring a convention in JSF 2.2. In the real life, every man has an ID (fingerprint, DNA) and a name. In the software life, most of the objects we display are entities. So if we don't provide the information on how to resolve the ItemValue or the ItemLabel, their values will be resolved by invoking getId() and getName() on our objects.

JSF 2.2

public List<Country> getCountries() {
        return dao.getCountries();
}
<f:selectItems value="#{bean.countries}"/>

means virtually

<f:selectItems value="#{bean.countries}" var="country" 
itemValue="#{country.id}" itemLabel="#{country.name}" />

The var attribute like the ItemValue and ItemLabel is now optional and by default its value is equal to 'it'.

<f:selectItems value="#{bean.countries}" itemValue="#{it.id}" itemLabel="#{it.name}" />

so adhere to our JSF 2.2 convention and you will save time.


Ed Burns added a comment - 10/May/11 03:13 PM

The most recent patch, a collaboration from Lamine and I, but mostly Lamine, shows one way to do this.


i_oss added a comment - 10/May/11 03:34 PM

Hi Lamine, only a short note

<f:selectItems value="#{bean.countries}"/>

at the moment is equivalent to

<f:selectItems var="country" itemValue="#{country}" itemLabel="#{country.toString()}"/>

With the #{country} being coerced to/from String with a converter (if needed and existing).
So I don't think we want to change the convention that already exists, possibly breaking existing apps.


lamine_ba added a comment - 11/May/11 06:07 AM

Hi Imre, I'm really sorry, I haven't thought about that.

There is already a behavior for

<f:selectItems value="#{bean.countries}"/>

If we change that, the existing apps will break and that is not really a good thing.

So I will just keep this part of my writing of course if you don't see any problem with it

The var attribute is now optional and by default its value is equal to 'it'.

<f:selectItems value="#{bean.countries}" itemValue="#{it.id}" itemLabel="#{it.name}" />

Jakob Korherr added a comment - 12/May/11 12:27 PM

> The var attribute is now optional and by default its value is equal to 'it'.

Why are we using the term "it" here exactly? Should it mean "item"? In that case I would propose naming it "item" instead of "it", because "item" is a LOT more significant than "it" (and it's just two more letters...).


lamine_ba added a comment - 12/May/11 07:56 PM

Hi Jakob. Why are we using the term "it"? That is a very good question. It is just something I have borrowed to Groovy.

Inside each closure, Groovy defines a default variable,
it, for one argument passed to the closure. This makes
it very easy to have a single parameter for your closures without
having to explicitly declare it. The it variable works
just like Perl’s $_ variable in subroutines:

namePrinter = { println "Hello, ${it}!" };
namePrinter("John"); // prints "Hello, John!"

If you prefer, we can name it "item". It is also a very nice proposal. I'll be happy with whatever name you'll choose as long as the var attribute is optional. That is the only thing I want.


Jakob Korherr added a comment - 13/May/11 02:36 AM

Hi Lamine,

Thanks a lot for the clarification. I did not know that!

Nevertheless, I prefer "item" over "it". It really is a lot more significant in this case, because most attributes of <f:selectItems> which will use this implicit var are starting with the term "item" --> "itemLabel", "itemValue".

So it would be very great, if we could change that!


lamine_ba added a comment - 13/May/11 10:31 AM

You are right Jakob and I vote for "item". Don't worry, nothing has been done yet. It was just an idea we were testing.


Ed Burns added a comment - 09/Aug/11 08:06 PM

I don't think we made the var attribute optional after all.