Google Apps Script で Web アプリを作る
今回は Google Apps Script で Web アプリを作ります。
Web アプリの基本
Web アプリは、コンテンツが固定されている単純な Web ページに対して、 アプリのようにコンテンツが変化したり、投稿・編集などができたりするもののことを言います。 Web アプリでは次図のように、Web ページを見たいというリクエストが送られてきたときに Web ページのデータを送り返します。 これを Google Apps Script で行うことができる仕組みが用意されています。

Google Apps Script による Web アプリの流れ
GAS で作ったプログラムは実行ボタンかトリガーによって実行することが多いですが、 Web アプリを作る場合には実行の仕方が変わります。
- ブラウザが Google のサーバにリクエストを送ると、Google Apps Scripts で作った
doGet()関数が実行される doGet()関数ではまず Web ページを作るのに必要なデータを Spreadsheet から読み込むなどの準備をするdoGet()関数の最後で HTML 文書をreturnすると、それがブラウザへのレスポンスとして送られる
Web アプリは様々な言語・環境で作ることができますが、Google Apps Script はその中でも特に初心者におすすめです。 完全無料であることに加え、データベースの代わりに Spreadsheet を使うのでデータの様子が見えて理解しやすいです。 Spreadsheet は共同編集できるので、サークルのページに必要なデータ(例:試合の勝敗記録)を分担して更新するようなことが専門知識不要でできます。
Google Apps Script は無料ですが、いくらでも実行できるというわけではありませんし、処理は速くないので、 大量のアクセスをさばかなければならないような用途には向きません。 卒論の実験やサークルのページ程度であれば問題になることはあまりないと思いますが、 必要に応じてもっと高度な技術にステップアップしましょう。 たとえ使う技術が変わっても今回の経験は必ず役立つはずです。
まずは体験してみよう
まずは具体例として、Spreadsheet に書いた情報をもとにリンク集ページを表示する Web アプリを示します。
こちらのページ が Google Apps Script で作成したものです。 元となるデータが記入されている Spreadsheet を授業中だけ編集可能にしますので共同で編集作業をしてみましょう。 (エラー処理をちゃんとしていないので下手に編集すると動かなくなってしまうかも)
作り方を見てみよう
続いて作り方を見ていきましょう。まずはスプレッドシートを作成してください。 先ほどの例で使用したデータと同じ構造でデータをいくらか入力して用意しておきましょう。
次にスクリプトエディタを開いてください。 作成する関数 doGet() は短くてシンプルです。
// (1) リクエストを受け取ると doGet が実行されるfunction doGet() {// (2) Spreadsheet からデータを読み込むlet sheet = SpreadsheetApp.getActive().getActiveSheet();let values = sheet.getDataRange().getValues();// (3) テンプレートを使ってHTML文書を作って returnlet template = HtmlService.createTemplateFromFile("list");template.links = values; // こうしておくとテンプレートの方で links という変数に値が入った状態で使えるreturn template.evaluate();}
データの読み込みについては、前回までにも出てきたのと同じやり方です。
次に読み込んだデータを使って HTML 文書を作って return します。 return HtmlService.createHtmlOutput('<b>Hello, world!</b>'); のように書くこともできますが、 この方法で長い HTML 文書を作成するのは大変なので、テンプレートという仕組みを使って作成することが多いです。 テンプレートというのは、あらかじめ HTML 文書のひな形を別のファイルとして用意しておいて、変化しうるデータ部分を後から穴埋めできるようにしておくことです。 テンプレートはスクリプトエディタの「ファイル」の横にある+ボタンから作成できます。

