Как заполнить параметры h: selectOneMenu из базы данных?
Я создаю веб-приложение, где вам нужно прочитать список объектов/сущностей из БД и заполнить его в JSF <h:selectOneMenu>
. Я не могу это кодировать. Может ли кто-нибудь показать мне, как это сделать?
Я знаю, как получить List<User>
из БД. Мне нужно знать, как заполнить этот список в <h:selectOneMenu>
.
<h:selectOneMenu value="#{bean.name}">
...?
</h:selectOneMenu>
Ответы
Ответ 1
Основываясь на истории вопроса, вы используете JSF 2.x. Итак, вот целевой запрос JSF 2.x. В JSF 1.x вам придется переносить значения/метки элемента в уродливые SelectItem
экземпляры. Это, к счастью, больше не требуется в JSF 2.x.
Основной пример
Чтобы ответить на ваш вопрос напрямую, просто используйте <f:selectItems>
, чей value
указывает на свойство List<T>
, которое вы сохраняете из базы данных во время bean (пост) строительства. Здесь приведен пример базового запуска, предполагающий, что T
фактически представляет a 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<String>
используете список сложных объектов, таких как List<SomeEntity>
, и вы не переопределили метод класса toString()
, вы увидите [email protected]
как значения элемента. См. Следующий раздел, как правильно его решить.
Также обратите внимание, что значение bean для <f:selectItems>
необязательно должно быть таким же bean как значение bean для <h:selectOneMenu>
. Это полезно, когда значения фактически являются общими масштабами, которые вам нужно загрузить только один раз во время запуска приложения. Затем вы могли бы сделать это свойством приложения с областью bean.
<h:selectOneMenu value="#{bean.name}">
<f:selectItems value="#{data.names}" />
</h:selectOneMenu>
Сложные объекты как доступные элементы
Всякий раз, когда T
относится к сложному объекту (javabean), например 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, обычно он должен был бы аннотировать его как @FacesConverter(forClass=User.class)
, но это, к сожалению, не позволяет @EJB
инъекции)
Не забудьте убедиться, что класс сложных объектов equals()
и hashCode()
правильно реализован, в противном случае JSF во время рендеринга не сможет отобразить предварительно выбранный элемент (ы), и вы отправите face Ошибка проверки: значение недействительно.
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>
без необходимости для создания пользовательского конвертера. 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>
См. также:
Ответ 2
View-страницы
<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
Чтобы отобразить определенную выбранную запись, это должно быть одно из значений в списке.
Ответ 3
Создайте собственный общий конвертер для сложных объектов в качестве выбранного элемента
Balusc дает очень полезный обзорный обзор по этому вопросу. Но есть одна альтернатива, которую он не представляет: Roll-your-общий универсальный конвертер, который обрабатывает сложные объекты как выбранный элемент. Это очень сложно сделать, если вы хотите обрабатывать все случаи, но довольно просто для простых случаев.
В приведенном ниже коде содержится пример такого преобразователя. Он работает в том же духе, что и OmniFaces SelectItemsConverter, поскольку он просматривает дочерние элементы компонента для UISelectItem(s)
, содержащих объекты. Разница в том, что он обрабатывает привязки только к простым коллекциям объектов сущности или к строкам. Он не обрабатывает группы элементов, коллекции SelectItem
s, массивы и, возможно, много других вещей.
Объекты, к которым должен привязываться компонент, должны реализовывать интерфейс IdObject
. (Это можно решить иначе, например, используя toString
.)
Обратите внимание, что сущности должны реализовать equals
таким образом, чтобы два объекта с одинаковым идентификатором сравнивались равными.
Единственное, что вам нужно сделать, чтобы использовать его, - это указать его как конвертер на компоненте select, привязать к свойству entity и списку возможных объектов:
<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");
}
}
Ответ 4
Я делаю это так:
-
Модели 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}" />
Если вы будете использовать идентификатор объекта, а не hashCode, вы можете столкнуться с конфликтом - если на одной странице несколько списков для разных объектов (классов) с тем же идентификатором
Ответ 5
Назовите меня ленивым, но кодирование конвертера похоже на массу ненужной работы. Я использую Primefaces и, не использовав простой список валиний JSF2 или выпадающее меню раньше, я просто предположил (ленивый), что виджет может обрабатывать сложные объекты, т.е. Передавать выбранный объект так же, как и его соответствующий геттер/сеттер многие другие виджеты. Я был разочарован тем, что (после нескольких часов царапин на голове) эта возможность не существует для этого типа виджетов без конвертера. На самом деле, если вы поставляете сеттер для сложного объекта, а не для строки, он терпит неудачу (просто не вызывает сеттер, исключение, отсутствие ошибки JS), и я потратил массу времени на BalusC - отличный инструмент устранения неполадок, чтобы найти причину, безрезультатно, поскольку ни одно из этих предложений не применялось. Мой вывод: виджет listbox/menu нуждается в адаптации к тому, что другие виджеты JSF2 этого не делают. Это кажется ошибочным и склонным к тому, чтобы вести неосведомленного разработчика, как я, к кроличьей дыре.
В конце концов я сопротивлялся кодированию конвертера и обнаружил через пробную версию и ошибку, что если вы установите значение виджета на сложный объект, например:
<p:selectOneListbox id="adminEvents" value="#{testBean.selectedEvent}">
... когда пользователь выбирает элемент, виджет может вызывать установщик String для этого объекта, например. setSelectedThing(String thingString) {...}
, а переданная строка - это строка JSON, представляющая объект Thing. Я могу проанализировать его, чтобы определить, какой объект был выбран. Это немного похоже на взлома, но меньше на взломе, чем на конвертер.