Как заполнить параметры h: selectOneMenu из базы данных?
Я создаю веб-приложение, где вы должны прочитать список объектов / сущностей из БД и заполнить его в JSF <h:selectOneMenu>
. Я не могу это закодировать. Может кто-нибудь показать мне, как это сделать?
Я знаю, как получить List<User>
из БД. Что мне нужно знать, как заполнить этот список в <h:selectOneMenu>
.
<h:selectOneMenu value="#{bean.name}">
...?
</h:selectOneMenu>
5 ответов:
основываясь на истории ваших вопросов, вы используете JSF 2.x. Итак, вот JSF 2.х целевых ответа. В JSF 1.x Вы были бы вынуждены обернуть значения/метки элементов в ugly
SelectItem
экземпляров. К счастью, это больше не нужно в JSF 2.x.
простой пример
чтобы ответить на ваш вопрос прямо, просто использовать
<f:selectItems>
чейvalue
точкиList<T>
свойство, которое вы сохраняете из БД во время Боба (пост)строительство. Вот основной пример старта, предполагающий, что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()
будет использоваться для представления как выпадающий элемент Label и value. Итак, когда вы вместоList<String>
использование списка сложных объектов, таких какList<SomeEntity>
и вы не переопределили класс'toString()
метод, тогда вы увидитеcom.example.SomeEntity@hashcode
в качестве значений элемента. См. следующий раздел как это сделать решите ее правильно.также обратите внимание, что боб для
<f:selectItems>
значение не обязательно должно быть таким же Бобом, как боб для<h:selectOneMenu>
значение. Это полезно, когда значения на самом деле являются константами всего приложения, которые вам просто нужно загрузить только один раз во время запуска приложения. Затем вы можете просто сделать его свойством компонента с областью приложения.<h:selectOneMenu value="#{bean.name}"> <f:selectItems value="#{data.names}" /> </h:selectOneMenu>
сложные объекты в качестве доступных элементов
всякий раз, когда
T
касается a сложный объект (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
свойство в бобе, а также и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 во время рендеринга не сможет показать предварительно выбранный элемент(Ы), и вы будете на submit 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.
сложные объекты без a пользовательский конвертер
библиотека утилит 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>
Читайте также:
View-Page
<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
чтобы отобразить определенную выбранную запись, она должна быть одним из значений в списке.
Roll-your-own generic converter для сложных объектов как выбранный элемент
Balusc дает очень полезный обзорный ответ по этому вопросу. Но есть одна альтернатива, которую он не представляет: Roll-your-own generic converter, который обрабатывает сложные объекты как выбранный элемент. Это очень сложно сделать, если вы хотите обрабатывать все случаи, но довольно прост для простых случаев.
следующий код содержит пример такого преобразователя. Он работает в том же духе, что и в OmniFaces SelectItemsConverter как он просматривает дочерние элементы компонента для
UISelectItem(s)
содержащих объекты. Разница в том, что он обрабатывает только привязки либо набора объектов или строк. Он не обрабатывает группы элементов, коллекцииSelectItem
s, массивы и, вероятно, много других вещей.сущности, к которым привязывается компонент, должны реализовать
IdObject
интерфейс. (Это можно решить другим способом, например, с помощьюtoString
.)обратите внимание, что сущности должны реализовать
equals
таким образом, что две организации с одинаковым идентификатором совпадает.единственное, что вам нужно сделать, чтобы использовать его, это указать его как конвертер на выбранном компоненте, привязать к свойству сущности и списку возможных сущностей:
<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}" />
Если вы будете использовать идентификатор сущности, а не хэш-код, вы можете столкнуться с коллизией - если у вас есть несколько списков на одной странице для разных сущностей (классов) с одним и тем же идентификатором
Назовите меня ленивым, но кодирование конвертера кажется много ненужной работы. Я использую Primefaces и, не используя обычный список ванили JSF2 или выпадающее меню раньше, я просто предположил (будучи ленивым), что виджет может обрабатывать сложные объекты, т. е. передавать выбранный объект как есть в соответствующий ему геттер/сеттер, как и многие другие виджеты. Я был разочарован, обнаружив (после нескольких часов почесывания головы), что эта возможность не существует для этого типа виджета без конвертера. В факт если вы предоставляете сеттер для сложного объекта, а не для строки, он не работает молча (просто не вызывает сеттер, без исключения, без ошибки JS), и я потратил кучу времени на прохождение отличный инструмент устранения неполадок BalusC чтобы найти причину, безрезультатно, так как ни одно из этих предложений не применяется. Мой вывод: виджет listbox/menu нуждается в адаптации, что другие виджеты JSF2 этого не делают. Это кажется вводящим в заблуждение и склонным вести неинформированного разработчика, такого как я кроличья нора.
в конце концов я сопротивлялся кодированию конвертера и нашел методом проб и ошибок, что если вы установите значение виджета на сложный объект, например:
<p:selectOneListbox id="adminEvents" value="#{testBean.selectedEvent}">
... когда пользователь выбирает элемент, виджет может вызвать задатчик строк для этого объекта, например
setSelectedThing(String thingString) {...}
, и переданная строка является строкой JSON, представляющей объект Thing. Я могу разобрать его, чтобы определить, какой объект был выбран. Это немного похоже на взлом, но меньше взлома, чем конвертер.