Help us understand the problem. What is going on with this article?

Googleスプレッドシートでサイドバーを使う場合の最小記述病 (MDS on CBS) 症例

More than 3 years have passed since last update.

これは何

こちらの記事に書いてるんですが、Google Apps Script の Container Bound Script において、できるだけ記述量を少なくする病1にかかっていて、たいていの場合は

code.gs
_.$(this);

でいけることがわかっているんですが、Googleスプレッドシートでサイドバーを使い、サイドバーの中でサーバサイド関数を呼ぶ場合にはこれじゃダメだったんで、じゃどっしよかあなー、と試行錯誤してどうにか動くようになったのがあるので、それについて記録しておくよん、的な。
ああ、そうです、病です、これは。

はい、こうなりました

スプレッドシートにくっついた、すなわちスクリプトエディタを開いたときに表示されるコード (すなわち Container Bound Script) の最小記述は、とりあえずこんなんなりました。

spreadsheet/code.gs
_.$(this); // 初期化処理
function $() { return _.$.apply(this, arguments); } // サーバサイド関数

えっと、1 行目は今までと同じ。主たる処理を書いたライブラリを "_" という名前でリンクしておいて、spreadsheet 側の this (言ってみれば global オブジェクト) を "$" 関数に渡して、global に必要な関数はそっちでつけてもらう、と。

2 行目がサイドバー内でサーバサイド関数を呼ぶときのエントリっすね。サイドバー内で呼ぶサーバサイド関数も global に定義してあればいいわけで、1 行目の処理で大丈夫かなあ、と思っていたけど駄目だったんで。
(あれ、昔はできたような気がしてきた ...。)

はい、2 時間で改訂

記事を書き終えて歩いていける温泉まで行って温まってきて、ふらふらと歩いて帰ってくる途中にもっと短いコードが降ってきました。それがこれ。

spreadsheet/code.gs
($=_.$)(this);

改訂前のコードとやっていることは同じです。ライブラリ _ が提供する関数 $ をグローバル変数に代入したうえで、this を引数に呼び出したってやつですね。病んでますね。
まあ、これができるようにするためには初期化関数とサーバサイド関数を同じ名前にする必要がありますな。

ライブラリ側の関数はこうなります

まずは主処理

liba/code.gs
var g = this;

function $(module) {
  var funcname = module;
  if (typeof funcname == 'string') { // <- (0)
    if (typeof g[funcname] == 'function') {
      return g[funcname].apply(g, (function(a, r, n, i) { n = a.length; for (i = 1; i < n; i++) r.push(a[i]); return r; })(arguments, []));
    } else {
      return;
    }
  }

  module.onOpen = function() {
    SpreadsheetApp.getUi()
    .createMenu('俺的メニュー')
    .addItem('サイドバー表示', 'showSideBar')   // <- (1)
    .addSeparator()
    .addSubMenu(SpreadsheetApp.getUi().createMenu('テスト')
                .addItem('テスト出力', 'outputTest')
               )
    .addToUi();
    showSideBar();
  };

  module.showSideBar = function() {  // <- (2)
    var ui = SpreadsheetApp.getUi();
    var tmpl = HtmlService.createTemplateFromFile('sideBar');
    module.initTemplate(tmpl);
    var html = tmpl.evaluate().setTitle('いろいろ').setWidth(300).setSandboxMode(HtmlService.SandboxMode.NATIVE);
    if (! isTest) ui.showSidebar(html);
    return;
  };

  module.initTemplate = function(tmpl) {
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    tmpl.clientMaster = new ClientMaster(ss);
    tmpl.include = function(name) {  // <- (3)
      return HtmlService.createHtmlOutputFromFile(name).getContent()
    };
  };
  ...
}

function fillClientCode(client_id, code) { // <- (4)
  return doSomething(client_id, code);
}

関数 "$" は、spreadsheet/code.gs の 1 行目で呼ばれるもの (便宜的に初期化処理と呼びます)、2 行目で呼ばれるもの (便宜的にサーバサイド関数と呼びます)、まったく違う機能のものひとつの関数で受けています。引数を見て処理を切り替えてます。えっと、別々の関数として定義した方がわかりやすくていいと思いますよ。まあ、「最小」と見た目のカッコよさ(主観的)にこだわってこんな変な実装になっているわけですが、まあ病気なのでしょうがないということで。

コメントの (0) からの部分はサーバサイド関数としての機能です。第一引数で与えられた名前の関数があれば、それをサーバサイド関数として呼び出します。 たとえばクライアントサイドから $('fillClientCode', 120, 'c5') と呼ばれると、この関数を経由して最終的に fileClientCode(120, 'c5') が呼ばれるわけです。

コメント (0)if ブロックが終わったところからは普通の「最小記述病」の初期化処理になります。

module.onOpen 関数をこのように定義しておいて、スプレッドシートを開いたときのトリガーで実行できるようにできます。ここにカスタマイズメニューを定義し、(1) のようにサイドバーを出せるようにできます。

本記事とは直接関係ないことですが、あとで使い方を示す HTML テンプレートの include 関数 (インスタンスメソッド) (3) はわりと便利かな、と思いますです。

サイドバーの定義

サイドバーの定義です。
include メソッドを使うと、css や javascript を別ファイルに書けますよ、的な。

sideBar.html
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<?!= this.include('style') ?>
<div id="sidebar">
  ...
</div> <!-- sidebar -->
<?!= this.include('sidebarjq') ?>

で、javascript はこんな感じ。ってか、面倒くさくなってきたので何らかのクリックイベントでサーバサイド関数を呼び出す処理だけ書いてみたよん。(5) のところですね。

sidebarjq.html
<script>
(function($) {
  $.fn.qzone = function() {
    ...
    $item.click(function(e){
      google.script.run
      .withSuccessHandler(function(msg, element) {
      })
      .withFailureHandler(function(err, element) {
      })
      .withUserObject(this)
      .$('fillClientCode', id, code); // <- (5)
    ...
  };

  $(function() {
    $("#qzone").qzone();
  });
})(jQuery);
</script>

昔は (5) のところ、.fillClientCode(id, code); って書けていた記憶があるのですが、今やってみると TypeError: fillClientCode is not a function てなエラーが発生します。

終わり

てな感じで―。

参考


  1. MDS on CBS; Minimum Description Syndrome on Container Bound Script ... アプリにくっついたスクリプト (Container Bound Script) の記述を最小にしようとしてしまう病 

unau
百姓。大工でないように、エンジニアでもない。 営農する上で必要なものは、パイプハウスでも顧客管理システムでもできるだけ自分で作る。それだけ。
https://mato.me/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away