Google表格自定义菜单可以将变量传递给函数吗?

时间:2017-10-02 15:46:48

标签: google-apps-script google-sheets

我在Google表格中创建一个菜单项,列出工作目录并提取该用户的电子邮件地址,以便稍后用于处理Google表格中的员工记录。我试图让它像这样工作:

function onOpen() {
  var ui = SpreadsheetApp.getUi();
  // Or DocumentApp or FormApp.
  var myapp = ui.createMenu('MyApp');
  var pullemp = myapp.addSubMenu(ui.createMenu('Pull Employee')
          .addItem('Nathaniel MacIver', menuItem2('nm@emailaddress.com')));
  myap.addToUi();
}

function menuItem2(email) {
  SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
     .alert('That email address is '+email);
}

现在,当我打开电子表格时,该项目立即触发,我得到了下面的结果,这就是我想要的:

enter image description here

但是我想在单击菜单中的按钮时触发它。事实上,当我点击我的菜单按钮时,我收到错误:

enter image description here

我知道链接提到函数名称需要是一个字符串值,但是为什么它会在我需要的时候运行onload然后在按下按钮时失败?

4 个答案:

答案 0 :(得分:4)

使用

创建菜单项时
.addItem('Nathaniel MacIver', menuItem2('nm@emailaddress.com'))

使用参数' nm@emailaddress.com'调用函数menuItem2。这会产生您看到的警报。函数的返回值是未定义的(因为您不会从中返回任何内容)。因此,您最终得到的菜单项就像

一样
.addItem('Nathaniel MacIver', undefined)

显然不会做任何事情。

method addItem只接受一个函数名,它不允许将参数传递给该函数。为了做你想做的事,你需要为每个人提供单独的功能,每个功能都有一个在该功能内部硬编码的电子邮件。

答案 1 :(得分:1)

尽管您不能通过.addItem()方法直接传递使用变量调用的函数,因为它仅接受字符串,但是您可以采取一些额外的步骤(相对于您尝试执行的步骤)动态创建预先设置有动态变量的函数。如果您拥有所有电子邮件地址的全局数组,例如:

var emails = ["email_2@email.com",
              "email_2@email.com",
              "email_2@email.com",
              "email_2@email.com"]

您可以使用'this'关键字创建一个以该电子邮件命名的自定义函数。

  

'this'关键字非常重要。 “这”是一个对象。此对象(无双关语)包含其键是GAS项目中的每个功能。例如,如果您的项目中有三个名为function1,function2和function3的函数,则“ this”对象将具有三个键,每个键分别命名为function1,function2和function3,并且这些键中的每一个都是函数本身。所以:

遍历数组,并使用方括号表示法为每个电子邮件创建一个函数。括号表示法允许您使用包含点符号不允许的字符的字符串来调用对象键。我无法通过说this.email_1@email.com创建对象,但是可以创建诸如this [“ email_1@email.com”]的对象:

emails.forEach(function (email) {
   this[email] = function () {
    SpreadsheetApp.getUi().alert('That email address is '+email);
  }
});

此代码将在服务器端的GAS项目中为每封电子邮件创建一个新功能,该功能本身的名称仅是电子邮件。对于视觉表示,理论上您将在项目中使用

function email_1@email.com () {
  SpreadsheetApp.getUi().alert('That email address is email_1@email.com');
}

function email_2@email.com () {
  SpreadsheetApp.getUi().alert('That email address is email_2@email.com');
}

function email_3@email.com () {
  SpreadsheetApp.getUi().alert('That email address is email_3@email.com');
}

function email_4@email.com () {
  SpreadsheetApp.getUi().alert('That email address is email_4@email.com');
}

尽管您实际上不会看到,但是运行函数时,这就是代码编译时后端发生的情况。话虽如此,然后您可以将那些相同的电子邮件名称作为字符串动态传递给.addItem()方法(在同一全局“电子邮件”数组上使用forEach循环),这是您可以传递的唯一变量类型进入这种方法。

var ui = SpreadsheetApp.getUi();
// Or DocumentApp or FormApp.
var myapp = ui.createMenu('MyApp');
var pullemp = myapp.addSubMenu(ui.createMenu('Pull Employee'))

emails.forEach(function (email) {
 pullemp.addItem(email,email)
});

myapp.addToUi();

