コンポーネントパラメータ

コンポーネントパラメータはTapestryの重要な一面です。コンポーネントのインスタンスが 存在 しているだけでは不十分で、 そのインスタンスが適切に動作するよう 設定 されている必要があります。設定はコンポーネントパラメータで行います。

コンポーネントは任意の数のパラメータを持つことができます。各パラメータは名前とJavaの型(プリミティブ型を含む)を持ち、 オプション または 必須 のどちらかとなります。

プライベートフィールドに対して Parameter アノテーションを用いることでパラメータを定義します。

以下に示すのはループを行うコンポーネントです。このコンポーネントは(ループの境界を設定する)startパラメータとendパラメータによって定義される回数分、 自身のボディを繰り返しレンダリングします。このコンポーネントはコンテナのプロパティにバインドされたvalueパラメータを更新することができ、 それはstartパラメータとendパラメータの大小関係によって自動的に増加または減少します。

package org.example.app.components;

import org.apache.tapestry5.annotations.AfterRender;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.SetupRender;

public class Count
{
    @Parameter
    private int start = 1;

    @Parameter(required = true)
    private int end;

    @Parameter
    private int value;

    private boolean increment;

    @SetupRender
    void initializeValue()
    {
        value = start;

        increment = start < end;
    }

    @AfterRender
    boolean next()
    {
        if (increment)
        {
            int newValue = value + 1;

            if (newValue <= end)
            {
                value = newValue;
                return false; 
            }
        }
        else
        {
            int newValue = value - 1;

            if (newValue >= end)
            {
                value = newValue;
                return false; 
            }
        }

        return true;
    }
}

パラメータ名はフィールド名を元に(先頭の "_" と "$" は取り除かれ)決定されます。 この例ではパラメータ名は "start" と "end" 及び "value" となります。

パラメータをバインドする

上記コンポーネントは他のコンポーネントテンプレートやページテンプレート内で使用することができます。

<html t:type="layout" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
    <p> Merry Christmas: <t:count end="3"> Ho! </t:count>
    </p>
</html>

endアトリビュートはCountコンポーネントのendパラメータを バインド するために使用されます。 この場合、文字列値"3"にバインドされますが、Tapestryによって自動的にint値の3に型変換されます。

任意のパラメータをこのようにバインドすることができます。

また、コンポーネントクラス内でComponentアノテーションを使うことでも コンポーネントパラメータをバインドすることができます。

Componentアノテーションによるバインディングとテンプレート内でのバインディングに矛盾がある場合、 Componentアノテーションのバインディングが優先されます。

バインディング式 (Binding Expressions)

上の例における "3" のようなテンプレート内の値を バインディング式 と呼びます。

値の前にプリフィックスを置くことで、式の残りの部分(コロンの後の部分)をTapestryがどのように解釈するか変更することができます:

プリフィックス説明
assetアセットファイルへの相対パス(存在していなければなりません)
blockテンプレート内のブロックのid
component同じテンプレート内の他のコンポーネントのid
literalリテラル文字列
nullfieldstrategy定義済みの NullFieldStrategy を指定します
messageコンポーネントのメッセージカタログから値を取り出す
prop参照または更新の対象となるコンテナのプロパティ名
translate設定済みTranslatorの名前
validateValidatorを作成するための Validator指定子
varレンダー変数の読み書きに使用します

パラメータにはプリフィックスが指定されなかった場合のデフォルトプリフィックスがあり、通常それは "prop:" です。

パラメータバインディングの継承をサポートするための 特別なプリフィックス、"inherit:" があります。

レンダー変数

コンポーネントは任意の数のレンダー変数を持つことができます。レンダー変数は、名前を持った型無しの値です(それらは内部的にはMapに格納されます)。 レンダー変数は、loopコンポーネントのループインデックスのような単純な値を保持し、他のコンポーネントへ渡すのに役立ちます。

例:

  <ul>
    <li t:type="loop" source="1..10" value="index">${index}</li>
  </ul>

Javaコード:

  @Property
  private int index;

... これは次のように書き換えることができます:

  <ul>
    <li t:type="loop" source="1..10" value="var:index">${var:index}</li>
  </ul>

