Google App Makerで社内サービスを作る #2 （ページ編）

## やろうとしていること

Google App Maker を使って、社内用の書籍管理アプリを作っています。

### 前回やったこと

前回はアプリで使用する予定のモデルを作成しました。

### 今回やること

画面を作っていきます。

## ページフラグメントを作成する

ページを作る前に、各ページに埋め込む共通部品（ページフラグメント）を作っておきます。

### ヘッダーを作成

各画面の上部に表示させます。
サービス名とログインユーザーのメールアドレスを表示するだけのシンプルなものにします。

#### ページフラグメントを作成

画面左側の `PAGES` → `+` を押し、次に `Page Fragment` を選択 → `CREATE` します。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/6c56bfe1-18b9-cfab-426f-195b4b274a39.png)

作成すると `PageFragment` という名前で画面左側の `PAGES` 下に出てくるので、名前を `Header` に変えます。
`PageFragment` → `⁝` → `Rename` から名前変更します。

#### ヘッダーのかたちにする

ここからヘッダーの形を作っていきます。

初期表示されている Panel を幅が `600`、高さが `75` くらいの横長なサイズに変えます。
幅、高さは Panel を選択して、画面右側の `Property Editor` 内の `Main` → `dimensions` 内から変更できます。

ヘッダーの色などのスタイルは、このパネル内に配置する子パネル側で設定したいと思うので、このパネルは透明にしておきます。
画面左上で `Regular` が選択されている箇所（スタイル）を `Transparent` にします。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/4fdd49a6-f9dc-44ba-8461-6a7cdc888809.png)

透明になりました。

#### 部品を配置するためのパネルを作成

次に子パネルを配置します。
画面左上の ![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/b2e441bc-a14b-9bc4-2f57-1643302e6202.png) を押して、ウィジェットの一覧を表示します。
この中から `Layout` → `Horizontal Panel` をドラッグアンドドロップで、パネル内に置きます。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/6b678cd7-714f-6403-71d7-50b24a52dfdd.png)

今置いた子パネルを選択した状態で、`Property Editor` から下記の設定をします。

1. Panel → name を `HeaderContainer` に変更（任意）
1. Layout → Left, Top を `0` に変更
1. Layout → Width の `px` を `auto` に変更（→するとオプションが増える）
1. Layout → Right を `0` に変更
1. Layout → Height を 外側のパネルの高さより少し小さいか同じに変更
1. Flow → alignChildren を `center` に変更

子パネルを親パネルにぴったり揃えています。

スタイルを `Regular` から `Primary` に変更します。青くなりました。
こんな感じになっていると思います。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/85bd8948-a140-64fc-c783-e1ef4913f758.png)

幅を auto にしたのは、表示するブラウザの幅によって横に伸縮してほしいためです。
また Horizontal Panel を配置したのは、ヘッダー内に表示する文字が固定位置ではなく、左寄せ／右寄せされるようにするためです。

#### ヘッダー内の文字などを作成

次にヘッダー内に文字を表示します。
ウィジェットの一覧から、サービス名とメールアドレス用に Label を２つと、Horizontal Panel を１つを HeaderContainer 内に配置します。
配置する順番は左から Label, Horizontal Panel, Label の順です。

* 左の Label
    1. ダブルクリックし、`書籍管理` に変える
    2. スタイルを `Title` に変える
    3. Spacing → Margin Left を `16` に変える
* Horizontal Panel
    1. スタイルを `Transparent` に変える
    2. Layout → Horizontal → Size を `fill parent` に変える（ヘッダーの範囲内で目一杯広がる）
* 右の Label
    1. Label → text の右端の逆三角を押し、`binding` を選択する
    2. 続けて出てくる Choose a binding で以下の順で選択して OK
        1. `datasources`
        2. `CurrentUser` （前回決めたデータソース名）
        3. `item`（`items` ではない）
        4. `PrimaryEmail`
    3. Spacing → Margin Right を `16` に変える

#### バインディングとは

**バインディングとは、あるデータソース内のフィールドと画面の部品（or その他）とを紐付けること** だと理解しています。
バインディングされていると、データソース内の値が変われば、バインディング先の画面部品に表示されている値も変わります。逆も同じです（双方向）。
なお、↑でやった `PrimaryEmail` の後に `#readonly` と追記すると、データソース→画面の単方向だけにできるようです。

ここまでで以下のようになっていると思います。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/aa25172b-2693-f4f0-a5d6-4375914b34b0.png)


### 「処理中...」を作成

データ読み込み中などによく出てくる、くるくる回るあれを作ります。
何か処理中の時に表示し、処理が終わったら消えるようにする予定です。

#### 部品を配置するためのパネルを作成
新たにページフラグメントを作り、名前を `Loading` にします。

