コンポーネントイベント

コンポーネントイベントは、リンクのクリックやフォームのサブミットのようなユーザーの操作をコンポーネントが認識するための手段です。

コンポーネントイベントは次のふたつの目的のために使用されます:

  • コンポーネントイベントはクライアントWebブラウザ上のリンクやフォームでユーザーが起こしたリクエストを表します。 これは ページナビゲーションリクエストの処理 でより詳細に述べられています。
  • コンポーネントイベントはリクエスト内の制御の流れを表し、あるコンポーネントがそのコンテナに ("フォームがサブミットされた"といった)状況を通知したり、コンテナからデータを取り出したりする事を可能にします。

多くの場合、(ユーザーが起こした)ナビゲーションリクエストがいくつかの制御の流れを引き起こします。 例えば、Formコンポーネントはアクションリクエストをきっかけにして、フォームのサブミットが処理されようとしていることを通知するイベントを送ります。 そして、フォームのサブミットの成否を表すイベントを送信します。

Tapestry 4 では、イベント(たいていはユーザーからのリクエスト)が発生したときに実行するメソッド名をコンポーネントのパラメータに指定します。

これにはいくつかの制限があります。ひとつのメソッドしか実行でないということや、 メソッド名によってメソッドとコンポーネントを強く結びつけてしまうので、テンプレートとJavaコードの間で注意深い調整が必要になるということです。

Tapestry 5 では、イベントハンドラメソッド の概念を導入します。イベントハンドラメソッドは命名規約または OnEventアノテーション によって指定します。 イベントハンドラメソッドは、プライベートも含めどの可視性であっても構いません。通常はテストのためにパッケージプライベートとします。

特定のメソッドを実行するようにコンポーネントを設定するのではなく、 ひとつまたは複数のメソッドに対してコンポーネントからのイベントを待ち受けるよう指定します。 また、ひとつのイベントハンドラメソッドが複数のコンポーネントから通知を受けることもできます。

次の例は、ユーザーが1から10までの数字を選択するページの一部です(これを"Chooser"と呼ぶことにしましょう):

<p> Choose a number from 1 to 10: </p>

<p>
    <t:count end="10" value="index">
        <a t:id="select" t:type="actionlink" context="index">${index}</t:comp>
    </t:count>
</p>

ActionLinkコンポーネントはアクションURLを生成します。

このURLには、ActionLinkコンポーネントを含むページ("chooser")、 イベントの種類(イベントの種類が含まれていない場合はデフォルトの "action" イベントとみなされます。これは最も一般的なイベントです)、 ActionLinkコンポーネントのページ内におけるid("select")、さらにコンテキスト値が含まれています。

URLの例: http://localhost:8080/chooser.select/3

追加のコンテキスト値がある場合、それらはパスに追加されます。

この例はアクション指向の伝統的なフレームワークとTapestryとの決定的な違いを示しています。 このURLはリンクをクリックしたときに何が起こるかを表していません。リンクをクリックしたときに どのコンポーネントが責任を負うか を表しています。

URLをコード部品へ対応付ける単純なマッピングはありません; かわりにイベントハンドラメソッドの実行という形でコンポーネントが通知を送ります。 そして、Tapestryはあなたが書いたコードの適切な箇所を実行します。

ActionLinkコンポーネントのリンクをユーザーがクリックするとJavaのメソッドが実行されます。

  @OnEvent(component = "select")
  void valueChosen(int value)
  {
    this.value = value;
  }

ここではTapestryはふたつのことを行います:

  • valueChosen()メソッドを実行すべきメソッドとして特定する。
  • コンテキスト値を文字列から整数に変換しメソッドへ渡す。

上の例では、select コンポーネント上でデフォルトのイベント "action" が(ひとつ以上のコンテキスト値を持って)発生すると、valueChosen()メソッドが実行されます。

いくつかのコンポーネントは何種類かのイベントを発行するので、受け取るイベントの種類を限定したくなるでしょう:

  @OnEvent(value = "action", component = "select")
  void valueChosen(int value)
  {
    this.value = value;
  }

OnEventアノテーションのvalueパラメータに、イベントハンドラメソッドが待ち受けるイベント名を指定します。

"action" がデフォルトのイベントです。ActionLinkコンポーネントとFormコンポーネントはどちらもこのイベントを使用します。 OnEventアノテーションのcomponentパラメータを省略した場合、内包する すべての コンポーネントからの通知を受け取ります。 (イベントバブリングのため)これにはネストされたコンポーネントも含みます。

他と同じように、イベントの種類とコンポーネントidも大文字小文字を区別しません。

どのコンポーネントからイベントを受け取りたいか明示的に指定すべきです。 コンポーネントidを指定せずに @OnEvent アノテーションを使用した場合、そのメソッドは どのコンポーネントからのイベントであっても 実行されてしまいます。

イベントハンドラメソッドの命名規約

アノテーションを使用するかわりにある決まりに沿ってイベントハンドラメソッドを命名すると、 アノテーションを使用するのとまったく同じようにTapestryはそのメソッドを実行します。

この方式でのイベントハンドラメソッドの名前は "on" で始まり、イベント名が続きます。 さらに "From" と先頭を大文字にしたコンポーネントidを続けることができます(イベント名とコンポーネントidの大文字小文字は区別しません)。

