如何从数据库中填充h:selectOneMenu的选项?

时间:2011-07-27 17:58:07

标签: database jsf selectonemenu

我正在创建一个Web应用程序,您必须从DB中读取对象/实体列表并在JSF <h:selectOneMenu>中填充它。我无法对此进行编码。有人可以告诉我该怎么做吗?

我知道如何从数据库中获取List<User>。我需要知道的是,如何在<h:selectOneMenu>中填充此列表。

<h:selectOneMenu value="#{bean.name}">
    ...?
</h:selectOneMenu>

5 个答案:

答案 0 :(得分:167)

根据您的问题历史记录,您使用的是JSF 2.x.所以,这是一个JSF 2.x目标答案。在JSF 1.x中,您将被迫在丑陋的SelectItem实例中包装项值/标签。幸运的是,JSF 2.x中不再需要这样做了。


基本示例

要直接回答您的问题,只需使用value List<T>指向T属性的<f:selectItems>,该属性是在bean(post)构造期间从DB保留的。这是一个基本启动示例,假设String实际上代表<h:selectOneMenu value="#{bean.name}"> <f:selectItems value="#{bean.names}" /> </h:selectOneMenu>

@ManagedBean
@RequestScoped
public class Bean {

    private String name;
    private List<String> names; 

    @EJB
    private NameService nameService;

    @PostConstruct
    public void init() {
        names = nameService.list();
    }

    // ... (getters, setters, etc)
}

T

这很简单。实际上,toString()的{​​{1}}将用于表示下拉项标签和值。因此,当您使用List<String>之类的复杂对象列表代替List<SomeEntity>并且您没有覆盖类“toString()方法”时,您会看到com.example.SomeEntity@hashcode作为项目值。请参阅下一节如何正确解决它。

另请注意,<f:selectItems>值的bean不一定需要与<h:selectOneMenu>值的bean相同。每当值实际上是应用程序范围的常量时,这非常有用,您只需在应用程序启动期间加载一次。然后,您可以将其设置为应用程序范围bean的属性。

<h:selectOneMenu value="#{bean.name}">
    <f:selectItems value="#{data.names}" />
</h:selectOneMenu>

复杂对象作为可用项

每当T涉及复杂对象(javabean),例如User属性为String的{​​{1}}时,您就可以使用name获取迭代变量的属性,您可以在var和/或itemValue属性中使用该迭代变量(如果省略itemLabel,则标签变为与值相同)。

示例#1:

itemLabel

<h:selectOneMenu value="#{bean.userName}">
    <f:selectItems value="#{bean.users}" var="user" itemValue="#{user.name}" />
</h:selectOneMenu>

或者当它有private String userName; private List<User> users; @EJB private UserService userService; @PostConstruct public void init() { users = userService.list(); } // ... (getters, setters, etc) 属性Long时,您宁愿将其设置为项目值:

示例#2:

id

<h:selectOneMenu value="#{bean.userId}">
    <f:selectItems value="#{bean.users}" var="user" itemValue="#{user.id}" itemLabel="#{user.name}" />
</h:selectOneMenu>

复杂对象作为选定项

每当您想将其设置为bean中的private Long userId; private List<User> users; // ... (the same as in previous bean example) 属性并且T代表T时,您需要烘焙自定义Converter它在User和唯一字符串表示(可以是User属性)之间进行转换。请注意id必须表示复杂对象本身,正好是需要设置为选择组件itemValue的类型。

value

<h:selectOneMenu value="#{bean.user}" converter="#{userConverter}">
    <f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>

private User user;
private List<User> users;

// ... (the same as in previous bean example)

(请注意@ManagedBean @RequestScoped public class UserConverter implements Converter { @EJB private UserService userService; @Override public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) { if (submittedValue == null || submittedValue.isEmpty()) { return null; } try { return userService.find(Long.valueOf(submittedValue)); } catch (NumberFormatException e) { throw new ConverterException(new FacesMessage(String.format("%s is not a valid User ID", submittedValue)), e); } } @Override public String getAsString(FacesContext context, UIComponent component, Object modelValue) { if (modelValue == null) { return ""; } if (modelValue instanceof User) { return String.valueOf(((User) modelValue).getId()); } else { throw new ConverterException(new FacesMessage(String.format("%s is not a valid User", modelValue)), e); } } } 有点hacky,以便能够在JSF转换器中注入Converter;通常会将其注释为@EJBbut that unfortunately doesn't allow @EJB injections

不要忘记确保复杂对象类具有equals() and hashCode() properly implemented,否则JSF将在渲染期间无法显示预先选择的项目,并且您将在提交面Validation Error: Value is not valid。< / p>

@FacesConverter(forClass=User.class)

具有通用转换器的复杂对象

回答这个问题:Implement converters for entities with Java Generics


没有自定义转换器的复杂对象

JSF实用程序库OmniFaces提供了一个特殊的转换器,允许您在public class User { private Long id; @Override public boolean equals(Object other) { return (other != null && getClass() == other.getClass() && id != null) ? id.equals(((User) other).id) : (other == this); } @Override public int hashCode() { return (id != null) ? (getClass().hashCode() + id.hashCode()) : super.hashCode(); } } 中使用复杂对象,而无需创建自定义转换器。 SelectItemsConverter只会根据<h:selectOneMenu>中的现有项目进行转换。

<f:selectItem(s)>

另见:

答案 1 :(得分:8)

查看页

<h:selectOneMenu id="selectOneCB" value="#{page.selectedName}">
     <f:selectItems value="#{page.names}"/>
</h:selectOneMenu>

<强>支持bean

   List<SelectItem> names = new ArrayList<SelectItem>();

   //-- Populate list from database

   names.add(new SelectItem(valueObject,"label"));

   //-- setter/getter accessor methods for list

