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>
. 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. What I need to know is, how to populate this list in a <h:selectOneMenu>
.
<h:selectOneMenu value="#{bean.name}">
...?
</h:selectOneMenu>
Based on your question history, you're using JSF 2.x. So, here's a JSF 2.x targeted answer. In JSF 1.x you would be forced to wrap item values/labels in ugly SelectItem
instances. This is fortunately not needed anymore in JSF 2.x.
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. Here's a basic kickoff example assuming that T
actually represents a 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. 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. 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. 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.
<h:selectOneMenu value="#{bean.name}">
<f:selectItems value="#{data.names}" />
</h:selectOneMenu>
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).
Example #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:
Example #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)
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). Do note that the itemValue
must represent the complex object itself, exactly the type which needs to be set as selection component's 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)
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.
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();
}
}
Head to this answer: Implement converters for entities with Java Generics.
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. The SelectItemsConverter
will simply do the conversion based on readily available items in <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>