ローカルPCでのCAPとFiori elementsでの画面モックアップ作成

仕事でモックアップ画面を作るので、CAPとFiori elementsを使って作ってみました。BTPにデプロイまではせず、ローカルPCで動くまでが記事の範囲です。
CAPやFioriの単語解説はせずに、作成手順を記載しています。
この記事は [SAP Advent Calendar 2022](https://adventar.org/calendars/7484) の12月23日分として執筆しています。

ここを参考に開発

https://developers.sap.com/group.btp-app-cap-create.html


# 環境
以下の環境で実施。インストールされていることが前提です。macOSで実施していますが、Windowsでも大きな差はないはずです。nvmはWindows向けがあるか調べていませんが、Node.jsのバージョン管理は必須ではないです。

|種類|種類(小分類)|バージョン|備考|
|:-:|:-:|:-:|:-:|
|デバイス|MacbookPro|14(M1 Chip)||
|OS|macOS|13.1(Ventura)||
|Node.jsバージョン管理|nvm|0.39.2|
|その他|Node.js|v18.12.1|
|その他|npm|9.2.0|
|Node.jsパッケージ|[SAPUI5 CLI](https://sap.github.io/ui5-tooling/pages/CLI/)|2.14.17|npmでインストール|
|Node.jsパッケージ|[CDS dev kit](https://cap.cloud.sap/docs/)|6.4.0|
|その他|CF CLI|8.4|
|IDE|VS Code|1.74.1||
|VS Code Extension|SAP CDS Language Support|6.4.0|
|VS Code Extension|SAP Fiori tools - Extension Pack|1.8.3|

# 手順
## CAP
### 1. Project Folder 作成
適当にフォルダを作成。今回はtest-uiにします。

```bash
mkdir test-ui
cd test-ui
```

### 2. プロジェクト初期化
CAP CLIから[`cds init`コマンド](https://cap.cloud.sap/docs/tools/#cds-initadd)でプロジェクト初期化します。[Project Setup and Layouts](https://cap.cloud.sap/docs/get-started/projects#cds-init)に少し詳しく書かれています。

```bash
% cds init
Creating new cap project in current folder

Adding feature 'nodejs'... 
Done adding features

Find samples on https://github.com/SAP-samples/cloud-cap-samples
Learn about next steps at https://cap.cloud.sap
```

treeで作られたフォルダ・ファイルを確認。[Default Project Layouts](https://cap.cloud.sap/docs/get-started/projects#default-project-layouts)にデフォルトのレイアウト記載あり。

```bash
% tree -al
.
├── .DS_Store
├── .cdsrc.json
├── .eslintrc
├── .gitignore
├── .vscode
│   ├── extensions.json
│   ├── launch.json
│   └── tasks.json
├── README.md
├── app
├── db
├── package.json
└── srv
```

### 3. Node.jsパッケージインストール
前ステップでpackage.json は以下のように作られています。

```json: package.json
{
  "name": "test-ui",
  "version": "1.0.0",
  "description": "A simple CAP project.",
  "repository": "<Add your repository here>",
  "license": "UNLICENSED",
  "private": true,
  "dependencies": {
    "@sap/cds": "^6",
    "express": "^4"
  },
  "devDependencies": {
    "sqlite3": "^5.0.4"
  },
  "scripts": {
    "start": "cds run"
  }
}
```

`npm install`でNode.jsパッケージをインストール。

```bash
% npm install
npm WARN deprecated @npmcli/move-file@1.1.2: This functionality has been moved to @npmcli/fs

added 175 packages, and audited 176 packages in 10s

12 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
```

`npm ls`でpackage.jsonの中身がインストールできているかを確認。

```bash
% npm ls
test-ui@1.0.0 /Users/i348221/Documents/repositories/test-ui
├── @sap/cds@6.4.0
├── express@4.18.2
└── sqlite3@5.1.4
```

### 4. CAPサーバー起動
[`cds watch`コマンド](https://cap.cloud.sap/docs/tools/#debugging-with-cds-watch)でCAPサーバーを起動。現時点では「何もないよ」と言われていますね。ターミナルはこの状態のままにしておきます。

```bash
% cds watch

 
cds serve all --with-mocks --in-memory? 
watching: cds,csn,csv,ts,mjs,cjs,js,json,properties,edmx,xml,env,css,gif,html,jpg,png,svg... 
live reload enabled for browsers 

        ___________________________

 

    No models found in db/,srv/,app/,schema,services.
    Waiting for some to arrive...
```

### 5. サンプルファイル作成
`cds add samples`コマンドでサンプルのドメインモデルとデータとサービスを作ります。

```bash
% cds add samples
Adding feature(s) to project in current folder

Adding feature 'samples'... 
Done adding features
```

作成されたものを確認(node_modulesフォルダは邪魔なので除外)。以下の3つが作成されています。
- db/data/my.bookshop-Books.csv
- db/data-model.cds
- srv/cat-service.cds

```bash
% tree -I node_modules
.
├── README.md
├── app
├── db
│   ├── data
│   │   └── my.bookshop-Books.csv
│   └── data-model.cds
├── package-lock.json
├── package.json
└── srv
    └── cat-service.cds
```

#### ドメインモデル(db/data-model.cds)

```typescript: db/data-model.cds
namespace my.bookshop;

entity Books {
  key ID : Integer;
  title  : String;
  stock  : Integer;
}
```

#### データ(db/data/my.bookshop-Books.csv)

```text: db/data/my.bookshop-Books.csv
ID;title;stock
1;Wuthering Heights;100
2;Jane Eyre;500
```

#### サービス(srv/cat-service.cds)

```typescript: srv/cat-service.cds
using my.bookshop as my from '../db/data-model';

service CatalogService {
    @readonly entity Books as projection on my.Books;
}
```

### 6. ODataサービス確認
先程放置した`cds watch`コマンドのターミナルを見ます。2つのファイルがロードされていることがわかります。

```bash
[cds] - loaded model from 2 file(s):

  db/data-model.cds
  srv/cat-service.cds

[cds] - connect using bindings from: { registry: '~/.cds-services.json' }
[cds] - connect to db > sqlite { database: ':memory:' }
 > init from db/data/my.bookshop-Books.csv
/> successfully deployed to sqlite in-memory db

[cds] - serving CatalogService { path: '/catalog' }

[cds] - server listening on { url: 'http://localhost:4004' }
[cds] - launched at 2022/12/20 15:47:25, version: 6.4.0, in: 4.154s
[cds] - [ terminate with ^C ]

[cds] - GET /catalog/$metadata 
[cds] - GET /catalog/Books 
```

ブラウザで`http://localhost:4004`で開いてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/b8369d29-e78f-b986-2742-a35cc8ab8437.png)

ここからのリンクでメタデータやODataのEndpoint結果を参照できます。

#### $metadata
`http://localhost:4004/catalog/$metadata`でメタデータ参照。

```xml: /catalog/$metadata
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
  <edmx:Reference Uri="https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Capabilities.V1.xml">
    <edmx:Include Alias="Capabilities" Namespace="Org.OData.Capabilities.V1"/>
  </edmx:Reference>
  <edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/Common.xml">
    <edmx:Include Alias="Common" Namespace="com.sap.vocabularies.Common.v1"/>
  </edmx:Reference>
  <edmx:Reference Uri="https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml">
    <edmx:Include Alias="Core" Namespace="Org.OData.Core.V1"/>
  </edmx:Reference>
  <edmx:DataServices>
    <Schema Namespace="CatalogService" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityContainer Name="EntityContainer">
        <EntitySet Name="Books" EntityType="CatalogService.Books"/>
      </EntityContainer>
      <EntityType Name="Books">
        <Key>
          <PropertyRef Name="ID"/>
        </Key>
        <Property Name="ID" Type="Edm.Int32" Nullable="false"/>
        <Property Name="title" Type="Edm.String"/>
        <Property Name="stock" Type="Edm.Int32"/>
      </EntityType>
      <Annotations Target="CatalogService.EntityContainer/Books">
        <Annotation Term="Capabilities.DeleteRestrictions">
          <Record Type="Capabilities.DeleteRestrictionsType">
            <PropertyValue Property="Deletable" Bool="false"/>
          </Record>
        </Annotation>
        <Annotation Term="Capabilities.InsertRestrictions">
          <Record Type="Capabilities.InsertRestrictionsType">
            <PropertyValue Property="Insertable" Bool="false"/>
          </Record>
        </Annotation>
        <Annotation Term="Capabilities.UpdateRestrictions">
          <Record Type="Capabilities.UpdateRestrictionsType">
            <PropertyValue Property="Updatable" Bool="false"/>
          </Record>
        </Annotation>
      </Annotations>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>
```

#### データ
CSVファイル`db/data/my.bookshop-Books.csv`の内容がODataサービスとして`http://localhost:4004/catalog/Books`から取得できています。

```json: /catalog/Books
{
    "@odata.context": "$metadata#Books",
    "value": [
        {
            "ID": 1,
            "title": "Wuthering Heights",
            "stock": 100
        },
        {
            "ID": 2,
            "title": "Jane Eyre",
            "stock": 500
        }
    ]
}
```

#### Fiori preview
「Fiori preview」のリンクから`http://localhost:4004/$fiori-preview/CatalogService/Books#preview-app`を開けます。
さすがにデータは見えないようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/ae7ed31d-f714-c4f1-a0a9-dbcb24739dda.png)

## Fiori elements
### 7. Fiori elements template を使ったUI生成
VS Codeのコマンドパレット(View → Command Palette or Shift + Command + P for macOS / Ctrl + Shift + P for Windows)から「Fiori: Open Application Generator.」を開きます。

#### 7.1. Template Selection
以下の項目を入力して「NEXT」ボタンを押下。
- Application Type: SAP Fiori elements
- Which template do you want to use?: List Report Page

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/5321ea06-8348-f166-fa42-d7c4df0c6413.png)

#### 7.2. Data Source and Service Selection
データソースをLocal CAPにして、先程作成したOData Serviceを選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/a447ef73-261a-39b9-afae-a86a871cda2b.png)

#### 7.3. Entity Selection
Main EntityにBooksを選び、設問「Automatically add table columns to the list page and a section to the object page if none already exists?」にはNoにしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/dc5064c7-a06e-4f37-fadc-63daf82249c5.png)

#### 7.4. Project Attributes
スクリーンショットの内容を入力して「FINISH」ボタン押下。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/4e60b2db-e334-9cfa-77d3-dbcaf347f550.png)

