Wicketで始めるオブジェクト指向ウェブ開発

第8回 組み込みAjax機能で動的に変化するページを実現する

この記事を読むのに必要な時間:およそ 6 分

お気に入りリンクのAjax化

お気に入りリンクは,クリックしたステータスをお気に入り登録すると同時に,ステータス上の「fav」リンクを「unfav」リンクに更新します。たった数文字が変化するだけなのですが,PagingTimelineまでの例では,常にページ全体が更新されていました。部分更新機能を利用して改善しましょう。

リスト5 お気に入りリンクのAjaxLink化

AjaxLink<Void> favLink = new AjaxLink<Void>("favLink") {
  @Override
  public void onClick(AjaxRequestTarget target) {
    try {
      Status currentStatus = getCurrentStatus(status.getId());
      Twitter twitterSession = AppSession.get().getTwitterSession();
      if(currentStatus.isFavorited()) {
        twitterSession.destroyFavorite(currentStatus.getId());
        info(getString("favorateRemoved"));
      } else {
        twitterSession.createFavorite(currentStatus.getId());
        info(getString("favorateRegistered"));
      }
      favName.setNeedRefresh(true);
      target.addComponent(feedback); //登録メッセージ表示のため,フィードバックパネルも更新する。
      target.addComponent(favName);
    } catch (TwitterException ex) {
      String message = getString("catNotCreateFavorite") + ": " + ex.getStatusCode();
      error(message);
      target.addComponent(feedback);
      LOGGER.error(message, ex);
    }
  }
};

少し長いですが,前回までのサンプルプログラムと見比べてみれば,大きく変化していないことが分かります。処理の最後で部分更新したいfavNameラベル※1をAjaxRequestTargetに登録している点と,同時にFeedbackPanelコンポーネントも登録している点が一番大きな変化です。AjaxRequestTargetを使ったページ更新では,addComponent()メソッドで登録したコンポーネント以外は一切更新されませんので,メッセージを表示するためにはFeedbackPanelもきちんと更新対象として登録する必要があります。

※1
favName変数は引用した部分では定義もなくいきなり使用されていますが,引用部分の外側で定義されています。

リスト上のitem要素を再利用する際の注意点

ところで,以前にListViewの使い方を説明したときに,setReuseItems()というメソッドについて説明をしました。このプログラムではsetReuseItems(true)とすることで,同じページオブジェクトである限りはListView内のItem要素(各行を表すオブジェクト)を再利用するように指定しています。この指定は,今回のように行の部分更新するためには重要です。

リンクをクリックすると,Wicketは直前に表示していたページ・オブジェクトをメモリ上に自動的に復元し,クリックされたコンポーネントを探し,onClick()を呼び出します。もしsetReuseItems(true)が呼ばれていなければ,Wicketはページを復元する段階で,ListViewの中身を再構築します。結果,クリックしたはずの行が正しく復元されるかどうかは確実ではなくなります。新しいステータス群で置き換わっているかもしれません。さきほどまで1行目に表示されていたステータスは,再構築時には5行目になっているかもしれません。

ListView内の要素への入力や操作を伴なうことを行う場合には,setReuseItems(true)を呼び出してください。

ローカルクラスで新しいクラスを定義する

サンプルプログラムを見ると,もう一箇所大きく変わっている場所があります。お気に入りリンクの文字を表示するためのラベルです。PagingTimelineまでのラベルは,各行のステータスがお気に入り登録されているかどうかに応じて「fav」「unfav」を表示するだけの,シンプルなプログラムでした。

AjaxTimelineクラス内では,より複雑なものとなっています。

リスト6 お気に入りリンク文字列を表示するためのローカルクラス

class FavoriteLabel extends Label {
  private static final long serialVersionUID = -2194580825236126312L;
  private Status targetStatus;
  private boolean needRefresh;
  
  public FavoriteLabel(String id) {
    super(id);
    this.targetStatus = status;
  
    setDefaultModel(new AbstractReadOnlyModel() {
      @Override
      public String getObject() {
        try {
          if(needRefresh) {
              targetStatus = getCurrentStatus(status.getId());
              needRefresh = false;
          }
          return targetStatus == null ? "" : targetStatus.isFavorited() ? "unfav" : "fav";
        } catch (TwitterException ex) {
          LOGGER.error("Can not fetch current status for status id = " + status.getId(), ex);
          return "error";
        }
      }
    });
  }
  
  public void setNeedRefresh(boolean needRefresh) {
      this.needRefresh = needRefresh;
  }
}

新しいラベルは,ただのLabelとしてではなく,FavoriteLabelという名前の新しいクラスとして定義しています。このクラス定義がコンストラクタ内に書かれている点に注目してください。この記法は「ローカルクラス」と呼ばれる,あるメソッド内でのみ有効なクラスを定義するための技法です。古くからJavaに存在する手法ですが,あまり多用されることはありません。

ローカルクラスは,概念的には今までも何度か使用した「匿名サブクラス」と同じようなものです。匿名サブクラスではクラス定義をすることなく即座にサブクラスを作ることができました。これは「名前のついていないローカルクラス」を作っているのと同じなのです。

ローカルクラスは匿名クラスと同じ特性を持っており,メソッド内のfinalのつけられたローカル変数にアクセスできますし,外部オブジェクトのフィールドにもアクセスすることができます。匿名クラスと異なるのは,完全なクラス定義をもつため,新しいpublicメソッドを定義できることです。

今回はローカルクラスの特性を生かし,ListView各行のステータスを表すstatusオブジェクトにアクセスしています。statusオブジェクトはFavoriteLabelクラスの外側にある点に注目してください。同時に,setNeedRefresh()というメソッドを新しく定義している点にも注目してください。このpublicメソッドの追加こそが,ここでローカルクラスを使っている理由です。

著者プロフィール

矢野勉(やのつとむ)

フリーランスのプログラマ。Wicket-ja主催。

ウェブ・アプリケーションの開発を中心にさまざまな案件に関わってきました。現在はWicketによる開発を支援しています。

URLhttp://d.hatena.ne.jp/t_yano/

著書