つまり、Javaコード内にプロパティを定義しなくてよいということです。しかし、レンダー変数にはプロパティ式を用いることができないという欠点があります。 レンダー変数のを渡すことはできますが、その値のプロパティを参照することはできません。

レンダー変数は、コンポーネントのレンダリングが終了する際に自動的にクリアされます。

レンダー変数の名前は大文字小文字を区別しません。

プロパティバインディング

"prop:" バインディングプリフィックスはプロパティバインディングの指定です。

プロパティバインディングの式はプロパティ名をドットで繋いだものです。単純なプロパティ式は "prop:userName" とプロパティ名ひとつだけになります。 "prop:userData.name" のような複雑なプロパティ式は、参照または更新の対象となるプロパティを得るまで少し辿ることになります。

プロパティ名を指定する他に、任意のメソッドを実行することもできます。 ただし、そのメソッドはpublicで戻り値がvoidではなく、チェック例外を投げず、パラメータを取らないものである必要があります。 プロパティ名ではなくメソッド名を指定するには、単に一組の丸括弧を付けるだけです。 従って、前出の例の場合 "prop:getUserName()" や "prop:getUserData().getName()" と書くことができます。 しかし、式の最後の項がメソッド名のときは参照のみのバインディングとなることに注意してください。

これは Collection.size() や Map.keySet() などのように、 プロパティ名としては不適当な標準コレクションクラスのプロパティにアクセスするのに役立ちます。

式の途中でnullポインタにつまずいて、ダメになってしまう? "." のかわりに "?." を使うことができます。これはnullチェックを行い、nullの項で式の評価を停止します。 従って "foo?.bar?.baz" では、fooやbarがnullの場合は式全体の値がnullとなります。 また "foo?.bar?.baz" の更新では、fooやbarがnullの場合は何もしません。

加えて、いくつかの特例をサポートしています。 多くの場合、これらの特別な値によって "literal:" プリフィックスを値に付ける手間を節約することができます。 これらの特例はプロパティ式の もうひとつの 形です。

  • "true" と "false" はbooleanに変換されます。
  • "null" はnull値となります。
  • "this" はコンポーネント自身です。
  • 単純な数値を与えることができます。これはLongまたはDoubleのオブジェクトとして解釈されます。 例: "prop:3.14"
  • ピリオドふたつで整数の範囲を表すことができます。例: "1..10"
  • シングルクオートで囲んだリテラル文字列。例: "'Hello World'"

これら全てに於いて余分な空白は無視されます。キーワード("true", "false", "this", "null")では大文字小文字は区別しません。

これらの値は参照のみで不変値です。

Validateバインディング

"validate:" バインディングプリフィックスはとても特化したものです。 これはTextFieldやCheckboxのようなフォーム用コンポーネントの入力検証を行うオブジェクトの生成と設定を行うための短い文字列を受け取ります。

その文字列は Validatorタイプ のカンマ区切りリストです。Validatorタイプとは検証を実行するオブジェクトの短い別名です。 多くの場合Validatorには何らかの設定が必要です: 例えば、文字列の長さに最小値を設けるValidatorはその最小長さを知る必要があります。 その値は等号の後に指定します。

例: validate:required,minLength=5 は入力が必須で5文字以上となります。

Translateバインディング

"translate:" バインディングプリフィックスも入力検証に関係しています。 これは設定済みTranslatorの名前で、 データのサーバ側表現とクライアント側表現の間の変換を担当します(クライアント側では文字列値でサーバ側では数値、等)。

TranslatorSource サービスで設定したTranslatorが利用可能となります。

非公式パラメータ

いくつかのコンポーネントは 非公式パラメータ をサポートします。非公式パラメータとは公式に定義されているパラメータ以外の追加のパラメータです。 コンポーネントによってレンダリングされるタグの追加アトリビュートとして非公式パラメータは出力されます。 概して言えば、(TextFieldと<input>のように) 特定のHTMLタグと1:1で対応するコンポーネントは非公式パラメータをサポートします。

非公式パラメータをサポートするのは SupportsInformalParameters でアノテーションされたコンポーネントだけです。

非公式パラメータは要素の CSS class を指定したりクライアントサイドのイベントハンドラを指定するのによく利用されます。

