【SAPUI5】ODataV4を触ってみる

#はじめに
こんにちは。
SAPUI5にてODataV4を触る機会があったので
備忘録としてCRUD操作などの方法を記事として書かせていただきます。

#前提
以下の記事を参考にODataサービスを作成し、そのサービスをODataV4として使用します。
今回はUIモジュールを適当に作成しそこで上記のサービスを触っていきます。
[【CAP】CAPで作ったODataをUI5アプリで使う(1) CAPサービスの作成](https://qiita.com/tami/items/d1b69a93da14e288faf2)

#manifest.jsonを変更する
```manifest.json
{
    "_version": "1.21.0",
    "sap.app": {

        // 〜省略〜

        "dataSources": {
            "capServicedevices": {
                "uri": "/capService/cap-mock/",
                "type": "OData",
                "settings": {
                    "localUri": "localService/capService/cap-mock/metadata.xml",
                    "odataVersion": "4.0"
                }
            }
        }
    },
    "sap.ui": {
        "technology": "UI5",
        "icons": {
            "icon": "",
            "favIcon": "",
            "phone": "",
            "phone@2": "",
            "tablet": "",
            "tablet@2": ""
        },
        "deviceTypes": {
            "desktop": true,
            "tablet": true,
            "phone": true
        }
    },
    "sap.ui5": {

        // 〜省略〜

        "models": {
            "oDataDevicesModel": {
                "type": "sap.ui.model.odata.v4.ODataModel",
                "settings": {
                    "synchronizationMode": "None",
                    "operationMode": "Server",
                    "earlyRequests": true,
                    "groupProperties": {
                        "default": {
                            "submit": "Auto"
                        }
                    },
                    "groupId": "$auto"
                },
                "dataSource": "capServicedevices"
            }
        }

        // 〜省略〜

    }
}
```
manifest.jsonのdataSourcesとmodelsに使用するODataサービスの設定を記載します。
<dl>
  <dt>synchronizationMode</dt>
  <dd>バインディング間の同期を制御するものだそうです。 'None'以外の値はまだサポートされていないらしいので'None'を指定してください。</dd>
  <dt>operationMode</dt>
  <dd>フィルタリングとソートの操作モードの設定値です。'Server'を設定することでODataサービスリクエストのサーバーで、対応するURLパラメーター（ \&filter、\&orderby）を追加することによって操作が実行されるようになるそうです。</dd>
  <dt>earlyRequests</dt>
  <dd>リクエスト要求の送る開始を高速化する？オプションだそうです。</dd>
  <dt>groupProperties</dt>
  <dd>アプリケーショングループのバッチリクエストの使用を制御する設定値です。詳しくはsap.ui.model.odata.v4.SubmitModeのリファレンスを参照ください。今回はいったんAutoにしておきます。</dd>
  <dt>groupId</dt>
  <dd>モデルによるバッチリクエストの使用を制御する設定値です。'\$auto'または'\$direct'を設定します。'\$auto'だとバッチリクエストを使用すするようになり、'\$direct'だとバッチを使用しない設定になります。</dd>
</dl>

#テーブルを作成しODataをバインドさせる
```Main.view.xml
        <content>
          <Toolbar>
            <Button text="reload" press="onloadData"></Button>
            <Button text="create" press="onCreate"></Button>
            <Button text="delete" press="onDelete"></Button>
            <Button text="update" press="onUpdate"></Button>
          </Toolbar>
          <SearchField id="searchField" value="" search="onSearch"></SearchField>
          <Table id="deviceTable" mode="SingleSelectLeft" items="{
						path: 'oDataDevicesModel>/Devices',
            parameters: {
									$count: true,
									$$updateGroupId : 'deviceGroup'
								}
					}" class="sapFDynamicPageAlignContent" width="auto">
            <columns>
              <Column id="column1">
                <Text text="{i18n>IMEI}"/>
              </Column>
              <Column id="column2">
                <Text text="{i18n>deviceModel}"/>
              </Column>
            </columns>
            <items>
              <ColumnListItem id="_IDGenColumnListItem1" type="Navigation">
                <cells>
                  <Input editable="false" value="{oDataDevicesModel>IMEI}" />
                  <Input editable="false" value="{oDataDevicesModel>deviceModel}" wrapping="false"/>
                </cells>
              </ColumnListItem>
            </items>
          </Table>
        </content>
        <footer>
          <OverflowToolbar>
            <ToolbarSeparator/>
            <Button text="save" press="onSave"></Button>
            <Button text="cancel" press="onCancel"></Button>
          </OverflowToolbar>
        </footer>
```
メインのページのコンテンツ部分にODataを呼び出すTableを設置します。
parametersに設定している$$updateGroupIdは任意のものを指定してください。
登録、更新、削除、検索処理を追加するために各種ボタンとSearchFieldを上につけています。

#検索処理
```Main.controller.js
        onSearch: function() {
            var oView = this.getView(),
                sValue = oView.byId("searchField").getValue(),
                oFilter = new Filter("deviceModel", FilterOperator.Contains, sValue);

            oView.byId("deviceTable").getBinding("items").filter(oFilter, FilterType.Application);
        },
```
検索処理を定義します。
バインド情報を取得しそこに対してfilterをかけることで検索処理をおこないます。

#登録処理、更新処理
![スクリーンショット 2021-05-12 18.20.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411770/54e5772a-016a-c650-0300-2bfe1f547dfd.png)

```Main.controller.js
        onCreate: function() {
            var oBinding = this.byId("deviceTable").getBinding("items");
            var oContext = oBinding.create({
                "IMEI": "",
                "deviceModel": ""
            });
            this._switchTableEditable(true);
        },
        _switchTableEditable: function(editable) {
            var table = this.byId("deviceTable");
            table.getItems().forEach(function(item) {
                for (var i = 0; i < item.getCells().length; i++) {
                    item.getCells()[i].setEditable(editable)
                }
            })
        },
```
上記の画像のように登録ボタンを押下した際にテーブルに入力欄を追加したいので
対象モデルに1行データを追加し、テーブルを編集可能にします。
ちなみにこのとき$$updateGroupIdやmanifest.jsonで指定したgroupIdを$directに設定している場合は
バッチを使用せずoBinding.createの時点で空のデータが作成されるリクエストが投げられるようになります。

さらにsaveボタン押下時の処理を定義していきます。

```Main.controller.js
        // saveボタン押下時の処理
        onSave: function() {
            var self = this;
            var fnSuccess = function() {
                MessageToast.show(self._getI18nText("changesSentMessage"));
                self._setUIChanges(false);
                this._switchTableEditable(false);
            }.bind(this);

            var fnError = function(oError) {
                self._setUIChanges(false);
                MessageBox.error(oError.message);
                this._switchTableEditable(false);
            }.bind(this);

            this.getView().getModel("oDataDevicesModel").submitBatch("deviceGroup").then(fnSuccess, fnError);
            this._bTechnicalErrors = false; // If there were technical errors, a new save resets them.
        },
        _setUIChanges: function(bHasUIChanges) {
            if (this._bTechnicalErrors) {
                bHasUIChanges = true;
            } else if (bHasUIChanges === undefined) {
                bHasUIChanges = this.getView().getModel("oDataDevicesModel").hasPendingChanges();
            }
        },
        // cancelボタン押下時の処理
        onCancel: function() {
            // 変更部分をリセット
            this.byId("deviceTable").getBinding("items").resetChanges();
            this._bTechnicalErrors = false;
            this._setUIChanges();
            this._switchTableEditable(false);
        },
```
this.getView().getModel("oDataDevicesModel").submitBatch("deviceGroup")
の部分でバッチ処理を開始しモデルデータの変更部分に対して登録、更新リクエストを送ります。

hasPendingChanges()
はサーバに送信されていない変更部分がある場合はtrueを返すメソッドです。

cancelボタン押下時（onCancel()内）にはテーブルに加えた変更部分をリセットするために
this.byId("deviceTable").getBinding("items").resetChanges();
にて加えた変更をリセットします。

![スクリーンショット 2021-05-12 18.43.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411770/64f172d7-f88f-0c08-f232-ff6d02e7e72f.png)
適当に値を入力し、saveボタンを押下すると
![スクリーンショット 2021-05-12 18.44.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411770/8065c27f-d7ff-3387-6bd3-b985d7dd89a0.png)
無事にバッチリクエストが投げられ、データの登録が行われました！

![スクリーンショット 2021-05-12 18.54.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411770/fb7eacd8-04e1-1296-eea4-a311578f87b2.png)

更新処理も同じように実装します。

```Main.controller.js
        onUpdate: function() {
            this._switchTableEditable(true);
        }
```
更新処理に関してもボタンが押下された際にテーブルを編集可能にし、saveボタン押下時に更新処理を行うよう処理をしたいので
更新ボタン押下時にテーブルの編集を可能にします。
保存ボタン押下時の処理は登録機能と同じく上記のonSaveの関数処理を呼び出すことで更新処理を行ってくれます。
テーブル内のバインドデータの変更部分を自動的に処理を行ってくれるのでV2のようにcreate,updateなどメソッドを分ける必要はありません。

先ほど追加したデータを更新してみます。
![スクリーンショット 2021-05-12 18.55.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411770/8433fdc0-513a-013f-4503-0d3f379f16f5.png)

先ほどのデータのdeviceModel部分を変更し、保存ボタンを押下すると

![スクリーンショット 2021-05-12 18.56.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411770/db006552-fec0-72f2-e6fe-b2850b62c29c.png)
バッチリクエストが送られ、データの更新が行われます。
![スクリーンショット 2021-05-12 19.00.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411770/e441b256-ba08-7010-8aa5-2dbebb3a3a51.png)

#削除処理
削除処理についても実装していきます。

```Main.controller.js
        onDelete: function() {
            var oSelected = this.byId("deviceTable").getSelectedItem();

            if (oSelected) {
                oSelected.getBindingContext("oDataDevicesModel").delete("$auto").then(function() {
                    MessageToast.show(this._getI18nText("mDeletionSuccessMessage"));
                    this._switchTableEditable(false);
                }.bind(this), function(oError) {
                    MessageBox.error(oError.message);
                    this._switchTableEditable(false);
                }.bind(this));
            }
        },
```
テーブルで選択されている行のBindingContext情報を取得し、削除処理を行います。
oSelected.getBindingContext("oDataDevicesModel").delete("\$auto")
の\$auto部分を\$directに変更することでバッチを使用せずそのままリクエストを行うように変更できます。

登録、更新の実装時に追加したデータを削除します。
対象の行を選択し、削除ボタンを押下すると
![スクリーンショット 2021-05-12 19.11.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411770/f2951513-85a4-30a0-de47-04f45addb82c.png)
無事リクエストが送られ、データが削除されました。

![スクリーンショット 2021-05-12 19.12.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411770/4f360f67-4de2-3b90-1b98-06ed1939a1c7.png)

#更新処理などでBatchを使わずにリクエストする方法
更新処理、登録処理などをバッチを使用せずリクエスト送信したい場合はテーブルにて指定している$$updateGroupIdの値を'\$direct'に変更します。

```Main.view.xml
<Table id="deviceTable" mode="SingleSelectLeft" items="{
						path: 'oDataDevicesModel>/Devices',
            parameters: {
									$count: true,
									$$updateGroupId : '$direct'
								}
					}" class="sapFDynamicPageAlignContent" width="auto">
```
上記のように変更し、更新処理を行うと
![スクリーンショット 2021-05-12 19.24.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411770/e42ab800-b3d7-5a87-82e1-1d51bc6923ec.png)
Batchが使用されていないことが確認できます。

#まとめ
登録、更新、削除についてODataV４サービスでの基本的な実装ができることが確認できました。
個人的に更新時にPATCHリクエストではなく、PUTに指定しリクエストを行う方法がないのか気になっています。
そちらも調査を継続し、方法を発見した場合追記を行いたいと思います。