Как заполнить опции h:selectOneMenu из базы данных?

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

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

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

5 ответов

Решение

Основываясь на истории ваших вопросов, вы используете 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 "s 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>

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

Просмотр страницы

<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

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

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

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

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

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

Обратите внимание, что сущности должны реализовать 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");
  }

}

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

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

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

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

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

  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}" />

Если вы будете использовать идентификатор сущности, а не хеш-код, вы можете столкнуться - если у вас есть несколько списков на одной странице для разных сущностей (классов) с одинаковым идентификатором

Другие вопросы по тегам