このページフラグメントは画面端に小さく出れば良いと思うので、初期表示されたパネルも小さくしておきます。幅：150、高さ：70 くらいで。
ヘッダーと同じく、パネルの色を透明にします。

続いて部品配置用のパネルを配置します。
部品は横に並べたいので Horizontal Panel を今あるパネル内にピッタリ配置して色を変えます。
下記の手順でやります。

1. パネル内に Horizontal Panel を配置する
2. （今置いたパネルの）Layout → Left, Top を `0` にする
3. （今置いたパネルの）Layout → Width, Height を `auto` にする
4. （今置いたパネルの）Layout → Right, Bottom を `0` にする
5. （今置いたパネルの）スタイルを `Dark` にする

これで最初からあったパネルの中に、Horizontal Panel がある状態になりました。

#### くるくる回る部分を作成

1. ウィジェット一覧から `Spinner` と `Label` を配置する
2. 配置する位置を中央にするため、Horizontal Panel を選択し、Flow → justifyとalignChildren を `center` にする
3. 配置した Label をダブルクリックして、`Loading...` にする（何でも良いです）

ここで画面右上の `！` に数字が付きます。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/0f4de0f0-f3f7-8778-3764-495b2d6ce85a.png)
「何か問題があるよ」と教えてくれていて、開くと問題が確認できてその箇所に飛べます。

問題の箇所に飛ぶと、さっき配置した Spinner の Display → visible がフォーカスされます。
visible には `@datasource.loading.....` と設定されています。 **データソースが読み込み中（または...）の時に表示する** という意味です。しかしスピナーのデータソースは現在 `None` です。従い、スピナーにデータソースを設定すれば問題は解決します。

が、そもそもこのフラグメントは何か処理中の時に表示し、処理が終わったら消える、という想定なので、スピナー自体はずっと表示されていれば良いです。
ということで、visible の逆三角から `value` を選択し、出てきたチェックボックスをチェック(true)します。
これで `datasource` を使わなくなったため、問題が消えました。

ここまでで以下のようなフラグメントができました。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/0629af28-4537-1793-07cf-7a2c015960e6.png)

## ページを作成する

いよいよ、ここからページを作ります。

### 書籍の登録画面を作成

画面左側の `PAGES` → `+` を押し、次に `Page` を選択 → `NEXT` します。
Page name は書籍の登録画面なので、 `BookCreate` としました。
Page datasource には、このページで主に使用するデータソースを設定します。`Book` を選択して `CREATE` します。

#### ヘッダーを配置

前に作成したヘッダーをページに配置します。
ウィジェット一覧の Your Page Fragments に出てきます。

* 配置したらページ上部に横いっぱいに広げます。

1. ヘッダーの Layout → Left, Top を `0` にする
2. ヘッダーの Layout → Width を `auto` にする
3. ヘッダーの Layout → Right を `0` にする

#### 入力フォームを作る

ウィジェット一覧の Form をページ内に配置します。

1. Choose datasource は `Inherited: Book` で良いです（ページのデータソースと同じになる）
2. Choose form type は作る画面は登録画面なので `Insert` にします
3. Choose form fields は入力フォームに出すフィールドです。書籍名、著者、URL、BookOwner、Location を選択しました

#### 「処理中...」を配置

ウィジェット一覧のヘッダーと同じところにあります。

* 下記のようにして、画面の左下に出るようにします。

1. Layout の 1 つめの `MORE` を押して Right, Bottom を表示する
2. Layout → Left は `20`、Top は `auto` にする（`20` は適当です）
3. Layout → Bottom を `20` にする

* 続いて、処理中の時だけ表示されるように設定します。

1. Display → visible の逆三角から `binding` を選択し、`More Options` を選択する
2. Choose binding の下部に `@datasource.loading || @datasource.creating || @datasource.deleting` と設定する

登録画面なので `@datasource.creating` だけでも良いと思います。

あと適当に体裁を整えて以下のような感じになりました。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/a7f8c823-df55-d82c-a748-0836d18f5e6d.png)

### 書籍所有者の登録画面を作成

「書籍の登録」画面と同様の手順でページを作り、ヘッダーと入力フォーム、処理中を配置します。
データソースは `BookOwner` にします。

以下のような感じです。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/9c2f2b88-0594-4b16-f365-f8db463d031b.png)

### 所在地の登録画面を作成

同じ手順で作成します。
データソースは `Location` にします。

以下のような感じです。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/5f2c9f3c-6c1d-7a62-d2c5-990fb0862b56.png)

### 書籍の一覧画面を作成

書籍の一覧画面は、このサービスのメイン画面です。
登録されている書籍の一覧をテーブルで表示して、行を選択したらその書籍の詳細が表示されるようなものにする想定です。

