OutSystems DataGrid 虎の巻

この記事は OutSystems Advent Calendar 2021 の４日目の記事です。

※ [2022/03/17 追記・更新] Forgeコンポーネントの Data Grid Reactive は Outsystems Data Grid という名前に変更になりました。本記事のスクリーンショット上ではData Grid Reactiveの表記のままとなっておりますが、最新版ではOutsystems Data Gridという名前に変わっています。また、APIについても変更がある可能性がありますので、ご注意ください。

OutSystems Forgeコンポーネントの Outsystems Data Grid について、基本的な使い方、カスタマイズ方法、利用上の注意点を書きます。

これを読めばあなたもDataGridマスター、一覧表示に関する様々なUI要件に答えることができるようになります。

なお、動画版もあります。基本的には本記事と同じ内容ですが、ハンズオン的に触ってみたい方は動画のほうが追いやすいかもしれません。

<iframe width="420" height="360" src="https://www.youtube.com/embed/qcPFzhUOfBc?vq=hd1080" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# DataGrid でできること

OutSystemsアプリ上でデータの一覧表示を行いたい場合、通常はTable要素を利用します。以下はTable要素に従業員情報を表示する例ですが、できることといえば一覧表示とソート、ページングぐらいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/fc9d5e9f-b4ff-d393-faf1-0ec245119af5.png)

Table要素を利用する場合、Excelのように一覧上で直接編集することもできませんし、フィルターやグループ化を行ったり集計結果を表示することもできません。

が、そういった要件を実現したい場合に利用するがDataGrid です。Forgeに公開されている部品で自由に使うことができます。※OutSystems DataGrid はReactive Web App向けの部品ですが、類似の部品でTraditional App向けのDataGrid Webというものがあります。利用方法や機能は似ていますが、若干APIが違うのでご注意ください。

以下はDataGridを使って従業員情報を部署ごとに一覧表示した例です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/bb326c95-f1c1-8da7-e19c-935bcd63bde7.png)
こちらはデータ一括更新の様子
![DataGridBulkUpdating.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/a91d4599-a224-0ea3-ccae-13d283456014.gif)

具体的には、以下のことが可能です。

 - Excelのような一覧上でのセル編集・一括更新
 - セルの範囲選択、コピー・貼り付け操作（MS ExcelやGoogle Spreadsheetとのコピー・貼り付けも可）
 - 列ごとのソート、フィルター、列移動、列の表示/非表示の切り替え、幅変更
 - 特定列でのグループ化（上記例では部署名でグループ化済み）、さらに集計表示
 - 列ヘッダ表現（上記例では住所情報というグループ列を設けている）
 - 行のチェックボックスによる選択、グループごとや全行の一括選択も可
 - セルの結合
 - 特定列または行の固定化（非固定部分がスクロールで動く）

細かいものも含めればまだまだありますが、主要な機能はこんな感じです。一番の使いどこはExcelライクな一括編集ができるところになると思います。一度に複数のレコードに修正を加えたい場合、１レコードづつ詳細画面を開くのは効率が悪いので、そういったシナリオが考えられる場合にはDataGridを使って一括更新の仕組みを提供すると良いです。

# DataGridリファレンス

Forge上のDataGridのページです。使い方を紹介している[Youtube動画へのリンク](https://www.youtube.com/watch?v=OFXOPrkRlrI)が紹介されていますが、少し情報量が少ないです。

https://www.outsystems.com/forge/component-overview/9764/outsystems-data-grid

OutSytems公式のYoutubeチャネルにDataGridの利用方法をまとめたプレイリストがあるのですが、<strong>これはTraditional Webアプリで利用できるDataGrid Webの解説となっており、DataGridは若干利用方法がことなるのでご注意ください↓↓↓</strong>

https://www.youtube.com/playlist?list=PLxALhSwsaivxRsBZ4kEfzvH4cqU9q0nso

# 1. 基本的な利用方法

基本的な利用方法を紹介します。※最新版のDataGridとスクリーンショットのイメージが異なる可能性がありますのでご注意くだい。

### DataGridを利用するための下準備

Data Gridのインストールと依存関係を追加を行います。

まず、Service Studioを起動してForge上で「Outsystems Data Grid」を検索、選択しInstallします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/75b73c3c-0864-c8fa-96b3-f5e49e11e986.png)

