BeanEditFormコンポーネントを使う

典型的なJavaBeanの生成/編集に必要な全てを備えたユーザーインターフェースを構築することができる強力なコンポーネントがTapestryにはあります。それは BeanEditForm です。

BeanEditFormはBeanのプロパティを解析し、プロパティが読み書き可能かを確認します。 既知のエディタにマップできる型のプロパティだけを残します(詳細は後述します)。

プロパティの順序は getterメソッド の定義順がデフォルトです。 親クラスで編集可能なプロパティが定義されている場合、そのプロパティの順序はサブクラスのプロパティよりも前になります。

サポートされている型

デフォルトでBeanEditFormがサポートするプロパティの型:

  • String: テキストフィールド
  • Number: テキストフィールド
  • Enum: ドロップダウンリスト
  • Boolean: チェックボックス
  • Date: JavaScriptカレンダー

プロパティをエディタに対応付ける際はプロパティの型の継承階層を辿ります: 例えば、Integer, Long, BigDecimal 等は Number が親クラスなので、データの入力にはテキストフィールドが使用されます。

サポートするプロパティの型は追加することができます(これについては後述します)。

オブジェクトの自動生成

ページがレンダリングされるとき、BeanEditFormは編集対象のJavaBeanとしてobjectパラメータを参照します (そして、そのJavaBeanのプロパティの現在値が各フィールドのデフォルト値となります)。 同様に、ユーザーによってフォームがサブミットされると、そのJavaBeanのプロパティにリクエストからの値を代入します。

objectパラメータの参照先にオブジェクトが存在しない場合、必要に応じてオブジェクトを生成します。参照先プロパティの型から生成するオブジェクトの型が決まるので、 参照先プロパティの型が具象型でなくインターフェースの場合に問題となります。

その場合、"prepare" イベントまたは "prepareForSubmit" イベントのイベントハンドラを用意して、 フォームからサブミットされた情報を受け取るインスタンスを生成することができます。

自動生成する際には、引数の数が 一番多い パブリックコンストラクタが使用されます。もし(例えば、例外が発生するなどで)これが不適切であれば、 自動生成に使用したいコンストラクタにInjectアノテーションを付けてください。 (訳注:おそらくコンストラクタの引数にはTapestry IoCコンテナからサービスがインジェクトされる。引数の型に対応する適切なサービスが見つからない場合に例外が発生する? 要確認)

暗黙のオブジェクトバインディング

objectパラメータが無い場合、コンテナコンポーネントのプロパティに暗黙のバインディングが行われます。 BeanEditFormのidと同じプロパティが存在する場合は、そのプロパティにバインドします。 したがって、BeanEditFormのidを(プロパティに一致するように)指定しobjectパラメータは使用しないのが一般的です。

非視覚的プロパティ

あるプロパティが更新可能で編集をサポートする型であっても、そのプロパティを編集できるようにユーザーに表示すべきでない場合があります: 例えば、データベースエンティティのプライマリキーを保持するプロパティです。 そのような場合、NonVisual アノテーションをそのプロパティに適用することができます (getterメソッド、setterメソッドのどちらでも構いません)。

デフォルトの入力検証

デフォルトの入力検証はプロパティの型によって決まります。

必要であればValidateアノテーションで検証を追加することができます。

プロパティの順序

デフォルトではプロパティの順序は定義順(getterメソッドの順序)となりますが、 ReorderProperties アノテーションで変更することができます。

デフォルトラベル

各フィールドのプロパティ名を基にそのフィールドのデフォルトラベルを提供します。 プロパティ名の先頭の文字を大文字にし、大文字に変わる箇所の前に空白を入れます。 例えば、"name" プロパティのラベルは "Name" となり、"streetAddress" プロパティのラベルは "Street Address" となります。

また、フィールドのラベルにはコンテナのコンポーネントメッセージカタログも使用されます。 プロパティ名の後ろに "-label" を加えたメッセージキーが存在する場合、その値がラベルに使われます。

プロパティエディタのオーバーライド

BeanEditFormにブロックパラメータを使うことで、特定のプロパティのエディタをオーバーライドすることができます。

通常のエディタはラベルと(TextFieldやTextAreaのような)フォームのフィールドコンポーネントで構成されます。

例えば、一カ所だけPasswordFieldコンポーネントを使用したい場合は次のようにできます:

  <t:beaneditform object="loginCredentials">
    <t:parameter name="password">
      <t:label for="password"/>
      <t:passwordfield t:id="password" value="loginCredentials.password"/>
    </t:parameter>
  </t:beaneditform>

他のフィールドは通常通りに表示されます(ビルトインのエディタが使用されます)。

BeanModelのカスタマイズ

ユーザーが編集すべきでないプロパティをフォームから取り除いたり、フォーム内でプロパティが表示される順序を変更したりと、 BeanModelをよりカスタマイズしたいことがあるでしょう。

