【SAP CP CF環境】Fiori Launchpadのタイルにグラフを表示する

# はじめに

SAP CloudPlatformのCF環境にFiori Launchpadを作成してみたので、その応用としてLaunchpadに表示するタイルにグラフを表示してみます。
下記のブログを参考に、カスタムタイルを作成してLaunchpadに登録・公開しました。
【参考URL】
[Custom Tiles with Cloud Foundry Portal – Cloud Platform](https://blogs.sap.com/2020/03/23/custom-tiles-with-cloud-foundry-portal-cloud-platform/)

本投稿は以下の内容をベースとしてSAP CloudPlatformのCF環境上にFiori Launchpadを作成・公開までしていることが前提となっています。
[SAP CP Trial環境でMulti Target Applicationを作ってみる【Fiori LanchPadモジュールの作成】](https://qiita.com/yonekura-h/items/8d4b95f2532f4f57702a)
[【SAP CP CF環境】Fiori LaunchpadでDynamic Tileを作ってみる](https://qiita.com/yonekura-h/items/dcbad9601500d13754d1)


# カスタムタイルアプリケーションの作成

Launchpadにグラフを表示するためにはまず、表示するタイルの見た目や処理を定義する必要があります。
そのため、専用のHTML5モジュールを作成してLaunchpadに表示するタイルを一つのアプリケーションとして作成します。

MTAプロジェクトを右クリック -> New -> HTML5 Moduleを選択し、新規モジュールを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/c6423826-5468-a816-8c84-64c1b4876b45.png)
テンプレートではSAPUI5 Applicationを選択しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/cad52106-962a-9311-9b49-0ced814f6ce4.png)

作成時にViewファイル名を`Tile`としています。
新規モジュール内に`controller/Tile.controller.js`と`view/Tile.view.xml`が確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/397a3615-4787-3632-935e-532371a53c69.png)

作成したHTML5モジュールの中身を編集し、カスタムタイルを作成します。
ViewファイルではGenericTileコンポーネントを作成するため、以下のように変更します。

通常の画面アプリケーションに存在する`Shell`や`App`は不要で、`View`配下に直接`GenericTile`を定義します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/267d02b6-2f18-9aa2-6239-ad7d9fdaf9c8.png)

```xml:view/Tile.view.xml
<mvc:View controllerName="jp.co.kyoso.demo.demoCustomTileMod.controller.Tile" 
	xmlns:mvc="sap.ui.core.mvc" displayBlock="true" 
	xmlns="sap.m"
	xmlns:microchart="sap.suite.ui.microchart">
	<GenericTile
		header="{view>/title}"
		subheader="{view>/subTitle}"
		frameType="OneByOne"
		press="onPress"
		busy="{view>/busy}"
		>
		<tileContent>
			<TileContent>
				<content>
					<microchart:HarveyBallMicroChart formattedLabel="true" total="{view>/total}" size="Responsive" alignContent="Center">
						<microchart:items>
							<microchart:HarveyBallMicroChartItem fraction="{view>/count}" color="Good" />
						</microchart:items>
					</microchart:HarveyBallMicroChart>
				</content>
			</TileContent>
		</tileContent>
	</GenericTile>
</mvc:View>
```

作成したViewに合わせて、Controllerファイルを編集します。

```javascript:controller/Tile.controller.js
sap.ui.define([
	"sap/ui/core/mvc/Controller",
	"sap/ui/model/json/JSONModel"
], function (Controller, JSONModel) {
	"use strict";

	return Controller.extend("jp.co.kyoso.demo.demoCustomTileMod.controller.Tile", {
		_oProperties: {},
		_oViewModel: undefined,

		onInit: function () {
			var oComponentData = this.getOwnerComponent().getComponentData();
			console.log("tile Init oComponentData:", oComponentData);
			this._oProperties = (oComponentData && oComponentData.properties) ? oComponentData.properties : {
				title: "Fallback Title",
				subtitle: "Fallback SubTitle",
				targetURL: "#"
			};
			console.log("tile set oProperties:", this._oProperties);
			this._oViewModel = this._initViewModel();
			this.getView().setModel(this._oViewModel, "view");
			// 固定値でグラフを表示
			this._oViewModel.setProperty("/total", 100);
			this._oViewModel.setProperty("/count", 20);
			this._oViewModel.setProperty("/busy", false);
		},

		/* =========================================================== */
		/*  internal methods                                           */
		/* =========================================================== */

		/**
		 * Creates view model (local view model).
		 *
		 * @returns {sap.ui.model.json.JSONModel} Local JSON model for fragment data
		 * @private
		 */
		_initViewModel: function () {
			var oData = {
				busy: false,
				title: this._oProperties.title,
				subTitle: this._oProperties.subtitle,
				notifications: 0,
				info: 0
			};

			if (!this._oViewModel) {
				return new JSONModel(oData);

			} else {
				this._oViewModel.setData(oData);
				return this._oViewModel;
			}
		},

		onPress: function () {
			var sTargetUrl = this._oProperties.targetURL;

			if (sTargetUrl) {
				if (sTargetUrl[0] === "#") {
					hasher.setHash(sTargetUrl);
				} else {
					window.open(sTargetUrl, "_blank");
				}
			}
		}
	});
});
```