今回、DataGridを組み込むために新規のReactive Webアプリを作成しています。一覧表示するための従業員と部署のデータを既に用意しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/de157b5b-0675-ce5f-f623-021d00750d29.png)

このモジュールにDataGridの依存関係を追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/22f80536-3166-62f3-3a8f-fa601c43f66d.png)

これでDataGridを利用する準備は完了です。

### グリッドの配置とデータの取得

EmployeeDataGridという名前でEmpty Screenを追加し、ツールバーよりGrid要素をMain Contentへ配置します。DataプロパティとIsDataFetchedプロパティが設定されておらずエラーとなりますが、一旦無視します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/6a8f8c44-dc70-7070-b4cf-8ce61c6110e0.png)

DataGridへのデータの受け渡しはJSONフォーマットでJavascriptを経由して行われます。そのため、JSONフォーマットへパースしたテキストデータを変数を用意してグリッドのDataPropertyへ設定する必要があります。

今回は、GridDataという名前のTextタイプの変数を用意しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/8d2ee60e-abc4-3d47-d87d-f9c986224da5.png)

続いて、グリッドに表示したいデータを取得します。今回はEmployeeとOrganizationの情報を取得するAggregateを定義します。

Screenの右クリック => Fetch Data From Database => EmployeeとDepartmentエンティティをドラッグ&ドロップ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/27b3096b-e314-008d-4276-6a36e076200f.png)

定義したAggregateのプロパティウィンドウからOn After Fetchイベントを新たに定義し、JSON Serializeを利用して先ほど定義したAggregateの取得結果のListをJSONフォーマットにシリアライズし、そのテキストデータをGridData変数へ格納します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/90715d1d-9a2c-6bb0-3385-10d7f79a8222.png)

次に、画面に配置したグリッドを選択し、プロパティウィンドウよりDataへGridData変数を、IsDataFetchedに定義したAggregateのIsDataFetchedを設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/d1be1dd6-915e-035a-c08b-201e8d02f658.png)

これで準備完了、簡単ですね。One Click Pubslishで実行結果を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/416f9d54-3a08-abe2-6fb9-fc5eefb64b47.png)

ひとまず一覧表示ができました。グループ化やフィルターなどの機能はデフォルトで有効化されているので既に利用できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/5d2805f6-f794-2fc0-869c-8aad770f0d20.png)

ちなみに、編集させるかどうかや列のソート・移動・幅変更を許可するかどうかの設定は、グリッドのプロパティ（OptionalConfigsを展開すると出てくる）で変更できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/c47f00e2-9ccf-c384-44a5-592b8f263327.png)

### 列の設定

列を指定しない場合、渡されたデータのスキーマ情報をみて勝手に列を定義してくれます。ただしこれでは不要な列を表示してしまったり、列ごとに編集可・不可などの設定が行えないので、実際には列を定義していく必要があります。

ツールバーより表示したい列のDataTypeにマッチするColumn要素を配置していきます。（Text型ならText Columnを、Date型なら Date Columnを）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/cbc99561-2482-4947-f2dc-5f22f871379a.png)

各列のBindingプロパティにはDataプロパティに設定されたJSONデータの各カラムへのパスを設定するのですが、"Department.Name"や"Employee.NameKana"といったようにEntity名.Attribute名を設定することとなります。（複雑なJSON構造になっている場合は、バインドするJSONデータの構造をでデバッグで確認するか、console.logなどで出力して確認すると良いです。）

