如何从数据库中填充h:selectOneMenu的选项?
我正在创build一个Web应用程序,您必须从DB读取对象/实体列表,并将其填充到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>
的value
指向一个List<T>
属性,这个属性在bean的(后期)构造过程中从DB保存。 假设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
的toString()
将用来表示下拉条目的标签和值。 因此,如果使用List<SomeEntity>
等复杂对象List<SomeEntity>
而不是List<String>
,并且没有重写类的toString()
方法,则会看到com.example.SomeEntity@hashcode
as item值。 请参阅下一节如何正确解决它。
另请注意, <f:selectItems>
值的bean不一定需要与<h:selectOneMenu>
值的bean相同。 只要这些值实际上是应用程序范围内的常量,在应用程序启动期间您只需加载一次,这就很有用。 然后你可以把它作为应用程序范围的bean的一个属性。
<h:selectOneMenu value="#{bean.name}"> <f:selectItems value="#{data.names}" /> </h:selectOneMenu>
复杂对象作为可用项目
每当T
涉及一个复杂的对象(一个javabean),比如具有name
的String
属性的User
,那么你可以使用var
属性来获得你可以在itemValue
和/或itemLabel
属性中使用的迭代variables如果你省略了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
中的T
属性,并且T
代表一个User
,那么您将需要烘烤自定义的Converter
,它在User
和唯一的string表示forms(可以是id
属性)之间进行转换。 请注意, itemValue
必须表示复杂的对象本身,正是需要设置为select组件的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); } } }
(请注意,为了能够在JSF转换器中注入@EJB
,转换器@FacesConverter(forClass=User.class)
;通常会将其注释为@FacesConverter(forClass=User.class)
, 但是不幸的是不允许@EJB
注入 )
不要忘记确保复杂的对象类已经正确实现了equals()
和hashCode()
,否则JSF将在渲染期间无法显示预选项,并且您将在提交时validation错误:值不是有效 。
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 Generics实现转换器 。
没有自定义转换器的复杂对象
JSF实用程序库OmniFaces提供了一个特殊的转换器,允许您在<h:selectOneMenu>
使用复杂对象,而不需要创build自定义转换器。 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>
wiki页面
查看页
<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
要显示特定的选定logging,它必须是列表中的一个值。
将自己的通用转换器作为选定项目复杂对象
Balusc给出了一个非常有用的概述在这个问题上的答案。 但是他没有提供一个替代scheme:将自己的通用转换器处理复杂的对象作为选定的项目。 如果你想处理所有的情况,这是非常复杂的,但是对于简单的情况来说很简单。
下面的代码包含了这样一个转换器的例子。 它与OmniFaces SelectItemsConverter的工作原理相同,因为它通过组件的子元素来查看包含对象的UISelectItem(s)
。 区别在于它只处理绑定到实体对象的简单集合或string。 它不处理项目组, SelectItem
的集合,数组,可能还有很多其他的东西。
组件绑定的实体必须实现IdObject
接口。 (这可以通过其他方式解决,比如使用toString
。)
请注意,实体必须以两个具有相同ID的实体equals
的方式实现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"); } }
我是这样做的:
-
模型是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,你可以碰到一个碰撞 – 如果你在一个页面上有很less的列表用于具有相同ID的不同实体(类)
打电话给我懒,但编码转换器似乎是很多不必要的工作。 我正在使用Primefaces,并没有使用简单的香草JSF2列表框或下拉菜单之前,我只是认为(懒惰),小部件可以处理复杂的对象,即传递选定的对象,像它这样的对应的getter / setter像这样许多其他小部件呢。 我很失望地发现(小时后头部刮),这个function不存在这个没有转换器的小部件types。 事实上,如果你为复杂的对象而不是一个String提供setter,它会静静的失败(根本不会调用setter,没有Exception,没有JS错误),而且我花了很多时间去处理BalusC的优秀的故障排除工具find原因,没有用,因为没有这些build议适用。 我的结论是:listbox / menu小部件需要适应其他的JSF2小部件。 这似乎有误导性,容易导致像我这样的不知情的开发者陷入一个兔子洞。
最后,我拒绝编码一个转换器,并通过反复试验发现,如果您将widget值设置为一个复杂的对象,例如:
<p:selectOneListbox id="adminEvents" value="#{testBean.selectedEvent}">
…当用户select一个项目时,小部件可以为该对象调用一个String setter,例如setSelectedThing(String thingString) {...}
,并且传递的string是表示Thing对象的JSONstring。 我可以parsing它来确定select了哪个对象。 这感觉有点像黑客,但比一个转换器less一点黑客。