GoogleAppsScript で SinglePageApplication を作るTips

はじめに
========================================
先日の [複数のGoogleカレンダーをマージする](http://qiita.com/ryskiwt/items/fe49f2834109f996d449) で、GoogleAppsScriptの知見が溜まってきたので、一旦分かるところまで纏めておきます。

サーバーサイドjs
----------------------------------------

```js:server.js
// GETリクエストのハンドリング
function doGet(e) {
  // なんか処理
  var result = func(e.parameter);

  // index.htmlからテンプレートを生成し、変数をバインド
  var html = HtmlService.createTemplateFromFile('index');
  html.title = 'title';
  html.hoges = ['hoge', 'fuga'];
  
  // HTMLの生成とviewpoetの設定
  var output = html.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
  output.addMetaTag('viewport', 'width=device-width, initial-scale=1, maximum-scale=1')
  return output;
}


// POSTリクエストのハンドリング
function doPost(e) {
  // doGetと同じ感じで
}

// なんか処理
function func(params) {
  // 結果の返却
  return {
    res1: params.param1,
    res2: params.param2,
  };
}
```

### GET/POSTリクエストのハンドリング

- `doGet`メソッドはGETリクエストを受けた場合に実行される
- `doPost`メソッドはPOSTリクエストを受けた場合に実行される
- パラメータの取得は、引数`e`から`e.parameter.[パラメータ名]`で取得

```js
function doGet(e) {
  var param1 = e.parameter.param1;
  var param2 = e.parameter.param2;
}

function doPost(e) {
  var param1 = e.parameter.param1;
  var param2 = e.parameter.param2;
}
```

### HTMLへの変数の埋め込み
- テンプレートを読み込むには`HtmlService`を利用
- テンプレートへの変数の受け渡しは`html`へプロパティを設定
- HTMLテンプレート側では`<?= [変数名] ?>`で取り出し

```js
var html = HtmlService.createTemplateFromFile('index');
html.title = 'title';
return html.evaluate();
```
```html
<title><?= title ?></title>
```


- viewportの設定はhtmlファイルにベタ書きしても効かないので`setSandboxMode`と`addMetaTag`で設定する

```js
var output = html.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
output.addMetaTag('viewport', 'width=device-width, initial-scale=1, maximum-scale=1')
return output;
```

### その他
パラメータのバリデートとかもできるっぽい...？（勉強中）

HTMLテンプレート
----------------------------------------

```html:index.html
<!DOCTYPE html>
<html lang='ja'>
<head>
  <meta charset='UTF-8'>
  <title><?= title ?></title>

  <!-- including stylesheets -->
  <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css'>
  <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/font-awesome/4.6.2/css/font-awesome.min.css'>
</head>

<body>
  <div class='container'>
    <div class='row'>
      <div class='col-xs-12'>

        <!-- NAVBAR -->
        <nav class='navbar navbar-default' role='navigation'>
          <div class='navbar-header'>
            <a class='navbar-brand' href=''><?= title ?></a>
          </div>
        </nav>

        <div class='raw'>
          <div class='col-xs-12'>
            <button class='btn btn-primary' onclick='triggerServerSideFunc();'>
              trigger serverside func
            </button>
          </div>
        </div>

        <div class='raw'>
          <div class='col-xs-12'>
            <ul class="list-group">
              <? hoges.forEach(function(hoge) { ?>
                <li class="list-group-item"><?= hoge ?></li>
              <? }); ?>
            </ul>
          </div>
        </div>

      </div>
    </div>
  </div>

  <!-- includint javascript -->
  <script src='https://code.jquery.com/jquery-2.2.3.min.js'></script>
  <script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js'></script>
  <?!= HtmlService.createHtmlOutputFromFile('client.js').getContent(); ?>
</body>
</html>
```

### Javascriptコードの埋め込み
- 変数の取り出しは`<?= [式] ?>`
- 文の記述は`<? [文] ?>`

```html
<ul class="list-group">
  <? hoges.forEach(function(hoge) { ?>
    <li class="list-group-item"><?= hoge ?></li>
  <? }); ?>
</ul>
```

### インクルード

- 他のテンプレートをインクルードするなら`<?!= [式] ?>`と`HtmlService#createHtmlOutputFromFile`を使用

```html
<?!= HtmlService.createHtmlOutputFromFile('client.js').getContent(); ?>
```

### その他
bootstrapとfont-awesomeは個人的な好みです

クライアントサイドjs
----------------------------------------

```html:client.js.html
<script>
$(function(){
  // 初期化処理
});

// merge/clean
function triggerServersideFunc() {
  var params = {
    param1: 'param1',
    param2: 'param2',
  };

  google.script.run.withSuccessHandler(function(result) {
    // resultはサーバーサイド関数の戻り値
    console.log(result.res1);
    console.log(result.res2);

  }).withFailureHandler(function(err) {
    // errはエラーオブジェクト
    console.log(err)

  }).func(params);
}
</script>
```

- クライアントサイドからサーバーサイドの関数を実行するには`google.script.run.[関数名]`、引数はサーバーサイド関数の引数でOK
- 成功時のコールバックは`.withSuccessHandler()`
- 失敗時のコールバックは`.withFailureHandler()`

その他Tips
----------------------------------------

コードにべた書きしたくない設定ファイルはスクリプトプロパティに書いておける

```js:script-prop.js
// 「スクリプトのプロパティ」からPROP1とPROP2を取得
var sp = PropertiesService.getScriptProperties();
var scProp1 = sp.getProperty('SC_PROP1') || 'prop1 default value';
var scProp2 = sp.getProperty('SC_PROP2') || 'prop2 default value';
  
// 「スクリプトのプロパティ」へPROP1とPROP2を設定
sp.setProperties({
  'SC_PROP1': params.param1,
  'SC_PROP2': params.param2,
});
```

トリガーの取得と設定

```js:trigger.js
// 設定されているトリガーをすべて削除
ScriptApp.getProjectTriggers().forEach(function(trigger) {
  ScriptApp.deleteTrigger(trigger);
});

// 時間ベースのトリガーを設定
ScriptApp.newTrigger('triggeredFunction')
.timeBased().everyMinutes(10) // every 10 min.
.create();
```

外部サービスへのアクセスは`UrlFetchApp`を使う

```js:access-api.js
// アクセス先
var url = 'https://url/to/application/api/you/want/to/access';

// リクエストパラメータ
var payload = { param1: 'param1', param2: 'param2' };
  
// フェッチ
var options = { method: 'POST', payload: payload };
var response = UrlFetchApp.fetch(url, options);
  
// JSON形式の返却値をオブジェクトへパース
var obj = JSON.parse(response.getContentText());

```