GAS(HtmlService)で作成するウェブアプリで、複数のページ間を遷移できるようにするには少し工夫が必要です。
この記事では、リンクをクリックする度に、GASでhtmlを作成して応答する・・・いわゆるMPA(Multi Page Application)的なページ遷移を実現する方法を紹介します。
なお、より高速なSPA(Single Page Application)的なページ遷移をする方法は別記事で紹介しています。
基本のコード
まずは、ベースとなるコードを紹介します。
function doGet(e) {
// URLのexec/(またはdev/)以降を取得
const page = e.pathInfo ? e.pathInfo : "index"
// 該当するテンプレートを取得する
const template = (() => {
try {
return HtmlService.createTemplateFromFile(page);
} catch(e) {
return HtmlService.createTemplateFromFile("error");
}
})();
// htmlを返す
template.url = ScriptApp.getService().getUrl(); // テンプレートにアプリのURLを渡す
return template.evaluate() // テンプレートを評価してhtmlを返す
.setTitle("テストサイト") // タイトルをセット
.addMetaTag('viewport', 'width=device-width,initial-scale=1'); // viewportを設定
}
doGet関数の引数(e)からは、URL末尾のパス情報を取得することができます。
通常、GASのウェブアプリのURLはhttps://script.google.com/macros/s/…省略…/execのように、/execまたは/devで終わる形式になりますが、…/exec/productsのようにしてアクセスしてきた場合、e.pathInfoでexec/より後のパス(例: products)を取得することができます。
上記コードは、取得したpathInfoと名前が一致するhtmlファイルを返すコードになります。
なお、15行目では、ウェブアプリのURLを取得してhtmlテンプレートに渡しています。
(html内のaタグのhref属性に利用します)
続いて、htmlファイルを見てみます。
* このサンプルでは、index.html、products.html、contact.html、error.htmlの4ファイルを用意しましたが、<main>タグの内容以外は全て同じなので、index.htmlのコードのみ紹介します。
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<!-- ナビゲーション -->
<nav>
<a class="link" href="<?= url ?>">ホーム</a>
<a class="link" href="<?= url ?>/products">製品紹介</a>
<a class="link" href="<?= url ?>/contact">お問合せ</a>
</nav>
<!-- コンテンツ -->
<main>
<h1>ホーム</h1>
<p>現在 index.html を表示しています。</p>
</main>
</body>
</html>
9-11行目で、ウェブアプリのURLを差し込んでいます。それ以外は特筆する点はありません。
これで、複数のページ遷移するウェブアプリが作成できました。
全ページ共通の部品を切り出してまとめる
ここから先は補足になります。
このサンプルアプリには、index.html、products.html、contact.html、error.htmlの4ページがありますが、ページ上部のナビゲーション(リンク)は、どのページも同じ内容です。
更新があった際の手間削減・ミス防止などのため、この部分は別ファイル(navigation.html)に切り出して、使いまわせるようにしました。
<nav>
<a class="link" href="<?= url ?>">ホーム</a>
<a class="link" href="<?= url ?>/products">製品紹介</a>
<a class="link" href="<?= url ?>/contact">お問合せ</a>
</nav>
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<!-- ナビゲーション -->
<?
const navigationTemplate = HtmlService.createTemplateFromFile("navigation");
navigationTemplate.url = url;
?>
<?!= navigationTemplate.evaluate().getContent(); ?>
<!-- コンテンツ -->
<main>
<h1>ホーム</h1>
<p>現在 index.html を表示しています。</p>
</main>
</body>
</html>
8-11行目で、navigation.htmlからテンプレートを作成し、コード.gsで取得したウェブアプリのurlを受け渡しています。
12行目で、navigation.htmlを評価して差し込んでいます。
アプリのURLをコード.gs → template.html → navigation.htmlと受け渡しているため、少しややこしく思われるかもしれません。
グローバル変数を使ってもよければ、コード.gsのグローバル変数としてconst url = ScriptApp.getService().getUrl();
のようにurlを取得しておくのもアリだと思います。そうすれば、受け渡しをせずとも、navigation.htmlから変数urlを参照できます。
雛形に各ページを埋め込んでみる
先ほどは、それぞれのページに共通部品を差し込む形にしましたが、逆の発想で、共通の雛形ページに各コンテンツを差し込む形にしてみました。
まずは、雛形ページとなるtemplate.htmlを作成しました。
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<!-- ナビゲーション -->
<nav>
<a class="link" href="<?= url ?>">ホーム</a>
<a class="link" href="<?= url ?>/products">製品紹介</a>
<a class="link" href="<?= url ?>/contact">お問合せ</a>
</nav>
<!-- コンテンツ -->
<?!= HtmlService.createHtmlOutputFromFile(page).getContent(); ?>
</body>
</html>
15行目に、指定されたページのコンテンツを差し込みます。
例えば、index.htmlはこんな感じです。
<main>
<h1>ホーム</h1>
<p>現在 index.html を表示しています。</p>
</main>
doGet関数も変更します。
function doGet(e) {
// htmlテンプレートを取得する
const template = HtmlService.createTemplateFromFile("template");
// htmlを取得
const html = (() => {
template.page = e.pathInfo ? e.pathInfo : "index"
template.url = ScriptApp.getService().getUrl();
try {
return template.evaluate();
} catch(e) {
template.page = "error";
return template.evaluate();
}
})();
// htmlを返す
return html
.setTitle("テストサイト")
.addMetaTag('viewport', 'width=device-width,initial-scale=1');
}
先ほどまでは、pathInfoと名前が一致するページを取得していましたが、今回は、pathInfoの値に関わらず、template.htmlを取得します。
コメント