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

# これは何
[link-mdscbs]:http://qiita.com/unau/items/75b97a312f81f08c0365
[こちらの記事][link-mdscbs]に書いてるんですが、Google Apps Script の Container Bound Script において、できるだけ記述量を少なくする病[^1]にかかっていて、たいていの場合は

```js:code.gs
_.$(this);
```

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

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

# はい、こうなりました

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

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

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

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

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

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


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

## まずは主処理

```js: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 を別ファイルに書けますよ、的な。

```html: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)` のところですね。

```html: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` てなエラーが発生します。


# 終わり
てな感じで―。

## 参考

- [GoogleAppsScript : Container Bound Script の記述量をできるだけ少なくする試み][link-mdscbs]