前の例は次のように書き直すことができます:

  void onActionFromSelect(int value)
  {
    this.value = value;
  }

Howardのメモ: 私は命名規約のアプローチの方が好きであることがわかりました。アノテーションでなければならない状況でなければアノテーションは使わないでしょう。

イベントハンドラメソッドの戻り値

(ActionLinkやFormコンポーネントなどから発生する)ページナビゲーションに関するイベントでは、イベントハンドラメソッドからの戻り値によってTapestryがどのようにレスポンスをレンダリングするのか決まります

イベントハンドラメソッドが複数ある場合

ひとつのイベントに対してイベントハンドラメソッドが複数ある場合もあります。

その時の実行順序は次のようになります:

  • 基底クラスのメソッドの方がサブクラスのメソッドよりも先です。
  • 同じクラス内のメソッド同士では、アルファベット順となります。
  • 同じメソッド名の場合、パラメータの数の多い方が先です。

ひとつのイベントに対して複数のメソッドが必要になることは滅多にありません。

基底クラスのイベントハンドラメソッドをサブクラスでオーバーライドした場合、そのイベントハンドラメソッドは基底クラスの他のメソッドと共に一度だけ実行されます。 サブクラスは、基底クラスのメソッドをオーバーライドすることでその 実装 を変更することはできますが、メソッドが実行される タイミング を変更することはできません。 参照: TAPESTRY-2311

イベントコンテキスト

どんな種類のオブジェクトでもコンテキスト値(ActionLinkコンポーネントのコンテキストパラメータ)とすることができます。 しかし、文字列への単純な変換が行われるだけです。これは Tapestry 4 とは対照的です。 Tapestry 4 には "DataSqueezer" という変わった名前の手の込んだ型のメカニズムがありました。

繰り返しますが、(文字列、数値、日付など)どのような値であってもただの文字列に変換されます。 これによりURLはより読みやすくなります。

(ActionLinkのコンテキストパラメータにリストや配列をバインドして)複数のコンテキスト値を与えた場合、 それらはひとつずつその順番通りにURLに加えられます。

イベントハンドラメソッドが実行されるときに文字列から元の値やオブジェクトに変換されます。 クライアント側の文字列からサーバ側のオブジェクトへの変換は ValueEncoder が使用されます。 ValueEncoderSource サービスが ValueEncoder を提供します。

実行されるメソッド

イベントハンドラメソッドが実行されるのは イベントハンドラメソッドの引数の数と同じかそれ以上の数のコンテキスト値があるとき のみです。 コンテキスト値の数よりも引数の数が多いメソッドは警告など無しにスキップされます。

コレクション

イベントハンドラメソッドの引数の型が Object[], List, EventContext のどれか ひとつ だけの場合は、 コンテキスト値の数がいくつであってもそのイベントハンドラメソッドは実行されます。

イベントバブリング

イベントは中止されるまで階層を上って行きます。イベントはイベントハンドラメソッドがnull以外の値を返したときに中止されます。

イベントハンドラメソッドからboolean値を返すのは特別な場合です。trueを返すと結果無しでイベントを中止します; これは、結果無しでイベントの処理を完了し、それ以降のイベントハンドラ(同じコンポーネント内や上層のコンポーネントのイベントハンドラ)を実行すべきでないときに使います。

falseを返すのはnullを返すのと同じです。

イベントハンドラメソッドから例外を投げる

イベントハンドラメソッドは(RuntimeExceptionだけでなく)どんな種類の例外も投げることができます。 イベントハンドラメソッドから例外が投げられると、Tapestryはそれを補足し例外のリポートページを表示します。

つまり、何もしなくてよいということです:

  void onActionFromRunQuery()
  {
    try
    {
      dao.executeQuery();
    }
    catch (JDBCException ex)
    {
      throw new RuntimeException(ex);
    }
  }

上のようにするかわりに、単に次のようにすればよいのです:

  void onActionFromRunQuery() throws JDBCException
  {
    dao.executeQuery();
  }

より簡単に済ませたいなら、"throws Exception" をイベントハンドラメソッドで宣言してもよいです。

イベントハンドラメソッドからの例外を補足する

イベントハンドラメソッドが例外(チェック例外、非チェック例外のどちらでも)を投げたとき、 リポートページを表示する前にコンポーネントやそれを含むページが例外を補足することができます。

Tapestryは "exception" というイベントを新たに発生させ、補足した例外をコンテキスト値として渡します。 実際には、例外は ComponentEventException の中にラップされます。 そこから元のイベントの種類とコンテキスト値を取り出すこともできます。

例:

  Object onException(Throwable cause)
  {
    message = cause.getMessage();

    return this;
  }

例外イベントハンドラの戻り値は、元のイベントハンドラメソッドの戻り値を 置き換えます。 典型的なケース("activate" イベントや "action" イベントで例外が発生した場合)では、 戻り値はページインスタンスやページ名のような ページナビゲーション となります。

これは、URL内に含まれるデータが不適切な場合の対処に便利な場合があります。

上の例では、自分自身のページに戻るようページナビゲーションしています。

例外イベントハンドラが無い場合または例外イベントハンドラがnullを返したとき(または戻り値がvoidの場合)は、 例外は RequestExceptionHandler サービスに渡され、(デフォルト設定では)リポートページが表示されます。