メディア掲載: レバテックフリーランス様のサイトで当ブログが紹介されました

GAS(HtmlService)で複数のページを遷移する

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.htmlproducts.htmlcontact.htmlerror.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.htmlproducts.htmlcontact.htmlerror.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をコード.gstemplate.htmlnavigation.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を取得します。

コメント

タイトルとURLをコピーしました