GASのHTMLテンプレートに
値を埋め込む方法|スクリプトレット
GASの画面に「ログイン中のユーザー名」や「スプレッドシートの一覧」を表示したいとき、createTemplateFromFile()(HTMLをテンプレートとして読み込むメソッド)とスクリプトレットを使えば、サーバー側の値をそのままHTMLに差し込めます。基本から実務での注意点まで、動くコードで解説します。
Table of Contents
HTMLテンプレートとは何か
結論から言うと、HTMLテンプレートは「HTMLの中にGASのコードを書き、表示する直前にサーバー側で実行して値を埋め込む仕組み」です。 静的なHTMLをそのまま返すだけのcreateHtmlOutputFromFile()との違いは、次の1点に集約されます。
createHtmlOutputFromFile()
HTMLファイルの中身をそのまま返す。<? ?> は処理されず、書いた文字がそのまま出力される。静的な画面向け。
createTemplateFromFile()
テンプレートとして読み込む。evaluate() を呼んだ時点で <? ?> の中のGASコードが実行され、結果が埋め込まれる。
重要なのは、スクリプトレット(<? ?>で囲んだGASコード)が動くのはサーバー側で1回だけという点です。 ブラウザに届いた後は、ただのHTMLになります。画面を開いたあとに値を更新したい場合は、後述のgoogle.script.runを使います。
基本の書き方:createTemplateFromFileとevaluate
手順は3ステップです。テンプレートを読み込み、渡したい値をプロパティとして代入し、evaluate()でHTMLに変換します。代入は必ずevaluate()を呼ぶ前に行ってください。
// コード.gs
function doGet() {
const template = HtmlService.createTemplateFromFile("index");
// テンプレートに渡す値をプロパティとして代入する
template.userName = Session.getActiveUser().getEmail();
template.today = Utilities.formatDate(
new Date(),
"Asia/Tokyo",
"yyyy/MM/dd"
);
// evaluate() でスクリプトレットを実行し、HTMLを生成する
return template
.evaluate()
.setTitle("業務ポータル")
.addMetaTag("viewport", "width=device-width, initial-scale=1");
}HTML側では、代入したプロパティ名をそのまま変数として参照できます。
<!-- index.html -->
<!DOCTYPE html>
<html>
<body>
<h1>おはようございます</h1>
<p><?= userName ?> さん</p>
<p>本日は <?= today ?> です</p>
</body>
</html>HTMLファイルは、スクリプトエディタの「ファイル追加 > HTML」で作成します。 ファイル名に拡張子.htmlは自動で付くため、コード側では"index"のように拡張子なしで指定します。
3種類のスクリプトレットの使い分け
スクリプトレットには3つの書き方があります。使い分けを間違えると、意図しない表示崩れやセキュリティ上の問題につながるため、最初に押さえておきましょう。
<!-- 1. 標準スクリプトレット <? ?> : 出力しない。制御構文に使う -->
<? const items = ["申請", "承認", "完了"]; ?>
<ul>
<? items.forEach(function (item) { ?>
<li><?= item ?></li>
<? }); ?>
</ul>
<!-- 2. 出力スクリプトレット <?= ?> : エスケープして出力(基本はこれ) -->
<p><?= userInput ?></p>
<!-- userInput が "<b>太字</b>" なら、太字にはならず文字列として表示される -->
<!-- 3. 強制出力スクリプトレット <?!= ?> : エスケープせずそのまま出力 -->
<?!= include("style") ?>
<!-- 自分が管理しているHTML断片だけに使う -->原則:外部由来のデータは必ず <?= ?>
スプレッドシートの値、フォームの入力、URLパラメータなど、自分以外が書き換えられるデータは<?= ?>で出力します。HTMLタグとして解釈されない形にエスケープされるため、悪意あるスクリプトの埋め込み(XSS)を防げます。<?!= ?>は、自分で用意したHTML断片を差し込むときだけに限定してください。
include関数でCSS・JSを分割する
GASのHTMLファイルは、通常のWebサイトのように<link rel="stylesheet">で別ファイルのCSSを読み込めません。そこで、ファイルの中身を文字列として取り出すヘルパー関数を用意し、テンプレートから呼び出すのが定番です。
// コード.gs
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}あとはテンプレート側で<?!= include("style") ?>と書くだけです。ここはエスケープするとタグが壊れるため、強制出力スクリプトレットを使います。
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<?!= include("style") ?>
</head>
<body>
<h1>申請一覧</h1>
<div id="app"></div>
<?!= include("script") ?>
</body>
</html>
<!-- style.html -->
<style>
body { font-family: sans-serif; margin: 24px; }
table { border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 6px 12px; }
</style>
<!-- script.html -->
<script>
document.addEventListener("DOMContentLoaded", function () {
console.log("画面を初期化しました");
});
</script>1つのHTMLファイルに数百行のCSSとJavaScriptを詰め込むと保守が一気に苦しくなります。画面が複雑になる前に分割しておくと、後からの修正が楽になります。
スプレッドシートのデータを表として描画する
実務で一番多いのが「シートの内容を一覧表示する画面」です。サーバー側でデータをオブジェクト配列に整え、テンプレート側はループで並べるだけにすると読みやすくなります。
// コード.gs
function doGet() {
const template = HtmlService.createTemplateFromFile("list");
template.rows = getRequestRows();
return template.evaluate().setTitle("申請一覧");
}
// スプレッドシートの行を { 申請者: "...", 件名: "...", 状態: "..." } の配列にする
function getRequestRows() {
const sheet = SpreadsheetApp.getActive().getSheetByName("申請");
const values = sheet.getDataRange().getDisplayValues();
const header = values[0];
return values.slice(1).map(function (row) {
const record = {};
header.forEach(function (key, i) {
record[key] = row[i];
});
return record;
});
}日付や数値の書式をシートの表示どおりにしたい場合は、getValues()ではなくgetDisplayValues()(画面に見えている文字列をそのまま取得するメソッド)を使うと、フォーマット処理を書かずに済みます。
<!-- list.html -->
<table>
<tr>
<th>申請者</th>
<th>件名</th>
<th>状態</th>
</tr>
<? rows.forEach(function (row) { ?>
<tr>
<td><?= row["申請者"] ?></td>
<td><?= row["件名"] ?></td>
<td><?= row["状態"] ?></td>
</tr>
<? }); ?>
</table>
<? if (rows.length === 0) { ?>
<p>表示できる申請はありません。</p>
<? } ?>テンプレート側に書くのは、ループと条件分岐までに留めるのがコツです。集計や絞り込みは.gs側の関数に寄せると、エラーが起きたときに原因を追いやすくなります。
JSONを安全にブラウザ側へ渡す
グラフ描画などで、まとまったデータをブラウザ側のJavaScriptに渡したいことがあります。オブジェクトはそのまま渡せないため、サーバー側でJSON.stringify()して文字列にします。
// コード.gs
function doGet() {
const template = HtmlService.createTemplateFromFile("chart");
// オブジェクトはJSON文字列にしてから渡す
template.chartDataJson = JSON.stringify(getMonthlyTotals());
return template.evaluate();
}ここで注意したいのが、<script>var data = <?= json ?>;</script>のようにJavaScriptコードへ直接埋め込むやり方です。エスケープによってダブルクォートが実体参照に変換され、構文エラーになります。 属性値として渡し、ブラウザ側でJSON.parse()する方法が安全かつ確実です。
<!-- chart.html -->
<script>
// <?= ?> はHTMLエスケープを行うため、JSONを直接埋めると
// " が " になり JSON.parse が失敗する。
// data属性経由で受け渡すと安全かつ確実。
</script>
<div id="chart-data" data-json="<?= chartDataJson ?>"></div>
<script>
var el = document.getElementById("chart-data");
var chartData = JSON.parse(el.dataset.json);
console.log(chartData.length + " 件のデータを受け取りました");
</script>テンプレートとgoogle.script.runの使い分け
テンプレートでの埋め込みには弱点があります。evaluate()が終わるまで画面は1文字も表示されないため、重い処理を書くと真っ白な待ち時間が長くなります。
テンプレート埋め込みが向くもの
ユーザー名、今日の日付、選択肢のマスタなど、初期表示に必ず必要で件数が少ないデータ。
google.script.runが向くもの
数百件以上の一覧、検索結果、ボタン操作で変わるデータ。先に画面を出してから読み込める。
<!-- 件数が多い・操作のたびに変わるデータは、テンプレートではなく非同期取得 -->
<script>
function reload() {
google.script.run
.withSuccessHandler(function (rows) {
render(rows);
})
.withFailureHandler(function (err) {
alert("読み込みに失敗しました: " + err.message);
})
.getRequestRows();
}
document.addEventListener("DOMContentLoaded", reload);
</script>そのほかの注意点
Webアプリとして公開する場合、doGet()が返す画面はiframeの中で表示されます。親ページのURLを操作したりCookieを共有したりはできません。 また、テンプレートの実行にもGASの実行時間の上限が適用されます。表示のたびに重い集計を走らせる構成は避け、集計結果を別シートやキャッシュに置いておくと安定します。
まとめ
GASのHTMLテンプレートは、createTemplateFromFile()でファイルを読み込み、プロパティで値を渡し、evaluate()で生成する、という3ステップで扱えます。 出力は原則<?= ?>、include用だけ<?!= ?>と決めておけば、エスケープ漏れの事故を防げます。 ロジックは.gs側、テンプレートは表示だけ、という役割分担にしておくと、画面が増えても保守しやすい構成になります。
よくある質問
createHtmlOutputFromFileはHTMLファイルをそのまま返すだけで、ファイル内の<? ?>は処理されません。createTemplateFromFileはテンプレートとして読み込み、evaluate()を呼んだ時点でサーバー側のコードを実行し、値を埋め込んだHTMLを生成します。サーバーの値を差し込みたいときはcreateTemplateFromFileを使います。
<?= ?>は値をHTMLエスケープして出力します。ユーザー入力やスプレッドシートの値など、外部由来のデータは必ずこちらを使います。<?!= ?>はエスケープせずそのまま出力するため、includeで読み込んだ自作のHTML断片のように、自分が内容を完全に管理しているものだけに使います。
createTemplateFromFile()が返すテンプレートオブジェクトにプロパティとして代入します。たとえばtemplate.userNameに文字列を代入すると、HTML側で<?= userName ?>として参照できます。代入はevaluate()を呼ぶ前に行う必要があります。
分けられます。GASのHTMLファイルは<link>や<script src>で相互参照できないため、include(filename)というヘルパー関数を作り、HTML側で<?!= include(ファイル名) ?>と書いて中身を差し込むのが定番の手法です。
画面表示時に必ず必要で、量が少ないデータはテンプレートで埋め込むと画面が速く表示されます。件数が多いデータや、ボタン操作のたびに変わるデータはgoogle.script.runで後から非同期に取得するほうが適しています。テンプレートの処理が重いと画面が表示されるまで待たされる点に注意してください。
evaluate()の実行時に例外が発生し、実行ログにエラーが表示されます。スクリプトレット内は通常のGASコードなので、変数のスペルミスや未定義プロパティの参照が主な原因です。ロジックはテンプレートに書かず、.gsファイル側の関数にまとめて呼び出すだけにすると、原因を切り分けやすくなります。
GASでの画面付き業務システムを
相談する。
申請フォーム・一覧画面・承認フローなど、スプレッドシートを裏側に使った社内Webシステムの開発をご相談いただけます。