5
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.

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

Last updated at Posted at 2015-07-06

#はじめに注意
この記事は、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);
結論がこれ。

client/コード.gs
_.$(this);

実コードを書いたプロジェクトを liba とすると、そこに function $() を定義しておき、その liba を _ という識別子で "Container Bound Script" からリンクする。
ちなみに、liba はデベロッパーモード On でリンクすると、liba の更新が client 側にも随時反映されるので、サクサク開発時はそうするとよいと思う。

liba の関数 $() は例えば次のようになる。

liba/コード.gs
function $(module) {
  var projectKey = ScriptApp.getProjectKey();
  if (projectKey == 'xxxxxxxxxxx') {
    module.fire = function() { Logger.log('fire'); };
  }
}

$() 関数内で module のプロパティとして関数を直接突っ込むと、それは client プロジェクトにおいてトップレベルで関数を置いたのと同じ意味になる。つまり、ここでの fire() 関数で言えば、

client/コード.gs
function fire() {
  ...
}

と書いたのと同じになる。

で、ここちょっと重要だけど、ScriptApp.getProjectKey() で取得できるプロジェクトキーは、このコードが liba プロジェクトの中に書かれていても、実行時にキックされた client プロジェクトのものになる。みたい。
ということは、$() 関数の中でプロジェクトキーの値でディスパッチすれば、関連する複数の "Container Bound Script" で同じライブラリを共有できるようになる。

ちなみに、_.$(this); の呼び出しにより、client プロジェクト側では何らかの関数、ここでは fire() がトップレベルに定義されるわけだが、GAS エディタ (ブラウザ上の統合開発環境) にはこの関数は認識さず、「関数を選択」で何も選択肢が現れない。
一応 work around があって、

client/コード.gs
_.$(this);
function fire() {
}

と、一旦 fire() を書いておいて、すぐに消すと、「関数を選択」に fire がリストされて選べるようになる。

##"Container" てか各アプリごとの実例
どの "Container" であっても、そこに Bound な Script については _.$(this); で全部対応できるはず。なので、問題は $() 関数での処理ですな。

###Googleサイトのガジェット

liba/コード.gs
function $(module) {
  module.doGet = function(e) {
    ...
  };

まあこんな感じで書けばいいはず。未確認。

###Googleスプレッドシート

liba/コード.gs
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 関数をグローバルに定義しておき、トリガの設定を終えてから消せばよい。

5
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
5
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?