[OutSystems]DataGridを使って複数行編集可能なテーブルを実現する

OutSystemsで結構聞く要望として、テーブル上でデータ編集したいというものがある。DataGridなら簡単に実現できそうなので手順をメモ。
DataGridコンポーネントも、Traditionalの頃に比べてだいぶ良くなっている。これなら使用を検討してもよいかと思う。

# 環境情報
Personal Environment(Version 11.18.1 (Build 37884))
Service Studio(Version 11.53.43)
OutSystems Data Grid(Version 2.10.1)

# サンプル
Forgeコンポーネント: [HousesoftSampleReactive](https://www.outsystems.com/forge/component-overview/7086/housesoftsamplereactive)のV1.0.16。

DataGridFlow > DataGridTableEdit Screen

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/a4c7f2e0-c785-dcf2-b8d9-b686012364dc.png)

# テストデータ
以下の構造のEntityを表形式で表示・編集するサンプル画面を作っていく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/8dd3aacf-d2ac-6f1f-e3af-0c79b0edfaaa.png)

NameとDescriptionがText型、PriceとStockがInteger型。

# Gridに表示
## 参照
以下のように、OutSystemsDataGridから必要な要素を参照する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/eb0c9483-b844-b198-10f6-4d227e95b2bc.png)

## データの取得
Data Gridに表示するデータは、AggregateではなくData Actionで取得する。
これは、List型をJSON形式に変換したデータが必要であるため。

- ArrangeData Action: JSONに変換するOutSystemsDataGrid/APIにあるAction。Object型を受け取るため、「ToObject(AggregateのList)」のように渡す
- Assign: ArrangeData.DataJSONを出力変数に編集

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/8c5d311b-3e2e-b0b9-4707-fc4aefd736f6.png)

ArrangeData Actionの代わりにJSONSerializeでJSON化すると、以下のエラーが表示され、データがGridに表示されない。
> You cannot use JSONSerialize and make the grid editable. Please use ArrangeData action for this scenario.

## UI
OutSystemsDataGrid/Structures/Gridを画面に配置する。
以下のプロパティを設定。
- Name: Widgetの名前。画面内のロジックでGridに対して操作するときに、「どのGridか」を表すために設定しておく必要がある
- Data: DataActionの出力（データのJSON）
- IsDataFetched: DataActionのIsDataFetched (データの取得が終わってクライアントに渡っているかを示すDataAction/Aggregateのプロパティ）
- OptionalConfigs.AllowColumnEdit: True（Grid上でのデータ入力を許可する設定）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/8f36b9df-9493-73d4-a7eb-ecc337a268de.png)

## 表示する列を手動で設定
実はここまでの設定で、渡したデータから自動的に表示するべき列を特定してくれるので、Grid自体は表示可能。
ただ余計な列を表示してしまったり、Date型の表示がうまくいかなかったりしたので、表示する列を明示的に設定しておく。

配置したGridのGridColumnsPlaceholder内に、OutSystemsDataGrid/Columnsにある列の種類に対応した**Columnという名前のWidgetを、表示したい列分だけ配置する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/7806328c-4be6-149c-5579-338d95eb1b0b.png)

最低限必要なプロパティは
- Header: 列の先頭に表示するテキスト
- Binding: JSONデータ内での該当列の位置。「Entity名.属性名」を指定

編集させたくないId列については、ColumnOptionalConfigs.AllowEditをFalseにしておく。

なお、今回は数値型（PriceとStock）がInteger型なので設定がいらないが、デフォルトでは小数点以下が入力できない。
NumberOptionalConfigs.DecimalPlacesを1以上に設定すれば、小数点以下を入力できるようだ。

# Gridで編集
追加・更新・削除は、クライアント側のGridで発生する。
一定のタイミングでこの修正内容をGridから取り出し、サーバーに送信。サーバー側ではServer Actionで通常通りEntityに書き込めばよい。

## Context Menuを整理しておく
DataGrid内に表示するContext Menu (右クリックメニュー) は、画面に配置したGridのContextMenuPlaceholderにあるContextMenu Widgetによって定義する。

Context MenuのMenuItems Placholderに、OutSystemsDataGrid/ContextMenuにあるMenuItem_で始まるWidgetを配置すると対応するメニューがContextMenuに表示される。

デフォルトでいくつか配置されているが、以下で編集用メニューを表示するときに邪魔なので、あらかじめContextMenu/MenuItem_Copy_WithHeaders (選択中のセルをヘッダ付きでクリップボードにコピーする機能）以外を削除しておく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/2c2bb9e8-89c8-c9ed-f0fc-142f2269be81.png)

## 追加
### Context Menuを利用する
Grid > ContextMenuPlaceholder > ContextMenu Block > MenuItems Placeholderに、OutSystemsDataGrid/ContextMenu/MenuItem_Rows_Addを配置することで、Context Menuに「Add Row(s)」というメニューが追加される。

クリックすると、選択中の行の上に空行を追加する。

### Actionを利用する
画面内にButtonを配置し、そのScreen ActionでOutSystemsDataGrid/API_Rows/AddNewRows Action (Client Action)を呼ぶ。

GridWidgetIdには「\<Grid名\>.Id」を設定する。追加する行数を指定するNumberOfRowsはデフォルトが1なので、何も設定しなくてもよい。

## 更新
更新については、GridのAllowColumnEditをTrueにしておくだけでよい。

## 削除
### Context Menuを利用する
Grid > ContextMenuPlaceholder > ContextMenu Block > MenuItems Placeholderに、OutSystemsDataGrid/ContextMenu/MenuItem_Rows_Deleteを配置することで、Context Menuに「Delete Row(s)」というメニューが追加される。

クリックすると、選択中の行を削除する。

### Actionを利用する
画面内にButtonを配置し、そのScreen ActionでOutSystemsDataGrid/API_Rows/RemoveSelectedRows Action (Client Action)を呼ぶ。

GridWidgetIdには「\<Grid名\>.Id」を設定する。

## 更新の反映
ここまでの作業では、修正はあくまでもクライアント側（Grid内）にとどまっている。
そこで、
1. Gridから変更点を抽出する（OutSystemsDataGrid/API_Data/GetChangedLines Action）
1. 変更点はJSON形式で、GetChangedLinesのOutput Parameterに設定されている。JSONDeserialize要素を使い、List型に戻す
    1. 追加データ→ChangedLines.AddedLines
    1. 更新データ→ChangedLines.EditedLines
    1. 削除データ→ChangedLines.RemovedLines
1. Server Actionnに2でデシリアライズしたデータを渡し、通常のデータ更新のように処理する
1. Refresh Data要素でデータ取得したData Actionを更新する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/2fddcfe0-2a9d-4f67-9cc5-80df4e604994.png)

上ではRefresh Dataを更新している。対象データが限られているときにはこの方法でもよい。
しかし全体のデータが多い時には、MarkChangesAsSaved Actionを使う方法もある。こちらは追加したレコードのIdが自動では反映されないので、そちらの面倒を見る必要がある。

# Screen Template化を検討
実際に各アプリケーションで実装していくにあたり、サンプルデータを使って

- Context Menuの構成
- 各種メッセージやUIの日本語化
- データ取得用のData Actionの構成
- Screen Actionの構成
- 更新用Server Actionの構成

のようなScreen Templateを作っておくと、標準化・作業効率化・必要スキルの簡易化などに役立ちそう。