VS Codeでこんな画面が自動で開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/2c5be3c2-79ea-97b7-2830-2e0c2efa5fbf.png)

#### 7.5. 生成オブジェクト確認
UI系オブジェクトは`app`フォルダに生成されるので、カレントディレクトリを`app`に移動後、中身を確認します。

```bash
app % tree
.
├── services.cds
└── test-ui
    ├── README.md
    ├── annotations.cds
    ├── package.json
    ├── ui5.yaml
    └── webapp
        ├── Component.js
        ├── annotations
        │   └── annotation.xml
        ├── i18n
        │   └── i18n.properties
        ├── index.html
        ├── localService
        │   └── metadata.xml
        ├── manifest.json
        └── test
            ├── flpSandbox.html
            ├── integration
            │   ├── FirstJourney.js
            │   ├── opaTests.qunit.html
            │   ├── opaTests.qunit.js
            │   └── pages
            │       ├── BooksList.js
            │       └── BooksObjectPage.js
            ├── testsuite.qunit.html
            └── testsuite.qunit.js
```

`cds watch`コマンドのターミナルに`app`フォルダ以下の2つのファイルが認識されています。

```bash
[cds] - loaded model from 4 file(s):

  db/data-model.cds
  srv/cat-service.cds
  app/services.cds
  app/test-ui/annotations.cds

[cds] - connect using bindings from: { registry: '~/.cds-services.json' }
[cds] - connect to db > sqlite { database: ':memory:' }
 > init from db/data/my.bookshop-Books.csv
/> successfully deployed to sqlite in-memory db

[cds] - serving CatalogService { path: '/catalog' }

[cds] - server listening on { url: 'http://localhost:4004' }
[cds] - launched at 2022/12/20 18:08:36, version: 6.4.0, in: 503.481ms
[cds] - [ terminate with ^C ]
```