BeanEditFormコンポーネントはこの目的のためにいくつかパラメータを備えています:

  • add: モデルに追加するプロパティの名前を並べたコンマ区切りのリスト。
  • exclude: モデルから取り除くプロパティの名前を並べたコンマ区切りのリスト。
  • reorder: 表示する順序でプロパティ名を並べたコンマ区切りのリスト。

reorderパラメータにリストされていないプロパティがモデルにある場合、そのプロパティはフォームの末尾に加えられます。

これらのパラメータはBeanModelを変更します。

追加するプロパティは通常のプロパティと衝突してはいけません。追加されたプロパティはオーバライドされなければ何も表示されません。 (訳注:「オーバーライド」とは、<t:parameter>とBlockによってUIをカスタマイズすることだと思われる。要確認)

BeanModelを与える

BeanEditFormコンポーネントの動作はBeanModelによって規定されます。 BeanModelはBeanEditFormが扱うプロパティを規定し、プロパティの表示順序やラベルなどを規定します。

通常、BeanEditFormはBeanModelを自動的に作成します。これはobjectパラメータにバインドされているオブジェクトの型を基にして行われます。

modelパラメータを使ってBeanModelを与えることもできます。これはremoveパラメータやreorderパラメータを使用するだけでは不十分な状況で役立ちます。 例えば、インターフェース型のプロパティを編集する場合、実装クラスについての情報を与えたBeanModelを明示的に提供することができます。

BeanModelはページが最初にインスタンス化されるときに作ることができます:

public class MyPage
{
  @Inject
  private BeanModelSource beanModelSource;
  
  @Inject
  private ComponentResources resources;

  @Property(write=false)
  @Retain
  private BeanModel model;

  @Property
  private MyBean bean;
  
  {
     model = beanModelSource.create(MyBean.class, true, resources);
     
     // Make other changes to model here.
  }  

}

そして、作成したBeanModelをコンポーネントテンプレート内でBeanEditFormコンポーネントに渡します:

  <t:beaneditform  object="bean" model="model"/>

新しいプロパティエディタを追加する

新しいプロパティエディタを追加するには3つのステップが必要です。

まず、データ型の論理名を決めます。例えば、アプリケーション内で通貨を表すのにBigDecimal型を使い、データ型の名前として "currency" を用いるとします。

次に、DataTypeAnalyzer または DefaultDataTypeAnalyzer サービスにcontribution設定を行い、プロパティと新しい名前の対応付けができるようにします。

DataTypeAnalyzerはコマンドチェーンで、プロパティの型かプロパティへのアノテーションを基にプロパティをデータ型に対応付けします。 通常はプロパティの型を基にするだけで良いのでDefaultDataTypeAnalyzerを使います。 DefaultDataTypeAnalyzerはプロパティの型の継承階層を辿ってデータ型への対応付けを行います。

public static void contributeDefaultDataTypeAnalyzer(MappedConfiguration<Class, String> configuration)
{
  configuration.add(BigDecimal.class, "currency");
}

"currency" データ型に対してエディタを提供しなければなりません。エディタはページ内のブロックとして定義します; このページは、それ自身を表示することはありませんが、ひとつまたは複数のブロックのコンテナとなります。

public class AppPropertyEditBlocks
{
    @Property
    @Environmental
    private PropertyEditContext context;
  
    @Component(parameters =
    { "value=context.propertyValue", "label=prop:context.label",
            "translate=prop:currencyTranslator", "validate=prop:currencyValidator",
            "clientId=prop:context.propertyId", "annotationProvider=context" })
    private TextField currency;

    @Inject
    private ComponentResources resources;

    public FieldValidator getCurrencyValidator()
    {
      return context.getValidator(currency);
    }
    
    public FieldTranslator getCurrencyTranslator()
    {
      return context.getTranslator(current);
    }
}

Translator は難しい部分です; Translator は、通貨の値をどのようにフォーマットしどのように解析するかを実装したコードの部品です。 Translator は FieldTranslator によってラップされます。

エディタを定義するコンポーネントテンプレート内のブロック:

  <t:block id="currency">
    <t:label for="currency"/>
    <t:textfield t:id="currency" size="10"/>
  </t:block>

最後に、BeanEditFormコンポーネントがこのエディタを使用できるようにするために、 BeanBlockSource サービスにcontribution設定を行います。

public static void contributeBeanBlockSource(Configuration<BeanBlockContribution> configuration)
{
  configuration.add(new BeanBlockContribution("currency", "AppPropertyEditBlocks", "currency", true));
}

これで、BeanEditFormがBigDecimal型のプロパティを見つけると、それを "currency" データ型に対応付けし、 AppPropertyEditBlocksページのcurrencyブロックが使用されるようになります。