Question

I want to dynamically create a dropdown (HtmlSelectOneMenu) with all possible options. The option currently set should be preselected. To achieve this I created a value expression for my form:

String jsfValue = String.format("#{%s.item.%s}", getControllerBeanName(), key);
ValueExpression valueExpression = JSFUtils.createValueExpression(jsfValue, String.class);
menu.setValueExpression("value", valueExpression);

The string #{%s.item.%s} evaluates to #{playlistController.item.category} and category is the object of type Category that I want to bind to my dropdown.

To use the SelectItemsConverter from OmniFaces I changed the toString() method of Category to:

@Override
public String toString() {
  return String.format("%s[id=%d]", getClass().getSimpleName(), getId());
}

My code for the form generation looks like this (Note: Category extends BaseEntity):

  private UIComponent createDropdown(FormInput property) {
    String key = property.getKey();
    String beanName = key + "Controller";
    GenFormBaseController controller = (GenFormBaseController) JSFUtils.getManagedBean(beanName);

    List<BaseEntity> list = controller.getService().findAll();
    List<SelectItem> selectItems = new ArrayList<>(list.size());
    for (BaseEntity itemInList : list) {
      selectItems.add(new SelectItem(itemInList, itemInList.getName()));
    }

    UISelectItems items = new UISelectItems();
    items.setValue(selectItems.toArray());

    HtmlSelectOneMenu menu = new HtmlSelectOneMenu();
    menu.setConverter(new SelectItemsConverter());
    menu.setId(key);
    menu.getChildren().add(items);

    String jsfValue = String.format("#{%s.item.%s}", getControllerBeanName(), key);
    ValueExpression valueExpression = JSFUtils.createValueExpression(jsfValue, String.class);
    menu.setValueExpression("value", valueExpression);

    return menu;
  }

If I set menu.setConverter(new SelectItemsConverter()); then the wrong item is preselected. But If I remove it, then the correct item is selected but when I try to save the form it fails because it has no converter for the dropdown.

Can anyone help me? I've published the code on GitHub. The method createDropdown can be found at the bottom of the linked code.

Was it helpful?

Solution 2

I found the error in the code. Simply the equals() and hashCode() of the BaseEntity were insufficient. And the error BalusC already mentioned. The resulting code looks like the following:

GenFormBaseController.java

private UIComponent createDropdown(FormInput property) {
  String key = property.getKey();
  Class<?> expectedType = property.getValue();

  String beanName = lowerFirstChar(expectedType.getSimpleName()) + "Controller";
  GenFormBaseController controller = (GenFormBaseController) JSFUtils.getManagedBean(beanName);

  List<BaseEntity> list = controller.getService().findAll();
  List<SelectItem> selectItems = new ArrayList<>(list.size());
  for (BaseEntity itemInList : list) {
    selectItems.add(new SelectItem(itemInList, itemInList.getName()));
  }

  UISelectItems items = new UISelectItems();
  items.setValue(selectItems);

  HtmlSelectOneMenu menu = new HtmlSelectOneMenu();
  menu.setConverter(new SelectItemsConverter());
  menu.setId(key);
  menu.getChildren().add(items);

  String jsfValue = String.format("#{%s.item.%s}", getControllerBeanName(), key);
  ValueExpression valueExpression = JSFUtils.createValueExpression(jsfValue, expectedType);
  menu.setValueExpression("value", valueExpression);

  return menu;
}

BaseEntity.java

@Override
public int hashCode() {
  int hash = 7;
  hash = 97 * hash + Objects.hashCode(this.id);
  return hash;
}

@Override
public boolean equals(Object obj) {
  if (obj == null) {
    return false;
  }
  if (getClass() != obj.getClass()) {
    return false;
  }
  final BaseEntity other = (BaseEntity) obj;
  if (!Objects.equals(this.id, other.id)) {
    return false;
  }
  return true;
}

OTHER TIPS

Your mistake is here, in the String.class type argument:

ValueExpression valueExpression = JSFUtils.createValueExpression(jsfValue, String.class);

A Category is not a String. Use Category.class instead and everything should be well. At least, theoretically. I can't copy'n'paste'n'run this piece of code without changes.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top