简体   繁体   English

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

[英]How to populate options of h:selectOneMenu from database?

I am creating a web application, where you have to read a list of objects / entities from a DB and populate it in a JSF <h:selectOneMenu> .我正在创建一个 Web 应用程序,您必须在其中从数据库中读取对象/实体列表并将其填充到 JSF <h:selectOneMenu>中。 I am unable to code this.我无法对此进行编码。 Can someone show me how to do it?有人可以告诉我怎么做吗?

I know how to get a List<User> from the DB.我知道如何从数据库中获取List<User> What I need to know is, how to populate this list in a <h:selectOneMenu> .我需要知道的是,如何在<h:selectOneMenu>中填充此列表。

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

Based on your question history, you're using JSF 2.x.根据您的问题历史,您使用的是 JSF 2.x。 So, here's a JSF 2.x targeted answer.所以,这是一个针对 JSF 2.x 的答案。 In JSF 1.x you would be forced to wrap item values/labels in ugly SelectItem instances.在 JSF 1.x 中,您将被迫将项目值/标签包装在丑陋的SelectItem实例中。 This is fortunately not needed anymore in JSF 2.x.幸运的是,在 JSF 2.x 中不再需要它了。


Basic example基本示例

To answer your question directly, just use <f:selectItems> whose value points to a List<T> property which you preserve from the DB during bean's (post)construction.要直接回答您的问题,只需使用<f:selectItems> ,其value指向您在 bean(后期)构建期间从数据库中保留的List<T>属性。 Here's a basic kickoff example assuming that T actually represents a String .这是一个基本的启动示例,假设T实际上代表一个String

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

with

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

Simple as that.就那么简单。 Actually, the T 's toString() will be used to represent both the dropdown item label and value.实际上, TtoString()将用于表示下拉项标签和值。 So, when you're instead of List<String> using a list of complex objects like List<SomeEntity> and you haven't overridden the class' toString() method, then you would see com.example.SomeEntity@hashcode as item values.因此,当您使用List<SomeEntity>之类的复杂对象列表而不是List<String> > 并且您没有覆盖类的toString()方法时,您会看到com.example.SomeEntity@hashcode作为项目价值观。 See next section how to solve it properly.请参阅下一节如何正确解决它。

Also note that the bean for <f:selectItems> value does not necessarily need to be the same bean as the bean for <h:selectOneMenu> value.另请注意, <f:selectItems>值的 bean 不一定需要与<h:selectOneMenu>值的 bean 相同。 This is useful whenever the values are actually applicationwide constants which you just have to load only once during application's startup.当这些值实际上是应用程序范围的常量时,这很有用,您只需在应用程序启动期间加载一次。 You could then just make it a property of an application scoped bean.然后,您可以将其设为应用程序范围 bean 的属性。

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

Complex objects as available items复杂对象作为可用项目

Whenever T concerns a complex object (a javabean), such as User which has a String property of name , then you could use the var attribute to get hold of the iteration variable which you in turn can use in itemValue and/or itemLabel attribtues (if you omit the itemLabel , then the label becomes the same as the value).每当T涉及复杂对象(javabean)时,例如User具有nameString属性,那么您可以使用var属性来获取迭代变量,然后您可以在itemValue和/或itemLabel属性中使用它(如果省略itemLabel ,则标签与值相同)。

Example #1:示例 #1:

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

with

private String userName;
private List<User> users;

@EJB
private UserService userService;

@PostConstruct
public void init() {
    users = userService.list();
}

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

Or when it has a Long property id which you would rather like to set as item value:或者当它有一个Long属性id时,您希望将其设置为项目值:

Example #2:示例 #2:

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

with

private Long userId;
private List<User> users;

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

Complex object as selected item复杂对象作为选定项

Whenever you would like to set it to a T property in the bean as well and T represents an User , then you would need to bake a custom Converter which converts between User and an unique string representation (which can be the id property).每当您想将其设置为 bean 中的T属性并且T表示User时,您将需要烘焙一个自定义Converter ,该转换器在User和唯一的字符串表示(可以是id属性)之间进行转换。 Do note that the itemValue must represent the complex object itself, exactly the type which needs to be set as selection component's value .请注意itemValue必须代表复杂对象本身,正是需要设置为选择组件value的类型。

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

with

private User user;
private List<User> users;

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

and

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

}

(please note that the Converter is a bit hacky in order to be able to inject an @EJB in a JSF converter; normally one would have annotated it as @FacesConverter(forClass=User.class) , but that unfortunately doesn't allow @EJB injections ) (请注意,为了能够在 JSF 转换器中注入@EJBConverter器有点笨拙;通常有人会将其注释为@FacesConverter(forClass=User.class)但不幸的是不允许@EJB注入

Don't forget to make sure that the complex object class has equals() and hashCode() properly implemented , otherwise JSF will during render fail to show preselected item(s), and you'll on submit face Validation Error: Value is not valid .不要忘记确保复杂对象类正确实现了equals()hashCode() ,否则 JSF 将在渲染期间无法显示预选项目,并且您将提交 face Validation Error: Value is not有效

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

}

Complex objects with a generic converter具有通用转换器的复杂对象

Head to this answer: Implement converters for entities with Java Generics .前往这个答案: 使用 Java 泛型实现实体转换器


Complex objects without a custom converter没有自定义转换器的复杂对象

