それなりの日記 このページをアンテナに追加 RSSフィード

2007-08-13

[][] 最新のWicketプラグインインストールについて

現時点での、最新のWicketプラグインインストール方法のメモ

# (プロキシを経由しないでインストールする場合)
> grails install-plugin wicket 0.3
# (プロキシを経由してインストールする場合)
> grails -Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 install-plugin wicket 0.3

い、いつの間に、0.3 (-_-;)・・・、い、いつの間に、"Wicket"から"wicket"に (-_-;)・・・。

2007-07-06

[][] Grailsを0.5.6にあげたら...

Grails 0.5 + Wicket Plugin 0.1 の環境Grails 0.5.6 + Wicket Plugin 0.1 にあげたら、

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'grailsUrlMappingsHolder': Invocation of init method failed; nested exception is org.codehaus.groovy.runtime.InvokerInvocationException: org.codehaus.groovy.grails.validation.exceptions.ConstraintException: Exception thrown applying constraint [notEqual] to class [interface org.codehaus.groovy.grails.web.mapping.UrlMapping] for value [app]: null

と例外発生...orz

仕方ないので、UrlMappingクラス

class XyzUrlMappings {
    static mappings = {
        "/$controller/$action?/$id?" {
            constraints {
                controller( notEqual : "app" )
            }
        }
    }
}

から

class XyzUrlMappings {
    static mappings = {
        "/xyz/$action?/$id?" {
            controller = "xyz"
        }
    }
}

に変更してしのいでいます...orz

2007-06-21

[] Incubator卒業みたいで

メーリングリストによると、Apache Incubatorを卒業するみたいで。

2007-06-04

[] 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

[] Wicket Examples - guestbook編 -

「サンプルのソースを見る」の第6回guestbook編。このサンプルでは、

  • ListViewコンポーネントのモデルオブジェクトを変更して通知する方法

がなんとなくわかります(^_^;)。

アプリケーションクラス(GuestBookApplication.java)

まずは、アプリケーションクラスです。こちらは、ホームページクラスを返しているだけなので、割愛します。

Pageクラス(GuestBook.java)

次に、Pageクラスです。

public final class GuestBook extends WicketExamplePage
{

Commentインスタンスを格納するリスト(commentList)と、ListViewです。commentListがstaticとなっており、全ユーザからの情報が追加・参照されるようです。

	private static final List commentList = new ArrayList();
	private final ListView commentListView;

コンストラクタでは、Formのインスタンスを追加し、ListViewコンポーネントのインスタンスを追加しています。populateItemメソッドでは、入力内容であるCommentインスタンスの、日付をLabelコンポーネントに、入力テキストをMultiLineLabelコンポーネントに設定しています。setVersionedメソッドを引数をfalseで呼び、バージョニングをしないようにしています・・・なんのことやらですが(^_^;)

	public GuestBook()
	{
		add(new CommentForm("commentForm"));

		add(commentListView = new ListView("comments", commentList)
		{
			public void populateItem(final ListItem listItem)
			{
				final Comment comment = (Comment)listItem.getModelObject();
				listItem.add(new Label("date", new Model(comment.getDate())));
				listItem.add(new MultiLineLabel("text", comment.getText()));
			}
		}).setVersioned(false);
	}

次にFormコンポーネントです。

	public final class CommentForm extends Form
	{

コンストラクタでは、スーパークラスのコンストラクタを呼んで、TextAreaコンポーネントを追加しています。

		public CommentForm(final String id)
		{
			super(id, new CompoundPropertyModel(new Comment()));
			add(new TextArea("text"));
		}

Submitボタンが押下された時に呼ばれるonSubmitメソッドをオーバーライドします。モデルオブジェクトであるCommentクラスのインスタンスを取得し、それをもとに新たなCommentインスタンスを生成します。生成したインスタンスに現在の日付を設定して、commentListの先頭に追加するわけですが、追加する前にListView#modelChangingメソッドを、追加したあとにListView#modelChangedメソッドを呼ぶことで、ListViewに反映を通知しているようです。

		public final void onSubmit()
		{
			// Construct a copy of the edited comment
			final Comment comment = (Comment)getModelObject();
			final Comment newComment = new Comment(comment);

			// Set date of comment to add
			newComment.setDate(new Date());

			// Add the component we edited to the list of comments
			commentListView.modelChanging();
			commentList.add(0, newComment);
			commentListView.modelChanged();

			// Clear out the text component
			comment.setText("");
		}
	}

Beanクラス(Comment.java)

次に、Beanクラスです。コメントと日付を持つクラスです。

public class Comment implements Serializable
{

HTMLテンプレートファイル(GuestBook.html)

最後に、HTMLテンプレートファイルです。

ListViewコンポーネントです。今までに入力された内容を、最新のものからここに表示します。表示内容は、日付と入力されたコメントです。

  
    

1/1/2004
Comment text goes here.

あと、wicket:remove要素があるのですが、実行には消されるようです。表示確認用ですかねぇ?

