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

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

GAS(HtmlService)でウェブアプリを作成する時、ページ遷移をどうするかで悩むことがあるかと思います。

Vue.jsやReactのRouter機能を使う方法もあるようですが、GASのお手軽感が損なわれてしまいます。

・・・ということで、上記のようなライブラリを使わずに、ブラウザの 戻る / 進む にも対応したSPAのページ遷移を実装してみました。

完成イメージ

URLフラグメント(#hoge)を利用した方法を例にしていますが、
記事の最後に、URLパラメーター(?page=hoge)を利用した方法も掲載しています。

スクリプト

function doGet() {
  return HtmlService.createTemplateFromFile("index").evaluate();
}
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <style>
      .page:not(.isActive) {
        display: none;
      }
    </style>
  </head>
  <body>
    <!-- ナビゲーション -->
    <div>
      <a class="link" href="home">ホーム</a>
      <a class="link" href="products">製品紹介</a>
      <a class="link" href="contact">お問合せ</a>
    </div>

    <!-- ここにコンテンツを表示 -->
    <div class="page" id="home">
      <h1>ホーム</h1>
      <p>homeを表示しています</p>
    </div>
    <div class="page" id="products">
      <h1>製品紹介</h1>
      <p>productsを表示しています</p>
    </div>
    <div class="page" id="contact">
      <h1>お問合せ</h1>
      <p>contactを表示しています</p>
    </div>
    <div class="page" id="error">
      <h1>NOT FOUND</h1>
      <p>ページが見つかりませんでした</p>
    </div>

    <!-- JavaScript -->
    <script>
      // 各ページ(div要素)を配列に格納
      const pages = Array.from(document.getElementsByClassName("page"));
        
      // ページ切り替え処理
      function switchPage(pageName) {
        // ページ未指定の場合はhomeを,ページが存在しない場合はerrorを表示
        if (!pageName) {
          pageName = "home";
        } else if (!pages.some(page => page.id === pageName)) {
          pageName = "error";
        }
        // 表示対象ページのみisActiveクラスを付与
        for (const page of pages) {
          page.id === pageName 
            ? page.classList.add("isActive")
            : page.classList.remove("isActive")
        }
      }

      // ウェブアプリアクセス時の処理
      google.script.url.getLocation(location => {
        switchPage(location.hash);
      });
      
      // ブラウザバック・フォワード時の処理
      google.script.history.setChangeHandler(e => {
        switchPage(e.location.hash);
      });      

      // aタグクリック時の処理(ページ切り替え・履歴追加)
      Array.from(document.getElementsByClassName("link")).forEach(el => {
        el.addEventListener("click", e => {
          e.preventDefault();                                // aタグのページ遷移動作を無効化
          const pageName = e.target.getAttribute("href");    // aタグのhref属性を取得
          switchPage(pageName);                              // ページを切り替える
          google.script.history.push(null, null, pageName);  // URLフラグメント(#ページ名)をブラウザの履歴にプッシュ
        });
      });
    </script>
  </body>
</html>

aタグをクリックした時、そのaタグのhref属性を取得し、その値と一致するidの要素のみにisActiveクラスを付与します。
isActiveクラスを持たない要素は、cssのdisplay: none;によって非表示になります。

その次に、google.script.history.pushを使い、ブラウザの履歴に記録させます。
第3引数に値を設定すると、URLフラグメントとして扱われますので、https://script.google.com/macros/…省略…/dev#homeのようなURLになります。

aタグのクリック時はhref属性からリンク先を取得しましたが、
ウェブアプリへのアクセス時は、google.script.url.getLocationでURLフラグメント(URLの#以降の文字列)を取得、
ブラウザバック・フォワード時については、google.script.history.setChangeHandlerでコールバック関数を設定し、その引数となるイベントオブジェクトからURLフラグメント(URLの#以降の文字列)を取得しています。

通常のウェブアプリであれば、もう少しシンプルな実装(参考ページ)も可能なのですが、HtmlServiceで作成したウェブアプリは、iframeの中で実行されるという特徴があるため、少し複雑になっています。

HtmlServiceにおけるブラウザの履歴管理については、こちらが参考になります。

コードの分割

先ほどのようにindex.htmlにすべて記載すると、読みにくくなってしまいますので、コードを分割するのが良いと思います。

HtmlServiceでのコード分割は非常に簡単です。

  1. htmlファイルを作成し、
  2. コードをそのファイルにカット&ペーストし、
  3. HtmlService.createHtmlOutputFromFile(“ファイル名“).getContent(); で切り出したコードを読み込む

という手順でOKです。

今回のプロジェクトでコード分割した場合のファイル構成は、このような感じです。

ファイル説明
コード.gsサーバー側スクリプト
index.htmlメインのhtmlファイル
navigation.html各ページへのリンク
home.html
products.html
contact.html
error.html
index.htmlに差し込むコンテンツ
script.htmlJavaScriptを切り出したファイル

各ファイルの中身は以下のとおりです。
products.html, contact.html, error.htmlは、home.htmlと同様なので省略しています。

function doGet() {
  return HtmlService.createTemplateFromFile("index").evaluate();
}
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <?!= HtmlService.createHtmlOutputFromFile("css").getContent(); ?>
  </head>
  <body>
    <!-- ナビゲーション -->
    <?!= HtmlService.createHtmlOutputFromFile("navigation").getContent(); ?>

    <!-- ここにコンテンツを表示 -->
    <?!= HtmlService.createHtmlOutputFromFile("home").getContent(); ?>
    <?!= HtmlService.createHtmlOutputFromFile("products").getContent(); ?>
    <?!= HtmlService.createHtmlOutputFromFile("contact").getContent(); ?>
    <?!= HtmlService.createHtmlOutputFromFile("error").getContent(); ?>

    <!-- JavaScript -->
    <?!= HtmlService.createHtmlOutputFromFile("script").getContent(); ?>
  </body>
</html>
<style>
  .page:not(.isActive) {
    display: none;
  }
</style>
<div>
  <a href="home">ホーム</a>
  <a href="products">製品紹介</a>
  <a href="contact">お問合せ</a>
</div>
<div class="page" id="error">
  <h1>ホーム</h1>
  <p>home.htmlを表示しています</p>
</div>
<script>
  // 各ページ(div要素)を配列に格納
  const pages = Array.from(document.getElementsByClassName("page"));
    
  // ページ切り替え処理
  function switchPage(pageName) {
    // ページ未指定の場合はhomeを,ページが存在しない場合はerrorを表示
    if (!pageName) {
      pageName = "home";
    } else if (!pages.some(page => page.id === pageName)) {
      pageName = "error";
    }
    // 表示対象ページのみisActiveクラスを付与
    for (const page of pages) {
      page.id === pageName 
        ? page.classList.add("isActive")
        : page.classList.remove("isActive")
    }
  }

  // ウェブアプリアクセス時の処理
  google.script.url.getLocation(location => {
    switchPage(location.hash);
  });
  
  // ブラウザバック・フォワード時の処理
  google.script.history.setChangeHandler(e => {
    switchPage(e.location.hash);
  });      

  // aタグクリック時の処理(ページ切り替え・履歴追加)
  Array.from(document.getElementsByClassName("link")).forEach(el => {
    el.addEventListener("click", e => {
      e.preventDefault();                                // aタグのページ遷移動作を無効化
      const pageName = e.target.getAttribute("href");    // aタグのhref属性を取得
      switchPage(pageName);                              // ページを切り替える
      google.script.history.push(null, null, pageName);  // URLフラグメント(#ページ名)をブラウザの履歴にプッシュ
    });
  });
</script>

HtmlService…getContents();の記述の繰り返しが冗長に感じられる場合は、こちらの記事を参考にされるとよいかと思います。

参考:URLパラメーターで実装する

ほとんど同じコードで、URLパラメーターを使う方法も実装できました。

先述の方法(URLフラグメント)では、https://script.google.com/macros/…省略…/dev#homeのようなURLになりましたが、こちらの方法ではhttps://script.google.com/macros/…省略…/dev?page=homeのようなURLになります。

参考として掲載させていただきます。(濃い色部分が変更箇所です)

<script>
  // 各ページ(div要素)を配列に格納
  const pages = Array.from(document.getElementsByClassName("page"));
    
  // ページ切り替え処理
  function switchPage(pageName) {
    // ページ未指定の場合はhomeを,ページが存在しない場合はerrorを表示
    if (!pageName) {
      pageName = "home";
    } else if (!pages.some(page => page.id === pageName)) {
      pageName = "error";
    }
    // 表示対象ページのみisActiveクラスを付与
    for (const page of pages) {
      page.id === pageName 
        ? page.classList.add("isActive")
        : page.classList.remove("isActive")
    }
  }

  // ウェブアプリアクセス時の処理
  google.script.url.getLocation(location => {
    switchPage(location.parameter.page);
  });
  
  // ブラウザバック・フォワード時の処理
  google.script.history.setChangeHandler(e => {
    switchPage(e.location.parameter.page);
  });      

  // aタグクリック時の処理(ページ切り替え・履歴追加)
  Array.from(document.getElementsByClassName("link")).forEach(el => {
    el.addEventListener("click", e => {
      e.preventDefault();                                       // aタグのページ遷移動作を無効化
      const pageName = e.target.getAttribute("href");           // aタグのhref属性を取得
      switchPage(pageName);                                     // ページを切り替える
      google.script.history.push(null, { page: pageName } );    // URLパラメーターをブラウザの履歴にプッシュ
    });
  });
</script>

コメント

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