web-dev-qa-db-ja.com

データベースからh:selectOneMenuのオプションを設定する方法は?

DBからオブジェクト/エンティティのリストを読み取り、JSF <h:selectOneMenu>に入力する必要があるWebアプリケーションを作成しています。これをコーディングできません。誰かがそれを行う方法を教えてもらえますか?

DBからList<User>を取得する方法を知っています。私が知る必要があるのは、このリストを<h:selectOneMenu>に取り込む方法です。

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

質問履歴に基づいて、JSF 2.xを使用しています。そこで、JSF 2.xをターゲットとした回答を以下に示します。 JSF 1.xでは、アイテムの値/ラベルをい SelectItem インスタンスでラップする必要があります。幸い、これはJSF 2.xではもう必要ありません。


基本的な例

質問に直接答えるには、 <f:selectItems> を使用します。このvalueは、Beanの(事後)構築中にDBから保持する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)
}

そのような単純な。実際、TtoString()は、ドロップダウン項目のラベルと値の両方を表すために使用されます。したがって、List<String>のような複雑なオブジェクトのリストを使用してList<SomeEntity>の代わりにクラスのtoString()メソッドをオーバーライドしていない場合、アイテム値としてcom.example.SomeEntity@hashcodeが表示されます。次のセクションで適切に解決する方法を参照してください。

また、<f:selectItems>値のBeanは、必ずしも<h:selectOneMenu>値のBeanと同じBeanである必要はないことに注意してください。これは、値が実際にアプリケーション全体の定数であり、アプリケーションの起動時に一度だけロードする必要がある場合に便利です。その後、アプリケーションスコープBeanのプロパティにするだけで済みます。

<h:selectOneMenu value="#{bean.name}">
    <f:selectItems value="#{data.names}" />
</h:selectOneMenu>

利用可能なアイテムとしての複雑なオブジェクト

Tは、UserプロパティがStringであるnameなどの複雑なオブジェクト(javabean)に関係する場合は常に、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)

選択されたアイテムとしての複合オブジェクト

BeanのTプロパティにも設定し、TUserを表す場合は常に、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は、JSFコンバーターに@EJBを挿入できるようにするために少しハッキングされていることに注意してください。通常は、@FacesConverter(forClass=User.class)しかし、残念ながら@EJBインジェクションは許可されていません

複雑なオブジェクトクラスに equals()およびhashCode()が適切に実装されている であることを忘れないでください。そうしないと、JSFはレンダリング中に事前選択されたアイテムの表示に失敗します。 'llの送信時に 検証エラー:値が無効です

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>

こちらもご覧ください:

168
BalusC

表示ページ

<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

選択した特定のレコードを表示するには、リスト内のいずれかの値である必要があります。

8
Nayan Wadekar

選択したアイテムとしての複雑なオブジェクト用の独自の汎用コンバーター

Baluscは、このテーマに関する非常に有用な概要の回答を提供します。しかし、彼が提示しない1つの選択肢があります。複雑なオブジェクトを選択されたアイテムとして処理する独自の汎用コンバーターです。すべてのケースを処理する場合、これは非常に複雑ですが、単純なケースでは非常に簡単です。

以下のコードには、このようなコンバーターの例が含まれています。これは、OmniFaces SelectItemsConverter と同じ精神で動作し、UISelectItem(s)を含むオブジェクトのコンポーネントの子を調べます。違いは、エンティティオブジェクトの単純なコレクションまたは文字列へのバインディングのみを処理することです。アイテムグループ、SelectItemsのコレクション、配列、およびその他多くのことは処理しません。

コンポーネントがバインドするエンティティは、IdObjectインターフェイスを実装する必要があります。 (これは、toStringを使用するなど、他の方法で解決できます。)

エンティティは、同じIDを持つ2つのエンティティが等しくなるように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");
  }

}
3
Lii

怠け者と呼んでも、コンバータのコーディングは多くの不必要な作業のように思えます。私はPrimefacesを使用しており、以前はプレーンなVanilla JSF2リストボックスまたはドロップダウンメニューを使用していなかったため、ウィジェットは複雑なオブジェクトを処理できると仮定しました(つまり、選択したオブジェクトをその対応するゲッター/セッターにそのまま渡す)他の多くのウィジェットが行います。この機能がConverterなしのこのウィジェットタイプには存在しないことを(何時間も頭を悩ませた後)見つけてがっかりしました。実際、文字列ではなく複雑なオブジェクトにセッターを提供すると、黙って失敗し(単にセッターを呼び出さず、例外もJSエラーもありません)、多くの時間を費やしました BalusCの優れたトラブルシューティングツール 原因を見つけるために、これらの提案のいずれも当てはまらないので役に立たない。私の結論:リストボックス/メニューウィジェットは、他のJSF2ウィジェットにはない適応を必要とします。これは誤解を招きやすく、私のような知識のない開発者をうさぎの穴に導く傾向があります。

結局、私はコンバーターのコーディングに抵抗し、試行錯誤を通して、ウィジェットの値を複雑なオブジェクトに設定すると、たとえば

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

...ユーザーがアイテムを選択すると、ウィジェットはそのオブジェクトの文字列セッターを呼び出すことができます。 setSelectedThing(String thingString) {...}、および渡される文字列は、Thingオブジェクトを表すJSON文字列です。これを解析して、選択されたオブジェクトを判別できます。これはハッキングに少し似ていますが、Converterほどハックではありません。

0
snakedog

私はこれを次のようにしています:

  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ではなくエンティティIDを使用する場合、衝突を起こすことができます-同じIDを持つ異なるエンティティ(クラス)の1つのページにいくつかのリストがある場合

0
user2512686