当单击其中任何一项时,代码将尝试运行名为email_1@email.com的功能(或单击第二,第三或第四封电子邮件)。因为您遍历了“ this”对象并创建了以每封电子邮件命名的四个函数,所以您的项目将成功通过其名称找到该函数并运行它。


编辑:我知道您最初是想用人员的名字而不是他们的电子邮件创建菜单。上面的菜单将在菜单本身中显示其电子邮件。因此,要创建一个菜单并将其名称和相应的电子邮件作为功能,则需要一个对象。我的建议是创建一个以该人的名字作为密钥并将其电子邮件分配给该密钥的对象:

var emails = {};
emails["Nathaniel MacIver"] = "nm@emailaddress.com"
emails["John Doe"] = "jd@emailaddress.com"
emails["Jane Smith"] = "js@emailaddress.com"

现在,您可以使用Object.keys(\\object name)这样的方法遍历该对象。请注意,Object.keys()方法返回该对象的键的数组。这就是下面的forEach()方法起作用的原因:

Object.keys(emails).forEach(function (name) {
  this[emails[name]] = function () {
  SpreadsheetApp.getUi().alert('That email address is: '+emails[name])
 }
});

之所以将其作为电子邮件的emails [name],是因为'name'本身是密钥的实际名称,而emails [name]是该密钥中存储的值。因此,“ name” =“ Nathaniel MacIver”和emails [name] = emails [“ Nathaniel MacIver”],如您在上面看到的,已分配给他的电子邮件地址。

现在,您将使用与上面提到的相同的概念来创建菜单,但是您将遍历对象而不只是数组,并在addItem()方法中分配菜单项,如下所示:

var ui = SpreadsheetApp.getUi();
// Or DocumentApp or FormApp.
var myapp = ui.createMenu('MyApp');
var pullemp = myapp.addSubMenu(ui.createMenu('Pull Employee'))

Object.keys(emails).forEach(function (name) {
 pullemp.addItem(name,emails[name])
});

myapp.addToUi();

这将产生与第一个代码示例完全相同的结果,但提供人员的姓名作为菜单项,而不是其电子邮件地址。

请原谅我很长的答案!我刚刚发现此问题是为了解决我自己的Google表格自定义菜单问题。我希望这有帮助!如果对此有任何疑问,请随时与我联系!

答案 2 :(得分:0)

Matthew Wolman的解决方案在Google表格中不起作用。一切都在创建时起作用,typeof看到功能。但是不确定它是否超出范围或是否有其他安全策略,但是当您使用动态功能调用菜单时会出现错误:

  Execution failed: Script function not found: 'dynfunc-item1'

能够在using globally scoped function defs中使用动态菜单。

Matthew Wolman的答案与起作用的主要区别在于:

/* generate all menuFunctions in immediatelly called closure */
(function (globalScope) {
for (var i = 0, l = items.length; i < l; i++)
    createMenuFunction(items[i])

// you need to generate menuFunction via another function,
// because that way you generate scope to keep param unchanged.
// If you'll generate menuFunction right inside for loop,
// all your menuFunctions will refer to same instance of var i,
// which will be undefined after this closure terminates.
function createMenuFunction(param) {
    // here we define new function inside global scope
    globalScope[MENU_ITEM_FUNCTION_PREFIX + param] = function() {
       alertNum(param);
    }
}
})(this) // here we passing global scope and call the closure

答案 3 :(得分:0)

这里值得一提的是,我努力通过一个论点,虽然有点啰嗦,但确实有效:(显然灵感来自 Matthew Wolman 的回答)

准备字典的字典

var strings = {};
strings["Item1"] = {'func':"function1",'arg':"yes"};
strings["Item2"] = {'func':"function2",'arg':"no"};
strings["Item3"] = {'func':"function3",'arg':"maybe"};

为每个使用参数“arg”调用 myGatewayFunction 的菜单项添加一个函数:

Object.keys(strings).forEach(function (name) {
  this[strings[name]['func']] = function () {
    myGatewayFunction(strings[name]['arg']);
 }
});

添加 onOpen 函数并添加所有菜单项:

function onOpen() {
  var ui = DocumentApp.getUi()
  var myMenu = ui.createMenu('Custom');

  Object.keys(strings).forEach(function (name) {
    myMenu.addItem(name,strings[name]['func'])
  });

  myMenu.addToUi();
}

添加一个由所有带参数的菜单项函数调用的函数:

function myGatewayFunction(arg){
  DocumentApp.getUi().alert(arg);
  }
}