【UI5・CAP】OData V4でドラフト対応したアプリを作る (5)Side Effectsを使ってヘッダの合計金額を更新


はじめに

この記事は、OData V4でドラフト対応したアプリを作るシリーズの5回目です。

前回の記事で、CAPのイベントハンドラで明細金額が更新、または明細削除されたら、ヘッダのTotal Amountを更新する処理を実装しました。
しかし、現状では明細の数量、金額を更新すると明細の金額は更新されますがヘッダのTotal Amountは更新されません。

今回は、Side Effectsというテクニックを使い、明細の金額変更や削除と、ヘッダのTotal Amountが連動するようにしたいと思います。

ソースコードは以下に格納しています。
https://github.com/miyasuta/odata-v4-freestyle/tree/master/app/demo.bookingapp

Side Effectsとは

あるエンティティを更新したときに、関連するエンティティの項目も更新される場合があります。そんなとき、コンテキストのrequestSideEffects()というメソッドにより影響を受ける項目を再取得することができます。
参考:Initialization and Read Requests>Side Effects

別の方法としてバインディングのrefresh()メソッドを使うこともできますが、その場合バインドしている全ての情報を取り直すことになるため、目的に合わせて使い分ける必要があります。

UI5での実装

1. 明細金額が変更されたタイミングをとらえる

以下は明細テーブルのビュー定義です。
変更を検知したい項目(quantity, price)にfieldGroupIdsというプロパティを設定します。さらに、上位のコントロールであるColumnListItemでvalidateFieldGroupというイベントにイベントハンドラを設定します。これで、quantityまたはpriceが変更されると、onValidateというイベントハンドラが動くことになります。
参考:Field Groups

fragments/Items.fragment.xml
        <ColumnListItem id="itemColumnListItem" validateFieldGroup="onValidate">
            <Text text="{itemNumber}"/>
            <Input value="{product}" editable="{viewModel>/editable}"/>
            <Input value="{quantity}" editable="{viewModel>/editable}" fieldGroupIds="amount" />
            <Input value="{price}" editable="{viewModel>/editable}" fieldGroupIds="amount"/>
            <Text text="{amount}" />
        </ColumnListItem>   
controller/Detail.controller.js
        onValidate: function (oEvent) {
            var filedGroupId = oEvent.getSource().getFieldGroupIds()[0];
            if (filedGroupId === "amount") {
                this._requestTotalAmount(); //Side Effectsをリクエスト
            }            
        },

2. 明細が削除されたタイミングをとらえる

明細の削除ボタンを押した際に動くイベントハンドラを使用します。

fragments/Items.fragment.xml
  <Table id="itemTable" 
        items="{
        path: 'to_items',
        parameters: {
            $orderby: 'itemNumber'
        }}" mode="{= ${viewModel>/editable} ? 'Delete' : 'None'}" delete="onDeleteItem">
controller/Detail.controller.js
        onDeleteItem: function (oEvent) {
            oEvent.getParameter("listItem").getBindingContext().delete("$auto")
            .then(()=> {             
                this._requestTotalAmount(); //Side Effectsをリクエスト
            })
            .catch(error => {
                MessageBox.error(error.message, {});                
            })
        },

3. Side Effectsをリクエスト

以下がSide Effectsをリクエストする処理です。コンテキストのrequestSideEffects()というメソッドを使用します。

controller/Detail.controller.js
        _requestTotalAmount: function () {
            this.getView().getObjectBinding().getBoundContext().requestSideEffects([{
                $PropertyPath: "totalAmount"
            }], "$auto");
        },

一つ目の引数にはロードしたいプロパティまたはナビゲーションプロパティを指定します。配列で複数プロパティを指定することが可能です。プロパティの指定方法には以下のパターンがあります。

パターン 意味
{$PropertyPath : "TEAM_ID"} プロパティ(単一項目)を指定
{$NavigationPropertyPath : "EMPLOYEE_2_MANAGER"} ナビゲーションプロパティを指定(ナビゲーション先のエンティティ全体をロードする場合)
{$PropertyPath : "EMPLOYEE_2_TEAM/Team_Id"} ナビゲーション先のプロパティ(単一項目)を指定

二つ目の引数にはgroup IDを指定します。このパラメータは任意で、指定がない場合はコンテキストバインディングの更新用のグループID(デフォルトは"$auto")が使用されます。

4. 動作確認

明細更新や削除のタイミングでTotal Amountが更新されるようになりました。
明細更新

明細削除

ネットワークタブを見ると、明細を更新したタイミングで2つの$batchリクエストが飛んでいます。2つ目のリクエストがSide Effectsをロードするリクエストで、totalAmountの項目のみを取得していることがわかります。

まとめ

  • あるエンティティを更新したときに、関連するエンティティの項目も更新される場合は、コンテキストのrequestSideEffects()というメソッドにより影響を受ける項目を再取得することができる。
  • requestSideEffects()は指定した最小限の項目しかロードしないため、バインディングのrefresh()と比べてパフォーマンスが良い。