上記変更後にindex.htmlを実行すると下図のようにタイルが表示できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/f4dd4273-c603-c5dc-c947-892138ca6f23.png)


# 作成したTileアプリをLaunchpadに設定する

## HTML5モジュール：manifest.jsonの更新
HTML5モジュールで作成したカスタムタイルをLaunchpadに設定します。
まず、HTML5モジュールの`webapp/manifest.json`に以下の設定を追加します。

```json:webapp/manifest.json
	"sap.flp": {
		"type": "tile",
		"tileSize": "1x1"
	},
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/ded44466-9b99-166e-eda2-4eecb89e11fb.png)

## Lunchpadモジュール：baCustomTile.jsonの作成

カスタムタイルの設定はLaunchpadモジュールの配下に特定のディレクトリとファイルを作成する必要があるとのことなので、
`portal-site/business-apps/baCustomTile.json`を作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/6c67162e-d138-bfcc-9c68-f26f9081c798.png)

`baCustomTile.json`には以下を記載します。

```json:baCustomTile.json
{
    "_version": "3.0.0",
    "identification": {
        "id": "jp.co.kyoso.demo.demoUIMod",
        "entityType": "businessapp",
        "i18n": "i18n/customTile.properties"
    },
    "payload": {
        "visualizations": { 
            "customTile-displayGraph": {
                "vizType": "jp.co.kyoso.demo.demoCustomTileMod",
                "vizConfig": {
                    "sap.app": {
                        "title": "{{customTile.title}}",
                        "subTitle": "{{customTile.subtitle}}"
                    },
                    "sap.flp": {
                        "target": {
                            "inboundId": "demoUI-display"
                        }
                    }
                }
            }
        }
    }
}
```
それぞれの設定値は以下のようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/bbc3d728-9c5b-85a8-589e-05bcc8506979.png)


|項目|値|
|---|---|
|identification.id|タイル押下時に呼び出すHTML5モジュールのnamespaceを指定|
|visualizations配下のKey|JSONのKeyに任意のIDを設定する（カスタムタイルのIDとしてCommonDataModel.jsonで使用する）|
|vizType|定義したID（この場合はcustomTile-displayGraph）に紐づくカスタムタイルのnamespaceを指定|
|inboundId|呼び出すHTML5モジュールの"SemanticObject-action"を指定|

また、title・subTitleはi18nの定義を参照しています。
読み込むプロパティファイルはLaunchpadモジュール内に定義しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/91569131-9620-15a8-ef62-126d0907749a.png)

## Lunchpadモジュール：CommonDataModel.jsonの編集

CommonDataModel.jsonをLaunchpad Editerで開き、タイル設定の追加を行います。
今回はグラフのタイルを押下した際に`demoUIMod`モジュールを表示するように設定しようと思うので、
まずは`demoUIMod`モジュールのタイルを追加しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/ccaab6fe-f186-a3bf-8293-a244a62d36c3.png)

Code Editerで確認するとgroups.payloadの配下にタイル設定が追加されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/a70fc422-f97c-1668-45e9-2059326ec2a1.png)

追加された設定をベースに、以下を変更します。

**groups.payload.viz**
追加された設定のうち、`vizId`をbaCustomTile.jsonのvisualizationsに定義したKeyに変更します。

```json:groups.payload.viz
{
	"id": "jp.co.kyoso.demo.demoUIMod-2-1603264832519",
	"appId": "jp.co.kyoso.demo.demoUIMod",
	"vizId": "customTile-displayGraph"
}
```
**catalogs.payload.viz**
catalogs.payload.vizにも以下を追加します。

```json:catalogs.payload.viz
{
	"id": "jp.co.kyoso.demo.demoUIMod",
	"vizId": "customTile-displayGraph"
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/2e0695ee-4ca7-b565-064d-340acd1d59cf.png)

## Build+デプロイして結果を確認

上記までの設定を実施したら、Build -> Deployを実施します。
デプロイ後にapprouterのRouteにアクセスすることで結果が確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/f271b9de-f43e-7cef-55f8-2602a9a79e85.png)


# オプション：表示を変えてみる

グラフが表示できたので、いくつかの変更を試してみました。

## タイルの大きさを変えてみる
対応しているタイルのサイズは
　・1x1
　・1x2
とのことなので、1x1 -> 1x2に変更してみます。

`Tile.view.xml`を下記のように変更します。

 - GenericTileのframeTypeプロパティを`OneByOne` -> `TwoByOne`に変更
 - TileContentを追加（ついでに違うmicroChartを設定）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/15a2683f-accd-ef54-1a5d-a4b004a251c2.png)