  <wicket:remove>
    <p>
	    1/2/2004<br/>
	    More comment text here.
    </p>
  </wicket:remove>

2007-06-02

[] Wicket Examples - stockquote編 -

「サンプルのソースを見る」の第4回、stockquote編。このサンプルでは、

  • 自分でコンポーネントを作ってみる
  • 既存のコンポーネントを拡張してみる
  • AbstractReadOnlyModelというModelを使う

がなんとなくわかります(^_^;)。ただ、わたしの環境では動きませんでしたが... orz

アプリケーションクラス(StockQuoteApplication.java)

まずは、アプリケーションクラスです。こちらは、ホームページクラスを返しているだけなので、割愛します。

Pageクラス(StockQuotePage.java)

次に、Pageクラスです。このサンプルでは、

  • WebComponentを拡張したラベルコンポーネント(StockQuoteLabel)
  • Labelコンポーネントを拡張したラベルコンポーネント(StockQuoteLabel2)

を作成し、使用しています。StockQuoteLabelの方は、"IBM"の株式情報(株価?)を取得するパターンと、テキストボックスに入力されたシンボルの株式情報を取得するパターンがあります。StockQuoteLabel2の方は、"IBM"取得するパターンだけになります。

コンポーネントクラス(StockQuoteLabel.java)

次に、コンポーネントクラスです。こちらは、WebComponentというクラスを拡張しています。

public class StockQuoteLabel extends WebComponent

コンストラクタは3つありますが、最終的に、1つ目はWebComponent(String)、2つ目3つ目はWebComponent(String, IModel)を呼んでいます。

	public StockQuoteLabel(String id)
	public StockQuoteLabel(String id, String symbol)
	public StockQuoteLabel(String id, IModel model)

このコンポーネントでは、Modelに設定されたシンボル("IBM"や入力された値)の値を取得、その値の株式情報をSOAPで取得し、コンポーネントの要素本体を取得した値でレンダリングします。その処理をしているのがこれ。

	protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag)
	{
		String symbol = getModelObjectAsString();
		StockQuote quote = new StockQuote(symbol);
		replaceComponentTagBody(markupStream, openTag, quote.getQuote());
	}

まず、Modelに設定された値を「文字列として」取得します。

		String symbol = getModelObjectAsString();

ここで、コンストラクタで指定された文字列の値、あるいは、コンストラクタで指定されたModelに設定された値が取得できます。次に、株式情報をSOAPで取得するクラス(StockQuote)のインスタンスを生成します。

		StockQuote quote = new StockQuote(symbol);

最後に、SOAPで株式情報を取得し、取得した値でコンポーネントの要素本体をレンダリングします。

		replaceComponentTagBody(markupStream, openTag, quote.getQuote());

コンポーネントクラス(StockQuoteLabel2.java)

次に、コンポーネントクラスです。こちらは、おなじみのLabelコンポーネントを拡張しています。

public class StockQuoteLabel2 extends Label

コンストラクタは1つで、Label(String, IModel)を呼んでいるのですが、ここではAbstractReadOnlyModelというModelの無名クラスを作成して設定しています。

	public StockQuoteLabel2(String id, final String symbol)
	{
		super(id, new AbstractReadOnlyModel()
		{
			public Object getObject(Component component)
			{
				final StockQuote quote = new StockQuote(symbol);
				return quote.getQuote();
			}
		});
	}

ここでは、Modelに設定された値を取得するメソッドである、getObjectをオーバーライドしています。取得したい内容は、シンボルに対応する株式情報ですので、StockQuoteLabel#onComponentTagBodyと同様にして株式情報を取得し、戻り値として返しています。

ユーティリティクラス(StockQuote.java)

次は、シンボルに対応する株式情報を取得するクラスです。このクラスはPOJOになっています。

public class StockQuote
{

内容は割愛しますが、やっていることは、

  • コンストラクタで指定されたシンボルを元に、リクエスト用のSOAPメッセージを作成
  • リクエスト用のSOAPメッセージを送信
  • レスポンス用のSOAPメッセージを受信
  • レスポンス用のSOAPメッセージから株価情報を取得

になります。

HTMLテンプレートファイル(StockQuotePage.html)

最後に、HTMLテンプレートファイルです。自作のコンポーネントを使用するわけですが、こんな風に使っています。

    	IBM stockquote (example 1): <span wicket:id="stockIBM">1.23</span>

    	<form wicket:id="form">
    		Type symbol: <input wicket:id="symbol" type="text" />
    		<input type="submit" />
    	</form>
    		Stock quote of <span wicket:id="symbol">symbol</span> is <span wicket:id="quote">quote</span>.

普通のLabelコンポーネントと同様に、wicket:id属性を持つspan要素の要素本体がレンダリングされます。wicket:id属性が"symbol"のTextField, Labelコンポーネントと、wicket:id属性が"quote"のStockQuoteLabelコンポーネントに、Pageクラスで同じModelが指定されていますので、テキストフィールドに入力された値が、LabelコンポーネントとStockQuoteLabelコンポーネントで使用されます。前者では入力された値を使ってそのままレンダリングし、後者ではその値をもとに株式情報を取得し、取得した株式情報を使ってレンダリングします。