75

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

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

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

5 回答 5

184

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


基本示例

要直接回答您的问题,只需使用<f:selectItems>whovalue指向List<T>您在 bean(后期)构建期间从数据库中保留的属性。这是一个基本的启动示例,假设它T实际上代表一个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'stoString()将用于表示下拉项标签和值。因此,当您不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>

复杂对象作为可用项目

每当涉及一个复杂对象(javabean ) ,T例如User它的String属性为标签变得与值相同)。namevaritemValueitemLabelitemLabel

示例 #1:

<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属性时:id

示例 #2:

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

private Long userId;
private List<User> users;

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

复杂对象作为选定项

每当您想将它也设置为Tbean 中的属性并T表示一个User时,您需要烘焙一个自定义,它在和一个唯一的字符串表示(可以是属性)Converter之间进行转换。请注意,必须表示复杂对象本身,正是需要设置为选择组件的类型。UseriditemValuevalue

<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);
        }
    }

}

(请注意,Converter为了能够@EJB在 JSF 转换器中注入一个有点 hacky;通常人们会将其注释为@FacesConverter(forClass=User.class)但不幸的是不允许@EJB注入

不要忘记确保复杂对象类已正确实现equals()hashCode()否则 JSF 将在渲染期间无法显示预选项目,并且您将在提交时遇到Validation Error: Value is not valid

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();
    }

}

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

前往这个答案:使用 Java 泛型实现实体转换器


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

JSF 实用程序库OmniFaces提供了一个开箱即用的特殊转换器,它允许您在<h:selectOneMenu>不需要创建自定义转换器的情况下使用复杂的对象。将SelectItemsConverter简单地根据 中现成的项目进行转换<f:selectItem(s)>

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

也可以看看:

于 2011-07-27T20:15:44.813 回答
11

查看页面

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

后备豆

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

   //-- Populate list from database

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

   //-- setter/getter accessor methods for list

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

于 2011-07-27T19:51:06.170 回答
3

滚动您自己的通用转换器,将复杂对象作为选定项目

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

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

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

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

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

<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");
  }

}
于 2014-10-18T11:37:01.423 回答
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);
            }
    }
    

并绑定到组件:

 <f:converter binding="#{viewScopedFacesConverter}" />

如果您将使用实体 id 而不是 hashCode,则可能会发生冲突 - 如果您在一页上针对具有相同 id 的不同实体(类)的列表很少

于 2017-10-03T16:18:39.800 回答
0

叫我懒惰,但编写转换器似乎是很多不必要的工作。我正在使用 Primefaces,并且以前没有使用过普通的 JSF2 列表框或下拉菜单,我只是假设(懒惰)小部件可以处理复杂的对象,即将选定的对象按原样传递给其相应的 getter/setter,就像这样许多其他小部件可以。我很失望地发现(经过数小时的挠头)没有转换器的这种小部件类型不存在此功能。事实上,如果你为复杂对象而不是 String 提供 setter,它会默默地失败(只是不调用 setter,没有异常,没有 JS 错误),我花了很多时间浏览BalusC 出色的故障排除工具找到原因,无济于事,因为这些建议都没有应用。我的结论:列表框/菜单小部件需要适应其他 JSF2 小部件不需要。这似乎具有误导性,并且容易将像我这样的不知情的开发人员带入兔子洞。

最后,我拒绝编写 Converter 并通过反复试验发现,如果将小部件值设置为复杂对象,例如:

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

...当用户选择一个项目时,小部件可以为该对象调用一个字符串设置器,例如setSelectedThing(String thingString) {...},传递的字符串是一个表示事物对象的 JSON 字符串。我可以解析它以确定选择了哪个对象。这感觉有点像 hack,但不像 Converter 那样是 hack。

于 2017-10-24T14:56:41.053 回答