非公式パラメータのデフォルトバインディングプリフィックスは、そのパラメータバインディングの指定が どこで 行われるかにより変わります。 そのパラメータがJavaクラス内のComponentアノテーションで バインドされるときは "prop:" がデフォルトのバインディングプリフィックスです。コンポーネントテンプレート内でバインドされる時は "literal:" です。 これは、Javaクラス内のアノテーションで指定するパラメータは計算された値であることが多く、 一方テンプレート内の値はHTMLにそのままコピーされて出力されればよいことが多い、ということを考慮した結果です。

非公式パラメータに対応するには

非公式パラメータを持つことができるのは SupportsInformalParameters でアノテーションされたコンポーネント だけ です。 このアノテーションが無いコンポーネントに指定された非公式パラメータは、警告無しに削除されます。

非公式パラメータをレンダリングするには、コンポーネントに ComponentResources をインジェクトし、renderInformalParameters() メソッドを実行してください。

パラメータは双方向です

パラメータはただの変数ではありません; パラメータはコンポーネントとコンテナのプロパティの連結や 束縛 を表すものです。 prop: バインディングプリフィックスを使った場合、コンポーネントのインスタンス変数にただ値を設定するだけで コンテナのプロパティも変更されます。

<t:layout xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
    <p> Countdown:
        <t:count start="5" end="1" value="index"> 
          ${index} ...  
        </t:count>
    </p>
</t:layout>

Countコンポーネントは自身のvalueパラメータ(_valueフィールド)を更新するので、コンテナのindexプロパティも更新されます。 Countのボディ内で ${index} を用いて現在のindexプロパティを出力しています。出力結果は次のようになります。

  <p> Countdown: 5 ... 4 ... 3 ... 2 ... 1 ... </p>

(空白は全く異なりますが)

コンポーネントは定数やコンテナの 動的な プロパティをを参照することができ、 またコンテナのプロパティを 変更 することもできるということです。

必須パラメータ

必須パラメータはバインドされなければ なりません。バインドされてない必須パラメータがあると実行時例外が発生します。

オプショナルパラメータ

必須ではないパラメータはオプションです。

オプショナルパラメータは、フィールドに初期値を与えることでデフォルト値を設定することができます。 Countコンポーネントではstartパラメータのデフォルト値は1です。startパラメータがバインドされなかった場合にこの値が使用されます。 バインドされた場合はデフォルト値に代わってバインドされた値が使用されます。

パラメータバインディングの継承

"inherit:" は特別なプリフィックスで、コンテナコンポーネントのパラメータ名を指定します。 コンテナ側でそのパラメータがバインドされた場合、埋め込みコンポーネント側も同じ値にバインドされます。

コンテナ側でそのパラメータがバインドされなかった場合、埋め込みコンポーネント側のパラメータはバインドされません (そして、埋め込みコンポーネント側のパラメータはデフォルトバインディングを使用します)。

バインディングの継承は複雑なコンポーネントで役に立ちます; 複雑なコンポーネントではしばしば、内側のコンポーネントがデフォルト値を持っていて 外側のコンポーネントでそれをオーバーライドできるようにしたいことがあります。

これに関する具体的なサンプルはもうすぐ用意します。

デフォルトパラメータバインディング

Parameterアノテーションのvalue()アトリビュートで バインディング式 を指定することで、パラメータのデフォルトバインディングとすることができます。 この指定が無い場合、パラメータはデフォルトではバインドされません。オンザフライで計算される値のプロパティ名を指定するのが典型的な使用方法です。

例:

  @Parameter("defaultMessage")
  private String message;
  
  @Parameter(required=true)
  private int maxLength;
  
  public String getDefaultMessage()
  {
    return String.format("Maximum field length is %d.", _maxLength);
  }

他の場所と同様、この値にはプリフィックスを付けることができます。 通常はローカライズされたメッセージにアクセスするためには "message:" プリフィックスを使います。

計算されたデフォルトパラメータバインディング

