Selectでoptgroupで動的で
プルダウンでoptgroupが使いたい場合、DropDownChoiceは使えないわけで、
org.apache.wicket.extensions.markup.html.form.select
パッケージのクラス群を使えとなっているのだけど、サンプルを見てもコードを見てもプルダウンの内容が動的に変わる場合、キビシイ。需要がないのかなあ?
ぐぐってみると、幾つか解決策がひっかかるのだけれども、あまり気に入らなかったので自作。
まず、グループの箱。インナークラス化したほうが良いやもしれない。
public class OptionGroup<T> implements Serializable{ private static final long serialVersionUID = 1L; /** optgroupのラベル */ private String groupName; /** グループ内の選択肢 */ private List<T> options; public OptionGroup(String groupName) { this.groupName = groupName; } public OptionGroup(String groupName, List<T> options) { this.groupName = groupName; this.options = options; } public String getGroupName() { return groupName; } public void setGroupName(String groupName) { this.groupName = groupName; } public List<T> getOptions() { return options; } public void setOptions(List<T> options) { this.options = options; } public void addOptionData(T data) { if(options == null) { options = new ArrayList<T>(); } options.add(data); } }
次に SelectOptions クラスの代わり。モデルの型の問題で SelectOptions の継承では実現できなかったので、その親の RepeatingView を継承したけど、ほぼ SelectOptions のコードのまま。
public class GroupableSelectOptions<T> extends RepeatingView { private static final long serialVersionUID = 1L; private boolean recreateChoices = false; private final IOptionRenderer<T> renderer; public GroupableSelectOptions(String id, IModel<Collection<OptionGroup<T>>> model, IOptionRenderer<T> renderer) { super(id, model); this.renderer = renderer; setRenderBodyOnly(true); } public GroupableSelectOptions(String id, Collection<OptionGroup<T>> groups, IOptionRenderer<T> renderer) { super(id, new WildcardCollectionModel<OptionGroup<? extends T>>(groups)); this.renderer = renderer; } public GroupableSelectOptions<T> setRecreateChoices(boolean refresh) { recreateChoices = refresh; return this; } @SuppressWarnings("unchecked") @Override protected final void onPopulate() { if (size() == 0 || recreateChoices) { removeAll(); Collection<OptionGroup<T>> modelObject = (Collection<OptionGroup<T>>) getDefaultModelObject(); if (modelObject != null) { if (!(modelObject instanceof Collection)) { throw new WicketRuntimeException("Model object " + modelObject + " not a collection"); } for (OptionGroup<T> group : modelObject) { int lastIndex = group.getOptions().size() - 1; for (int i = 0; i < group.getOptions().size(); i++) { T value = group.getOptions().get(i); WebMarkupContainer row = new WebMarkupContainer(newChildId()); row.setRenderBodyOnly(true); add(row); boolean isStart = (i == 0); boolean isEnd = (i == lastIndex); row.add(newOption(group, value, isStart, isEnd)); } } } } } protected SelectOption<T> newOption(OptionGroup<? extends T> group, T value, boolean isGroupStart, boolean isGroupEnd) { IModel<T> model = renderer.getModel(value); String text = renderer.getDisplayValue(value); GroupSelectOption<T> option = new GroupSelectOption<T>("option", model, text); option.isGroupStart = isGroupStart; option.isGroupEnd = isGroupEnd; if(isGroupStart) { option.groupName = group.getGroupName(); } return option; } private static class GroupSelectOption<V> extends SelectOption<V> { private static final long serialVersionUID = 1L; private String groupName; private boolean isGroupStart = false; private boolean isGroupEnd = false; private String text; public GroupSelectOption(String id, IModel<V> model, String text) { super(id, model); this.text = text; } @Override protected void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) { replaceComponentTagBody(markupStream, openTag, text); } //これがしたかったのだけど、ずいぶん遠回りしたもんだ @Override protected void onRender(MarkupStream markupStream) { if (isGroupStart) { getResponse().write("<optgroup label=\"" + groupName + "\">" + groupName + "\n"); } super.onRender(markupStream); if (isGroupEnd) { getResponse().write("</optgroup>\n"); } } } }
最後に使い方。ページクラスの方。
@SuppressWarnings("serial") public class OptgroupPage extends WebPage implements Serializable{ private static List<OptionGroup<Prefecture>> prefectures; // 県のデータ static class Prefecture implements Serializable { String code; String name; Prefecture(String code, String name) { this.code = code; this.name = name; } } // 選択肢の初期化 static { prefectures = new ArrayList<OptionGroup<Prefecture>>(); OptionGroup<Prefecture> touhoku = new OptionGroup<Prefecture>("東北"); touhoku.addOptionData(new Prefecture("02", "青森県")); touhoku.addOptionData(new Prefecture("03", "岩手県")); touhoku.addOptionData(new Prefecture("04", "宮城県")); prefectures.add(touhoku); OptionGroup<Prefecture> kanto = new OptionGroup<Prefecture>("関東"); kanto.addOptionData(new Prefecture("13", "東京都")); kanto.addOptionData(new Prefecture("14", "神奈川県")); prefectures.add(kanto); } public OptgroupPage() { super(); final Label display = new Label("display", Model.of("未選択")); add(display); Form<Void> form = new Form<Void>("form"); add(form); // Select 作って final Select select = new Select("select", new Model<Prefecture>()); // Renderer 準備するところまでは従来通り IOptionRenderer<Prefecture> renderer = new IOptionRenderer<Prefecture>() { @Override public IModel<Prefecture> getModel(Prefecture value) { return Model.of(value); } @Override public String getDisplayValue(Prefecture object) { return object.name; } }; // 違うのはここだけ select.add(new GroupableSelectOptions<Prefecture>("options", prefectures, renderer)); form.add(select); Button submit = new Button("submit"){ @Override public void onSubmit() { Prefecture selected = (Prefecture)select.getModelObject(); if(selected != null) { display.setDefaultModelObject(selected.name); } } }; form.add(submit); } }
OptgroupPage.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta http-equiv="content-script-type" content="text/javascript"> <meta http-equiv="content-style-type" content="text/css"> <meta name="description" content=""> <title>Wicket Test</title> </head> <body> <h2>GroupableSelectOptions</h2> <h4 wicket:id="display">Selected Prefecture Name</h4> <form wicket:id="form"> <select wicket:id="select"> <!-- 未選択状態を作りたいときは書く。value属性を書かないとException --> <option value="">選択してください</option> <wicket:container wicket:id="options"> <option wicket:id="option">Option Label</option> </wicket:container> </select> <input type="submit" value="選択" wicket:id="submit"/> </form> </body> </html>
需要があるのか、そもそもこれ使いやすいのか疑問はあるのだけれども、とりあえず晒してみる。