DBからオブジェクト/エンティティのリストを読み取り、JSF <h:selectOneMenu>
に入力する必要があるWebアプリケーションを作成しています。これをコーディングできません。誰かがそれを行う方法を教えてもらえますか?
DBからList<User>
を取得する方法を知っています。私が知る必要があるのは、このリストを<h:selectOneMenu>
に取り込む方法です。
<h:selectOneMenu value="#{bean.name}">
...?
</h:selectOneMenu>
質問履歴に基づいて、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)
}
そのような単純な。実際、T
のtoString()
は、ドロップダウン項目のラベルと値の両方を表すために使用されます。したがって、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
プロパティにも設定し、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
は、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>
表示ページ
<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は、このテーマに関する非常に有用な概要の回答を提供します。しかし、彼が提示しない1つの選択肢があります。複雑なオブジェクトを選択されたアイテムとして処理する独自の汎用コンバーターです。すべてのケースを処理する場合、これは非常に複雑ですが、単純なケースでは非常に簡単です。
以下のコードには、このようなコンバーターの例が含まれています。これは、OmniFaces SelectItemsConverter と同じ精神で動作し、UISelectItem(s)
を含むオブジェクトのコンポーネントの子を調べます。違いは、エンティティオブジェクトの単純なコレクションまたは文字列へのバインディングのみを処理することです。アイテムグループ、SelectItem
sのコレクション、配列、およびその他多くのことは処理しません。
コンポーネントがバインドするエンティティは、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");
}
}
怠け者と呼んでも、コンバータのコーディングは多くの不必要な作業のように思えます。私はPrimefacesを使用しており、以前はプレーンなVanilla JSF2リストボックスまたはドロップダウンメニューを使用していなかったため、ウィジェットは複雑なオブジェクトを処理できると仮定しました(つまり、選択したオブジェクトをその対応するゲッター/セッターにそのまま渡す)他の多くのウィジェットが行います。この機能がConverterなしのこのウィジェットタイプには存在しないことを(何時間も頭を悩ませた後)見つけてがっかりしました。実際、文字列ではなく複雑なオブジェクトにセッターを提供すると、黙って失敗し(単にセッターを呼び出さず、例外もJSエラーもありません)、多くの時間を費やしました BalusCの優れたトラブルシューティングツール 原因を見つけるために、これらの提案のいずれも当てはまらないので役に立たない。私の結論:リストボックス/メニューウィジェットは、他のJSF2ウィジェットにはない適応を必要とします。これは誤解を招きやすく、私のような知識のない開発者をうさぎの穴に導く傾向があります。
結局、私はコンバーターのコーディングに抵抗し、試行錯誤を通して、ウィジェットの値を複雑なオブジェクトに設定すると、たとえば
<p:selectOneListbox id="adminEvents" value="#{testBean.selectedEvent}">
...ユーザーがアイテムを選択すると、ウィジェットはそのオブジェクトの文字列セッターを呼び出すことができます。 setSelectedThing(String thingString) {...}
、および渡される文字列は、Thingオブジェクトを表すJSON文字列です。これを解析して、選択されたオブジェクトを判別できます。これはハッキングに少し似ていますが、Converterほどハックではありません。
私はこれを次のようにしています:
モデルは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}" />
HashCodeではなくエンティティIDを使用する場合、衝突を起こすことができます-同じIDを持つ異なるエンティティ(クラス)の1つのページにいくつかのリストがある場合