Wicket Examples - forminput編 -
「サンプルのソースを見る」の第5回forminput編。このサンプルでは、
- いろいろなフォーム要素の使い方
がなんとなくわかります(^_^;)。
アプリケーションクラス(FormInputApplication.java)
まずは、アプリケーションクラスです。こちらは、ホームページクラスを返しているだけなので、割愛します・・・といきたいところですが、このクラスでは、initメソッドをオーバーライドして処理を追加しています。
まずは、例外処理設定のインスタンスを取得して、指定したりソースが見つからなかった場合でも例外をスローしないように設定します。
getExceptionSettings().setThrowExceptionOnMissingResource(false);
次に、各言語用のボタンの設定を行います。ここでは日本語の設定を見てみます。まず、Fontのインスタンス生成します。次に、ボタンイメージ(DefaultButtonImageResourceクラス)のインスタンスを生成し、先ほど生成したFontインスタンスを設定します。DefaultButtonImageResourceクラスのコンストラクタには、ボタンのラベル、ここでは"保存"を指定しています。
Font fontJa = new Font("Serif", Font.BOLD, 16); DefaultButtonImageResource imgSaveJa = new DefaultButtonImageResource("\u4fdd\u5b58"); imgSaveJa.setFont(fontJa);
次に、共有リソース(SharedResourcesクラスのインスタンス)を取得し、先ほど生成したボタンイメージを追加します。addメソッドの第1引数には、リソースの論理名を指定しますが、これは各言語共通みたいです。
SharedResources sharedResources = getSharedResources(); sharedResources.add("save", Locale.JAPANESE, imgSaveJa);
Pageクラス(FormInput.java)
次に、Pageクラスです。コンストラクタでは、FeedbackPanelというコンポーネントと、Formコンポーネントを自分で拡張したコンポーネントを設定しています。FeedbackPanelコンポーネントは、メッセージを表示するさせることが出来るパネル、みたいです。このサンプルでは、"保存"ボタンを押下した際に、入力された内容が表示されます。
public FormInput() { final FeedbackPanel feedback = new FeedbackPanel("feedback"); add(feedback); add(new InputForm("inputForm")); }
次に、InputFormクラスです。Pageクラスの内部クラスで、Formコンポーネントを拡張しています。
private class InputForm extends Form
コンストラクタでは、まずスーパークラスのコンストラクタを呼んでいます。Modelとして、CompoundPropertyModelを使用しています。CompoundPropertyModelクラスのコンストラクタには、Modelに結びつくBeanを設定しますが、今までのサンプルではPageクラスを設定していましたが、このサンプルでは、別クラスを設定しています。
public InputForm(String name) { super(name, new CompoundPropertyModel(new FormInputModel()));
Formにコンポーネントを追加していきます。まずは、ロケールを選択するドロップダウンリストです。
add(new LocaleDropDownChoice("localeSelect"));
次に、デフォルトロケールに表示を切り替えるためのハイパーリンクのコンポーネントを追加しています。ハイパーリンクがクリックされた時に呼ばれるonClickメソッドをオーバーライドしています。onClickでは、リクエストからロケールを取得し、セッションに設定しています(setLocaleって、FormInput#setLocaleなんですかねぇ?)。
add(new Link("defaultLocaleLink") { public void onClick() { WebRequest request = (WebRequest)getRequest(); setLocale(request.getLocale()); } });
次に、テキストフィールドをFormに追加します。入力必須としたいため、RequiredTextFieldコンポーネントを使用します。このコンポーネントは、TextFieldコンポーネントにRequiredValidatorというバリデータが付加されたコンポーネント、ということができます。入力されなかった場合は、バリデーションのエラーとなるのですが、そのエラーメッセージ中のラベルをRequiredTextField#setLabelで設定しているようです。設定しないと、RequiredTextFieldのコンストラクタで指定したidが使用されるようです。
RequiredTextField stringTextField = new RequiredTextField("stringProperty"); stringTextField.setLabel(new Model("String")); add(stringTextField);
次も、RequiredTextFieldコンポーネントですが、コンストラクタの第2引数にInteger.classを指定することで、入力内容がIntegerかどうかのバリデーションも動作するようになります。加えて範囲を0以上に制限しています。よって、全角で"−100"や"-1"を入力すると、バリデーションエラーとなります。
RequiredTextField integerTextField = new RequiredTextField("integerProperty", Integer.class); add(integerTextField.add(NumberValidator.POSITIVE));
次も、RequiredTextFieldコンポーネントですが、入力内容がDoubleかどうかのバリデーションも動作します。
add(new RequiredTextField("doubleProperty", Double.class));
次は日付の入力です。まず、表示用のラベルとしてWebMarkupContainerクラスを使用していますが、「何故?」という感じです... orz TextFieldコンポーネントを使用していますので、入力の省略が可能です。コンストラクタの第2引数にDate.classを指定していますので、Date型として不適切な入力はエラーとなります。あと、Wicket Extensionで提供されているDatePickerを使い、日付を入力しやすいようにしています。この時、DatePickerで選択した日付がテキストフィールドに反映されるように、DatePickerのコンストラクタに、ラベルのコンポーネントとテキストフィールドのコンポーネントを指定しているようです。
WebMarkupContainer dateLabel = new WebMarkupContainer("dateLabel"); add(dateLabel); TextField datePropertyTextField = new TextField("dateProperty", Date.class); add(datePropertyTextField); add(new DatePicker("datePicker", dateLabel, datePropertyTextField));
次は、0から100までIntegerが入力できるテキストフィールドです。NumberValidator.rangeメソッドで、チェック範囲を持つValidatorを生成するようです。
add(new RequiredTextField("integerInRangeProperty", Integer.class).add(NumberValidator .range(0, 100)));
次は、単独のチェックボックスです。CheckBoxコンポーネントを使用します。
add(new CheckBox("booleanProperty"));
次は、ラジオボタンです。RadioChoiceコンポーネントを使用します。RadioChoiceのコンストラクタに、各ラジオボタンにつけるラベルのリストを設定します。また、RadioChoice#setSuffixでラジオボタン間につけるセパレータを設定します。また、エラーメッセージ中のラベルを設定し、選択必須としています。
RadioChoice rc = new RadioChoice("numberRadioChoice", NUMBERS).setSuffix(""); rc.setLabel(new Model("number")); rc.setRequired(true); add(rc);
次も、ラジオボタンです。RadioChoiceコンポーネントとは違う方法です。最初に、RadioGroupコンポーネントで、ラジオボタンをグループ化するインスタンスを生成します。次に、各ラジオボタン用に、ListViewインスタンスを生成します。ListView#populateItemをオーバーライドし、ラジオボタンごとにRadioコンポーネントとLabelコンポーネントを設定しているようです。
RadioGroup group = new RadioGroup("numbersGroup"); add(group); ListView persons = new ListView("numbers", NUMBERS) { protected void populateItem(ListItem item) { item.add(new Radio("radio", item.getModel())); item.add(new Label("number", item.getModelObjectAsString())); }; }; group.add(persons);
次は、複数選択のチェックボックスです。基本的に、RadioGroupを使ったラジオボタンのときと同じです。CheckGroupコンポーネントで、チェックボックスをグループ化するインスタンスを生成します。次に、各チェックボックス用に、ListViewインスタンスを生成します。ListView#populateItemをオーバーライドし、チェックボックスごとにCheckコンポーネントとLabelコンポーネントを設定しているようです。
CheckGroup checks = new CheckGroup("numbersCheckGroup"); add(checks); ListView checksList = new ListView("numbers", NUMBERS) { protected void populateItem(ListItem item) { item.add(new Check("check", item.getModel())); item.add(new Label("number", item.getModelObjectAsString())); }; }; checks.add(checksList);
次は、複数選択可能なリストボックスです。ListMultipleChoiceコンポーネントを使用します。
add(new ListMultipleChoice("siteSelection", SITES));
次も、テキストフィールドなわけですが、今までのStringやInteger型ではない型を使用する場合、Stringからある型へ、ある型からStringへ、相互変換するコンバータを用意します。ここでは、TextFieldのコンストラクタの第2引数にURL.classを指定し、コンバータを返すgetConverterをオーバーライドします。コンバータ自体は、SimpleConverterAdapterを拡張し、ある型から文字列に変換するtoStringメソッドと、文字列からある型に変換するtoObjectをオーバーライドします。
add(new TextField("urlProperty", URL.class) { public IConverter getConverter() { return new SimpleConverterAdapter() { public String toString(Object value) { return value != null ? value.toString() : null; } public Object toObject(String value) { try { return new URL(value.toString()); } catch (MalformedURLException e) { throw new ConversionException("'" + value + "' is not a valid URL"); } } }; } });
次もテキストフィールドなわけですが、今度はUsPhoneNumberという自作クラスを型として使用する場合です。ここでもgetConverterメソッドをオーバーライドしコンバータを返しているのですが、ここではMaskConverterという、マスクパターンを使用して変換を行うコンバータを使用しています。
add(new TextField("phoneNumberUS", UsPhoneNumber.class) { public IConverter getConverter() { return new MaskConverter("(###) ###-####", UsPhoneNumber.class); } });
次に、ListViewクラスを拡張したクラスを追加しています。ListViewクラス自体、よくわかっていませんが... orz
add(new LinesListView("lines"));
ここまでで入力用のコンポーネントは終わりで、あとは"保存""リセット"のボタンになります。まずは、"保存"用のイメージボタンを追加します。ImageButtonコンポーネントを使用しています。
add(new ImageButton("saveButton"));
次に、"リセット"用のイメージボタンです。LinkコンポーネントとImageコンポーネントを組み合わせて実現しています。これで、入力内容のリセットが出来るようですが、1) "InputForm.this"って何を指しているの? 2) modelChangedメソッド呼び出しでどうやってリセットされるの?、と不明点だらけです... orz
add(new Link("resetButtonLink") { public void onClick() { InputForm.this.modelChanged(); } }.add(new Image("resetButtonImage"))); }
やっと、InputFormのコンストラクタが終わりました(^_^;) 次は、"保存"ボタンが押下された時に呼ばれるInputForm#onSubmitメソッドのオーバーライドです。バリデーションエラーにならなかった場合に実行されるようで、FeedbackPanelに、Modelに設定されているインスタンス(FormInputModelのインスタンス)のtoStringメソッドを実行した結果を、infoメッセージとして登録するようです(FeedbackPanelにメッセージが表示されます)。
public void onSubmit() { info("Saved model " + getModelObject()); } }
次は、Localeを選択するドロップダウンリスト用のコンポーネントです。DropDownChoiceを拡張しています。
private final class LocaleDropDownChoice extends DropDownChoice {
LocaleDropDownChoiceクラスのコンストラクタでは、まずスーパークラスのコンストラクタを呼んでいます。この時、ドロップダウンリスト表示用のRendererクラスのインスタンスを設定しています。次にModelを設定しているわけですが、FormInputインスタンスのlocaleプロパティと結びつけるようです。でも、FormInputクラスにはsetLocaleしかないのですが、いいんですかねぇ?
public LocaleDropDownChoice(String id) { super(id, LOCALES, new LocaleChoiceRenderer()); setModel(new PropertyModel(FormInput.this, "locale")); }
次に、ドロップダウンリストの選択項目が変更された場合に、DropDownChoice#onSelectionChangedが呼ばれるようにするかどうかを指定します。デフォルトはfalseのようで、ここではtrueを指定してDropDownChoice#onSelectionChangedが呼ばれるようにしています。
protected boolean wantOnSelectionChangedNotifications() { return true; }
そのonSelectionChangedメソッドですが、空実装です(^_^;) PropertyModelを使っていますので、FormInput#setLocaleが呼ばれて変更内容が反映されますので、ここでは何もしていないみたいです。
public void onSelectionChanged(Object newSelection) { } }
ドロップダウンリストの表示内容をレンダリングするクラスです。
private final class LocaleChoiceRenderer extends ChoiceRenderer {
ドロップダウンリストの各表示項目を返します。返したいのは、現在のロケールでの、各ロケールの表示名です。現在のロケールがLocal.JAPANESEなら"日本語"、Locale.ENGLISHなら"Japanese"、といった具合です。
public Object getDisplayValue(Object object) { Locale locale = (Locale)object; String display = locale.getDisplayName(getLocale()); return display; } }
さて、最後の内部クラスです。
private static final class LinesListView extends ListView {
コンストラクタでは、setReuseItemsメソッドを呼んでいます。フォーム中で使用する場合は、設定する必要があるようです。
public LinesListView(String id) { super(id); setReuseItems(true); }
各項目ごとにテキストフィールドを用意します。PropertyModelのコンストラクタの第1引数は、item.getModel()の戻り値は、FormInputModel.Lineになります。FormInputModel.Lineのプロパティtextにアクセスします。
protected void populateItem(ListItem item) { item.add(new TextField("lineEdit", new PropertyModel(item.getModel(), "text"))); } }
モデルオブジェクトクラス(FormInputModel.java)
次は、モデルオブジェクトクラスです。Serializableを実装するのは必須、みたいで。基本的に、Beanですね(^_^;)
public final class FormInputModel implements Serializable {
フォーム中で使用するListView用のクラスです。textプロパティのみを持ちます。
public final class Line implements Serializable {
このクラスでは、toStringメソッドをオーバーライドしています。toStringメソッドでは、プロパティに設定されている値を表示するようになっています。この値が"保存"ボタンが押下された場合に表示される値になります。
HTMLテンプレートファイル(FormInput_ja.html)
HTMLテンプレートファイルは、ロケールごとに用意されており、Javaのプロパティファイルと同様の命名規則になります。
ハイパーリンクは、こんな感じです。
<a href="#" wicket:id="defaultLocaleLink">[default]</a>
日付選択のコンポーネントも、こんな感じ。
<span wicket:id="datePicker"></span>
フォーム中のListViewコンポーネントも、こんな感じ。
<ul> <li wicket:id="lines"> <input type="text" wicket:id="lineEdit" /> </li> </ul>
単独のチェックボックスは、こんな感じ。
<input wicket:id="booleanProperty" id="booleanProperty" type="checkbox"/>
ラジオボタンはこんな感じ。2通りのやり方がある、みたいです。
<span valign="top" wicket:id="numberRadioChoice" id="numberRadioChoice"> <input type="radio">foo</input> <input type="radio">bar</input> </span> <span wicket:id="numbersGroup"> <span wicket:id="numbers"> <input type="radio" wicket:id="radio"/> <span wicket:id="number">[this is where number will be]</span> </span> </span>
複数選択のチェックボックスは、こんな感じです。
<span wicket:id="numbersCheckGroup"> <span wicket:id="numbers"> <input type="checkbox" wicket:id="check"/> <span wicket:id="number">[this is where number will be]</span> </span> </span>
複数選択のリストボックスは、こんな感じです。
<select wicket:id="siteSelection" id="siteSelection"> <option>foo</option> <option>bar</option> </select>
"保存"のイメージボタン、"リセット"のハイパーリンク+イメージボタンは、こんな感じです。
<input wicket:id="saveButton" type="image" value="buttonFactory:save:Save"/> <a wicket:id="resetButtonLink" src=""> <img wicket:id="resetButtonImage" value="buttonFactory:reset:Reset"/> </a>
バリデーションエラーメッセージを表示したり、モデルオブジェクトの内容を表示したりするための、FeedbackPanalは、こんな感じです。div要素とspan要素の違いがよくわかっていませんが(^_^;)
<div id="feedbackPanel"> <span wicket:id="feedback"/> </div>
プロパティファイル(FormInput_ja.properties)
さて、最後のファイルとなる、プロパティファイルです。このファイルを何に使うかと言いますと、バリデーションエラーメッセージをロケールに合わせて表示する時に使用する、みたいです。
形式としては、[フォームのwicket:id] "." [フィールドのwicket:id] "." "TypeValidator"、みたいです。で、入力内容は、${input}、に埋め込まれるみたいです。
inputForm.urlProperty.TypeValidator='${input}' \u306f\u6b63\u3057\u3044URL\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 inputForm.phoneNumberUS.TypeValidator='${input}' \u306f\u6b63\u3057\u3044\u96fb\u8a71\u756a\u53f7\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002