GAS(Google Apps Script)の操作方法 👉 タスク管理アプリの作成

# GASとは
Google Apps Script(GAS)とは、Googleが提供するサーバーサイド・スクリプト環境で、Googleのあらゆるサービスを統合処理できます。
[公式サイト](https://developers.google.com/apps-script/)
例えば、Google フォームに記入した内容を、スプレッドシートやカレンダーに反映させたり、Gmailに送信したりできます。
また、他のアプリのAPIを使って連携させることもできます。
僕の職場では、Chatworkとの連携が流行っています。すぎょい。

[こちらのサイト](http://makimakimakino.hatenablog.com/entry/2017/03/16/010759)には、GASでできることがいくつか書いてあります。

そんなGASはテキストエディタや環境が必要ないので、初心者でも取っつきやすいですが、
- 実行に制限がある
- 外部向けの本格的な業務サービスには向かない

といった面もあります。
メールの送信は1日100通までで、スクリプトの実行時間は1回6分以内です。
ちょっとした社内の業務改善には使えます。

## GASを使ってみる
それでは、GASを使ってみましょう。
初学者御用達の[ドットインストール](https://dotinstall.com/lessons/basic_google_apps_script_v2)を参考にしました。
こちらの動画を参考に、シートとスクリプトを用意しましょう。

### ログ
GASは言語的には、JavaScriptで書かれます。
しかし、GASのサーバー上で動くので、ブラウザの部品は動かせません。
例えば、ブラウザにアラートを出す```alert()```は実行できません。
このように、言語的にはJSですが、使えない命令がいくつかあります。

ただ、代用することはできます。```alert()```の代わりには、```Logger```を使えます。

```javascript
function myFunction() {
  Logger.log('ログを出すぞぉぉぉ');
}
```

関数```myFunction```を実行し、ログを確認します。
Ctrl+Enter、もしくは「表示→ログ」でログを確認できます。

![GASの勉強_ドットインストール_ログ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298112/26b9ee85-437f-a748-299e-34c5717e3a9d.png)

プログラムを組んでいて値などを確かめたい場合によく使います。

### 自分で関数を作ってみる
スプレッドシートには、元々たくさんの関数が用意されています。
「挿入」 ➔ 「関数」から、SUMやAVERAGEを使えます。
この関数ですが、自分で作ることもできます。

こんな関数を作ってみます。

```javascript
function getResult(input) {
  if (input >= 80){
    return 'PASS';
  } else {
    return 'FAIL';
  }
}
```

シートに記入された数字が80以上ならPASSを表示させ、80未満ならFAILを出します。
ただ、数字が多くなると、少し面倒になります。
このままだと、1個のセルごとに関数を反映させなければいけません。(セルを選択して右下のとこをピーッと引っ張ればいいのですが。)
そこで、map関数を使えば、楽になります。

```javascript
function getResult(input) {
  if(input.map){
    return input.map(getResult);
  } else {
    return input >= 80 ? 'PASS' : 'FAIL';
  }
}
```

こんな感じで「A1:A5」と、まとめてセルを選択できます。

![GASの勉強_ドットインストール_map関数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298112/46693d5c-5250-903b-3c86-1725b7c43070.png)

データの数が多い時は、map関数を使えば楽です。

### スクリプトからデータを追加する
関数だけでなく、データも追加できます。
例えば、以下のコード。

```javascript
function initSheet() {
  var sheet = SpreadsheetApp.getActiveSheet(); //スプレッドシートへのアクセス
  var names = ['牛肉', '豚肉', '鶏肉','魚'];
  var i;

  sheet.clear(); //シートをクリアにする

  for (i = 1; i <= 100; i++) {
    sheet.getRange(i, 1).setValue(names[Math.floor(Math.random() * names.length)]);
    sheet.getRange(i, 2).setValue(Math.floor(Math.random() * 101));
  }
  sheet.getRange(40, 3).setValue('肉祭り').setBackground('skyblue');
}
```

for文のループ処理のところを整理すると、、、
・```getRange(i,1)```でセルを取得する。(i行目1列目)
・```setValue()```でセルに値をセットする。
・```Math.floor()```で数字の小数点以下を無くして整数にする。
・```Math.random()```で0~1の乱数を作る。
・```names.length()```で文字列```names```の長さを取得する。
・```setBackground```で色を指定できます。
となります。
ちなみに、```Math.floor```は、Mathオブジェクトのfloor関数(メソッド)です。

![GASの勉強_ドットインストール_肉祭り.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298112/35a381c5-fe67-3b2f-ddb4-c30eb667e48d.png)


こんな感じで自動で値を割り振ってくれます。

### コードを高速化
先程のコードですが、```getRange()```や```setValue()```の処理をセルの個数分やってしまっています。
わざわざセルを読み取るのは重い処理だそうです。
そのため、先程のループ処理で```i <= 1000```として値を1000個にすると、処理に2秒くらいかかります。
そこで、以下のようにコードを書き換えます。

```javascript
function initSheet() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var names = ['牛肉', '豚肉', '鶏肉','魚'];
  var i;
  var startTime = new Date();  //処理の開始時間
  var scores = [];  //多次元配列を用意

  sheet.clear();

  for (i = 1; i <= 1000; i++) {
    scores.push([
      names[Math.floor(Math.random() * names.length)],
      Math.floor(Math.random() * 101)
    ]);
  }

  sheet.getRange(1, 1, 1000, 2).setValues(scores);

  Logger.log(new Date() - startTime);
}
```

for文のループのところで先にデータを作り、配列の要素に入れ込む処理をしています。
最後に、1行目1列目から1000行×2列の値を取得し、配列```score```に入れます。
また、処理を終了した時間から開始した時間を引いて、処理時間を計算しています。

すると、処理時間が0.042秒に縮まりました！
処理速度はログ(Ctrl+Enter)で確認できます。(単位はミリ秒)
このように、書き込む処理を1度にすることで、処理がかなり高速化します。

# 判定処理を実装してみる
先程のコードに判定処理を実装してみます。(データの数は面倒なので、先ほどのコードの1000個のデータは10個にしておきます。)

```javascript
function showResults() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var scores = [];
  var i;
  var results = [];

  scores = sheet.getRange(1, 2, sheet.getLastRow(), 1).getValues();

  for (i = 0; i < scores.length; i++) {
    results.push([scores[i] >= 80 ? 'PASS' : 'FAIL']);
  }
  sheet.getRange(1, 3, Results.length,1).setValues(results);
}
```

配列を渡すので、```results```の部分は```[]```でくくります。
最後の行までは```getLastrow()```で表します。
ここでは最後の行は10行目までなので、1行2列目から10行×1列の値を取得し、配列```scores```にセットしています。

こうして、判定処理を実装できましたが、毎回スクリプトから実行するのは面倒です。
そのため、シート中のボタンから判定できるようにします。
ボタンにスクリプトを割り当てるのですが、詳しいやり方はドットインストールの講座を見てください。
ボタン作って、右クリックで「：」を選択して「スクリプトを割り当て」で割り当てるだけです！

![GASの勉強_ドットインストール_シート初期化確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298112/c7e34b4e-f254-fb5e-628d-4a9b5effcfcf.png)

また、初期化する際に確認するボタンを表示するには、関数```initSheet()```に以下を追加します。

```javascript
  if (Browser.msgBox('シートの初期化', '実行していいですか？', Browser.Buttons.OK_CANCEL) === 'cancel') {
    return;
  }
```

こうして、シートに判定処理機能を追加できました。

# タスク管理アプリを作る
この講座の最終目標である、タスク管理アプリを作っていきます。

順序は下記の通り。

**①シート・フォーム・スクリプトを用意する**
まず、スプレッドシートの「ツール」⇒「フォームを作成」から、タスクを書いてもらうフォームを作ります。
その後、「ツール」⇒「スクリプトエディタ」から、スクリプトを開きます。

**②Webページで追加したタスクを公開する**
「公開」「→Webアプリケーションとして導入」から公開の設定をします。

**③HTMLファイルを表示**
公開したWebページに、HTMLを反映させるには以下のようにします。

```javascript
function doGet() {
  return HtmlService.createHtmlOutput('<h1>タスク</h1>');
}
```

ただ、直接コードを記入するのはめんどくさいので、HTMLファイルをリンクさせます。

```javascript
function doGet() {
  var template = HtmlService.createTemplateFromFile('index');
  
  template.title = 'タスク管理アプリ';
  template.tasks = getTasks();
  
  return template.evaluate();
}
```

⑤フォームへのリンクを作る
GASで公開されるWebページはセキュリティ上の問題から「iframe(インラインフレーム)」内で展開されています。

＊ iframeとは
HTMLタグのひとつで、文書内にフレーム（枠）を作り、その中に別のテキストや画像などのコンテンツを配置することができます。
これを利用した、「クリックジャッキング」というWeb攻撃が脅威となっています。
あるページ上に、iframeで作った害悪ページ(CSSで透明にしている)を重ね、ボタンなどをクリックさせるのです。

＊ クリックジャッキング
Webブラウザを悪用して、ユーザーに不利益をもたらすセキュリティ上の攻撃手法の一つです。
ボタンやリンクなどを透明で見えない状態にして、通常のWebページの上にかぶせてしまいます。そして、クリックさせることで、コンピュータの利用者や企業、官公庁などに危害を加えます。
□ 参考文献
[クリックジャッキングとは](https://cybersecurity-jp.com/security-measures/6935)

そこで、以下のコードで制限をかけています。

```
<iframe sandbox=""></iframe>
```

![Qiita_iframe.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298112/bc423487-88d2-d692-ab92-19b0de10289a.png)


詳しくは👇より。
[インラインフレーム内のコンテンツに制限をかける](https://www.tagindex.com/html5/embed/iframe_sandbox.html)
こうして制限をかけることで、クリックジャッキングを防いでいるのです。

このような制限がかかっているので、iframeの外のフォームに跳ぶには、HTMLファイルで以下のように書く必要があります。

```html
<p>→ <a href="https://docs.google.com/forms/..." target="_blank">タスク追加用フォームへ</a></p>
```

⑥メール送信処理を実装〜完成
後は、メール送信処理を実装して、バージョンを上げれば完成です。
バージョンを上げないと、公開用のページには反映されません。(開発用のページには常に反映されますが。)
トリガーあたりをいじれば、毎朝タスク内容をメールで送るようにもできます。

### 完成コード(例)

```javascript:cord.js
function doGet() {
  var template = HtmlService.createTemplateFromFile('index');
  
  template.title = 'タスク管理アプリ';
  template.tasks = getTasks();
  
  return template.evaluate();
}

function getTasks(){
  var sheet = SpreadsheetApp.getActiveSheet();
  return sheet.getRange(2, 2, sheet.getLastRow() - 1, 1).getValues();
}

function sendReport(){
  var to = 'kokubu.miraidenshi@gmail.com';
  var subject = 'タスク一覧';
  var url = 'https://script.google.com/macros/s/AKfycbw08TyaVISA4rDOLC1ecOxYUp2peYS26KAaunjJb27gNnP2o0I/exec';
  var body = getTasks().join('\n') + '\n\n' +url;
  
  MailApp.sendEmail(to, subject, body);
}
```

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <h1><?= title ?></h1>
    <p>→ <a href="https://docs.google.com/forms/...">
    タスク追加用フォームへ</a></p>
    <ul>
      <? for(var i = 0; i < tasks.length; i++){ ?>
        <li><?= tasks[i] ?></li>
      <? } ?>
    </ul>
  </body>
</html>
```