The JSF utility library OmniFaces offers a special converter out the box which allows you to use complex objects in <h:selectOneMenu> without the need to create a custom converter. JSF 实用程序库OmniFaces提供了一个开箱即用的特殊转换器,它允许您在<h:selectOneMenu>中使用复杂的对象,而无需创建自定义转换器。 The SelectItemsConverter will simply do the conversion based on readily available items in <f:selectItem(s)> . 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>

See also:也可以看看:

View-Page查看页面

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

Backing-Bean后备豆

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

   //-- Populate list from database

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

   //-- setter/getter accessor methods for list

To display particular selected record, it must be one of the values in the list.要显示特定的选定记录,它必须是列表中的值之一。

Roll-your-own generic converter for complex objects as selected item滚动您自己的通用转换器,将复杂对象作为选定项目

The Balusc gives a very useful overview answer on this subject. Balusc 给出了一个非常有用的关于这个主题的概述答案。 But there is one alternative he does not present: The Roll-your-own generic converter that handles complex objects as the selected item.但是他没有提出另一种选择:Roll-your-own 通用转换器,它将复杂对象作为选定项处理。 This is very complex to do if you want to handle all cases, but pretty simple for simple cases.如果要处理所有情况,这将非常复杂,但对于简单的情况则非常简单。

The code below contains an example of such a converter.下面的代码包含这样一个转换器的示例。 It works in the same spirit as the OmniFaces SelectItemsConverter as it looks through the children of a component for UISelectItem(s) containing objects.它的工作原理与 OmniFaces SelectItemsConverter相同,因为它通过组件的子项查找包含对象的UISelectItem(s) The difference is that it only handles bindings to either simple collections of entity objects, or to strings.不同之处在于它只处理与实体对象的简单集合或字符串的绑定。 It does not handle item groups, collections of SelectItem s, arrays and probably a lot of other things.它不处理项目组、 SelectItem的集合、数组以及可能很多其他的东西。

The entities that the component binds to must implement the IdObject interface.组件绑定的实体必须实现IdObject接口。 (This could be solved in other way, such as using toString .) (这可以通过其他方式解决,例如使用toString 。)

Note that the entities must implement equals in such a way that two entities with the same ID compares equal.请注意,实体必须以具有相同 ID 的两个实体比较相等的方式实现equals

The only thing that you need to do to use it is to specify it as converter on the select component, bind to an entity property and a list of possible entities:要使用它,您唯一需要做的就是在选择组件上将其指定为转换器,绑定到实体属性和可能的​​实体列表:

<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>

Converter:转换器:

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

}

I'm doing it like this:我这样做是这样的:

  1. Models are ViewScoped模型是 ViewScoped

  2. converter:转换器:

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

and bind to component with:并绑定到组件:

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

If you will use entity id rather than hashCode you can hit a collision- if you have few lists on one page for different entities (classes) with the same id如果您将使用实体 id 而不是 hashCode,则可能会发生冲突——如果您在一页上针对具有相同 id 的不同实体(类)的列表很少

Call me lazy but coding a Converter seems like a lot of unnecessary work.叫我懒惰,但编写转换器似乎是很多不必要的工作。 I'm using Primefaces and, not having used a plain vanilla JSF2 listbox or dropdown menu before, I just assumed (being lazy) that the widget could handle complex objects, ie pass the selected object as is to its corresponding getter/setter like so many other widgets do.我正在使用 Primefaces,并且之前没有使用过普通的 JSF2 列表框或下拉菜单,我只是假设(懒惰)小部件可以处理复杂的对象,即将选定的对象按原样传递给其相应的 getter/setter,就像这样许多其他小部件可以。 I was disappointed to find (after hours of head scratching) that this capability does not exist for this widget type without a Converter.我很失望地发现(经过数小时的挠头)没有转换器的这种小部件类型不存在此功能。 In fact if you supply a setter for the complex object rather than for a String, it fails silently (simply doesn't call the setter, no Exception, no JS error), and I spent a ton of time going through BalusC's excellent troubleshooting tool to find the cause, to no avail since none of those suggestions applied.事实上,如果你为复杂对象而不是 String 提供一个 setter,它会默默地失败(只是不调用 setter,没有异常,没有 JS 错误),我花了很多时间浏览BalusC 出色的故障排除工具找到原因,无济于事,因为这些建议都没有应用。 My conclusion: listbox/menu widget needs adapting that other JSF2 widgets do not.我的结论:列表框/菜单小部件需要适应其他 JSF2 小部件不需要。 This seems misleading and prone to leading the uninformed developer like myself down a rabbit hole.这似乎具有误导性,并且容易将像我这样的不知情的开发人员带入兔子洞。

In the end I resisted coding a Converter and found through trial and error that if you set the widget value to a complex object, eg:最后,我拒绝编写 Converter 并通过反复试验发现,如果将小部件值设置为复杂对象,例如:

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

... when the user selects an item, the widget can call a String setter for that object, eg setSelectedThing(String thingString) {...} , and the String passed is a JSON String representing the Thing object. ...当用户选择一个项目时,小部件可以为该对象调用一个 String 设置器,例如setSelectedThing(String thingString) {...} ,传递的字符串是一个表示 Thing 对象的 JSON 字符串。 I can parse it to determine which object was selected.我可以解析它以确定选择了哪个对象。 This feels a little like a hack, but less of a hack than a Converter.这感觉有点像 hack,但不像 Converter 那样是 hack。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM