LoginSignup
2
1

More than 1 year has passed since last update.

【GAS】スプレッドシートのカスタムメニューでアイテム毎に関数を動的(?)に割り当てる

Last updated at Posted at 2021-01-31

概要

これに回答したので。
カスタムメニューって便利なんだけど、関数指定が文字列だけだったり、関数に引数が渡せなくていやんな感じなので、それを打開したい!

結論からいうと!

カスタムメニューアイテム毎に別々の関数を定義する事で、引数が無くても何とかなるように作れた。
今回のサンプルだとシート数が変わった時に、スプレッドシートをリロードしないと正常に動かないので動的に割り当てというと若干語弊があるかも。。。

出来上がったもののプレビュー

メニューからシート名を選択すると、選んだシート名をアクティブにするよ。

gas.gif

ソースと解説

ソース全文

const APP_ACTIVE = SpreadsheetApp.getActive();
const SHEETS = APP_ACTIVE.getSheets();
const ITEM_FUNCTIONS = (function() {
  let result = {};
  for (let i = 0; i < SHEETS.length; i++) {
    // 各メニューアイテムに紐づける関数
    result['_moveTo' + i] = function() {
      SHEETS[i].activate();
      APP_ACTIVE.toast(SHEETS[i].getSheetName() + ' に切り替えた!', 'カスタムメニューさん');
    };
  }
  return result;
})();

/**
 * スプレッドシートを開いた時のハンドラ
 */
function onOpen() {
  let userMenu = [];

  // メニューと動的関数を生成する
  for (let i = 0; i < SHEETS.length; i++) {
    const customFunctionName = 'ITEM_FUNCTIONS._moveTo' + i;

    // カスタムメニューを追加
    userMenu.push({
      name: SHEETS[i].getSheetName(),
      functionName: customFunctionName,
    });
  }
  SpreadsheetApp.getActiveSpreadsheet().addMenu('シート名一覧', userMenu);
}

解説や注意点

掲載したソースや実装に当たっての注意点について書く。

カスタムメニューで指定する関数名がグローバルスコープから辿れる事

辿れれば良いのでこんな感じの関数であれば…

// 宣言
const hoge = {
  fuga: function() { Browser.msgBox('hello'); };
};

こんな感じにドットで繋いでメニューアイテムを入れれば、オブジェクト配下に入れた関数も呼び出せる。

// 呼び出しメニューアイテム
SpreadsheetApp.getActiveSpreadsheet().addMenu('シート名一覧', {[
  name: 'fugaを呼べ',
  functionName: 'hoge.fuga'
]});

一応、globalThisが使えるのでこういう書き方も出来た。まあお好みで。

// 宣言
globalThis['piyo'] = function() { Browser.msgBox('piyoさんだよ!'); };
// 呼び出しメニューアイテム
SpreadsheetApp.getActiveSpreadsheet().addMenu('シート名一覧', {[
  name: 'piyoを呼べ',
  functionName: 'piyo'
]});

動的追加関数は、初期化タイミングで追加しておく必要がある

私が検証した範疇ではonOpenより前。
onOpen後は、globalThis等に関数を追加してもメニューからは呼び出せませんでした。
下のようなコードで関数が定義されているかどうかをチェックしてたんですが、
追加処理をした関数内では生きているが、処理を抜けてしまうとglobalThisから消えてしまっていた。

let msg = '';
Object.keys(globalThis).filter(key => typeof(globalThis[key] === 'function').forEach(key => {
  if (msg) {
    msg += '\\n';
  }
  msg += 'key: ' + key + ', funcName: ' + globalThis[key]?.name;
});
Browser.msgBox(msg);
// こんな感じに書いても、「はーい、testさんだよ!」とは言ってくれずに眠ったまま。
const funcs = {
  'test': function() {
    Browser.msgBox('…zzZ');
  }
};

function onOpen() {
  funcs['test'] = function() {
    Browser.msgBox('はーい、testさんだよ!');
  };
  let userMenu = [{
    name: 'test関数を実行!',
    functionName= 'funcs.test'
  }];
  SpreadsheetApp.getActiveSpreadsheet().addMenu('カスタムメニュー', userMenu);
}

参考

2
1
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
2
1