【GAS】クイズアプリで忘年会を盛り上げたい

[corp-engr 情シスSlack（コーポレートエンジニア x 情シス） Advent Calendar 2019](https://adventar.org/calendars/3995)の1日目の記事です。
Google Apps Script（以下GAS）でネタを探していたところで、忘年会で使おうとクイズアプリを作ったので紹介します。
~~コーポレートエンジニアあんまり関係ないかもw~~

#はじめに
12月に入って本格的に忘年会シーズンですね。
忘年会盛り上げるためにクイズ大会でもしたいな、と思って[Kahoot!](https://kahoot.com/)使おうと思ってたのですが、Free版だと人数やなにやら制限がありそうなので作れないかとやってみました。
結果、似たようなことができそうだったので紹介します。

クイズアプリに限らずこういったウェブアプリが作れるということを知ってもらい、GAS好きがもっと増えてほしいな。

#想定読者
- GASは触ったことあるけどWebアプリは作ったことなく、興味のある方
- 忘年会何するか決めかねている方

#注意点
- GASってなんぞ？という方には少し難しいかもしれません。
- 本記事ではCSSを記述していません。これから。
- 数十人で使った場合ちゃんと動くのか怪しいです。
- 結局、盛り上がるかは司会者次第でしょう。

#こんな感じの出来上がり
![quiz1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312018/401b34d7-f5fc-c304-5fa7-b17998777198.gif)

#ユーザー登録画面
まずはスプレッドシートを開いて、`ツール > スクリプトエディタ`でScriptEditorを開きます。
プロジェクト名、コード.gsの名前を適当に変更しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312018/f3b4145f-3695-b60d-46b6-5b8300a1bb5c.png)
最初に呼び出すHTMLを作成します。
`ファイル > New > HTMLファイル` loginと名前をつけて`OK`

```login.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <form method="post" action="(ウェブアプリケーションのURL)?p=0">
      <p><label>ユーザー名を入力してください。</label></p>
      <p><input type="text" name="name"></p>
      <p><input type="submit" value="ENTER"></p>
    </form>
  </body>
</html>
```
`(ウェブアプリケーションのURL)`は後ほど説明します。
GASの処理で利用するためURLに`?p=0`とパラメータを付与しています。
こうすると、GASで`e.parameter['p']`とすることで付与した`0`が取得でき、現在のページの判定として使用しています。
`0 = login` `1 = start` など

HTMLを表示させるため`quiz.gs`に書きます。

```javascript
function doGet(e){
    return HtmlService.createHtmlOutputFromFile('login');  
}
```
[Google Apps Script Class HtmlService](https://developers.google.com/apps-script/reference/html/html-service)

動作確認のためデプロイしてみましょう。
`公開 > ウェブアプリケーションとして導入...`
<img width="465" alt="スクリーンショット 2019-11-29 23.53.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312018/053ed41c-d06d-6f2c-bc11-18fca0eb00d7.png">
デプロイするとURLが発行されるのでコピーして先ほどのlogin.htmlの`(ウェブアプリケーションのURL)`に設定します。

そしてもう一度デプロイしましょう。
HTMLやGASの変更を動作確認する際は都度デプロイする必要があります。毎回です。
変更を適用するデプロイは必ず`Project version`を`New`にするのを忘れないようにしてください。
ちなみに何度デプロイしてもウェブアプリケーションのURLは変わりません。

URLにアクセスしてみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312018/82e525cc-152a-42a5-bbab-fee46a2be0c8.png)

#スタート画面
ユーザー名を入力したらスプレッドシートに記録します。
そしてスタート画面に遷移させたいのでHTMLファイルを追加してください。

```start.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <form method="post" action="(ウェブアプリケーションのURL)?p=1">
      <p><label><?=name?>さん</label></p>
      <input type="hidden" name="name" value=<?=name?>>
      <p><label>案内があるまでお待ちください...</label></p>
      <p><input type="submit" value="START!!!"></p>
    </form>
  </body>
</html>
```
login画面の次に表示される画面になるのでパラメータには`?p=1`を設定します。
GASの`doPost`に以下の処理を追記します。

- login.htmlからのパラメータを受け取る
- スプレッドシートにユーザー名を記録
- ユーザー名を付与してstart.htmlを呼び出す

```javascript
function doPost(e) {
  var sheet = SpreadsheetApp.getActiveSheet(); 
  var columnValue = sheet.getRange('A:A').getValues();
  var lastRow = columnValue.filter(String).length;  //最終行を取得
  var pages = [  //全ページを配列で持つ
    "login",
    "start"
  ];
  var currentPage = pages[e.parameter["p"]];  //URLに付与したパラメータ（?p=0）から現在のページを取得
  var nextPage = pages[parseInt(e.parameter["p"], 10) + 1];  //次のページを取得

  if (currentPage === "login") {
    sheet.appendRow([lastRow, e.parameter.name]);  //ユーザー名をスプレッドシートに追加
  }

  var tpl = HtmlService.createTemplateFromFile(nextPage);
  tpl.name = e.parameter.name;
  return tpl.evaluate();
}
```
デプロイするとスプレッドシートへのアクセス承認が必要になりますので指示に従って承認します。
終わったらlogin画面で`test`と入力して`ENTER`をクリックして確認してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312018/71cd38ce-b40e-20c6-6f36-87eeb22500d8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312018/61ba2f7f-b416-0405-3ef3-9700f10d89ea.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312018/53b52edb-188f-2eb8-e981-0bdeda8b1598.png)
#あとは複製するだけ
ここまで理解できればあとは複製してクイズ画面を作っていきます。
`quiz1.html` を作り、複製して`quiz2.html` `quiz3.html`とクイズの終わりに表示する`end.html`ファイルを作成しそれぞれHTMLを記述します。