### グループ列の設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/1edcf415-e885-27d9-aa15-4ba6b4536b6f.png)

上記イメージのようにグループ列を設定したい場合には、Group Columnを配置します。Group Columnの中にGroupedColumnsというPlaceholderができるので、この中にさらにColumn要素を配置していきます。

以下のようにWidget Tree表示を使うと設定が楽です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/990420c4-ddb0-ea63-bdb7-c2b2dc5c2929.png)


ここまでで、一通り一覧表示に関する設定が完了です。

# 2. グリッド上での編集と一括更新

Excelライクな一括編集を実現します。やろうと思えば一覧上で新規行を追加したり行削除させることもできますが、処理が少し複雑になるのでここでは既存レコードの更新のみとします。（この仕組みを理解すれば新規追加や削除もできるはず）

### セル編集できるように設定

DataGridのOptionalConfigs => AllowColumnEditにTrueを設定します。列によっては変更させたくない場合もあるので、Column要素のAllowEditも必要により設定を変えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/2d9f230a-4714-878c-7cb6-94363aa4e63c.png)

UI上はこれだけでセル編集できるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/3a8b2bfc-36ba-dca9-e974-94aa8710f22d.png)

続いて、Saveボタンを配置します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/2e960e23-179e-08a9-aabc-a9a6f2d61ea2.png)

SaveOnClickアクションを開いてデータ更新処理を組みます。ctrl + F でGetChangedLinesメソッドを検索して配置します。もし検索に出ない場合は、DataGridの依存関係を再度確認して、すべての依存家計を追加するようにしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/e1d8cb2d-41d6-8224-cf95-e6d72e47a298.png)

このGetChangedLinesは、DataGridにバインドされているデータの中で変更のあったレコードのみを取得するために利用するロジックです。GridWidgetIdには、配置したグリッド名.Idを設定します。

続いて、JSONDeserializeRecordを配置し、DataGrid上で変更のあったレコードデータ（JSON形式）を以下のようにOutSystems上でリスト型で扱えるようにデシリアライズします。JSON StringにはGetChangedLines.ChangedLines.EditedLinesを設定し、DataTypeには以下画像のようにList of Record型を設定します。（※データ取得時に単一Entityから取得している場合は、この型設定が変わってくるので要注意です。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/b341ce8e-cd3c-2593-018f-2be98ca5a7c9.png)

変更データがあったかどうかの確認と、不正なデータがなかったかの確認を以下のように行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/cdb52925-11ea-5de7-6f50-70dc19bf583a.png)

続いて、データを更新するためのサーバーアクションを作成します。ValidationのIF要素の下にRun Server Actionをドラッグ&ドロップして、NEW SERVER ACTIONを選択し、UpdateEmployeesForGridを定義します。更に、作成したサーバーアクションに、デシリアライズしたデータを渡せるように同じ型のInput Parameterを定義します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/23e78317-a25f-e47e-3775-2d9d14c3648d.png)

ロジックは引数のリストをループで回してEmployee EntityのUpdateEmployeeを呼び出し、それぞれ対応する値を設定するだけです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/4b00521d-3e51-60b1-05c0-111954d28678.png)

SaveOnClickメソッドに戻って、サーバー処理への引数にデシリアライズされたデータを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/ab761c56-f00b-2379-6b65-2e3bd95282d1.png)

更新処理が終わったら、MarkChangesAsSavedを配置(GetChangedLines同様に検索すると楽です)し、グリッド上の変更データをコミットしします。最後に更新完了のメッセージを出して終わりです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/4d942645-29b3-5923-1824-07e201f763a8.png)

上記イメージではMarkChangesAsSavedによるグリッド上のコミット処理と、Refresh GetEmployeeによるデータの再取得（この後OnAfterFetchが走るので、つまりはグリッドの再描画）を両方行っていますが、どちらか一方でOKです。

