0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

CAP JavaでObject Pageの項目の動的な入力制御を実装する

0
Last updated at Posted at 2025-10-13

やりたいこと

Object Pageで"status"に2が設定された場合、"stock"を入力不可にします。

image.png

ドメインモデル

namespace my.bookshop;

entity Books {
  key ID : Integer;
  title  : localized String;
  stock  : Integer;
  status : Association to Status;
}

entity Status {
  key ID : Integer enum { 
    OnSale = 1; 
    Discontinued = 2; };
  name   : String;
}

サービスモデル

using my.bookshop as my from '../db/data-model';

service CatalogService {
    @odata.draft.enabled
    entity Books as projection on my.Books;
    entity Status as projection on my.Status;
}

方法

ステップは以下の通りです。

  1. 制御用の項目を用意する
  2. 制御したい項目にアノテーションを設定する
  3. Side Effectの設定により、ある項目が変更されたら制御用の項目を再読み込みする

status.drawio (1).png

制御用の項目

制御用の項目は、Edm.Byteというデータ型で定義します。値の意味は以下のようになっています。

値 意味
7 必須
3 任意
1 照会のみ
0 現在のエンティティの状態では意味を持たない(いわゆるN/A)

参考:FieldControlType

CAPにはByteというデータ型はないので、@odata.TypeというアノテーションによりODataとして公開されるデータ型を指定します。

fieldControl: Int16 @odata.Type:'Edm.Byte'

項目制御用のアノテーション

制御対象項目に対し@Common.FieldControlというアノテーションで制御用の項目を指定します。

annotate CatalogService.Books with {
    stock @Common.FieldControl: fieldControl;
}

Side Effectの設定

Side Effectは項目が変更されたときやアクションが実行されたときに、他の項目またはエンティティを再取得する目的で使用します。
SourcePropertiesにSide Effectのトリガとなる項目、TargetPropertiesに再取得する項目または、1:1のアソシエーション先であるエンティティを指定します。
※1:nのアソシエーション先のエンティティについては、TargetEntitesで指定します。

参考:Side Effects

例
annotate CatalogService.Books with @(Common: {
    SideEffects #changeStatus : {
        $Type : 'Common.SideEffectsType',
        SourceProperties : ['status_ID'],
        TargetProperties : ['fieldControl'],
    },
});

制御用の項目の持たせ方

調べた限りでは2つのアプローチがありました。いずれも制御用の項目はDBに持たせません。

1. 制御用項目にデフォルト値を設定する

項目制御のロジックが比較的単純な式で表せる場合に使える方法です。

1.1. デフォルト値の設定

例として、(ID = #Discontinued ? #ReadOnly : #Optional )という式によって、IDが2のときにReadOnly (1)、そうでないときにOptional (3) を設定しています。

extend my.Status with {
    fieldControl: Int16 @odata.Type:'Edm.Byte' enum {Inapplicable = 0; ReadOnly = 1; Optional = 3; Mandatory = 7;}
    = (ID = #Discontinued ? #ReadOnly : #Optional );
}

参考リポジトリ:SAP-samples/cap-sflight

補足

  • #Discontinuedはデータモデルで定義されたenumのシンボルを参照しています
データモデル
  key ID : Integer enum { 
    OnSale = 1; 
    Discontinued = 2; };
  • ここではextend my.Statusというキーワードにより、データモデルで定義したエンティティを拡張していますが、もともとのStatusエンティティの中で制御用の項目を定義することも可能です。制御用であることをわかりやすくするために拡張という形をとっています

1.2. Side Effectの設定

Side Effectを以下のように設定します。TargetPropertiesにアソシエーション先のエンティティであるstatusを指定しています。

annotate CatalogService.Books with @(Common: {
    SideEffects #changeStatus : {
        $Type : 'Common.SideEffectsType',
        SourceProperties : ['status_ID'],
        TargetProperties : ['status'],
    },
});

制御対象項目と同じエンティティに制御用項目を持たせられない?

上の例では、Books(制御対象のstock項目が含まれる)と1:1のアソシエーション関係にあるStatusというエンティティに制御用の項目を持たせています。

Booksエンティティに制御用の項目を持たせてみると、Side EffectによりfieldControlが再取得されたときに古い値のままだったため、入力可否が切り替わりませんでした。一旦保存し、再度編集モードにしたときには正しく制御が行われました。

image.png
statusを2に変更したが、fieldContorlが切り替わらない

2. Virtual Elementを作成し、イベントハンドラでコントロールする

項目制御のロジックが複雑な場合、virtual elementとイベントハンドラ(After READ)の組み合わせで実現することが可能です。

2.1 Virtual Elementの追加

Booksエンティティにvirtual elementとして制御用の項目を追加します。

extend my.Books with {
    virtual fieldControl : Int16 @odata.Type: 'Edm.Byte' enum {
                Inapplicable = 0;
                ReadOnly = 1;
                Optional = 3;
                Mandatory = 7;
            };
};

補足

  • ここではextend my.Booksというキーワードにより、データモデルで定義したエンティティを拡張していますが、もともとのBooksエンティティの中で制御用の項目を定義することも可能です

2.2 Side Effectのアノテーション

ポイントは、TargetPropertiesにfieldControlの設定に必要なstatus_IDも含めることです。これがないと、After READのイベントハンドラにstatus_IDが入ってきません。

annotate CatalogService.Books with @(Common: {SideEffects #changeStatus: {
    $Type           : 'Common.SideEffectsType',
    SourceProperties: ['status_ID'],
    TargetProperties: ['fieldControl','status_ID'],
}, });

イベントハンドラ
イベントハンドラを以下のように実装します。

	@After(event = CqnService.EVENT_READ, entity = Books_.CDS_NAME)
	public void updateFieldControl(Stream<Books> books) {
		// 各bookのfieldControlを設定
		books.forEach(book -> {
			Integer statusId = book.getStatusId();
			if (statusId != null) {
				if (statusId == 2) { // Discontinued
					book.setFieldControl((short) 1); // ReadOnly
				} else {
					book.setFieldControl((short) 3); // Optional
				}
			}
		});
	}

サンプルリポジトリ

以下のリポジトリにサンプルコードを格納しています。

ブランチ

  • main: 制御用項目にデフォルト値を設定する方法
  • virtual_element: Virtual Elementsにより入力制御を行う方法
  • fieldcontrol_on_same_entity: アノテーションのみで入力制御(制御項目が同一のエンティティにあるパターン)⇒うまくいっていない
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?