`manifest.json`を下記のように変更します。

 - tileSizeを`1x1` -> `1x2`に変更
 - 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/a5c6af00-de32-fee9-00b8-3d90405decd6.png)


## ODataからデータを取得する

通常のSAPUI5アプリケーションと同様の方法でODataServiceから結果の取得が可能でした。
Controllerファイルは以下のように変更しています。
※`manifest.json`、`xs-app.json`にも設定の追加が必要です。
　[xs-app.jsonに追加](https://qiita.com/yonekura-h/items/dda7d3c0e0babd45abaa#%E5%AE%9B%E5%85%88url%E3%81%B8%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%82%92%E5%AE%9A%E7%BE%A9)
　[manifest.jsonに追加](https://qiita.com/yonekura-h/items/dda7d3c0e0babd45abaa#%E3%83%87%E3%83%BC%E3%82%BF%E3%82%BD%E3%83%BC%E3%82%B9%E3%83%A2%E3%83%87%E3%83%AB%E8%A8%AD%E5%AE%9A%E3%82%92%E8%BF%BD%E5%8A%A0)

```javascript:Tile.controller.js
sap.ui.define([
	"sap/ui/core/mvc/Controller",
	"sap/ui/model/json/JSONModel"
], function (Controller, JSONModel) {
	"use strict";

	return Controller.extend("jp.co.kyoso.demo.demoCustomTileMod.controller.Tile", {
		_oProperties: {},
		_oViewModel: undefined,

		onInit: function () {
			var oComponentData = this.getOwnerComponent().getComponentData();
			console.log("tile Init oComponentData:", oComponentData);
			this._oProperties = (oComponentData && oComponentData.properties) ? oComponentData.properties : {
				title: "Fallback Title",
				subtitle: "Fallback SubTitle",
				targetURL: "#",
				readInterval: 5000
			};
			console.log("tile set oProperties:", this._oProperties);
			this._oViewModel = this._initViewModel();
			this.getView().setModel(this._oViewModel, "view");
			// 固定値でグラフを表示
			this._oViewModel.setProperty("/total", 100);
			this._oViewModel.setProperty("/count", 0);
			this._oViewModel.setProperty("/busy", false);
			this._oViewModel.setProperty("/percentage", 90);
			// ODataServiceから取得する
			this.readCount(this._oViewModel, "/count");
			
			// var iInterval = this._oProperties.readInterval;              
			// // 定期的なポーリングを実施
			// var fnGetCount = function(){
			// 	this.readCount(this._oViewModel, "/count");
			// };
			// window.setInterval(fnGetCount.bind(this), iInterval);
		},

		/* =========================================================== */
		/*  internal methods                                           */
		/* =========================================================== */

		/**
		 * Creates view model (local view model).
		 *
		 * @returns {sap.ui.model.json.JSONModel} Local JSON model for fragment data
		 * @private
		 */
		_initViewModel: function () {
			var oData = {
				busy: false,
				title: this._oProperties.title,
				subTitle: this._oProperties.subtitle,
				notifications: 0,
				info: 0
			};

			if (!this._oViewModel) {
				return new JSONModel(oData);

			} else {
				this._oViewModel.setData(oData);
				return this._oViewModel;
			}
		},

		onPress: function () {
			var sTargetUrl = this._oProperties.targetURL;

			if (sTargetUrl) {
				if (sTargetUrl[0] === "#") {
					hasher.setHash(sTargetUrl);
				} else {
					window.open(sTargetUrl, "_blank");
				}
			}
		},
		
		readCount: function(oViewModel, sProp){
			var oDemoServiceModel = this.getOwnerComponent().getModel("DemoServiceModel");
			oDemoServiceModel.read(
				"/People",
				{
					success: function(oData, oResponse){
						console.log("get People:", oData);
						oViewModel.setProperty(sProp, oData.results.length);
					},
					error: function(oError){
						console.log("get People:", oError);
					}
				}
			);
		}
	});
});

```

ただ、継続的なデータの更新に関しては上手な方法が見つかりませんでした。。
`setInterval`によるポーリングを行った場合はFiori Launchpadから各アプリケーションに遷移した場合でもずっと定期的なリクエストを行ってしまうためやるべきではなさそうです。


## Build+デプロイして結果を確認

Build -> Deployを実施しすると以下のようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/0bfed18e-839c-d3cb-f7e8-731640e80133.png)

うまくいってよかったですが、Busyの表示がずれているのは気になる。。。。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159467/a2a59f76-e9e0-d89e-16f2-b80af181c3b5.png)



# おわりに

カスタムタイルの設定は思ったよりも簡単に行えました。
タイルに表示するのはグラフ以外も可能なので、Lunchpad上でもある程度の表現が可能になったかなと思います。