Google Apps Scriptでツリーメニューを実現する【GAS】

ここ数ヶ月、Windows10およびMicrosoft365の導入に関連した仕事をしていますが、とりわけ後者のMicrosoft365関連として、これまで使ってきたIBM Lotus Notesに纏わるセミナーや関連情報を漁っています。当たり前ですが、Notesという非常に前世代のシステムを、Microsoft365のShare Pointで実現するのは、ほぼ無理です。

また、SharePointはOnlineとオンプレ版では違う製品であり、その中での開発も過去と今とでは異なっています。とりわけNotesの掲示板機能にある「ツリーメニュー」が課題になっています。今回これをSharePoint上ではなく、Google Apps Scriptで作成しSharePoint貼り付けてみようと思います。

図:非常にスタイリッシュにメニューを生成します。

今回使用するスプレッドシート等

ファイルと関連リンク

サンプル

使い方

スプレッドシートを開くとメニューに「リンク集作成」が出てきます。必ず、使う場合にはセットアップを実行してください。スプレッドシートのIDをスクリプトプロパティに格納してからでないと利用が出来ません。

スプレッドシートの記述方法ですが、今回のスクリプトは、ブログカスタマイズの雑談BLOGさんのCSSのみで実現するCSSを利用していますので、それに合わせたスプレッドシートの構造になっています。それぞれの列は、

  1. セクション = 一番大きな大分類。大きな塊に同じ数値で分類しています。
  2. 階層レベルは最大で4階層まで対応。CSSの改造とJavaScriptの改造を施せばいくらでも階層は増やせます。数値はそれぞれどの位置にいるのか数値で表現。
  3. 上から順番に読んでいきますので、スプレッドシートで見たままにツリーメニューを実現しています。
  4. カテゴリーはブログの場合にメニューになり、コンテンツの場合リンクとなります。
  5. ラベルはグループ名とコンテンツの名称で利用しています。
  6. URLはコンテンツの場合にリンク先として設定しています。

設定が完了したら、スクリプトエディタにて「公開」⇒「ウェブアプリケーションとして導入」を実行し、URLをiFrameなどで外部のブログなどに公開したり、Google Sitesに貼り付けたり、SharePointのコード埋め込み時にiFrameで埋め込めます。

ソースコード

GAS側コード

function onOpen(e) {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('▶リンク集作成')
      .addItem('セットアップ', 'setup')
      .addToUi();
}

//セットアップ
function setup(){
  var sheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheetId = sheet.getId();
  var Properties = PropertiesService.getScriptProperties();
  var spfile = Properties.setProperty("sheetid",sheetId);
  
  SpreadsheetApp.getUi().alert("セットアップ完了");
}

//プレビュー表示用
function doGet(){
  //スクリプトレットを使えるようにしておく
  var html =  HtmlService.createTemplateFromFile("linkman").evaluate()
              .setSandboxMode(HtmlService.SandboxMode.IFRAME)
              .setHeight(530)
              .setWidth(1000)
              //外部貼り付けできるようにxframeoptionをつける
              .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
  return html;
}

//ウェブアプリケーションへデータをコールバック
function retdata(){
  var Properties = PropertiesService.getScriptProperties();
  var sheetid = Properties.getProperty("sheetid");
  
  //シートデータの取得
  var sheet = SpreadsheetApp.openById(sheetid);
  var ss = sheet.getSheetByName("treemenu");
  var range = ss.getRange("A2:E").getValues();
  return JSON.stringify(range);
}
  • .setXFrameOptionsModeをつけないと外部サイトには貼り付けが出来ませんので注意!
  • セットアップを実行すると、スクリプトプロパティのsheetidセクションにスプレッドシートのIDが格納されます。

HTML側コード

