これは何
こちらの記事に書いてるんですが、Google Apps Script の Container Bound Script において、できるだけ記述量を少なくする病1にかかっていて、たいていの場合は
_.$(this);
でいけることがわかっているんですが、Googleスプレッドシートでサイドバーを使い、サイドバーの中でサーバサイド関数を呼ぶ場合にはこれじゃダメだったんで、じゃどっしよかあなー、と試行錯誤してどうにか動くようになったのがあるので、それについて記録しておくよん、的な。
ああ、そうです、病です、これは。
はい、こうなりました
スプレッドシートにくっついた、すなわちスクリプトエディタを開いたときに表示されるコード (すなわち Container Bound Script) の最小記述は、とりあえずこんなんなりました。
_.$(this); // 初期化処理
function $() { return _.$.apply(this, arguments); } // サーバサイド関数
えっと、1 行目は今までと同じ。主たる処理を書いたライブラリを "_
" という名前でリンクしておいて、spreadsheet 側の this (言ってみれば global オブジェクト) を "$
" 関数に渡して、global に必要な関数はそっちでつけてもらう、と。
2 行目がサイドバー内でサーバサイド関数を呼ぶときのエントリっすね。サイドバー内で呼ぶサーバサイド関数も global に定義してあればいいわけで、1 行目の処理で大丈夫かなあ、と思っていたけど駄目だったんで。
(あれ、昔はできたような気がしてきた ...。)
はい、2 時間で改訂
記事を書き終えて歩いていける温泉まで行って温まってきて、ふらふらと歩いて帰ってくる途中にもっと短いコードが降ってきました。それがこれ。
($=_.$)(this);
改訂前のコードとやっていることは同じです。ライブラリ _
が提供する関数 $
をグローバル変数に代入したうえで、this
を引数に呼び出したってやつですね。病んでますね。
まあ、これができるようにするためには初期化関数とサーバサイド関数を同じ名前にする必要がありますな。
ライブラリ側の関数はこうなります
まずは主処理
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 を別ファイルに書けますよ、的な。
<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)
のところですね。
<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
てなエラーが発生します。
終わり
てな感じで―。
参考
-
MDS on CBS; Minimum Description Syndrome on Container Bound Script ... アプリにくっついたスクリプト (Container Bound Script) の記述を最小にしようとしてしまう病 ↩