1. ページのデータソースを `Book` にして、ページを新規作成する
2. ウィジェット一覧から `Table` を配置する
    3. データソースは `Inherited: Book`
    4. テーブルに表示する項目は、書籍名、著者、貸し出し日時、借りている人、BookOwner、Location
    5. `More options` の `Deletable rows` のチェックを外す

#### 一度アプリを起動してみる

画面右上の `PREVIEW` を押すとアプリが起動します。
初回起動時はおなじみの認証画面が表示されます。
プレビュー中の画面では、画面左上のドロップダウンからページを切り替えられます。

今作った書籍の一覧画面（一覧は空っぽ）が表示されれば OK です。
書籍登録画面で書籍を登録すれば、書籍の一覧に現れるようになります。

#### 日付欄に時刻まで表示する

日付は標準だと `yyyy-MM-dd` 形式で表示されます。
時刻も表示されるように変更します。

1. テーブル内の貸し出し日時の明細行のラベル（`BorrowedAt`）を選択
2. Label → text → More Options
3. 既に入力されている `@datasource.item.BorrowedAt` に続けて、`#formatDate('yyyy-MM-dd HH:mm:ss')` と入力

`#formatDate` はバインディングトランスフォーマーという機能を利用しています。
日付のフォーマット以外にも用意されています。
https://developers.google.com/appmaker/scripting/api/transformers

#### 検索機能を追加

一覧に表示する書籍を、書籍名と所在で絞り込めるようにします。

##### 書籍名の絞り込み

1. ウィジェット一覧から TextBox を空いているところに配置する
    2. Text Box → **value** の右端の逆三角を押して `binding` を選択し、`More Options` を選択する
        3. Choose a binding で、 `datasource` → `query` → `filters` → `Name` → `_contains` の順で選択して OK
    9. Text Box → **label** の右端の逆三角を押して `binding` を選択し、`More Options` を選択する
        10. Choose a binding で、 `datasource` → `model` → `fields` → `Name` → `displayName` の順で選択して OK

value にデータソースのフィルタリング条件を設定しています。
やっているのは、 **テキストボックスに入力した値が、書籍の名前 (Book.Name) に含まれて (contains) いるレコードを抽出せよ** 、という設定です。
`_contains` なので部分一致です。`_equals` だと完全一致になります。

label で設定したのは、名前の通りでテキストボックスに表示するラベルです。
前回の記事で Book モデルの定義をしましたが、そこで設定した `Display Name` を利用します。

##### 所在の絞り込み

1. ウィジェット一覧から Dropdown を空いているところに配置する
    2. Dropdown → **label** の右端の逆三角を押して `binding` を選択し、`More Options` を選択する
        3. Choose a binding で、 `datasources` → `Location` → `model` → `fields` → `Name` → `displayName` の順で選択して OK
    3. Dropdown → **options** は、Choose a binding で、 `datasources` → `Location` → `items` の順で選択して OK
    4. Dropdown → **value** は、Choose a binding で、`datasource` → `query` → `filters` → `Location` → `_equals` の順で選択して OK

label は入力欄のラベルです。

options にはドロップダウンで選択できる項目を設定します。既に作成した、所在地を作る画面で作った所在地を表示するようにしています。

value にデータソースのフィルタリング条件を設定しています。
**ドロップダウンで選択した値が、書籍の所在地 (Book.Location) と一致 (equals) するレコードを抽出せよ** 、という設定です。
なお、options で設定したドロップダウンに表示する値の型と、value で設定する比較対象の型は一致していないとダメです。一致していないと Choose a binding 画面で OK が押せず設定できません。

ここまでで 2 つ条件を設定できるようにしましたが、両方とも入力して検索するとそれぞれの AND 検索になります。

##### 検索ボタン

1. ウィジェット一覧から Button を空いているところに配置する
  2. Button → text を `検索` にする
  3. Button → onClick を `Reload Datasource` にする

これで検索ボタンを押すと、データソースが再読み込みされるようになります。
条件が入っていれば絞り込まれるし、入ってなければ全件表示されます。

こんな感じになっています。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/71b76d6b-33d6-7568-ebdf-292f772d9d2a.png)

### 画面同士をつなげる

書籍の一覧画面から、書籍・書籍所有者・所在地の登録画面へ遷移できるようにします。
例えば書籍登録画面への遷移処理は以下の手順で設定します。

1. ウィジェット一覧からボタンを配置
2. Button → onClick で `Navigate to Page...` → `BookCreate` を選択
3. ボタン名をわかりやすいものに変更

書籍一覧画面からそれぞれに遷移できるようになったら、それぞれの画面から書籍一覧画面に戻るボタンも作成します。