<!DOCTYPE html>
<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <!-- ツリーメニュー用のCSSを読み込む -->
    <?!= HtmlService.createHtmlOutputFromFile("css").getContent(); ?>
    
    <!-- HTML遷移先のベースターゲットを親ページにする -->
    <base target="_top">
    
    <script>
      //起動時にシートデータを読み取りメニューを構成する
      google.script.run.withSuccessHandler(onSuccess).retdata();
      
      //リターンデータからHTML生成
      function onSuccess(data){
        //JSONデータをパースする
        var json = JSON.parse(data);
        var length = json.length;
        
        //HTML生成用変数
        var html = "";
        var subcnt = 0;
        var sublevcnt = 0;
        var groupflg = "";
        
        //まずは普通のリストを一括で生成しておく
        for(var i = 0;i<length;i++){
          //Null判定
          if(json[i][0] == null || json[i][0] == ""){
            break;
          }
          
          //サブグループ判定
          if(json[i][3] == "グループ"){
            //階層レベルに応じて処理を分岐
            switch(json[i][1]){
              case 1:
                groupflg = 1;
                break;
              case 2:
                subcnt = subcnt + 1
                groupflg = 2;
                break;
              case 3:
                sublevcnt = sublevcnt + 1;
                groupflg = 3
                break;
            }
          }

          //liエレメントを一括で生成する
          if(json[i][3]=="グループ"){
            console.log(groupflg);
            switch(groupflg){
              case 1:
                //グループレベル1
                //チェックボックスとラベルを追加
                var input = "<input type='checkbox' name ='group-" + json[i][0] + "' id='group-" + json[i][0] + "'>";
                var label = "<label for='group-" + json[i][0] + "'>" + json[i][2] + "</label>"
                
                //HTMLを生成
                html += "<li class='l" + json[i][1] + "'>" + input + label + "</li>";
                break;
              case 2:
                //グループレベル2
                //チェックボックスとラベルを追加
                var input = "<input type='checkbox' name ='sub-group-" + subcnt + "' id='sub-group-" + subcnt + "'>";
                var label = "<label for='sub-group-" + subcnt + "'>" + json[i][2] + "</label>"
                
                //HTMLを生成
                html += "<li class='l" + json[i][1] + "'>" + input + label + "</li>";              
                break;
              case 3:
                //グループレベル3
                //チェックボックスとラベルを追加
                var input = "<input type='checkbox' name ='sub-group-level" + sublevcnt + "' id='sub-group-level" + sublevcnt + "'>";
                var label = "<label for='sub-group-level" + sublevcnt + "'>" + json[i][2] + "</label>"
                
                //HTMLを生成
                html += "<li class='l" + json[i][1] + "'>" + input + label + "</li>";                 
                break;
            }
          }else{
            //リンクを追加
            var links = "<a href='" + json[i][4] + "'>" + json[i][2] + "</a>";
            html += "<li class='l" + json[i][1] + "'>" + links + "</li>";
          }
        }
        
        //生成したHTMLを挿入
        document.getElementById("menulist").innerHTML = html;
        
        //class構造を読み取ってネストさせる
        $("li", "ul.sitemap").each(function () {
            var subElements = $(this).nextUntil("li[class=" + $(this).attr("class") + "]");
            if (subElements.length > 0) {
                var wrapped = $("<ul class='sitemap' />").append(subElements);
                $(this).append($(wrapped));
            }
        });
      }
    </script>
  </head>
  <body>
    <div>
      <ul id="menulist" class="tree-menu animated sitemap"></ul>
    </div>
  </body>
</html>
  • CSS側の作りに合わせる為に、class名生成のために少し変わった作りになっています。
  • カテゴリーがグループで、階層レベルの値をフラグにし、チェックボックスとラベル、コンテンツのリンクを生成しています。
  • 生成したデータの後、ネスト構造を実現するコードで変更を実行し、ツリー構造を実現します。
  • ツリーメニュー用のCSSはcss.htmlの中身に記述しておき、linkman.html側の冒頭でスクリプトレットで呼び出しています。
  • 最大4階層までに対応したコードになっているので、改造する時はCSS側も含めて拡張が必要です

注意点

前述のコードのままだといつの間にかGoogle Apps Script側のセキュリティ対策によって、リンクをクリックしても動作しなくなっていました。以前のコードはbase targetが_parentとなっていてこれがNGでした。そこで、これを_topに変更してみたところ、従来の動作をするようになりました。

それまでのコードの場合、リンクをクリックしても無反応でDeveloper Toolには以下のようなエラーが検出されていました。もちろん、_blankでも動作します。この場合新しいタブを開いてそこに表示するようになります。

Unsafe attempt to initiate navigation for frame with URL 
'https://.googleusercontent.com/userCodeAppPanel' from frame with URL 
'https://.googleusercontent.com/userCodeAppPanel'. 

The frame attempting navigation is sandboxed, and is therefore disallowed from navigating its ancesto

図:_parentは使えないのです

SharePoint Onlineに埋め込む場合

SharePointは本来、スクリプトエディタWebパーツ(クラシックUI)や、SharePoint Framework(モダンUI)にて開発するのがセオリーですが、非常に開発しにくいものなので、iFrameタグを使って、このアプリを埋め込むことが可能です。埋め込む手順は以下の通り。

  1. ウェブアプリケーションとして導入の際のURLを控えておく。(最後がexecのURLです。)
  2. SharePoint側ではクラシックUIでも、モダンUIでも埋め込むことが可能ですが、クラシックUIの場合縦横のサイズに%指定がうまく反映しませんので注意。
  3. モダンUIにて、埋め込みたいポイントで➕マークをクリック。
  4. おすすめの「埋め込み」をクリックする。
  5. 右サイドバーに埋め込み欄が出てくるので、iFrameタグにて、1.を埋め込みます。ページに合わせてサイズ変更はONにしておきます。
  6. これで埋め込み完了。

iFrameの場合には以下のようなタグになります。

<iframe id="googlegadget" title="ツリーメニュー" 
   width="100%" 
   height="80%" 
   src="ここにウェブアプリのURLを入力します。">
</iframe>

図:SharePoint OnlineのモダンUIに貼り付けた

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)