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


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

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

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

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 ' s toString() будет использоваться для представления как выпадающий элемент 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) содержащих объекты. Разница в том, что он обрабатывает только привязки либо набора объектов или строк. Он не обрабатывает группы элементов, коллекции SelectItems, массивы и, вероятно, много других вещей.

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

}

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

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

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

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

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

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

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