3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-01-18

これは何

こちらの記事に書いてるんですが、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) の記述を最小にしようとしてしまう病

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?