我正在创建一个Web应用程序,您必须从DB中读取对象/实体列表并在JSF <h:selectOneMenu>
中填充它。我无法对此进行编码。有人可以告诉我该怎么做吗?
我知道如何从数据库中获取List<User>
。我需要知道的是,如何在<h:selectOneMenu>
中填充此列表。
<h:selectOneMenu value="#{bean.name}">
...?
</h:selectOneMenu>
答案 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
;通常会将其注释为@EJB
,but 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)
我这样做:
模型是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的不同实体(类)列出的列表很少
答案 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字符串。我可以解析它以确定选择了哪个对象。这感觉有点像黑客,但比转换器更少黑客。