まれな ケースとして、パラメータのデフォルトバインディングに計算されたバインディングを使いたいことがあるかもしれません。 この場合 デフォルトバインディングメソッド を用意することになります。このメソッドは引数を取りません。戻り値がパラメータのバインドに使われます。 その戻り値はBindingインスタンスか単純な値(こちらの場合の方がより多い)です。

メソッド名は "default" に最初の文字を大文字にしたパラメータ名を繋げたものです。

この方法を使うと前の例は次のように書くことができます:

  @Parameter
  private String message;
  
  @Parameter(required=true)
  private int maxLength;
  
  @Inject
  private ComponentResources resources;
  
  @Inject
  private BindingSource bindingSource;
  
  Binding defaultMessage()
  {
    return bindingSource.newBinding("default value", resources, "defaultMessage");
  }
  
  public String getDefaultMessage()
  {
    return String.format("Maximum field length is %d.", maxLength);
  }

この例では、動的にメッセージにアクセスするためにプロパティ式 "defaultMessage" を用いています。

この例は次のようにより簡潔に書くこともできます。

  @Parameter
  private String message;
  
  @Parameter(required=true)
  private int maxLength;
  
  @Inject
  private ComponentResources resources;
  
  String defaultMessage()
  {
    return String.format("Maximum field length is %d.", maxLength);
  }  

この書き方は "literal:" バインディングプリフィックスを使うのに似ていますが、リテラル文字列が defaultMessage() メソッドによって計算されるという点が異なります。

明らかに、これはParameterアノテーションにただデフォルト値を指定するよりも多くのことを行います。 この方法が実際に使われている数少ない場所は、コンポーネントidから適切なバインディングを推測するデフォルトバインディングメソッドです。 例えば、TextFieldコンポーネントはidと同名のコンテナのプロパティにvalueプロパティをバインドすると推測します。

Parameterアノテーションにデフォルトバインディングが与えられなかった場合に のみ デフォルトバインディングメソッドは実行されます。

バインドされていないパラメータ

バインドされいない(そしてオプションの)パラメータの値はいつでも参照や 更新 ができます。

バインドされていないパラメータを更新しても副作用はありません。最初の例のCountコンポーネントはvalueパラメータがバインドされていませんが、 これは完全に有効です。

注意: このようなフィールドの更新は一時的なものです; コンポーネントの レンダリングが終了する とそのフィールドはデフォルト値に戻ります。

TODO: これは矛盾しているようにみえる。コンポーネントがレンダリングされていないときにバインドされていないパラメータを更新することは何を意味するのか?

パラメータキャッシング

パラメータの値の参照は(型変換のために)少しだけ高くつくかもしれません。 そのため、少なくともコンポーネントが自分自身をレンダリングしている間にパラメータの値をキャッシュすることは有意義です。

まれにキャッシュされないことが望まれる場合もあります; これはParameterアノテーションのcache()アトリビュートをfalseに設定することでできます。

パラメータの型変換

Tapestryは自動型変換の仕組みを持っています。 最もよくあるのはリテラル文字列から適切な値への変換ですが、もっと複雑な変換もよく行われます。

パラメータ名

デフォルトでは、フィールド名の先頭に "$" か "_" があればそれを取り除いてパラメータ名とします。

これはParameterアノテーションのname()アトリビュートで変更することができます。

バインドされているかどうかの判定

まれに、パラメータがバインドされているかどうかによって異なる処理を行いたいことがあるでしょう。 これはコンポーネントリソースに問い合わせることで実現できます。 コンポーネントリソースはInjectアノテーションを用いて コンポーネントにインジェクトすることができます。

public class MyComponent
{
  @Parameter
  private int myParam;
  
  @Inject
  private ComponentResources resources;
  
  @BeginRender
  void setup()
  {
      if (resources.isBound("myParam"))
      {
        . . .
      }
  }
}

上の概略はその方法を示しています。パラメータの型がプリミティブのint型であるため、 バインドされていないのか 0 に明示的にバインドされているのか区別するのは困難です。

InjectアノテーションがComponentResourcesをコンポーネントにインジェクトします。 このリソースはあなたが提供するJavaクラスとTapestryがあなたのクラスの周りに構築した基盤とを連結するものです。 リソースがいったんインジェクトされると、どんな場合でもそのリソースに問い合わせることが可能です。