はじめに注意
この記事は、Google Apps Script において、いわゆる "Container Bound Script" の記述量をできるだけ少なくする、オレオレベストプラクティスについて書いたものである。
実践してみた "Container" の種類 (たとえば Googleスプレッドシートや Googleサイト) が増えたりしたら書き加える可能性大。
"Container Bound Script" の記述量をできるだけ少なくする、目的はなに?
Googleサイトや Googleスプレッドシートなどを拡張するのに Google Apps Script を書くわけだが、その際は、それら拡張対象の "Container" に結びついたスクリプトを書く必要がある。
しかし、それら "Container Bound Script" はスクリプト (この文脈ではほぼプロジェクトに等しい)として単独で存在しえないので、ローカルファイルシステムにダウンロードとか、ローカルからアップロードとかできないのである。私はやりたいの。
新しいプロジェクトを作ったときにも、基本的にはブラウザ上でテキストエリア同士の間でコピペが必要になり、再利用しにくい。スマートじゃない。
ということで、できるだけ "Container Bound Script" の記述量を少なくして、残りは "Standalone Script" で書いたライブラリをリンクするようにする、ということを考えるわけだが、実際どこまで少なくできるのか。
⇒ _.$(this);
結論がこれ。
_.$(this);
実コードを書いたプロジェクトを liba とすると、そこに function $()
を定義しておき、その liba を _
という識別子で "Container Bound Script" からリンクする。
ちなみに、liba はデベロッパーモード On でリンクすると、liba の更新が client 側にも随時反映されるので、サクサク開発時はそうするとよいと思う。
liba の関数 $()
は例えば次のようになる。
function $(module) {
var projectKey = ScriptApp.getProjectKey();
if (projectKey == 'xxxxxxxxxxx') {
module.fire = function() { Logger.log('fire'); };
}
}
$()
関数内で module
のプロパティとして関数を直接突っ込むと、それは client
プロジェクトにおいてトップレベルで関数を置いたのと同じ意味になる。つまり、ここでの fire()
関数で言えば、
function fire() {
...
}
と書いたのと同じになる。
で、ここちょっと重要だけど、ScriptApp.getProjectKey()
で取得できるプロジェクトキーは、このコードが liba
プロジェクトの中に書かれていても、実行時にキックされた client
プロジェクトのものになる。みたい。
ということは、$()
関数の中でプロジェクトキーの値でディスパッチすれば、関連する複数の "Container Bound Script" で同じライブラリを共有できるようになる。
ちなみに、_.$(this);
の呼び出しにより、client
プロジェクト側では何らかの関数、ここでは fire()
がトップレベルに定義されるわけだが、GAS エディタ (ブラウザ上の統合開発環境) にはこの関数は認識さず、「関数を選択」で何も選択肢が現れない。
一応 work around があって、
_.$(this);
function fire() {
}
と、一旦 fire()
を書いておいて、すぐに消すと、「関数を選択」に fire
がリストされて選べるようになる。
"Container" てか各アプリごとの実例
どの "Container" であっても、そこに Bound な Script については _.$(this);
で全部対応できるはず。なので、問題は $()
関数での処理ですな。
Googleサイトのガジェット
function $(module) {
module.doGet = function(e) {
...
};
まあこんな感じで書けばいいはず。未確認。
Googleスプレッドシート
function $(module) {
module.onOpen = function() {
SpreadsheetApp.getUi()
.createMenu('俺的メニュー')
.addItem('サイドバー表示', 'showSideBar') // <- (1)
.addSeparator()
.addSubMenu(SpreadsheetApp.getUi().createMenu('テスト')
.addItem('テスト出力', 'outputTest')
)
.addToUi();
showSideBar();
};
module.showSideBar = function() { // <- (2)
...
};
...
}
でちゃんとスプレッドシートを起動したときにカスタムメニューが表示された。
そして、メニューから「サイドバー表示」を選ぶと、サイドバーが表示された。
ここで確認しておくべきは、メニューから呼び出される関数名はいっさいの修飾なし、すなわち (1) のように書く一方で、その関数は呼び出し側の global 相当の module
のプロパティとして、すなわち (2) のように定義する必要がある。
一応、また時間が取れたら書くかも知れないけれど、サイドバーの中のクリックイベントでスプレッドシートに値を入れる、という処理もうまく動いた。
追記 2015-12-13
スプレッドシートを開いたときに起動される関数や、セルを編集したときに起動される関数は、トリガとして登録するが、この方式ではグローバルにある関数が認識されないので、トリガを設定する際のダイアログに、たとえば onOpen がリストアップず、そのせいでトリガの設定ができない。
ここでも「関数を選択」のときに GAS エディタを騙した work around が使える。すなわち、一旦空でいいので onOpen 関数をグローバルに定義しておき、トリガの設定を終えてから消せばよい。