他の誰かが裏でデータ更新した可能性があり、すべてのデータを毎回リロードさせたいのであればRefreshすべきですが、例えばグリッドに1万件のデータを表示しているがグリッドの更新頻度が高く毎回リロードさせたくないといった場合は、Refreshせずに変更データのみを確定させるMarkChangesAsSavedを行うことになります。

全体像はこんな感じ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/286e0eb7-06a1-76de-5dc1-31453f17db8d.png)

### 結果確認

ここまでコードは１行も書かずにグリッド上での一括更新まで作れました。仕組みを理解できれば30分程でこういったグリッド一括更新の機能を作れてしまします。

さて、結果を見てみましょう。
![DataGridBulkUpdating.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/a91d4599-a224-0ea3-ccae-13d283456014.gif)

一括更新できていますね！

ですが、この時点では１点Excelライクではない部分があります。セルを十字キーやTab/Enterで移動して入力に移る際に、直接日本語入力に入ろうとすると最初にタイプした文字がIME変換対象とならず、そのまま英文字で入力されてしまします。
![DataGridImeIssue.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/d4178d10-1846-5f65-b0c4-9e2fdb4c32ef.gif)

これを改善するためにDataGridのカスタマイズをコードレベル（Javascript）で行う必要がでてきます。

# DataGridをJavascriptでカスタマイズする

DataGridの内部では、FlexGridというコンポーネントが使われています。FlexGridはグレープシティ社が提供しているJavascript開発ライブラリWijmo(ウィジモ)に収録されているグリッドコンポーネントです。DataGridコンポーネントは、OutSystemsチームがこのFlexGridをOutSystemsのUI開発で利用できるようにラップしたForgeコンポーネントなのです。

クレープシティ社もBlog記事で公表しています。

https://devlog.grapecity.co.jp/newsrelease-outsystems-wijmo-datagrid/

このFlexGridは様々な要件に対応するために多種多様なAPIが用意されています。DataGridの中のFlexGridのAPIに直接アプローチすることで、できることが増えるということです。

以下はFlexGridを含むWijmoの公開サンプルですが、こちらで様々なサンプルコードやAPIの説明を確認することができます。

https://demo.grapecity.com/wijmo/demos/Grid/Overview/purejs

このサンプルで出来ていることは、ほぼほぼDataGridでも同じことができるということになります。（※多段明細表示(MultiRow)など、追加のライブラリ参照が必要となる一部の機能は利用できません。）

### Javascriptの設定

DataGridのOnInitizalizeイベントで新規Actionを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/2d5fd952-4e17-2705-b6a0-7023cd9c7409.png)

JavaScriptを置いて、引数にGridWidgetIdを設定します。こうすることで、Javascriptの中でグリッドのウィジェットIDを利用することができるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/898ccdb6-5026-bc73-3ac6-11e7a1041809.png)

Javascriptは以下のように設定します。

```javascript:JavaScript1.JavaScript
// GridWidgetIdよりグリッド要素を取得
var grid = GridAPI.GridManager.GetGridById($parameters.GridWidgetId).provider;

//日本語IME直接入力をサポート
grid.imeEnabled = true; 

// Department.Nameを初期表示でグループ化
grid.selectionMode = wijmo.grid.SelectionMode.MultiRange;
grid.columns.getColumn("Department.Name").visible = false;
var groups = grid.itemsSource.groupDescriptions;
groups.clear();
groups.push(new wijmo.collections.PropertyGroupDescription('Department.Name'));

// Organization.Nameセル結合
grid.allowMerging = 'Cells';
grid.columns.getColumn("Department.Name").allowMerging = true;
```

