1

I got

j_idt7:city: Validation Error: Value is not valid

Cutting to the cheese, ManagedBean code:

 //few imports here
 @ManagedBean
 @SessionScoped
 public class CountriesAndCities implements Serializable{
 private List<SelectItem> countries;
 private List<SelectItem> cities;
 private  Map<String,List> m;
 private String selectedCountry;

 public String getSelectedCountry() {
return selectedCountry;
 }

 public void setSelectedCountry(String selectedCountry) {
this.selectedCountry = selectedCountry;
 }

 public CountriesAndCities(){
countries = new ArrayList<SelectItem>();
cities = new ArrayList<SelectItem>();
m = new HashMap<String,List>();
m.put("France", Arrays.asList("paris","marseille"));
m.put("England", Arrays.asList("Munchester","liverpoor"));

 }

 public  List<SelectItem> getCountries(){   
cities.removeAll(cities);
countries.removeAll(countries); 
countries.add(new SelectItem("select country"));
for(Map.Entry<String, List> entry: m.entrySet()){
    countries.add(new SelectItem(entry.getKey()));
    }

return countries;
 }
 public List<SelectItem> getCities(){   
for(Map.Entry<String, List> entry: m.entrySet())
      {if(entry.getKey().toString().equals(selectedCountry)){
        cities.addAll(entry.getValue());
        break;
    }
}
return cities;
 }
 public void checkSelectedCountry(ValueChangeEvent event){
selectedCountry = event.getNewValue().toString();   
 }

Here's the snippet of my .xhtml :

 <h:selectOneMenu immediate="true" value="#{countriesAndCities.selectedCountry}" 
 onchange="submit()" valueChangeListener="#{countriesAndCities.checkSelectedCountry}">
 <f:selectItems value="#{countriesAndCities.countries}"></f:selectItems>
 </h:selectOneMenu>
 <br/>
 <h:selectOneMenu id="city">
 <f:selectItems value="#{countriesAndCities.cities}"></f:selectItems>
 </h:selectOneMenu>     
 </h:form>

The code does what is supposed to be, But I get the error mentioned above at first line, only when i click on England and select country choices, I dunno why, I've written the same task in Ajaxized code, and It worked fine, any hand would be dead thankful .

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Rehme
  • 323
  • 3
  • 6
  • 20

3 Answers3

3

The error Validation Error: Value is not valid will be thrown when the equals() test of the selected item has not returned true for any of the available items in the list. So, it has basically the following two causes:

  1. The equals() method of the item value type is missing or broken.
  2. The list of available items has incompatibly changed during the submit.

The item value type is String, so its equals() method is undoubtedly properly implemented (else it would break everything in Java world). So cause 1 can be scratched. Left over cause 2. Yes, indeed, you're emptying the list of available cities in the getter of countries for some unclear reason.

public List<SelectItem> getCountries() {   
    cities.removeAll(cities);
    // ...
}

This code just doesn't make sense. The getter is also called when the selected country is to be validated. This way the selected city can't be found in the list of available cities when it's about to be validated later on. By the way, your getCities() also doesn't make any sense. You're looping over the whole map instead of just using Map#get().

This all is just broken code design. Getters should not contain any business logic at all. Getters should solely return bean properties. Those bean properties should already be preinitialized long before in (post)constructor or in action(listener) method.

How to fix this is a good second problem. The <h:selectOneMenu valueChangeListener> is basically abused here. It's the wrong tool for the job. You should be using <f:ajax listener> for this instead. But if you really insist in abusing the valueChangeListener for the job, then you should have solved it as follows, whereby you queue the ValueChangeEvent to INVOKE_APPLICATION phase and perform the job during that phase (as if it were an (ajax) action listener method):

private List<String> countries;
private List<String> cities;
private Map<String, List<String>> countriesAndCities;
private String selectedCountry;

@PostConstruct
public void init() {
    countriesAndCities = new LinkedHashMap<String, List<String>>(); // Note: LinkedHashMap remembers insertion order, HashMap not.
    countriesAndCities.put("France", Arrays.asList("paris","marseille"));
    countriesAndCities.put("England", Arrays.asList("Munchester","liverpoor"));
    countries = new ArrayList<String>(countriesAndCities.keySet());
}

public void checkSelectedCountry(ValueChangeEvent event) { // I'd rename that method name to loadCities() or something more sensible.
    if (event.getPhaseId() != PhaseId.INVOKE_APPPLICATION) {
        // Move the job to the INVOKE_APPLICATION phase. 
        // The PROCESS_VALIDATIONS phase is the wrong phase for the "action listener"-like job.
        event.setPhaseId(PhaseId.INVOKE_APPLICATION);
        event.queue();
        return;
    }

    cities = countriesAndCities.get(selectedCountry);
}

// Getters and setters. Do not change them! They should just get and set properties. Nothing more!

public List<String> getCountries(){   
    return countries;
}

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

public String getSelectedCountry() {
    return selectedCountry;
}

public void setSelectedCountry(String selectedCountry) {
    this.selectedCountry = selectedCountry;
}

Note that this still causes potential problems when you have some form validation around. A much better solution is to just use <f:ajax> instead.

See also:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Hey BalusC, thank you is not enough, I guess I was so blind to let all these errors pass by, But I try your code out, getting this exception : – Rehme Dec 05 '12 at 06:29
  • Never mind about the comment above, my mistake, I retried your code and it ran without a hitch, You're the man of the JSF :), keep up the magnificent work, many thanks, sir . – Rehme Dec 05 '12 at 06:40
1

get rid of immediate="true" and onchange="submit()"

add

<f:ajax render="city"/>

inside your first <h:selectOneMenu

like this

<h:selectOneMenu  value="#{countriesAndCities.selectedCountry}" valueChangeListener="#{countriesAndCities.checkSelectedCountry}">
     <f:selectItems value="#{countriesAndCities.countries}"></f:selectItems>
     <f:ajax render="city"/>
</h:selectOneMenu>

and read this Learning JSF2: Ajax in JSF – using f:ajax tag

Daniel
  • 36,833
  • 10
  • 119
  • 200
  • first of all, thank you for putting time into this, But If you've read my question carefully, You would've noticed that I said, I've done the task in Ajaxized way, But I just want to do it in event-handling way . – Rehme Dec 04 '12 at 15:44
  • I read it , but had no idea that *Ajaxized code* equals `using f:ajax` – Daniel Dec 04 '12 at 15:46
1

I'd suggest you do less processing(read : no processing) in your getters. JSF will throw a validation error if for any reason the content of a datasource bound to a component is modified mid request, as a soft security feature. Move the population of those lists to lifecycle hooks (a public void no-args method, annotated with @PostConstruct) that will run immediately after the bean has been to instantiated by the context

I assume you're backing your view with a session scoped bean for the sake of ajax (bad design). Change that bean to an @ViewScoped to smooth out your design and enjoy your ajax processing

kolossus
  • 20,559
  • 3
  • 52
  • 104