前回、Enumを拡張しました
vermeer.hatenablog.jp
これを使ってJSFのSelectItemを実装します。
ちなみに、Enumだけでも十分対応は出来るようです。
【メモ】JSFでenumのオブジェクトをselectOneMenuに使う方法 - mike-neckのブログ
JSFのConverterで見た闇の話 - mike-neckのブログ
固定値であればEnumが良いですが、例えば何かしらの一覧をリストから選択したい場合、コード値とプロパティのペアを動的に設定したいことは良くあります。
今回、Enumに加えてMapも同様に扱うようにしました。
中途半端ではありますが、ResourceBundleも扱ってみました。*1*2
propertyはmessage.propertiesで設定にして表示文字を外部に持たせてます。
Map側はRDBに保存している値を使用する想定なので国際化対応はしない想定です。*3
以下の実装では、Enum has code and propertyの性別を変更して【更新】ボタンを押下したら、Mapのお菓子リストが更新されます。
実行対象のクラス・インターフェース・メッセージプロパティ
Enum
@AllArgsConstructor @Getter public enum Gender implements EnumCodePropertyInterface { MALE(1, "gender_male"), FEMALE(2, "gender_female"); private final Integer code; private final String property; }
Interface
public interface EnumCodePropertyInterface { public Object getCode(); public String getProperty(); }
Enum(拡張)のFactory
public class EnumCodeProperty { public static <E extends Enum<E> & EnumCodePropertyInterface> E codeOf(Class<E> enumType, @NonNull Object code) { for (E type : enumType.getEnumConstants()) { if (type.getCode().equals(code)) { return type; } } throw new IllegalArgumentException(); } public static <E extends Enum<E> & EnumCodePropertyInterface> E propertyOf(Class<E> enumType, @NonNull String property) { for (E type : enumType.getEnumConstants()) { if (type.getProperty().equals(property)) { return type; } } throw new IllegalArgumentException(); } }
message.properties
noselect=選択無し gender_male=男 gender_female=女
JsfのSelectItemのFactory
Enum も Map も同じメソッド名で使えるようにオーバーロードしています。
package com.mycompany.samplejsf.infrastructure.part.jsf; @RequiredArgsConstructor(staticName = "of") @Value public class JsfSelectItem { private final List<SelectItem> values; public static <E extends Enum<E> & EnumCodePropertyInterface> JsfSelectItem of(Class<E> enumClass) { List<SelectItem> items = new ArrayList<>(); ResourceBundle bundle = ResourceBundle.getBundle("message"); for (E item : enumClass.getEnumConstants()) { String value = bundle.containsKey(item.getProperty()) ? bundle.getString(item.getProperty()) : item.getProperty(); SelectItem selectItem = new SelectItem(item.getCode(), value); items.add(selectItem); } return JsfSelectItem.of(items); } public static JsfSelectItem of(Map<?, String> itemMap) { List<SelectItem> items = new ArrayList<>(); itemMap.entrySet().stream() .forEachOrdered(map -> { SelectItem item = new SelectItem(map.getKey(), map.getValue()); items.add(item); }); return JsfSelectItem.of(items); } }
実行資産
ManagedBean
package com.mycompany.samplejsf.domain.selectitem; @Named(value = "selectItem") @SessionScoped @NoArgsConstructor @Getter public class SelectItemController implements Serializable { private static final long serialVersionUID = 1L; @Setter private Integer selectItemValueDirectWrite; @Setter private Integer selectItemValueEnumCodeProperty; @Setter private Integer selectItemValueMap; private JsfSelectItem enumCodeProperty; private JsfSelectItem selectItemMap; private Map<Integer, String> dummuyMap; @PostConstruct public void init() { this.selectItemValueDirectWrite = GenderEnumOnly.MALE.ordinal(); this.selectItemValueEnumCodeProperty = Gender.FEMALE.getCode(); this.enumCodeProperty = JsfSelectItem.of(Gender.class); this.replaceSelectItemMap(); this.printLog("init"); } public void submit() { this.replaceSelectItemMap(); this.printLog("submit"); } private void replaceSelectItemMap() { if (this.selectItemValueEnumCodeProperty.equals(Gender.MALE.getCode())) { this.maleSnack(); return; } this.femaleSnack(); } private void maleSnack() { Map<Integer, String> map = new LinkedHashMap<>(); map.put(1, "大福"); map.put(2, "おはぎ"); map.put(3, "みたらしだんご"); map.put(4, "せんべい"); this.selectItemValueMap = map.keySet().iterator().next(); this.selectItemMap = JsfSelectItem.of(map); this.dummuyMap = map; } private void femaleSnack() { Map<Integer, String> map = new LinkedHashMap<>(); map.put(5, "チョコ"); map.put(6, "クッキー"); map.put(7, "プリン"); map.put(8, "ゼリー"); this.selectItemValueMap = map.keySet().iterator().next(); this.selectItemMap = JsfSelectItem.of(map); this.dummuyMap = map; } private void printLog(String label) { System.out.println("label = " + label); System.out.println("selectItemValueDirectWrite = " + selectItemValueDirectWrite); System.out.println("selectItemValueEnumCodeProperty = " + selectItemValueEnumCodeProperty); System.out.println("selectItemValueEnumCodeProperty codeOf = " + EnumCodeProperty.codeOf(Gender.class, selectItemValueEnumCodeProperty)); System.out.println("selectItemValueMap = " + selectItemValueMap); System.out.println("selectItemValueMap value = " + this.dummuyMap.get(this.selectItemValueMap)); } }
xhtml
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"> <head> <title>JSF SELECT ITEM</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></meta> </head> <body> <form jsfc="h:form" enctype="multipart/form-data"> <h1>JSF SELECT ITEM PATTERN</h1> <dir> <h2>Direct Write</h2> <select jsfc="h:selectOneMenu" value="#{selectItem.selectItemValueDirectWrite}"> <option jsfc="f:selectItem" itemLabel="#{msg['gender_male']}" itemValue="0"></option> <option jsfc="f:selectItem" itemLabel="#{msg['gender_female']}" itemValue="1"></option> </select> </dir> <hr /> <dir> <h2>Enum has code and property </h2> <select jsfc="h:selectOneMenu" value="#{selectItem.selectItemValueEnumCodeProperty}"> <option jsfc="f:selectItems" value="#{selectItem.enumCodeProperty.values}"></option> </select> </dir> <hr /> <dir> <h2>Map</h2> <select jsfc="h:selectOneMenu" value="#{selectItem.selectItemValueMap}"> <option jsfc="f:selectItems" value="#{selectItem.selectItemMap.values}"></option> </select> </dir> <hr /> <dir> <input type="submit" jsfc="h:commandButton" value="更新" action="#{selectItem.submit()}"/> </dir> </form> </body> </html>
実行結果
画面
コンソールログ
label = init selectItemValueDirectWrite = 0 selectItemValueEnumCodeProperty = 2 selectItemValueEnumCodeProperty codeOf = FEMALE selectItemValueMap = 5 selectItemValueMap value = チョコ label = submit selectItemValueDirectWrite = 0 selectItemValueEnumCodeProperty = 1 selectItemValueEnumCodeProperty codeOf = MALE selectItemValueMap = 1 selectItemValueMap value = 大福 label = submit selectItemValueDirectWrite = 0 selectItemValueEnumCodeProperty = 2 selectItemValueEnumCodeProperty codeOf = FEMALE selectItemValueMap = 5 selectItemValueMap value = チョコ
Code
2018/4/17 追加
vermeer_etc / jsf-selectitem / source / — Bitbucket
さいごに
JSFのSelectItemは、今回のクラスで、Enumであれ、Mapであれ統一して処理できます。また、lombokのおかげで 可視化されていませんが of(List
冗長的で使わないように思いますが、ファーストクラスコレクションとして統一した実装が出来るので用途が無いわけではないと思います。
今回はSubmitでMapのリストが変更するようにしました。
でも、リストの更新のためだけにForm全てをリクエストするのは性能面で好ましくないです。
次回はAjaxを使って、性能面にも配慮のある実装をしてみたいと思います。
*1:mike-neckのブログで「あぁ、そうかちゃんとしたいんだったら国際化対応すべきだ」と思い至り、とりあえずでも実装しておこうと思いました。
*2:ResourceBundleは、ちゃんとやろうとすると結構なボリュームになるので今後の課題にしたいと思います
*3:RDBの表現と同じものをmessage.propertiesにすべて準備する?いえいえ、それはそもそも設計から見直した方が良いように思います