何をやっているのかは、Javascript上のコメントの通りです。Wijmoのサンプルで紹介されているように、[IMEの有効化設定](https://demo.grapecity.com/wijmo/demos/Grid/Editing/IME/purejs) や [グループ化の設定](https://demo.grapecity.com/wijmo/demos/Grid/Grouping/Groups/purejs) や [セル結合の設定](https://demo.grapecity.com/wijmo/demos/Grid/Merging/CellMerging/purejs) を行っています。

### 結果確認

それでは実行して結果を確認します。
![DataGridImeFixed.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197866/10472d84-398a-da07-b4a1-81c3df410036.gif)

セル選択状態からの日本語入力も問題無いし、初期表示での部署でのグループ化表示やセル結合表示もできています。

### 注意点

DataGridを使うと一覧表示や更新に関わる様々な要件を実現できることは分かりました。非常に便利で使いどころは多そうですが、注意すべき点もいくつかあります。

このコンポーネントはOutSystemsチームが作成している部品ですが、あくまでForgeコンポーネントの一つとして提供されており、システムUIとして提供されているものではありません。そのため、将来的にこの部品がメンテナンスされない可能性もありますし、利用できなくなる可能性もあります。そのため、<strong>システムUIとして提供されているTableやその他UI要素を組み合わせて実現できることは、できるだけシステムUIを利用するようにしましょう。</strong>

(逆に、システムUIではなくJavascriptの世界で実現されていることなので、OutSystemsプラットフォームの基盤が変わったとしてもJavascriptでカスタマイズできるAPIさえ残れば、引き続きこのアプローチは使えるという考え方もできます。。。
なお、推奨はしませんが、Forgeコンポーネントの中身を開いて自分でメンテナンスを行っていくことも可能ではあります。)

また、今回ご紹介したWijmoのサンプルページをご紹介しましたが、Wijmoはグレープシティ社が提供している有償製品です。<strong>ForgeコンポーネントのDataGridを利用する分にはフリーで利用できますが、Wijmoに収録されている他のコンポーネントを利用したりグレープシティ社からサポートを受ける(またはDataGridについて問い合わせをする)ことはできませんのでご注意ください。</strong>なお、Wijmoを有償部品として購入して、OutSystemsプラットフォームの中で利用することは可能です。

### もっとExcelライクを実現したい方へ

グレープシティ社は「SpreadJS for OutSystems」というOutSystems上でMS Excelのような外観や操作性を実現する有償コンポーネントを提供しています。複雑な条件付き書式の埋め込みや400種類以上の表計算関数が利用できるようです。DataGridでは満足できない方は検討しても良いかも。販売は伊藤忠テクノソリューションズ社からとなっているので、詳細は以下URLリンクよりご確認ください。

https://www.ctc-g.co.jp/solutions/outsystems/option/

# まとめ

 - DataGridは機能盛りだくさん、一覧UIにまつわる大体の機能要求は実現できる。
  - Excelのような一覧上でのセル編集・一括更新
  - セルの範囲選択、コピー・貼り付け操作（MS ExcelやGoogle Spreadsheetとのコピー・貼り付けも可）
  - 列ごとのソート、フィルター、列移動、列の表示/非表示の切り替え、幅変更
  - 特定列でのグループ化（上記例では部署名でグループ化済み）、さらに集計表示
  - 列ヘッダ表現（上記例では住所情報というグループ列を設けている）
  - 行のチェックボックスによる選択、グループごとや全行の一括選択も可
  - セルの結合
  - 特定列または行の固定化（非固定部分がスクロールで動く）
 - 一部機能はJavascriptを使ったカスタマイズが必要。カスタマイズする場合は[Wijmoサンプル](https://demo.grapecity.com/wijmo/demos/Grid/Overview/purejs)を参照すると良い。
    - 特に注意が必要な部分として、セル編集を有効化して日本語入力をサポートするためにはJavascript経由での設定が必要。
 - 機能豊富で便利な一方、OutSystemsプラットフォームのシステムUIではないので、メンテナンスやサポートの観点より、Table要素などシステムUIを活用して実現できることはを可能な限りシステムUIを利用したほうが良い。