tags:

views:

44

answers:

4

A question we keep getting from users is to have selections (drop-down lists or multi-selects) with dependent values. For instance, a user would pick a country, then the system populates a city dropdown with the cities in that country.

I've made this work often enough in session (or conversation scope) but now, for a real lightweight scenario, I would like to have it work in request scope.

Here's some dummy code showing the problem (usually we would use A4J to populate the dropdowns without a full refresh, but the problem can be demonstrated with plain jsf as well):

JSF:

<h:form>
    <p>
        <h:selectOneMenu value="#{bean.selectedSourceValue}">
            <f:selectItems value="#{bean.sourceValues}" />
        </h:selectOneMenu>
    </p>
    <p>
        <h:selectOneMenu value="#{bean.selectedDependentValue}">
            <f:selectItems value="#{bean.dependentValues}" />
        </h:selectOneMenu>
    </p>
    <p>
        <h:commandButton value="submit" />
    </p>
</h:form>

Backing bean:

public class Bean {

    private Integer selectedSourceValue;
    private Integer selectedDependentValue;

    /**
     * @return values for the first selection.  Numbers from 1 to 10.
     */
    public List<SelectItem> getSourceValues(){
        List<SelectItem> r = new ArrayList<SelectItem>();
        for(int i=1; i<=10; i++){
            r.add(new SelectItem(i));
        }
        return r;
    }

    /**
     * @return values for the second selection.  First ten powers of the selected first value.
     */
    public List<SelectItem> getDependentValues(){
        if (selectedSourceValue==null) return Collections.emptyList();
        List<SelectItem> r = new ArrayList<SelectItem>();
        for(int i=1; i<=10; i++){
            r.add(new SelectItem((int)Math.pow(selectedSourceValue, i)));
        }
        return r;
    }

        // ... snipped some basic getter and setters 
}

Seems simple enough. The problem is when making the second selection. When the second dropdown is submitted, the combos are validated. But in the validation phase, the request-scoped bean is not populated yet, so getDependentValues() returns null. This causes jsf to throw a NoSuchElementException (using Sun RI).

Any idea on how to solve this, or even whether it's possible?

A: 

i think, you should look for ajax-based components (Icefaces allow this trick, maybe richfaces, primefaces, jsf2 (f:ajax) etc.). Or use validation in save button actionListener (as it turned out, it is much more flexible, at least in my project + you always can remove ajax to avoid sending enormous amount of requests to server in case of ajax-based components with partialSubmit=true).

foret
We're already using richfaces. But as mentioned, that doesn't really change anything about the heart of the issue. Doing the validation in an actionalistener is not a solution; it doens't prevent jsf from doing it's own validation anyway. (right now I don't even need any validation).
Joeri Hendrickx
1.jsf has no necessary validations. There are convertors, but i dont think you meant them.2.do you use partialSubmit attribute in selectBoxes, or whatever richfaces has for ajax?
foret
@foret: Yes, sure it does. One is the validation that the selected value was in fact an option. Yes, we're using ajaxSingle, which I think is about the same thing.
Joeri Hendrickx
+1  A: 

Right, you want to prepopulate the second dropdown based on the selected option of the first dropdown during the 2nd/3rd phases, far before JSF has done the update model values phase, thus the selectedSourceValue is still null. There are basically two ways to overcome this problem:

  1. Grab the submitted source value as request parameter from the request parameter map.

    selectedSourceValue = (Integer) externalContext.getRequestParameterMap().get("clientId");
    

    This is however nasty.

  2. Bind the dropdown component to an UIInput property and use its getSubmittedValue() method to obtain the submitted value.

    <h:selectOneMenu binding="#{bean.sourceMenu}">
    

    with

    private UIInput sourceMenu; // +getter +setter
    

    and then in getDependentValues()

    if (selectedSourceValue == null && sourceMenu.getSubmittedValue() != null) {
        selectedSourceValue = Integer.valueOf(sourceMenu.getSubmittedValue());
        // ...
    }
    

    A bit more work, but more abstract.

BalusC
Thanks for the answer. The project I'm working on right now is very dynamic though, and it's pretty hard to incorporate this. I did another workaround (which is also pretty hacky, but is still okay considering the nature of the project). I'll post it as an aswer.
Joeri Hendrickx
A: 

If you are using JSF2, try this:

<h:form>
  <p>
    <h:selectOneMenu id="selectionSource" value="#{bean.selectedSourceValue}">
        <f:selectItems value="#{bean.sourceValues}" />
        <f:ajax execute="selectionSource" render="dependentSelection"/>
    </h:selectOneMenu>
  </p>
  <p>
    <h:selectOneMenu id="dependentSelection" value="#{bean.selectedDependentValue}">
        <f:selectItems value="#{bean.dependentValues}" />
    </h:selectOneMenu>
  </p>
  <p>
    <h:commandButton value="submit" />
  </p>
</h:form>

Solution from here: http://pawelstawicki.blogspot.com/2010/03/simple-ajax-with-jsf-20.html

amorfis
I'm on JSF1.2, but still, this isn't an answer to the question.
Joeri Hendrickx
A: 

I ended up working around it by subclassing UISelectMany to not check whether the submitted value is part of the list. This works, and in our scenario, does not hurt.

Joeri Hendrickx