```quiz1.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <form method="post" action="(ウェブアプリケーションのURL)?p=2">
      <p><label><?=name?>さん</label></p>
      <input type="hidden" name="name" value=<?=name?>>
      <p><label>第 1 問</label></p>
      <p><label>Twitterで、一度に投稿できるテキストの文字数は最大何文字？</label></p>
      <p><input type="submit" name="quiz" value="120文字"></p>
      <p><input type="submit" name="quiz" value="140文字"></p>
      <p><input type="submit" name="quiz" value="160文字"></p>
      <p><input type="submit" name="quiz" value="180文字"></p>
    </form>
  </body>
</html>
```
```end.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <form method="post" action="(ウェブアプリケーションのURL)?p=5">
      <p><label><?=name?>さん</label></p>
      <input type="hidden" name="name" value=<?=name?>>
      <p><label>お疲れ様でした〜！</label></p>
    </form>
  </body>
</html>
```
そして最終的な`quiz.gs`はこんな感じです。

```javascript
function doGet(e){
    return HtmlService.createHtmlOutputFromFile('login');  
}
function doPost(e) {
  var sheet = SpreadsheetApp.getActiveSheet(); 
  var columnValue = sheet.getRange('A:A').getValues();
  var lastRow = columnValue.filter(String).length;
  var pages = [
    "login",
    "start",
    "quiz1",
    "quiz2",
    "quiz3",
    "end"
  ];
  var currentPage = pages[e.parameter["p"]];
  var nextPage = pages[parseInt(e.parameter["p"], 10) + 1];
  var tpl = HtmlService.createTemplateFromFile(nextPage);

  if (currentPage === "login") {
    sheet.appendRow([lastRow, e.parameter.name]);
  }
  if(currentPage.indexOf("quiz") === 0){
    //quizの場合は回答を記録
    answer(sheet, lastRow, e.parameter.name, e.parameter.quiz, parseInt(e.parameter["p"]) + 1)
  }

  tpl.name = e.parameter.name;
  return tpl.evaluate();
}
function answer(sheet, row, user, answer, column) {
  var users = sheet.getRange(1,2,row).getValues();

  for(var i = 0; i < users.length; i++) {
    if (users[i] == user) {
      sheet.getRange(i + 1, column).setValue(answer);
    }
  }
}

```
#これからやること
- CSS
- 結果やランキングがわかるようにする

#最後に
とりあえず作ってみようって感じで調べつつ、動かしつつ進めたのですが、なんだかんだであとデザインちゃんとしたら普通に使えそうだなって思いました。
スプレッドシートへの書き込みとか大人数で期待通り動くかは少し心配。
あと問題文はプロジェクターで投影して、回答のみ手元のアプリでやってもらう方がいいんじゃ...？

#おまけ
CSS適用するとなかなか良い感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312018/0593b4f1-73e4-6ce7-54fc-6d5a4189f2fa.png)

#参考記事
[GoogleAppsScript(GAS)でページ遷移を擬似的に実装する方法](https://qiita.com/kakkiichan/items/a6a653bbe113a1dee2eb)
[Google Apps Scriptで簡単にWebアプリケーションを公開する方法](https://www.indetail.co.jp/blog/181219/)
[Google Apps ScriptでWebアプリケーションを作る最初の一歩 HtmlServiceオブジェクトとは](https://tonari-it.com/gas-web-application-beginner/)
[htmlとgoogle apps script間の値の引渡しについて](https://teratail.com/questions/43021)
[GASでGetパラメータを受け取ってスプレッドシートに書き込む方法](https://qiita.com/hirohiro77/items/a947416f803f45777338)
[GASでWebページを作るときにHTMLとCSSを別ファイルに記述する方法](https://tonari-it.com/gas-web-html-css-scriptlet/)
[コピペでできる！cssとhtmlのみでフォームのテキストエリアをわかりやすくするデザイン15選](https://copypet.jp/692/)