要显示特定的选定记录,它必须是列表中的一个值。

答案 2 :(得分:3)

将复杂对象的自制通用转换器作为选定项

Balusc为这个主题提供了一个非常有用的概述答案。但是他没有提供一种替代方案:Roll-your-own通用转换器,它将复杂对象作为选定项目处理。如果你想要处理所有情况,这是非常复杂的,但对于简单的情况则非常简单。

以下代码包含此类转换器的示例。它的工作原理与OmniFaces SelectItemsConverter相同,因为它可以查看包含对象的UISelectItem(s)组件的子组件。不同之处在于它只处理对实体对象的简单集合或字符串的绑定。它不处理项目组,SelectItem s集合,数组以及其他很多东西。

组件绑定到的实体必须实现IdObject接口。 (这可以通过其他方式解决,例如使用toString。)

请注意,实体必须以两个具有相同ID的实体相等的方式实现equals

要使用它,唯一需要做的就是在select组件上将其指定为转换器,绑定到实体属性和可能的​​实体列表:

<h:selectOneMenu value="#{bean.user}" converter="selectListConverter">
  <f:selectItem itemValue="unselected" itemLabel="Select user..."/>
  <f:selectItem itemValue="empty" itemLabel="No user"/>
  <f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>

转换器:

/**
 * A converter for select components (those that have select items as children).
 * 
 * It convertes the selected value string into one of its element entities, thus allowing
 * binding to complex objects.
 * 
 * It only handles simple uses of select components, in which the value is a simple list of
 * entities. No ItemGroups, arrays or other kinds of values.
 * 
 * Items it binds to can be strings or implementations of the {@link IdObject} interface.
 */
@FacesConverter("selectListConverter")
public class SelectListConverter implements Converter {

  public static interface IdObject {
    public String getDisplayId();
  }

  @Override
  public Object getAsObject(FacesContext context, UIComponent component, String value) {
    if (value == null || value.isEmpty()) {
      return null;
    }

    return component.getChildren().stream()
      .flatMap(child -> getEntriesOfItem(child))
      .filter(o -> value.equals(o instanceof IdObject ? ((IdObject) o).getDisplayId() : o))
      .findAny().orElse(null);
  }

  /**
   * Gets the values stored in a {@link UISelectItem} or a {@link UISelectItems}.
   * For other components returns an empty stream.
   */
  private Stream<?> getEntriesOfItem(UIComponent child) {
    if (child instanceof UISelectItem) {
      UISelectItem item = (UISelectItem) child;
      if (!item.isNoSelectionOption()) {
        return Stream.of(item.getValue());
      }

    } else if (child instanceof UISelectItems) {
      Object value = ((UISelectItems) child).getValue();

      if (value instanceof Collection) {
        return ((Collection<?>) value).stream();
      } else {
        throw new IllegalStateException("Unsupported value of UISelectItems: " + value);
      }
    }

    return Stream.empty();
  }

  @Override
  public String getAsString(FacesContext context, UIComponent component, Object value) {
    if (value == null) return null;
    if (value instanceof String) return (String) value;
    if (value instanceof IdObject) return ((IdObject) value).getDisplayId();

    throw new IllegalArgumentException("Unexpected value type");
  }

}

答案 3 :(得分:0)

我这样做:

  1. 模型是ViewScoped

  2. 转换器:

    @Named
    @ViewScoped
    public class ViewScopedFacesConverter implements Converter, Serializable
    {
            private static final long serialVersionUID = 1L;
            private Map<String, Object> converterMap;
    
            @PostConstruct
            void postConstruct(){
                converterMap = new HashMap<>();
            }
    
            @Override
            public String getAsString(FacesContext context, UIComponent component, Object object) {
                String selectItemValue = String.valueOf( object.hashCode() ); 
                converterMap.put( selectItemValue, object );
                return selectItemValue;
            }
    
            @Override
            public Object getAsObject(FacesContext context, UIComponent component, String selectItemValue){
                return converterMap.get(selectItemValue);
            }
    }
    
  3. 并使用:

    绑定到组件
     <f:converter binding="#{viewScopedFacesConverter}" />
    

    如果您将使用实体ID而不是hashCode,则可以遇到冲突 - 如果您在一个页面上为具有相同ID的不同实体(类)列出的列表很少

答案 4 :(得分:0)

叫我懒惰,但编译转换器似乎是很多不必要的工作。我之前使用的是Primefaces并没有使用普通的JSF2列表框或下拉菜单,我只是假设(懒惰)小部件可以处理复杂对象,即将所选对象原样传递给相应的getter / setter像许多其他小部件一样。我很失望地发现(经过数小时的头部刮擦)没有转换器的这种小部件类型不存在此功能。事实上,如果你为复杂对象而不是String提供一个setter,它会无声地失败(根本就不会调用setter,没有Exception,没有JS错误),而且我花了很多时间经过{{ 3}}找到原因,但无效,因为这些建议均未适用。我的结论是:列表框/菜单小部件需要调整其他JSF2小部件不需要。这似乎具有误导性,并且容易导致像我这样的不知情的开发人员陷入一个兔子洞。

最后,我拒绝对转换器进行编码,并通过反复试验发现,如果将窗口小部件值设置为复杂对象,例如:

<p:selectOneListbox id="adminEvents" value="#{testBean.selectedEvent}">

...当用户选择一个项目时,该小部件可以调用该对象的字符串设置器,例如setSelectedThing(String thingString) {...},传递的String是表示Thing对象的JSON字符串。我可以解析它以确定选择了哪个对象。这感觉有点像黑客,但比转换器更少黑客。