### 書籍の詳細画面を作成

書籍の一覧から、行を選択した時にポップアップで開く画面です。
ここで「借りる」「返す」といった操作や、これまでの貸し出し履歴を見れるようにします。

1. ページの新規作成で `Popup` を選択し、`Empty popup` を作成する
2. Main → name は、`BookDetail` 
3. Main → hasGlass をチェックする
4. Main → escapeToClose をチェック外す

ポップアップ部分を選択して、

1. Popup → datasource を `Book` にする
2. ポップアップ部分を適当に大きくする

#### 書籍名を表示

ポップアップ内に書籍名を表示します。

1. ウィジェット一覧から Label を配置する
2. Label → text を `binding` にし、ドロップダウンから `Name` を選ぶ
3. スタイルを変更する（文字を大きく表示する）

#### リンクを表示

書籍が持っている URL を画面から利用できるようにします。

1. ウィジェット一覧から Link を配置する
2. Link → text を任意の文字列にする
3. Link → href を `binding` にし、ドロップダウンから `Url` を選ぶ
4. Link → target を `_blank` にする（任意）
5. Display → visible を `binding` にし、`!!@datasource.item.Url` にする

画面にリンクを表示し設定されている URL に飛べるようにしました。
URL 未設定だとリンクは表示されません。

#### 借りる、返すボタンを作成

書籍の貸し借り状況に応じて、

* 借りる
* 返す
* 貸出中です

の表示をします。一度に表示されるのは上記のどれか１つとなる想定です。

1. ウィジェット一覧から Horizontal Panel を配置する
    2. Flow → justify を `center` にする
    3. Flow → alignChildren を `center` にする
4. Horizontal Panel 内に、下記を配置する
    5. 「借りる」ボタン
    6. 「返す」ボタン
    7. 「貸出中です」ラベル
8. 「借りる」ボタン
    9. Display → visible を `binding` にして、`!@datasource.item.BorrowedBy` とする
10. 「返す」ボタン
    11. Display → visible を `binding` にして、`@datasource.item.BorrowedBy && @user.email === @datasource.item.BorrowedBy` とする
12. 「貸出中です」ラベル
    13. Display → visible を `binding` にして、`@datasource.item.BorrowedBy && @user.email !== @datasource.item.BorrowedBy` とする

#### 貸し出し履歴を表示

ウィジェット一覧のテーブルを使って貸し出し履歴を表示します。
このテーブルのデータソースはデフォルトで用意される `BorrowHistory` でも良いのですが、自作のデータソースも使ってみたいので、書籍ごとに貸し出し履歴を保持するデータソースを作成します。

1. 画面左の DATA → BorrowHistory を開き、 DATASOURCES → ADD DATASOURCE を押す
2. Name は `BorrowHistoryByBook` として、Query は `Query script` を選択し、Server Script に下記を入力する
3. + ADD PARAMETER を押下し、`String` を選択する（`NewParameter` が新規追加される）
4. `NewParameter` を `BookKey` に変更する
5. Sorting に `BorrowedAt` を選択し、`Ascending` のチェックを外す（降順になる）

```javascript:ServerScript
var bookKey = query.parameters.BookKey;
if (bookKey) {
  query.filters.Book._key._equals = bookKey;
}
return query.run();
```

Server Script に設定したのは、このデータソースのデータを取得するスクリプトです。
書籍ごとの貸し出し履歴を取得します。
追加した `BookKey` パラメーターは書籍のキーです。このデータソースを利用する前に設定しておくことで、特定の書籍に紐付いている貸し出し履歴のみを取得します。

データソースができたら貸し出し履歴のテーブルを作ります。

1. ウィジェット一覧からテーブルを配置する
2. データソースは `BorrowHistoryByBook` にする
3. 表示するフィールドは任意、`Editable` のチェックを外す
4. `Deletable rows` のチェックも外す

#### 閉じるボタンを作成

このポップアップを閉じるためのボタンを作ります。
ボタンにはアイコンを表示してみます。

1. ウィジェット一覧からボタンを配置する
2. Button → text を `close` にする
3. スタイルを `Icon` にする（ × に変わる）
4. Button → onClick を、 `Hide Popup...` → `BookDetail` にする

スタイルを `Icon` にすることで、アイコン表示に切り替わります。
アイコンは他にもあります。
https://material.io/resources/icons/

書籍の詳細画面ができました。こんな感じです。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/525650/20b8fd1c-55ee-a402-cfbd-81cbd237cdb4.png)

## ここまでで画面の作成は終わりです

画面の作成が一通り終わりました。
次回は各種処理を実行するためのスクリプトを書いていきます。

## 参考サイト

https://developers-dot-devsite-v2-prod.appspot.com/appmaker/ui/overview