GASでWebアプリを作る

# はじめに
　GASを使用してウェブアプリを作成する方法を説明します。
　ここでは、ウェブアプリをデプロイして使用できる状態にする事と、その為の手順について説明する事を目的にしていますので、プログラムの細かい説明は省きます。

# 今回作成するアプリ
　スプレッドシートのデータを基にして、Web画面に一覧表示するプログラムです。
　Web画面からデータの追加や削除も行え、その結果がスプレッドシートにも反映されます。
![Webアプリの作成_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/10e454f6-fc70-0428-0ef8-a0c9cd350e19.png)

# 最初に用意する事
　次の２つのファイルをGoogleのドライブ上に用意します。
　　①「**Webアプリの作成**」スプレッドシート　※ファイル名は任意です。
　　②「**Webアプリの作成**」GASファイル　※ファイル名は任意です。
![Webアプリの作成_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/bd734b52-0471-3a66-01b3-c5a0440721a9.png)
### ①「Webアプリの作成」スプレッドシート
　データは、［シート1］のB列に入力します。2行目は見出しで、3行目以降が実際のデータとなります。
![Webアプリの作成_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/419040ee-3262-e4b2-bb30-a8b4bca25e5b.png)
### ②「Webアプリの作成」GASファイル
　GASファイルには、「**Code.gs**」と「**index.html**」を用意します。
![Webアプリの作成_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/77311c9d-9fce-a4e5-c7ff-ae34d1add395.png)
#### ・Code.gas
　下記のコードをそのままコピーしてください。
　`fileId`には、「**Webアプリの作成**」スプレッドシートのファイルIDを設定します。**※下図を参照**
```javascript:Code.gs
// スプレッドシートの設定
const fileId = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';  // ←ココにスプレッドシートのファイルIDを指定します。
const spreadSheet = SpreadsheetApp.openById(fileId);  
const sheet       = spreadSheet.getSheetByName('シート1'); 

function doGet() {  
  const html = HtmlService.createTemplateFromFile('index');  
  return html.evaluate();  
}

/**
 * スプレッドシートのデータを取得する（返す）。
 * データが無い場合は、null を返す。
 */
function getData() {
  const lastRow = sheet.getLastRow();
  if(lastRow >= 3) {
    const data = sheet.getRange(3, 2, lastRow - 2, 1).getValues();
    return data;
  }
  return null;
}

/**
 * スプレッドシートにデータを追加する。
 */
function addData(value) {
  const lastRow = sheet.getLastRow();
  sheet.getRange(lastRow + 1, 2).setValue(value);
}

/**
 * 引数（行番号）で指定されたデータを、スプレッドシートから削除（行削除）する。
 */
function delData(rowNo){
  sheet.deleteRow(rowNo);
}
```
　※スプレッドシートのファイルIDは、ブラウザーのURLの一部として表示されます。（下図のピンク部分）
![Webアプリの作成_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/c07f7a12-edd2-fbf8-78e6-83dd2778ca4d.png)
#### ・index.html
　下記のコードをそのままコピーしてください。
```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <style type="text/css">
      form.main {background-color:lightgreen; margin-top:50px; margin-left:50px; width:400px; padding:10px;}
      div.header {font-weight:bold; color:blue; line-height:1.5;}
    </style>
    <script>
      /**
       * サーバ側のデータ取得処理を呼び出す。
       */ 
      function getData() {
        google.script.run.withSuccessHandler(
          function(data, element) {
            drawList(data);
        })
        .withFailureHandler(
          function(msg, element) {
            console.log('### ERROR: ' + msg.toString());
        })
        .withUserObject(this)
        .getData();
      }

      /**
       * 引数で指定されたデータを元に、リスト（ラジオボタン）を表示する。
       * 引数は、2次元配列の文字列。ex.[['ミカン'],['リンゴ'],['バナナ'],…]
       */
      function drawList(data) {
        let list = document.getElementById('form-list');

        // <div class="list" id="form-list"> の有無を確認して、無い場合は作成する。
        if(list === null) {
          const newList = document.createElement('div');
          newList.setAttribute('class', 'list');
          newList.setAttribute('id', 'form-list');
          const targetElement = document.getElementById('form-input');
          const main          = document.getElementById('form-main');
          main.insertBefore(newList, targetElement);
          list = newList;
        }

        if(data !== null) {
          // リストの明細行（ラジオボタン）を追加する。
          for(let i = 0; i < data.length; i++) {
            const newElement = document.createElement('div');
            const newInput   = document.createElement('input');
            newInput.setAttribute('type', 'radio');
            newInput.setAttribute('name', 'kudamono');
            newInput.setAttribute('value', (i + 3));
            const newContent = document.createTextNode(data[i][0]);
            newElement.appendChild(newInput);
            newElement.appendChild(newContent);
            list.appendChild(newElement);
          }
        }
      }

      /**
       * サーバ側のデータ追加処理を呼び出す。
       */
      function addData() {
        const formText = document.getElementById('form-text');
        google.script.run.withSuccessHandler(
          function(v, element) {
            formText.value = '';
            const list = document.getElementById('form-list');
            list.remove();
            getData();
        })
        .withFailureHandler(
          function(msg, element) {
            console.log('### ERROR: ' + msg.toString());
        })
        .withUserObject(this)
        .addData(formText.value);
      }

      /**
       * サーバ側のデータ削除処理を呼び出す。
       */
      function delData() {
        const elements = document.getElementsByName('kudamono');
        const rowNo = getRadioValue(elements);
        if(rowNo === null) {
          return;
        }
        google.script.run.withSuccessHandler(
          function(v, element) {
            const list = document.getElementById('form-list');
            list.remove();
            getData();
        })
        .withFailureHandler(
          function(msg, element) {
            console.log('### ERROR: ' + msg.toString());
        })
        .withUserObject(this)
        .delData(rowNo);
      }

      /**
       * ラジオボタンから値を取得する（返す）。
       * ラジオボタンが選択されていない場合は、null を返す。
       * 引数には、document.getElementsByName() で取得した Elements を渡す。
       */ 
      function getRadioValue(radioElements) {
        let value = null;
        for(let i = 0; i < radioElements.length; i++) {
          if(radioElements.item(i).checked) {
            value = radioElements.item(i).value;
            break;
          }
        }
        return value;
      }
    </script>
  </head>
  <body onload="getData();">
    <form class="main" id="form-main">
      <div class="header">くだもの</div>
      <div class="list" id="form-list">
        <!-- ex.
        <div><input type="radio" name="kudamono" value="3">ミカン</div>
        -->
      </div>
      <div class="input" id="form-input">
        <input type="text" id="form-text"></input>
        <button type="button" onClick="addData();">追加</button>
        <button type="button" onClick="delData();">削除</button>
      <div>
    </form>
  </body>
</html>
```
　準備するものは以上です。
# ウェブアプリとしてデプロイする
　ウェブアプリとして使用できる状態にする事をデプロイと言います。
　以下に、デプロイする手順を説明します。
### ①〔デプロイ〕プルダウンリスト
　GASエディタの右上にある〔デプロイ〕プルダウンリストを選択します。
　選択するとリストが表示されますので、［新しいデプロイ］をクリックします。
![Webアプリの作成_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/9c274d85-5106-8819-da5e-a2d675a138c5.png)
### ②［新しいデプロイ］画面
　［新しいデプロイ］画面が表示されますので、次の選択を行い〔デプロイ〕ボタンをクリックします。
- 次のユーザーとして実行：**ウェブ アプリケーションにアクセスしているユーザー**
- アクセスできるユーザー：**自分のみ**
![Webアプリの作成_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/5e705578-b072-3a47-862e-18dcce92d617.png)
**【注意】上記の画面が表示されない場合は、［種類の選択］の歯車アイコンを選択して、［ウェブアプリ］をチェックしてください。**
![Webアプリの作成_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/5a899a82-26c0-c00e-6b15-ed7e9a219eea.png)
### ③ ウェブアプリの表示
　下記の画面が表示されればデプロイは完了しています。
　「ウェブアプリ」のURLのリンクをクリックすると、アプリが表示されます。
![Webアプリの作成_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/f6957025-bd1c-68e7-ba93-1b5172f5d5a1.png)
　レビューの許可を確認する画面が表示されますので、〔REVIEW PERMISSIONS〕ボタンをクリックします。
![Webアプリの作成_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/75893d59-30ba-311b-663e-b18b75deb0b1.png)
　下記の画面が表示されたら、自分のアカウントを選択します。
![Webアプリの作成_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/c7f2fe4c-a76f-a695-bb8b-ef2eafd02134.png)
　続けて下記の画面が表示されたら、〔許可〕ボタンをクリックします。
![Webアプリの作成_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/0a6b45ff-88aa-c8f9-db5e-7f1b14a516eb.png)
　Web画面が表示されます。
![Webアプリの作成_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/2a5f938a-436f-501b-9d8a-b57592c7c7f4.png)
　以降、「**③ ウェブアプリの表示**」の最初の画面で表示されたURL（リンク）をブラウザーに入力する事により、いつでもウェブアプリを表示する（呼び出す）事ができます。
# ウェブアプリを使ってみる
　では、アプリを使ってみましょう。
　テキストボックスに「パイナップル」と入力して〔追加〕ボタンをクリックします。
![Webアプリの作成_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/e3d36877-25bd-036a-6c3d-7e0093ef6229.png)
　くだものリストに「パイナップル」が追加されました。
![Webアプリの作成_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/6bb8ba45-c49b-f079-5cf9-5fc76a771e66.png)
　「**Webアプリの作成**」スプレッドシートにも「パイナップル」が追加（７行目）されています。
![Webアプリの作成_16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/c9b1c8b0-0869-d166-5de9-779a493f095c.png)
　今度は、くだものリストの「リンゴ」を選択（ラジオボタン）して〔削除〕ボタンをクリックします。
![Webアプリの作成_17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/46c47a32-c22e-f744-6459-a1980d1b2d82.png)
　くだものリストから「リンゴ」が削除されました。
![Webアプリの作成_18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/f6140b35-30bd-1224-e4d0-286198f2a13a.png)
　「**Webアプリの作成**」スプレッドシートの「リンゴ」も削除されています。
![Webアプリの作成_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/ef32fcab-660d-2e48-25c2-23a7f673ea82.png)
# プログラムを修正する
　データの追加と削除ができる様になったので、更新処理を追加してみます。
　「**Webアプリの作成**」GASファイルの２つのファイルを修正します。
#### Code.gs
　以下の関数（`updData()`）を追加します。
```javascript:Code.gs
/**
 * スプレッドシートのデータを更新する。
 *   引数 rowNo：更新する行の行番号。／ value：更新するデータ。
 */
function updData(rowNo, value) {
  sheet.getRange(rowNo, 2).setValue(value);
}
```
#### index.html
　`<script>`タグ内に次の関数（`updData()`）を追加します。
```html:index.html
    <script>
        ：
      /**
       * サーバ側のデータ更新処理を呼び出す。
       */
      function updData() {
        const elements = document.getElementsByName('kudamono');
        const rowNo = getRadioValue(elements);
        if(rowNo === null) {
          return;
        }
        const formText = document.getElementById('form-text');
        google.script.run.withSuccessHandler(
          function(v, element) {
            formText.value = '';
            const list = document.getElementById('form-list');
            list.remove();
            getData();
        })
        .withFailureHandler(
          function(msg, element) {
            console.log('### ERROR: ' + msg.toString());
        })
        .withUserObject(this)
        .updData(rowNo, formText.value);
      }
    </script>
  </head>
```
　`<form>`タグ内に`<button type="button" onClick="updData();">更新</button>`を追加します。
```html:index.html
    <form>
        ：
      <div class="input" id="form-input">
        <input type="text" id="form-text"></input>
        <button type="button" onClick="addData();">追加</button>
        <button type="button" onClick="delData();">削除</button>
        <button type="button" onClick="updData();">更新</button>  // ←この行を追加する。
      <div>
    </form>
```
　プログラムの修正は上記で完了ですが、ブラウザーを再読込してもWeb画面に変化はありません。
![Webアプリの作成_18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/f6140b35-30bd-1224-e4d0-286198f2a13a.png)
　プログラムを修正した場合、再度デプロイを実行しないと修正内容がアプリに反映されません。
　以下の手順で行います。
### ①〔デプロイ〕プルダウンリスト
　GASエディタの右上にある〔デプロイ〕プルダウンリストから［デプロイを管理］をクリックします。
**【重要】ここで前回と同じ様に［新しいデプロイ］を選択してしまうと、ウェブアプリを呼び出すURLが変わってしまいます。**
![Webアプリの作成_20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/efd0b02d-7004-ae7a-d610-acca7c46f4c6.png)
### ②［デプロイを管理］画面
　［デプロイを管理］画面が表示されたら、編集（鉛筆アイコン）をクリックします。
![Webアプリの作成_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/508138ac-a404-a403-83ec-cf952283dac9.png)
　「バージョン」プルダウンリストが変更できる様になりますので、［新バージョン］を選択します。
　続けて、〔デプロイ〕ボタンをクリックします。
![Webアプリの作成_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/8a402a05-4b30-f6f5-aac1-f79e763effab.png)
　下記の画面が表示されれば、デプロイの更新は完了です。
![Webアプリの作成_23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/f9e763c1-38cc-cb5c-15ce-29dafdb82400.png)
### ③ プログラム修正の確認
　ブラウザーを再読込するとWeb画面の表示が変わります（〔更新〕ボタンが表示されました）。
![Webアプリの作成_24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/6700033a-1524-ca00-386e-f8a7c251022a.png)
　くだものリストの「ミカン」を選択（ラジオボタン）します。
　テキストボックスに「グレープフルーツ」と入力して〔更新〕ボタンをクリックします。
![Webアプリの作成_25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/c9bbed3b-7007-4fc3-faf7-d0078d9a210d.png)
　「ミカン」が「グレープフルーツ」に更新されます。
![Webアプリの作成_26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/47a45765-ecaa-f6ef-ab60-7996f9b49850.png)
　「**Webアプリの作成**」スプレッドシートも「グレープフルーツ」（３行目）に更新されています。
![Webアプリの作成_27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/eb36ace7-52c1-d837-0cd4-a069521fce08.png)
# 開発中のアプリの確認方法
　上記の手順でプログラムの修正がアプリに反映されますが、開発中に毎回デプロイを実行してアプリの動作を確認するのは大変です。
　次の手順で簡単にプログラムの修正を確認する事ができます。
### ①〔デプロイ〕プルダウンリスト
　GASエディタの右上にある〔デプロイ〕プルダウンリストから［デプロイをテスト］をクリックします。
![Webアプリの作成_28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/ffe64ebe-6183-2b53-80bb-d05d0aaee307.png)
### ②［デプロイをテスト］画面
　［デプロイをテスト］画面が表示されるので、表示されているURLのリンクをクリックするとアプリが表示されます。
　このURL（リンク）を使用する事により、デプロイを実行しなくてもプログラムの修正を確認する事ができます。
![Webアプリの作成_29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/08ad7c0f-e470-b640-5276-607ef8d74149.png)