Francesco
Francesco

Reputation: 2382

h:selectOneMenu : validation error

I defined the following drop down box in my page: ...

<td>
  <h:selectOneMenu id="bootenvironment1" value="#{detailModel.selectedBootenvironment1}"
                   disabled="#{detailModel.mode == detailModel.viewMode}">
    <f:selectItems value="#{detailModel.availableBootenvironments}"/>
  </h:selectOneMenu>
</td>

In my model I have:

...
  private Map<String, Bootenvironment> availableBootenvironments;

  public DefinitionDetailModel()
  {
    super();
  }

  public String getSelectedBootenvironment1()
  {
    if (((Definition) getAfterObject()).getBootenvironment1() != null)
    {
      return ((Definition) getAfterObject()).getBootenvironment1().getEnvironmentName();
    }

    return "--Please select one--";
  }

  public void setSelectedBootenvironment1( String selectedBootenvironment )
  {
    ((Definition) getAfterObject()).setBootenvironment1(availableBootenvironments.get(selectedBootenvironment));
  }
  ...

And in the controller I set the availableBootenvironments map:

private void fetchBootenvironments()
  {
    ...
    @SuppressWarnings( "unchecked" )
    List<Bootenvironment> bootenvironments = (List<Bootenvironment>) ...

    Map<String, Bootenvironment> availableBootenvironments = new HashMap<String, Bootenvironment>();

    availableBootenvironments.put("--Please select one--", null);

    for(Bootenvironment bootenvironment : bootenvironments)
    {
      availableBootenvironments.put(bootenvironment.getEnvironmentName(), bootenvironment);
    }

    ((DefinitionDetailModel) detailModel).setAvailableBootenvironments(availableBootenvironments);
  }

The problem is that when I click a button in the page (which is bound to an action), I get the error:

detailForm:bootenvironment1: Validation error: value is not valid.

I don't understand where the error is; the value for selectItems is a map with the object's name-field(so a string) as key and the object itself as value. Then the value for the default selected (value="#{detailModel.selectedBootenvironment1}") is a string too as you can see in the getter/setter method of the model.

Another problem (maybe related to the previous one) is that when the page first loads, the default selected value should be --Please select one--- as the getBootenvironment1() returns null, but this does not happen: another one from the list is selected.

Can you please help me understanding what/where am I doing wrong?

EDIT

I implemented the Converter as you said:

@FacesConverter( forClass = Bootenvironment.class )
public class BootenvironmentConverter implements Converter
{

  @Override
  public String getAsString( FacesContext context, UIComponent component, Object modelValue ) throws ConverterException
  {
    return String.valueOf(((Bootenvironment) modelValue).getDbId());
  }

  @Override
  public Object getAsObject( FacesContext context, UIComponent component, String submittedValue ) throws ConverterException
  {
    List<Bootenvironment> bootenvironments = ... (get from DB where dbid=submittedValue)

    return bootenvironments.get(0);
  }

}

But now I have the following error:

java.lang.ClassCastException: java.lang.String cannot be cast to ch.ethz.id.wai.bootrobot.bo.Bootenvironment

Upvotes: 0

Views: 440

Answers (1)

BalusC
BalusC

Reputation: 1108632

You will get this error when the selected item value doesn't pass the equals() test on any of the available item values.

And indeed, you've there a list of Bootenvironment item values, but you've bound the property to a String which indicates that you're relying on the Bootenvironment#toString() value being passed as submitted value and that you aren't using a normal JSF Converter at all. A String can never return true on an equals() test with an Bootenvironment object.

You'd need to provide a Converter which converts between Bootenvironment and its unique String representation. Usually, the technical ID (such as the autogenerated PK from the database) is been used as the unique String representation.

@FacesConverter(forClass=Bootenvironment.class)
public class BootenvironmentConverter implements Converter {

    @Override
    public void getAsString(FacesContext context, UIComponent component, Object modelValue) throws ConverterException {
        // Write code to convert Bootenvironment to its unique String representation. E.g.
        return String.valueOf(((Bootenvironment) modelValue).getId());
    }

    @Override 
    public void getAsObject(FacesContext context, UIComponent component, Object submittedValue) throws ConverterException {
        // Write code to convert unique String representation of Bootenvironment to concrete Bootenvironment. E.g.
        return someBootenvironmentService.find(Long.valueOf(submittedValue));
    }

}

Finally, after implementing the converter accordingly, you should be able to fix the selectedBootenvironment1 property to be a normal property without any mess in getter/setter:

private Bootenvironment selectedBootenvironment1;

public Bootenvironment getSelectedBootenvironment1() {
    return selectedBootenvironment1;
}

public void setSelectedBootenvironment1(Bootenvironment selectedBootenvironment1) {
    this.selectedBootenvironment1 = selectedBootenvironment1;
}

Upvotes: 2

Related Questions