GAS×スプレッドシート×GlideApps×Slack×Pic2shopで作る書籍管理アプリの実装

# 概要
今回、知見共有・技術向上意欲の促進を図ることを目的に、社員が会社や個人の所有する書籍の把握を容易にするために書籍管理アプリを作成しました。(あとGASを使ってみたかった😌笑)
GAS(Google Apps Script)を中心に、様々なサービスを連携します。
記事では主要な作業部分のみ解説しています。

# 構成図
![Book List Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/bd6052bc-da2b-3edb-5f08-8c4eb2dd6bad.png)

| サービス | 用途 |
|:---|:---:|
| [Glideapps](https://www.glideapps.com/) | UI/CRUD |
| [Pic2shop](http://www.pic2shop.com/) | バーコードスキャン |
|[Google SpreadSheet](https://www.google.com/intl/ja_jp/sheets/about/) | データベース |
| [Google Apps Script](https://developers.google.com/apps-script/) | スクリプト |
| [Slack](https://slack.com/) | 通知 |

# 完成画面
実際にできたものがこちらです。 
https://booklist.glideapp.io/
https://github.com/mgmgmgmi/BookList
~~※アプリは社内用なので公開していないです…要望があればコピーして公開します。~~
社内向けのものを作ったので公開しました。

**登録画面**
バーコードを読み取って書籍を登録できます。
![スキャン登録のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/5afe9e65-9eb4-c589-3668-9feedff65fdb.png)


**ホーム画面 / 詳細画面 / 編集画面**
書籍一覧を閲覧・編集できます。
<img width="170" alt="登録完了.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/88f61b5e-e0f1-5755-dd41-75d3a4c89beb.png"> <img width="170" alt="詳細画面.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/5410d6f0-9ec3-fc4f-148f-5e02dc3749bf.png"> <img width="170" alt="編集.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/848efbd2-84b5-dac8-30b1-e21bfc7a556b.png">

**slack通知**
書籍登録時や貸借時にSlackで通知を行います。
<img width="1127" alt="スクリーンショット 2019-10-09 16.45.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/df2c8ee2-2945-8855-f6f5-613bd968c992.png">

# 開発環境について
今回は[clasp](https://github.com/google/clasp)を利用しました。
GASのコードとGitを連携してバージョン管理できるスグレモノです。
自分の好きなエディタで実装できるのも嬉しいポイントです。

**参考**：[claspを使い、Google Apps Scriptプロジェクトをgitでバージョン管理する - Qiita](https://qiita.com/rf_p/items/7492375ddd684ba734f8)

# 実装
## 1. GAS×スプレッドシートの連携
詳しい解説は[コチラ](https://qiita.com/mgmgOmO/items/5e45ee88c217cd072ea4)
DB代わりのスプレッドシートのカラムは以下のような構造にしました。
<img width="1440" alt="スクリーンショット 2019-10-09 18.39.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/96135f12-9780-167a-50ce-df9110d7e7cc.png">
A列目にISBNコードを入力すると、B~F列に書籍情報が登録されます。
書籍の登録時に書籍情報を取得するため、[Google Books API](https://developers.google.com/books)を使用しています。

## 2. スプレッドシート×GlideAppsの連携
#### GlideAppsとのサービス連携
[GlideApps](https://go.glideapps.com/) からGoogleアカウントで認証を行います。
その後、該当のスプレッドシートを指定してGlideAppsのプロジェクトを作成します。

<img width="1440" alt="スクリーンショット 2019-10-09 19.05.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/75f8ffc3-7ffb-9a35-ce09-e573441b0a95.png">
GlideAppsプロジェクトを開くと、ブラウザ上でUIや表示項目などをカスタマイズすることができます。
とても直感的でわかりやすいです。
これでスプレッドシートに対する簡単なCRUD機能が使えるようになります。

**参考**：[簡単過ぎる！GoogleスプレッドシートからPWAアプリを開発できる「Glide」を使ってみた！ - paiza開発日誌](https://paiza.hatenablog.com/entry/2019/04/10/%E7%B0%A1%E5%8D%98%E9%81%8E%E3%81%8E%E3%82%8B%EF%BC%81Google%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E3%82%B7%E3%83%BC%E3%83%88%E3%81%8B%E3%82%89PWA%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E9%96%8B)

## 3. GAS×slackの連携
#### Slack APIを用いた投稿リクエストの実装
Slackに対する投稿は、Slack APIの[chat.postMessage](https://api.slack.com/methods/chat.postMessage)を利用します。
トークンとチャンネルIDは自分のものを確認して記載してください。

```Slack.js
function postSlack(text){
  const token = 'hogehoge';
  const channel = 'fugafuga';
  
  //Slack投稿
  const response = UrlFetchApp.fetch(
    'https://slack.com/api/chat.postMessage?'
    + 'token=' + token
    + '&channel=' + channel
    + '&text=' + encodeURIComponent(text)
  );

  //レスポンス確認
  Logger.log(JSON.parse(response.getContentText())); 
}
```
ここでの注意点は、textなどパラメータの値に特殊文字等が入っているとGASのバリデーションにかかって[エラーになります](https://qiita.com/mgmgOmO/items/1ad19d1b48055eff4a98)。
そのため[encodeURIComponent](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent)関数でエンコードを行います。


#### スクリプトプロパティを利用したトークンの管理
Web APIを利用する際、Slack含めサービスによってはAPIキーなどの認可情報が必要な場合があります。
間違ってGitなどにアップしないように、ローカルでの開発では環境変数や外部ファイルなどに格納することが多いと思います。
クラウドで動作するGASでは環境変数の代わりに**スクリプトプロパティ**という機能を利用します。
<img width="1260" alt="スクリーンショット 2019-10-10 10.29.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/fd0b571d-892a-f3b6-5dcb-67e5a7dffebf.png">
GASプロジェクトのメニューから　**ファイル > プロジェクトのプロパティ > スクリプトのプロパティ** を開き、名前と値を入れて保存します。

取得する際には、以下のようにプロパティの名前を指定して値を取得することができます。

```Slack.js
  const properties = PropertiesService.getScriptProperties();
  const token = properties.getProperty('SLACK_TOKEN');
  const channel = properties.getProperty('SLACK_CHANNEL');
```

**参考**：[【初心者向けGAS】プロパティストアの概要とスクリプトプロパティの編集方法](https://tonari-it.com/gas-property-store/)

## 4. pic2shop×GASの連携
バーコードの読み取り機能として、[Pic2shop](http://www.pic2shop.com/developers.html)というアプリを利用します。
ユーザがバーコード読み取りを行う際、このアプリのダウンロードが必要になります。

#### カスタムURLスキームによるpic2shopアプリの呼び出し
アプリを起動する場合、**カスタムURLスキーム**を利用します。
Pic2shopでは以下のようなURLでアプリの機能を呼び出します。

```
pic2shop://scan?callback=(コールバック関数)
```

コールバック関数の引数として、今回はGETパラメータでGASのWebアプリケーションに渡すことにしました。
バーコードを読み込んだ結果は`EAN`という文字に格納されます。
※pic2shopに渡す際はURLエンコードが必要です。

```
https://(WebアプリケーションのURL)?isbn=EAN
```

GladeAppsのリンクから呼び出したかったので、 URL(`pic2shop://scan?callback=https%3A%2F%2F...%3Fisbn%3DEAN`)をスプレッドシートに記載して表示しました。

**参考**：[iPhoneで簡単にバーコードリーダーを作る方法 – 伊藤清徳の垂直落下式ムーンサルトプレス](http://agilmente.com/blog/2011/09/16/iphone_barcode/)

#### GASのWebアプリケーションにおけるパラメータ受け取り部分の実装
GlideAppsにAPIなどがあればよいのですが、対応していないため代わりにGASのWebアプリケーションで値を受け取ってスプレッドシートに登録します。

GASのWebアプリケーションを有効化し、GETパラメータを受け取る準備をします。
**参考**：[Google Apps Scriptをウェブアプリケーションとして公開する手順](https://www.virment.com/how-to-deploy-google-apps-script-as-webapp/)

そしてパラメータを受け取るためのトリガーである[doGet](https://developers.google.com/apps-script/guides/triggers#dogete_and_doposte)関数を実装します。

```Trigger.js
function doGet(e){
  const isbn = e.parameter.isbn;  
  Logger.log(isbn); //確認

  return HtmlService.createHtmlOutput(JSON.stringify(e));
}
```

ブラウザ等で`https://(WebアプリケーションのURL)?isbn=EAN`へアクセスし、GASのログでisbnの値が受け取れていることを確認します。

## 5. 登録後の画面作成

#### 画面作成と値の受け渡し
Webアプリケーションの画面の作成は[HtmlService](https://developers.google.com/apps-script/reference/html)クラスを用います。
GAS <=> HTML間の値の受け渡しは、[スクリプトレット(scriptlets)](https://developers.google.com/apps-script/guides/html/templates#scriptlets)と呼ばれる3つの特別なタグ`<? ... ?>` `<?= ... ?>` `<?!= ... ?>`を利用します。

```Trriger.js
function doGet(e){
  const isbn = e.parameter.isbn;

  //GASからスプレッドシートへの書き込み
  addBook(isbn);

  // index.htmlの読み込み
  var htmlTemplate = HtmlService.createTemplateFromFile("index");

  // isbnの値をHTMLへ渡す
  htmlTemplate.isbn = isbn;
  return htmlTemplate.evaluate();
}
```

```index.html
<!DOCTYPE html>
<html>
  <body>
    <p>ISBNコード: <?= isbn; ?> </p>
  </body>
</html>
```

**参考**：[Google Apps Script (GAS)のScriptletsを使って外にあるJavaScriptやCSSを読み込む – CodeAid（コードエイド）](https://codeaid.jp/gas-html/)



#### 画面レイアウトの変更
最後にHTMLファイルにてBootstrapやCSSの読み込みなどを行います。
Bootstrapの読み込みは[公式ドキュメント](https://getbootstrap.com/docs/4.3/getting-started/introduction/)にある通り、以下を記述して利用します。
\<head>タグ内

```html
<head>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
```

\<body>タグ内の最後の行

```html
<body>
  ...
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>
```

**参考**：[GASでWebページを作成する際にCSSフレームワークBootstrapを導入する方法](https://tonari-it.com/gas-web-bootstrap/)

CSSの読み込みは、css.htmlファイルを作成したのち、\<head>タグ内に以下を記述して利用します。
(.cssはGASの対象外のため、.htmlとして保存します)

```html
<head>
  <?!= HtmlService.createHtmlOutputFromFile('css').getContent(); ?>
</head>
```

```css.html
<style>
body {
    font-family: -apple-system, BlinkMacSystemFont, Roboto, sans-serif
    font-size:14px;
}
</style>
```

**参考**：[GASでWebページを作るときにHTMLとCSSを別ファイルに記述する方法](https://tonari-it.com/gas-web-html-css-scriptlet/)

# 最後に
細かいカスタマイズは難しいですが、すでにある便利なサービスを組み合わせることで一週間程度で機能の大部分の実装を終えることができました。
ツールを利用することで開発効率を上げられるのはいいなと思いました。
あと単純に実装楽しかったです！笑
記事に間違いや不明な点があれば遠慮なくご指摘ください。