前回までで,Twitterにログインしタイムライン表示用ページに遷移するところまでは実現できました。今回は,いよいよTwitterタイムラインを表示します。
Twitterタイムラインは,Twitterユーザの発言をリスト上に表示します。HTML上で見ると,同じ構造のタグが発言数分,ずらりと並んだ形をしています。JSPでそのような構造を作るときには,Javaのfor文もしくは同じような動作をするカスタムタグを使って,タグを繰り返し表示することで実現しました。
Wicketでは,このような繰り返しリスト全体を「繰り返し項目コンポーネント」として扱います。それがListViewクラスです。
ListViewによるコンポーネントの繰り返し
ListViewはHTML要素を繰り返すときに使うコンポーネントです。ListViewに対応するwicket:idは,「繰り返したいHTML要素」につけます。たとえば,次のようにすれば,repeatViewというwicket:idのついた<tr>タグ(内側の<td>タグも含めた全体)を繰り返し生成することができます。
リスト1 twitterスクリーンネーム一覧を表示するためのHTML
<table>
<tr wicket:id="repeatView">
<td wicket:id="userName">twitterのスクリーンネーム</td>
</tr>
</table>
この<tr>要素を繰り返すためのプログラムが、次のプログラムです。
リスト2 ListViewの使い方
ListView view = new ListView("id", new TwitterStatusListModel<List<Status>>()) {
@Override
protected void populateItem(final ListItem<Status> item) {
//itemが行を表すコンポーネント
//itemにコンポーネントをadd()することで,1行のコンポーネントを組み立てる
Status status = item.getModelObject();
item.add(new Label("userName", status.getUser().getScreenName()));
}
}
ListViewがデータを表示する際には,自身のモデルのgetObject()を呼び出します。モデルはgetObject()呼び出しの結果としてListを返します。ListViewはListの要素ひとつひとつを使ってpopulateItem()メソッドを呼び出します。populateItem()メソッドの引数は常にListItemで,このListItemが表の1行を表します。上記例でいえば,ListItemは<tr>タグを表しています。まさに、wicket:idをつけたタグです。wicket:idをつけたタグが、ListItemとして繰り返しpopulateItem()メソッドに渡されるのです。
ListViewに渡しているモデル「TwitterStatusListModel」はList<Status>型オブジェクトを返します。ListViewはこのListからStatusオブジェクトを1つずつ,populateItem()メソッドに渡します。Statusは行を表すコンポーネントであるListItemのモデルから取り出すことができます。item.getModelObject()が、ListItemからStatusを取り出しています。
あとはStatusオブジェクトを使って1行分のコンポーネントを組み立てるだけです。ListItemにコンポーネントを追加することで、<tr>タグの子要素を組み立てることができます。
この例では,wicket:id="userName"に対応するLabelコンポーネントを適用しています。LabelコンポーネントはStatusオブジェクトから取得できるスクリーンネームを表示します。
LoadableDetachableModelでリクエスト中のキャッシュを行う
ListViewの作り方がおおよそ分かりました。しかし,TwitterStatusListModelのような、Statusのリストを返すモデルがなければこのプログラムは動きません。
サンプルコード内のjp.gihyo.wicket.page.simple.Timelineクラスでは、TwitterStatusListModelの代わりに次のようなプログラムでTwitterのステータスを取得しています。
リスト3 Twitterのステータスリストを返すモデル
IModel<List<Status>> statusModel = new LoadableDetachableModel<List<Status>>() {
@Override
protected List<Status> load() {
try {
Twitter twitter = AppSession.get().getTwitterSession();
return twitter.getFriendsTimeline(new Paging(1, ITEMS_PER_PAGE));
} catch (TwitterException ex) {
MyTimeline.this.error(getString("canNotRetrieveFriendTimeline"));
return Collections.emptyList();
}
}
};
Statusクラスは,twitter4jが提供するTwitterのステータスを表現するクラスです。TwitterタイムラインはListView<Status>によって表示しますので,必要なモデル型はIModel<List<Status>>です。ListViewは常に自分が使うオブジェクトのListをモデルに期待します。IModel<List<Status>>に適合するモデルであれば何でもかまいません。
ここでは,見慣れないクラスLoadableDetachableModelを使いました。
LoadbaleDetachableModelクラスは,リクエスト毎の初回アクセス時にデータをロードし,キャッシュするモデルです。一度キャッシュされたデータは,レスポンスが返るまでは使い回されます。Twitterのステータス取得はTwitterサイトへのリモートAPI呼び出しを伴いますので,非常に重い処理です。リクエスト中に何度も呼ばれるとアプリケーション全体が重くなります。LoadableDetachableModelはこのようなケースに便利なモデル実装です。
Statusの取得
LoadbaleDetachableModelのload()メソッドをオーバーライドすることで,データのロード処理を記述します。
サンプルでは,前回までに作ったAppSessionクラスのgetTwitterSession()メソッドを使って,ログイン時に生成したTwitterオブジェクトを取得し,getFriendsTimeline()メソッドでタイムラインを取得します。結果値はList<Status>ですので,モデルの型と一致します。
getFriendsTimeline()は失敗する可能性がある操作です。失敗した場合にはTwitterExceptionがスローされますので,キャッチしてエラーメッセージを表示します。ここでは,FeedbackPanelに情報を表示するerror()メソッドを呼び出しています(※1)。
また,エラーの時でもモデルは何らかの値を返す必要がありますので,空のリストを返却しています。
このモデルをListViewに渡すことで,タイムライン表示リストを作ることができます。
- ※1
- MyTimeline.this.error()という表記を見慣れない方もいるでしょう。この記法はJavaの匿名サブクラスとローカルクラス特有のもので,自分を取り囲む親オブジェクトのthisにアクセスするための記法です。もし単純にthis.error()と書いた場合,thisはLoadableDetachableModelオブジェクトを意味しますので,error()メソッド呼び出しはコンパイルエラーになります(LoadableDetachableModelにそのようなメソッドはないからです)。MyTimeline.thisを付けることで,MyTimelineオブジェクトのerror()を呼び出すことを明示しているのです。