ここでは list.html という名前で次のようなテンプレートのファイルを作成しました。テンプレートはだいたい HTML です。
<!DOCTYPE html><html><head><base target="_top"></head><body><h1>みんなで作るリンク集</h1><a href="https://docs.google.com/spreadsheets/d/14z_w8R4U2kP2t9G9oWQAR3-SpMAVrnj-OCOCJM_k7Yc/edit#gid=0" target="_blank">編集用スプレッドシート(授業のときだけ編集可にします)</a><table border="1"><tr><th>タイトル</th><th>推薦人</th><th>推薦メッセージ</th></tr><? for(let i = 1; i < links.length; i++){ ?><? let link = links[i]; ?><tr><td><a href="<?= link[0] ?>"><?= link[1] ?></a></td><td><?= link[2] ?></td><td><?= link[3] ?></td></tr><? } ?></table></body></html>
<? ... ?> は通常の HTML には無い書き方で、テンプレートを穴埋めするためのプログラムを書くことができます。 通常の HTML にもプログラムを書く場合がありますので、それと区別がつくように特別な書き方が用意されているのだと思ってください。 evaluate() を実行したときにテンプレート内のプログラムが実行されて、その結果がテンプレートに埋め込まれ、埋め込み後のデータがブラウザに送信されます。 埋め込みを行うためのプログラムはブラウザに送信されるデータには含まれません。
補足すると:
<?= ... ?>(イコールがある場合)実行結果がそこに埋め込まれます。<? ... ?>(イコールがない場合)埋め込まれません。繰り返しのための for 文を書くためなどに使います。
doGet() で読み込んだデータをテンプレートの方で使えるように受け渡しする方法は上記のプログラムに倣ってください。 links という変数名で使えるように代入しています。
テンプレート中に書くプログラムは HTML と混ざっているせいで読みづらくなってしまいがちですし、書くのもより一層難しく感じると思います。 コツはまずできあがりのHTMLを自分で書いてみて、穴埋めしたい部分を <?= ... ?> にすることです。 今回の場合は完成した Web アプリがありますので、ブラウザに届いた HTML (=埋め込み後) と埋め込み前のテンプレートを見比べてみると理解しやすくなると思います。
Web アプリをテストする
次にプログラムが正しくできたかどうか Web アプリの動作確認をします。 右上にある青い「デプロイ」ボタンから「デプロイをテスト」をクリックします。 URLが発行されるのでクリックして開いてください。 最初に示した例と同じようなページが開くはずです。 実際の開発工程ではこまめに動作確認を行った方がいいでしょう。
Web アプリを公開する
プログラムが完成して動作確認もできたら Web アプリとして公開します。注:テストURLは開発に使っている Google アカウントでしか開けません
右上にある青い「デプロイ」ボタンから「新しいデプロイ」の画面を開きます。 種類の選択で「ウェブアプリ」を選択すると下の図のような画面になります。

- 「説明文」は空のままでもOKです。
- 「次のユーザーとして実行」は「自分」に設定します。スプレッドシートにアクセスするのに作成者の権限が必要ですので、作成者の権限でプログラムも動く必要があるからです。
- 「アクセスできるユーザ」は、誰でも見れるようにしたいときは「全員」に設定します。開発中は「自分のみ」にしておくのも手です。
いつも通り動作の承認を求められた後に Web アプリとして公開され、アクセスするための URL が用意されます(下図)。クリックしてアクセスしてみてください。

Web アプリを更新する
プログラムを書き換えるだけでは公開されたアプリは変更されないので注意してください。 プログラムを書き換えるごとにこまめに動作確認をするときにはテスト用のURLを使うのが便利です。 公開アプリを更新するには「デプロイを管理」から「編集(鉛筆アイコン)」する必要があります。 編集画面ではバージョンのプルダウンリストで「新バージョン」を選択して右下の「デプロイ」ボタンを押すと更新が完了します。

