jakartaee / faces

Jakarta Faces
Other
109 stars 55 forks source link

Solve "Cascading Dropdown Problem" #987

Open eclipse-faces-bot opened 13 years ago

eclipse-faces-bot commented 13 years ago

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.

Affected Versions

[1.1, 1.2, 2.0, 2.1]

eclipse-faces-bot commented 5 years ago
eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented Reported by @edburns

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented lamine_ba said: 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.

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented lamine_ba said: 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..

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented i_oss said: 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?

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented lamine_ba said: 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;

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

  }
eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented i_oss said: 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?

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented lamine_ba said: 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.

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented lamine_ba said:

public class Country {

    private Long id;
    private String name;    

}

public class City {

    private Long id;
    private String name;
    private Country country;

}
public interface Dao {

public List<Country> getCountries();

public List<City> getCities(Long country_id);

}
@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;
    }

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

}
<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>

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.......

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented lamine_ba said:

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))

    }

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

}
eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented @edburns said: In progress prototype

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented i_oss said: 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 basis, so the labels would always be the same and the value would be the full bean 2) Use itemValue and itemLabel on the 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}

")

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented lamine_ba said: 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

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 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.

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented @edburns said: The most recent patch, a collaboration from Lamine and I, but mostly Lamine, shows one way to do this.

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented i_oss said: 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.

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented lamine_ba said: 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}" />
eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented jakobkorherr said:

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...).

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented lamine_ba said: 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.

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented jakobkorherr said: 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 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!

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented lamine_ba said: 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.

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented @edburns said: I don't think we made the var attribute optional after all.

eclipse-faces-bot commented 10 years ago

@glassfishrobot Commented @edburns said: Set priority to baseline ahead of JSF 2.3 triage. Priorities will be assigned accurately after this exercise.

eclipse-faces-bot commented 10 years ago

@glassfishrobot Commented @manfredriem said: Setting priority to Minor

eclipse-faces-bot commented 13 years ago

@glassfishrobot Commented File: 20110510-i_spec_987.patch Attached By: @edburns

eclipse-faces-bot commented 7 years ago

@glassfishrobot Commented This issue was imported from java.net JIRA JAVASERVERFACES_SPEC_PUBLIC-987