Как заполнить опции h: selectOneMenu из базы данных? - PullRequest
69 голосов
/ 27 июля 2011

Я создаю веб-приложение, в котором вы должны прочитать список объектов / сущностей из БД и заполнить его в JSF <h:selectOneMenu>.Я не могу закодировать это.Может кто-нибудь показать мне, как это сделать?

Я знаю, как получить List<User> из БД.Что мне нужно знать, это как заполнить этот список в <h:selectOneMenu>.

<h:selectOneMenu value="#{bean.name}">
    ...?
</h:selectOneMenu>

Ответы [ 5 ]

168 голосов
/ 28 июля 2011

Исходя из истории ваших вопросов, вы используете JSF 2.x. Итак, вот целевой ответ JSF 2.x. В JSF 1.x вы будете вынуждены оборачивать значения / метки элементов в уродливые 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 toString() будет использоваться для представления как метки выпадающего элемента, так и значения. Итак, когда вы вместо List<String> используете список сложных объектов, таких как List<SomeEntity>, и вы не переопределили метод класса 'toString(), вы увидите com.example.SomeEntity@hashcode в качестве значений элемента. Смотрите следующий раздел, как решить это правильно.

Также обратите внимание, что компонент для значения <f:selectItems> не обязательно должен быть тем же компонентом, что и компонент для значения <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 в компоненте, а 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 во время рендеринга не сможет показать предварительно выбранные элементы, и вы на лице отправки Ошибка проверки: значение недействительно .

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>

Смотри также:

8 голосов
/ 27 июля 2011

View-страница

<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

Чтобы отобразить определенную выбранную запись, это должно быть одно из значений в списке.

3 голосов
/ 18 октября 2014

Свернуть свой собственный универсальный конвертер для сложных объектов в качестве выбранного элемента

Balusc дает очень полезный обзорный ответ на эту тему.Но есть одна альтернатива, которую он не представляет: универсальный конвертер Roll-your-own, который обрабатывает сложные объекты как выбранный элемент.Это очень сложно сделать, если вы хотите обрабатывать все случаи, но довольно просто для простых случаев.

Приведенный ниже код содержит пример такого преобразователя.Он работает в том же духе, что и OmniFaces SelectItemsConverter , поскольку просматривает дочерние элементы компонента для UISelectItem(s) содержащих объектов.Разница в том, что он обрабатывает только привязки либо к простым коллекциям объектов, либо к строкам.Он не обрабатывает группы элементов, коллекции SelectItem s, массивы и, возможно, множество других вещей.

Объекты, с которыми связывается компонент, должны реализовывать интерфейс IdObject.(Это может быть решено другим способом, например, с помощью toString.)

Обратите внимание, что сущности должны реализовывать equals таким образом, чтобы два объекта с одинаковым идентификатором сравнивались равными.1017 * Единственное, что вам нужно сделать, чтобы использовать его, это указать его как конвертер в компоненте 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>

Converter:

/**
 * 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");
  }

}
0 голосов
/ 24 октября 2017

Зовите меня ленивым, но кодирование конвертера кажется ненужной работой. Я использую Primefaces и, не использовав ранее простой ванильный список JSF2 или выпадающее меню, я просто предположил (будучи ленивым), что виджет может обрабатывать сложные объекты, то есть передавать выбранный объект как есть его соответствующему получателю / установщику, как так многие другие виджеты делают. Я был разочарован, обнаружив (после нескольких часов царапин головы), что эта возможность не существует для этого типа виджетов без конвертера. На самом деле, если вы предоставляете установщик для сложного объекта, а не для String, он завершается сбоем молча (просто не вызывает установщик, нет исключения, нет ошибки JS), и я потратил кучу времени на просмотр BalusC отличный инструмент для поиска и устранения неисправностей , чтобы найти причину, но безрезультатно, поскольку ни одно из этих предложений не применимо. Мой вывод: виджет списка / меню нуждается в адаптации, чего не делают другие виджеты JSF2. Это вводит в заблуждение и склоняет к тому, чтобы вести неосведомленного разработчика, такого как я, в кроличью нору.

В конце концов я отказался от кодирования Конвертера и обнаружил методом проб и ошибок, что если вы установите значение виджета для сложного объекта, например ::100100

<p:selectOneListbox id="adminEvents" value="#{testBean.selectedEvent}">

... когда пользователь выбирает элемент, виджет может вызывать установщик строки для этого объекта, например, setSelectedThing(String thingString) {...}, и переданная строка является строкой JSON, представляющей объект Thing. Я могу разобрать его, чтобы определить, какой объект был выбран. Это немного похоже на взлом, но меньше взлома, чем конвертер.

0 голосов
/ 03 октября 2017

Я делаю это так:

  1. Модели ViewScoped

  2. конвертер:

    @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, вы можете столкнуться - если у вас есть несколько списков на одной странице для разных сущностей (классов) стот же идентификатор

...