我正在创建一个 Web 应用程序,您必须在其中从数据库中读取对象/实体列表并将其填充到 JSF<h:selectOneMenu>
中。我无法对此进行编码。有人可以告诉我怎么做吗?
我知道如何List<User>
从数据库中获取。我需要知道的是,如何将此列表填充到<h:selectOneMenu>
.
<h:selectOneMenu value="#{bean.name}">
...?
</h:selectOneMenu>
我正在创建一个 Web 应用程序,您必须在其中从数据库中读取对象/实体列表并将其填充到 JSF<h:selectOneMenu>
中。我无法对此进行编码。有人可以告诉我怎么做吗?
我知道如何List<User>
从数据库中获取。我需要知道的是,如何将此列表填充到<h:selectOneMenu>
.
<h:selectOneMenu value="#{bean.name}">
...?
</h:selectOneMenu>
根据您的问题历史,您使用的是 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
属性为标签变得与值相同)。name
var
itemValue
itemLabel
itemLabel
示例 #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)
每当您想将它也设置为T
bean 中的属性并T
表示一个User
时,您需要烘焙一个自定义,它在和一个唯一的字符串表示(可以是属性)Converter
之间进行转换。请注意,必须表示复杂对象本身,正是需要设置为选择组件的类型。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);
}
}
}
(请注意,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>
查看页面
<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
要显示特定的选定记录,它必须是列表中的值之一。
Balusc 给出了一个非常有用的关于这个主题的概述答案。但是他没有提出另一种选择:Roll-your-own 通用转换器,它将复杂对象作为选定项处理。如果要处理所有情况,这将非常复杂,但对于简单的情况则非常简单。
下面的代码包含这样一个转换器的示例。它的工作原理与 OmniFaces SelectItemsConverter相同,因为它通过组件的子项查找UISelectItem(s)
包含对象。不同之处在于它只处理与实体对象的简单集合或字符串的绑定。它不处理项目组、SelectItem
s 的集合、数组以及可能很多其他的东西。
组件绑定的实体必须实现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");
}
}
我这样做是这样的:
模型是 ViewScoped
转换器:
@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 的不同实体(类)的列表很少
叫我懒惰,但编写转换器似乎是很多不必要的工作。我正在使用 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。