２つのファイルの中身を見てみます。

```typescript: app/services.cds

using from './test-ui/annotations';
```

```typescript: app/test-ui/annotations.cds
using CatalogService as service from '../../srv/cat-service';
```

### 8. Fiori画面確認
`http://localhost:4004/`の画面をリフレッシュすると「Web Applications」に作成したFioriへのリンクが追加されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/66312936-330b-c7f7-15ee-658154f6e652.png)

リンク`/test-ui/webapp/index.html`から`http://localhost:4004/test-ui/webapp/index.html`を開きます。
この時点では、検索条件もなく検索をしても検索結果は出てきません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/e1c06e0e-a32d-1e97-467e-f7b6f7a53d2f.png)

### 9. UI Annotation追加
UI Annotationとして `srv`フォルダにcat-service-ui.cdsを以下のデータで新規作成。

```typescript: srv/cat-service-ui.cds
annotate CatalogService.Books with {
    ID    @title: 'ID';
    title @title: 'Title';
    stock @title: 'Stock';
}

annotate CatalogService.Books with @(UI: {
    SelectionFields: [
        ID,
        title,
        stock
    ],
    LineItem       : [
        {Value: ID},
        {Value: title},
        {Value: stock},
    ]
});
```

`http://localhost:4004/test-ui/webapp/index.html`をリフレッシュすると検索条件が追加され、検索も可能な状態になりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/430cebfe-f9c1-5d33-da60-c7c80af5b71c.png)

# 後続
後続作業として、検索ヘルプ追加も調べました。

https://qiita.com/FukuharaYohei/items/18f9560f6db622bb4d1d