デプロイ作業について以下にまとめます。
開発中
「デプロイをテスト」で作れるテスト用のURLを使用する
最初に公開するとき
「新しいデプロイ」を行う(公開URLができる)
公開バージョンを更新するとき
「デプロイを管理」から「編集」(公開URLは変わらない)
現在の公開バージョンを残したまま、別の公開バージョンを作る
「新しいデプロイ」を行う(公開URLがもう1つできて、使うURLによって新旧のプログラムがそれぞれ動く)
練習
説明した手順通りに作業して Web ページの動作確認と公開をしてみましょう。 また、テンプレートを少し書き換えてテスト用URLと公開URLの挙動の違いを確認してみましょう。
他のWebページに埋め込む
iframe を使うと、他のWebページの一部として別のページを埋め込むことができます。
このテキストにも先ほどのリンク集を埋め込んでみました。埋め込み部分は少し遅れて表示されると思います。 (埋め込まれている部分がわかるように目印をつけていますが、目印が無ければ普通のページの一部に見えます)
-------- 埋め込みここから --------
-------- ここまで --------
セキュリティの都合上、他のページに埋め込めるようにするには埋め込んでもいいよという許可をする必要があります。 埋め込んで使いたい場合は doGet() の最後の部分を次のように書き換えてください。
// これをreturn template.evaluate();// こう書き換えるreturn template.evaluate().setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
あとは次のように埋め込む方のページで埋め込みたいところにコンテンツの URL を指定するだけで完了です。 このタグをどこにどう書けばいいかわからないと思う方は、このページに実際に埋め込んでいますので、その部分の HTML を探して真似してみてください。
<iframe src="https://script.google.com/macros/s/AKfycbw6M4E6cq2BRCT19zhk1LwOJcdY8cPJhFOA0Ew2M6Xjt738VJ0/exec"></iframe>
補足:埋め込むと便利な状況
GAS で作ったページを埋め込むことができてうれしい状況にはどのようなものがあるでしょうか。 たとえば神戸大学の情報基盤センターが提供している 個人Webページサービス では機能制限が強く、 訪問者がデータを送信できる掲示板のような機能を持ったページを作ることができません。 無料でホームページが作成できることをうたうサービスは多いですが、同様に機能制限が強いものが多いです。 そのようなサービス上に所属サークルのページが作られていて、不便だけど全体を引っ越すのは大変という場合などもあるでしょう。
そのような状況でたとえば、みんなで編集したいところだけ GAS で作成して埋め込むと更新作業が楽になるなどの活用方法が思い浮かびます。
Web ページからデータを送信する
Web アンケート や SNS のように、Web ページからサーバー側にデータを送信(投稿)するようなページを作ることもできます。ここまでできるとかなり使い勝手があります。
- Google Forms (Google アプリのアンケート機能) の機能では足りないちょっと変わった調査をしたいとき
- 例:一問ごとに制限時間をつけたい
- 例:p5.js で作ったゲームのスコアを記録してランキング機能を作りたいとき
- 実話:非ネイティブの英語発話音声を聞いて、5秒以内に発音の流ちょうさを5段階で答えるような実験をしたいとき
- ちょっと変わった SNS のようなものを作って試してみたいとき
- 例:しりとり専用SNS
- 実話:穴埋め式の質問箱アプリを提案する卒論を書きたいとき
ここでは、4択問題の答えを選択するたびに「ファイナルアンサー?」と聞いてくるウザいクイズページを作ってみます。まずは 実物 を触ってみてください。ウザいですね。問題と回答が記録される Spreadsheet は こちら。 質問と回答を別のシートに記録するようにしています。
作り方
プログラムは doGet() に加えてもうひとつ、送られてきたデータを処理する関数を作ります。 こちらは自由に名前を付けていいので checkAnswers(answers) という名前にしました。
// doGet はあまり変わりませんfunction doGet(){let sheet = SpreadsheetApp.getActive().getSheetByName("質問"); // 「質問」シートから読み込みlet template = HtmlService.createTemplateFromFile("show");template.qs = sheet.getDataRange().getValues();return template.evaluate();}// 送信された回答を記録して、正解数を返す関数function checkAnswers(answers){let sheet = SpreadsheetApp.getActive().getSheetByName("質問"); // 「質問」シートから読み込みlet values = sheet.getDataRange().getValues();let row = [new Date()];let n = 0; // 正解数をカウントするための変数for(let i = 1; i < values.length; i++){let input = answers["Q" + i]; // 送信された回答let answer = values[i][5]; // 「質問」シートにある正解if(input == answer) n++;row.push(input);}SpreadsheetApp.getActive().getSheetByName("回答記録").appendRow(row);return n;}
次にテンプレート (show.html) です。
<!DOCTYPE html><html><head><base target="_top"></head><body><h1>クイズ</h1><form id="answers"><? for(let i = 1; i < qs.length; i++){ ?><? let q = qs[i]; ?><div><h2><?= q[0] ?></h2><? for(let j = 1; j <= 4; j++){ ?><input type="radio" name="Q<?= i ?>" value="<?= j ?>" onclick="ask(event)"><?= q[j] ?><br/><? } ?></div><? } ?></form><button style="margin-top: 32px" onclick="sendAnswers()">回答を送信</button><span id="status"></span></body><script><!-- ブラウザで動くプログラム -->function ask(e){if(!confirm("ファイナルアンサー?")){ e.preventDefault(); }}function sendAnswers(){let answers = document.getElementById("answers");google.script.run.withSuccessHandler(success).checkAnswers(answers);}function success(n){document.getElementById("status").textContent = n + "問正解でした!";}</script></html>
show.html には二種類のプログラムが含まれています。
- 9-17行目:
<? ... ?>に囲まれた、テンプレートを穴埋めするプログラム。サーバから送信される前に Google のサーバ上で実行されます。 - 25-36行目:ブラウザ上で実行されるプログラム。ユーザの行った操作に対応する処理を記述しています。
前者ではスプレッドシートに用意されているクイズデータの問題数分だけ繰り返しを行って HTML を作成しています。 選択肢の数分だけ繰り返しを行っている部分もあります。 最初の例よりも複雑になっていますが、やはり完成 Web アプリがありますのでその HTML (=埋め込み後) と埋め込み前を見比べてみると理解しやすくなると思います。
ブラウザ側で動くプログラムではいろいろなことをしています。3つある関数がそれぞれ指定のタイミングで動作するよう指定されていることに注意してください。
function ask(e):選択肢をクリックしたときに動作するように onclick イベントに登録(14行目)function sendAnswers():回答を送信ボタンをクリックしたときに動作するように onclick イベントに登録(20行目)function success(e):回答を送信して、それに対する返信を受け取ったときに動作するように withSuccessHandler で指定(31行目)
選択肢をクリックしたときには confirm("ファイナルアンサー?") でダイアログを表示しています。confirm はユーザーが選んだボタンによって true か false を返しますので if 文に入れることで条件分岐できます。 ユーザーが「キャンセル」を選んだときには e.preventDefault() でデフォルトの動作(=ラジオボタンをクリックしたら選択される)をキャンセルするようにしています。
google.script.run を使ったデータの送受信
データの送受信を行うプログラムは一般に以下のように2つの処理を組み合わせて書く場合が多いです。
- 送信側:「○○なデータを送る」プログラム
- 受信側:「○○なデータが来たとき~する」プログラム
たいていの場合、送信→受信、返信→返信を受信というように往復で通信しますので、4つの処理を組み合わせることになって書くのが大変です。
GAS ではこのような処理を少し簡潔に書ける仕組み google.script.run が用意されています。 データの送受信ではなく、関数呼び出しの引数としてデータを渡しているように書けるので、プログラムの見た目が簡潔になります。
google.script.run.withSuccessHandler(success).checkAnswers(answers); で実際に裏で何が起きるかをかみ砕くと
- (ブラウザにて)変数
answersの内容をサーバに送信する - (サーバにて)1. の送信内容を引数として
checkAnswers(answers)が実行される - (サーバにて)2. の戻り値
nの内容をブラウザに返信する - (ブラウザにて)3. の送信内容を引数として
success(n)が実行される
裏で起きていることはデータの送受信なのですが、ただの関数呼び出しのように書けるということです。
演習
架空のサークルの試合結果ページを作ってみましょう。 対戦相手、勝敗、スコア、試合日時などをスプレッドシートに記録して、それを表示するページを作ります。 勝敗によって表示(色など)が変わるようにしてみてください。
物足りない人向け:投稿内容をスプレッドシートに保存できて(後半のクイズのように)、スプレッドシートの内容をウェブページにできる(前半のリンク集のように)ということは、ウェブ掲示板も作れるということですね。 がんばれば「いいねボタン」なんかも作れるでしょう。普通のSNSにはないような機能を考えるのもワクワクしませんか?ぜひ挑戦してみましょう。