dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

[GAS] Google Apps Script のHtmlServiceまとめ

とりあえず、公式リファレンスであるこのあたりを読んでみた。

Create and Serve HTML

Communicate with Server Functions

Templated HTML

以下、要点まとめ。

Create and Serve HTML

スクリプトの作成からWebアプリとして公開するまで

新規 > Google Apps スクリプト

スタンドアロンスクリプトファイルを作成。

新規作成 > HTMLファイル

index.html を作成。

コード.gsindex.html にそれぞれ以下を記述。

コード.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
      .setSandboxMode(HtmlService.SandboxMode.IFRAME);
}

index.html

<html>
  <body>
    Hello, world!
  </body>
</html>

保存したら、

公開 > ウェブアプリケーションとして導入...

を開く。

f:id:dackdive:20150201004753p:plain

「プロジェクト バージョン」では「新しいバージョンを保存」をクリック。

「次のユーザーとしてアプリケーションを実行」はどちらでも。

最後に「導入」をクリックすると無事アプリケーションとして公開され、URL が取得できるのでアクセスすると Hello, World! と表示されるはず。

Communicate with Server Functions

<script>
  google.script.run.[関数名]()
</script>

.gs 側の関数を呼ぶことができる

  • 一度に10個まで同時に実行可能(その前にブラウザの制限に引っかかるはず)
  • 非同期なので順番は保証されない

Paremeters and return values

  • クライアント側からサーバ側の関数を引数つきで呼ぶこともできるし、
    サーバ側の関数の戻り値は success handler(後述) に引数として渡される
  • 引数・戻り値に渡せるもの

    • Number, Boolean, String null や配列、プリミティブ型からなるオブジェクト
    • (引数のみ) form element
  • 渡せないもの

    • Date, Function
    • form 以外の DOM
  • サーバー側に渡した object は参照ではなくコピー

Success handlers & Failure handlers

基本形としてこのようにメソッドチェーンで書く。

index.html

<script>
function [サーバ側の関数が正常終了した時に呼ぶ関数]([サーバ側の関数の戻り値]) {
   // do something
}

function [サーバ側の関数が失敗した時に呼ぶ関数]([エラーオブジェクト]) {
   // do something
}

google.script.run
   .withSuccessHandler([サーバ側の関数が正常終了した時に呼ぶ関数])
   .withFailureHandler([サーバ側の関数が失敗した時に呼ぶ関数])
   .[サーバ側の関数]([引数]);
</script>

User objects

クライアント側の handler に引数として渡せるのは通常サーバ側の戻り値1つだが、
withUserObject を使うと第二引数以下に任意の object を渡すことができる。
これによって同じ handler を使い回すことが可能。

<script>
function updateButton(email, button) {
   button.value = 'Clicked by ' + email;
}
</script>

<input type="button" value="Not Clicked"
    onclick="google.script.run
        .withSuccessHandler(updateButton)
        .withUserObject(this)
        .getEmail()" />
function getEmail() {
  return Session.getActiveUser().getEmail();
}

この例における this は button 自身を指す

  • 引数を複数渡したい時は?

無理矢理やるならこう。

<script>
function updateButton(email, params) {
   var button = params[0],
       message = params[1];
   button.value = 'Clicked by ' + email + ': "' + message + '"';
}
</script>

<input type="button" value="Not Clicked"
    onclick="google.script.run
       .withSuccessHandler(updateButton)
       .withUserObject([this, 'hello world'])
       .getEmail()" />

Private functions

  • サーバ側の関数名の末尾に _ (アンダースコア) をつけたものは private function となる
  • google.script で呼べない
  • ライブラリ化しても同様

Templated HTML

  • html 側で解釈・実行されるスクリプトのこと
  • 3種類ある(後述)
  • Scriptlets はページがクライアントに提供される に実行されるため、1ページにつき一度しか実行されない
  • テンプレートを使う時はサーバ側は HtmlService.createTemplateFromFile('index').evaluate() という書き方になる
    • 単純な HTML の場合は HtmlService.createHtmlOutputFromFile('index') だった
    • createTemplateFromFile(fileName) が返すのは HtmlTemplate クラスのオブジェクト
    • これを evaluate() した結果は HtmlOutput クラスのオブジェクト = createHtmlOutputFromFile(fileName) と同じもの

3種類のScriptlets

1. Standard scriptlets
  • <? ... ?> で記述されるもの
  • タグ内の文字はスクリプトとして実行されるが、ページに 表示はされない
2. Printing scriptlets
  • <?= ... ?> で記述されるもの
  • コードの実行結果を contextual escaping を使ってページ内に出力する
  • contextual escaping
    • 出力先が html なのか、<script> タグの中なのか、など自動的にコンテキストを解釈してエスケープしてくれる(っぽい)
  • NOTE: <?= 'Hello, world!; 'abc' ?> => Hello, world! のみ出力(first argument のみ)
3. Force-printing scriptlets
  • <?!= ... ?> で記述されるもの
  • 基本は printing scriptlets と同じだが、contextual escaping を行わない
  • セキュリティの観点からも、基本は printing scriptlets を利用した方がいい
    • ユーザーの入力した文字列を出力するのに force-printing は危険

Apps Script code in scriptlets

scriptlets で Apps Script を実行することができる。
以下、その3つのパターン(3だけコードサンプルあり)

注) いずれの方法もページをロードした時に1度だけしか実行できない、というのは上述した通り

  1. scriptlets 内でApps Scriptの関数を呼び出す
  2. scriptlets 内で直接 Apps Script の API を使う (SpreadsheetApp など)
  3. Pushing variables to templates

HtmlTemplate オブジェクト XXX に対して XXX.[変数名] で変数に値を渡すことができる。

function doGet() {
  var t = HtmlService.createTemplateFromFile('index');
  t.data = SpreadsheetApp
      .openById('1234567890abcdefghijklmnopqrstuvwxyz')
      .getActiveSheet()
      .getDataRange()
      .getValues();
  return t.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
<table>
  <? for (var i = 0; i < data.length; i++) { ?>
    <tr>
      <? for (var j = 0; j < data[i].length; j++) { ?>
        <td><?= data[i][j] ?></td>
      <? } ?>
    </tr>
  <? } ?>
</table>

TODO

公式リファレンスとしては残り2ページ。

Restrictions

Best Practices

あとは、、、

  • 外部のJavascriptライブラリがどこまで使えるか
    • だいたい使える、とのことだけど jQuery 以外は期待しない方が...?
  • HtmlService で Polymer 使っている例があってこれは面白そう。
    (だが、やり方がやや強引ぽい)