GASでアプリケーションを作成する際のフォーマット

GASのアプリケーションを作成する際に
- リンククリックでの画面遷移
- ボタンをクリックでの画面遷移
を作成するのにかなりの時間を費やしました。

doPostで受け取るとCORSエラーやsameoriginで下記のようなエラーがでてきます。

```
Access to XMLHttpRequest at 'https://script.google.com/macros/s/********/exec' from origin 'https://www.bugbugnow.net' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
```

```
Refused to display 'https://script.google.com/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.
```

これを回避するためにdoGetでやると良いという記事はすぐにみつかりましたが、doGetにしてもlinkとボタンのどちらも入れるとなぜかエラーが出てきたりしました。

トライ&エラーをしすぎて、何が原因で何を変えたから回避できたのかもはやわからなくなりましたが、今後はこれをフォーマットにしたいと思います。


# 実装内容

### 初期画面

リンクとボタンを並べただけですが、
こんな画面になります。

![2021-08-05_15h52_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428239/b8bdabaa-73e3-f115-738f-b0da5c4871de.png)


### フォルダ構成

```:フォルダ
.
├── main.gs
├── index.html
├── test1.html
├── test2.html
└── test3.html
```

### 遷移先のHTML

```html:test1～3
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <h1>test1</h1> <!-- 1をファイル番号と同じ番号にしておく -->
  </body>
</html>
```


### 初回表示のHTML

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <!-- jquery -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
</head>
<body>
  <h2>本番</h2>
  <a href="https://script.google.com/a/macros/{アカウント}/s/***********/exec?file=test1"></a>
  <a href="https://script.google.com/a/macros/{アカウント}/s/***********/exec?filw=test2""></a>

  <h2>テスト</h2>
  <a href="https://script.google.com/a/macros/{アカウント}/s//***********//dev?file=test1">test1へ</a>
  <a href="https://script.google.com/a/macros/{アカウント}/s//***********//dev?file=test2">test2へ</a>
  <button id="btn">test3へ</button>

  <script>
    function callAppsScript(file, params='') {
      var request = {'file': file, 'params': params};
      $.ajax({
        type:"get",
        url: 'https://script.google.com/a/macros/{アカウント}/s//***********//dev', //テスト用
        // url: 'https://script.google.com/a/macros/{アカウント}/s/***********/exec', //本番用
        data: request,
        dataType: "jsonp",
        success: function(data) {
          $('html').html(data.html); //読み込んだファイルとHTMLの内容を置き換える
        }
      }).fail(function(jqXHR, textStatus, errorThrown ) {
        console.log("XMLHttpRequest : " + jqXHR.status + "\ntextStatus     : " + textStatus + "\nerrorThrown    : " + errorThrown.message);
      });
    }

    $('#btn').on('click', () => {
      // alert('clicked');
      callAppsScript('test3'); //読み込みたいファイルを選択
    });


  </script>
</body>
</html>
```

開発途中だと`デプロイをテスト`で発行されるURLを使った方が、
反映が楽で良いです。

callAppScript()関数の1つ目の引数で、次に呼び出したいファイルを入れています。


### GAS

```js:main.gs
function doGet(e) {

  const params = e.parameter;
  const file = params.file;

  // 初回にindexの内容を表示
  if(!file) {
    const htmlOutput = HtmlService.createTemplateFromFile('index').evaluate();

    htmlOutput
      .setTitle('index')
      .addMetaTag('viewport', 'width=device-width, initial-scale=1');

    return htmlOutput;
  }

  const callback = params.callback;

  // リンククリック時に実行
  if(!callback) {
    const htmlOutput = HtmlService.createTemplateFromFile(file).evaluate();
    htmlOutput
      .setTitle(file)
      .addMetaTag('viewport', 'width=device-width, initial-scale=1');

    return htmlOutput;
  }

  // ボタンクリック時に実行
  if(callback) {
    const htmlOutput = HtmlService.createTemplateFromFile(file).evaluate().getContent();

    const responseText = callback + '(' + JSON.stringify({'html': htmlOutput}) + ')';
    const output = ContentService.createTextOutput();

    output.setMimeType(ContentService.MimeType.JAVASCRIPT);
    output.setContent(responseText);
    return output;
  }
}
```

# 遷移先にデータを渡す方法

htmlの遷移先のファイルを下記のように変更します。

```html:text1.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <h1>test1</h1>
  </body>
</html>

<script>

  function init(data) {
    alert(data.params);
  }

</script>
```

これでinit関数を作成しておきます。
ここに立ち上がり時に下記のようになるようにGASでコードを追加します。

```html:text1.html(GASでこの状態にする)
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <h1>test1</h1>
  </body>
</html>

<script>
  function init(data) {
    alert(data.params);
  }
</script>

<script>
  init({data}); //valueにはその時に渡したい内容を入れておく
</script>
```

こうしておくと立ち上がり時にinit関数が引数を持った状態で実行されます。
そのためにmain.gsを下記の様に変更します。

```js:main.gs
function doGet(e) {

  const params = e.parameter;
  const file = params.file;

  // 初回にindexの内容を表示
  if(!file) {
    const htmlOutput = HtmlService.createTemplateFromFile('index').evaluate();

    htmlOutput
      .setTitle('index')
      .addMetaTag('viewport', 'width=device-width, initial-scale=1');

    return htmlOutput;
  }

  const callback = params.callback;

  // リンククリック時に実行
  if(!callback) {
    let htmlOutput = HtmlService.createTemplateFromFile(file).evaluate().getContent();

    htmlOutput += '\n<script>\n';
    htmlOutput += 'init(' + JSON.stringify({params: 'GASからJSへデータをパス'}) + ');\n';
    htmlOutput += '</script>';

    htmlOutput = HtmlService.createTemplate(htmlOutput).evaluate();

    htmlOutput
      .setTitle(file)
      .addMetaTag('viewport', 'width=device-width, initial-scale=1');

    return htmlOutput;
  }

  // ボタンクリック時に実行
  if(callback) {
    let htmlOutput = HtmlService.createTemplateFromFile(file).evaluate().getContent();

    htmlOutput += '\n<script>\n';
    htmlOutput += 'init(' + JSON.stringify({params: params.params}) + ');\n';
    htmlOutput += '</script>';

    const responseText = callback + '(' + JSON.stringify({'html': htmlOutput}) + ')';
    const output = ContentService.createTextOutput();

    output.setMimeType(ContentService.MimeType.JAVASCRIPT);
    output.setContent(responseText);
    return output;
  }
}
```


1. `getCotent()`でHTMLの文字列を取得
2. init(params)のデータが入った関数を作成
3. `createTemplate()`でhtmlを読み込んでevaluateで評価

というような形に変更しています。


## 参考記事
[GASの関数を実行可能APIじゃない方法で実行する](https://qiita.com/Cyber_Hacnosuke/items/1db9085af83df2aaf42c)
[Passing data to html service](https://ramblings.mcpher.com/gassnippets2/passing-data-to-html-service/)
[Google Apps ScriptのHTML Serviceで擬似的に画面遷移](https://officeforest.org/wp/2018/12/02/google-apps-script%E3%81%AEhtml-service%E3%81%A7%E6%93%AC%E4%BC%BC%E7%9A%84%E3%81%AB%E7%94%BB%E9%9D%A2%E9%81%B7%E7%A7%BB/)