この記事を書いたきっかけ
OData V2 + Smart Controlを使った画面で、ヘッダ + 明細という構造のデータを入力します。「登録」ボタンを押したらヘッダ、明細データを同時にDeep createでバックエンドに送信したいという要件がありました。
課題
JSONモデルであれば、「登録」ボタンを押したタイミングでODataModelのcreateメソッドを使ってディープな構造を渡すことができます。
const model = this.getView().getMoel()
model.create("/EntitiySet", {
field1: "...",
field2: "...",
items: [{
field3: "",
field4: ""
}]
});
しかし、Smart ControlではOData Modelをバインドする必要があるため上記の方法が使えません。SAPUI5 SDKに参考になりそうなコードサンプルがあったのでこれを利用して実装してみようと思いました。
方法
サンプルアプリを使ってDeep createする方法について説明します。
ソースコード:https://github.com/miyasuta/ui5-deep-create/tree/main
使用したメタデータ(抜粋)は以下です。
<EntityType Name="Order">
<Key>
<PropertyRef Name="ID"/>
</Key>
<Property Name="createdAt" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="createdBy" Type="Edm.String" MaxLength="255"/>
<Property Name="modifiedAt" Type="Edm.DateTimeOffset" Precision="7"/>
<Property Name="modifiedBy" Type="Edm.String" MaxLength="255"/>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<Property Name="customer" Type="Edm.String"/>
<NavigationProperty Name="items" Type="Collection(CatalogService.Item)" Partner="order">
<OnDelete Action="Cascade"/>
</NavigationProperty>
</EntityType>
<EntityType Name="Item">
<Key>
<PropertyRef Name="order_ID"/>
</Key>
<NavigationProperty Name="order" Type="CatalogService.Order" Partner="items">
<ReferentialConstraint Property="order_ID" ReferencedProperty="ID"/>
</NavigationProperty>
<Property Name="order_ID" Type="Edm.Guid" Nullable="false"/>
<Property Name="product" Type="Edm.String"/>
<Property Name="quantity" Type="Edm.Int32"/>
</EntityType>
View
以下のようなビューを作成します。明細にはListBindingができるテーブル系のコントロールを使用します。ポイントは、rows="{items}"
のようにリストの部分にナビゲーションプロパティを指定することです。
<mvc:View controllerName="demo.ui.controller.View1"
xmlns:mvc="sap.ui.core.mvc" displayBlock="true"
xmlns:m="sap.m"
xmlns:smartForm="sap.ui.comp.smartform"
xmlns:smartField="sap.ui.comp.smartfield"
xmlns:smartTable="sap.ui.comp.smarttable"
xmlns:table="sap.ui.table">
<m:Page id="page" title="{i18n>title}">
<smartForm:SmartForm id="smartForm"
editable="true"
validationMode="Async"
title="{Name}">
<smartForm:Group label="Order">
<smartForm:GroupElement>
<smartField:SmartField value="{customer}" />
</smartForm:GroupElement>
</smartForm:Group>
</smartForm:SmartForm>
<table:Table
id="table"
rows="{items}">
<table:extension>
<m:Toolbar >
<m:ToolbarSpacer />
<m:Button id="addButton" text="Add" press="onAdd" />
<m:Button id="deleteButton" text="Delete" press="onDelete" />
</m:Toolbar>
</table:extension>
<table:columns>
<table:Column width="11rem">
<m:Label text="Item No." />
<table:template>
<m:Text text="{itemNo}" wrapping="false" />
</table:template>
</table:Column>
<table:Column width="11rem">
<m:Label text="Product" />
<table:template>
<m:Input value="{product}" wrapping="false" />
</table:template>
</table:Column>
<table:Column width="11rem">
<m:Label text="Quantity" />
<table:template>
<m:Input value="{quantity}" wrapping="false" />
</table:template>
</table:Column>
</table:columns>
</table:Table>
<m:footer>
<m:OverflowToolbar >
<m:ToolbarSpacer />
<m:Button text="Save" press="onSave" />
</m:OverflowToolbar>
</m:footer>
</m:Page>
</mvc:View>
OData V2の場合、Smart Tableを使って入力させることができません。Smart Tableに表示するレコードはバックエンドに保存済みである必要があるためです。そのためここではsap.ui.table.Tableを使用しています。
参考
https://answers.sap.com/questions/13097516/add-new-row-in-the-smart-table.html
Controller
以下のステップでディープなリクエストを送ることができます。
- ODataModelのcreateEntryメソッドでヘッダのコンテキストを作り、ビューにバインドする
- テーブルコントロールからlistBindingを取得し、createメソッドで明細を作成する
- ODataModelのsubmitChangesメソッドでバックエンドにリクエストを送信する
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast",
"sap/m/MessageBox"
],
/**
* @param {typeof sap.ui.core.mvc.Controller} Controller
*/
function (Controller, MessageToast, MessageBox) {
"use strict";
return Controller.extend("demo.ui.controller.View1", {
onInit: function () {
const router = this.getOwnerComponent().getRouter();
router.getRoute("RouteView1").attachPatternMatched(this._onObjectMatched, this);
this._itemIndex = 0;
},
onSave: function () {
//3. バックエンドにリクエストを送信
const that = this;
this.getView().getModel().submitChanges({
success: function () {
if (that.getView().getModel().hasPendingChanges()) {
MessageBox.error("Data was not fully saved");
} else {
MessageToast.show("Data saved successfully.");
}
},
error: function (error) {
MessageBox.error(JSON.stringify(error));
}
})
},
onAdd: function () {
//2. 明細を作成
this._itemIndex ++;
const table = this.getView().byId("table");
const itemsBinding = table.getBinding("rows");
itemsBinding.create({itemNo: this._itemIndex, product: "", quantity: 1}, true /*create at end*/);
},
onDelete: function () {
const table = this.getView().byId("table");
const selectedIndices = table.getSelectedIndices();
const rows = table.getRows();
selectedIndices.map(index => {
rows[index].getBindingContext().delete();
});
},
_onObjectMatched: function () {
//1. ヘッダのコンテキストを作りビューにバインド
const model = this.getOwnerComponent().getModel();
const context = model.createEntry("/Order", {
properties: {
customer : 'default'
} })
this.getView().setBindingContext(context);
}
});
});
おわりに
OData V2では全体をSmart Controlでは作成できないことがわかりました。Smart Controlを使えば検索ヘルプが自動で(アノテーションだけで)付くのでできればSmart Controlで統一したかったのですが。
なお、OData V4 + ドラフトが使えるODataサービスであれば、Flexible Programming Modelによって入力可能なテーブルを作成することもできます。