【问题标题】:Google Apps Script: Dynamically creating spreadsheet menu itemsGoogle Apps 脚本:动态创建电子表格菜单项
【发布时间】:2015-07-21 13:07:04
【问题描述】:

我正在尝试使用 Google Apps 脚本在 Google 表格中创建一个动态填充的菜单。

  1. 我有一张“课程”表,其中列出了我教授的课程。
  2. 在运行我的脚本时,我让我的脚本读取这些类并将其加载到一个数组中。
  3. 为了只对原始“类”表中的值进行硬编码,我想为这些类中的每一个创建一个子菜单项。

工作表称为“类”。 类表中的值是 8H、9p1、9p2 等。 它们位于单元格 A1:A12 中。 在调试器中,数组 menuItemArray 正确加载了“类”表中的所有预期类。

我得到的错误是:

TypeError:在对象 9p1 中找不到函数 addSubMenu。 (第 13 行, 文件“代码”)

这是踏入队伍的时候

menuItemArrayClass =  menuItemArray [menuCount]

对于我做错的事情或任何更好的方法,我将非常感激。

这是我的代码:

function onOpen(e) {
    var ui = SpreadsheetApp.getUi(); 
    var menuCount = 0; 
    ui.createMenu('Manage Timetable')
    .addItem('First item', 'menuItem1')
    .addSeparator()

    var menuItemArray =     SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Classes').getDataRange().getValues();  
    for (menuCount=1;menuCount < menuItemArray.length;++menuCount) {
        var menuItemArrayClass = [] 
        menuItemArrayClass =  menuItemArray [menuCount]
        .addSubMenu(ui.createMenu('Manage Classes')
            .addItem(menuItemArrayClass [menuCount] + 'Schedule Timetable', 'runBatch1'))
        .addToUi();
    }     
}

【问题讨论】:

  • 非常感谢 Akshin 我会尝试一下,谢谢,Simon

标签: google-apps-script google-sheets


【解决方案1】:
  • 您想要创建自定义菜单。
  • 您想通过编辑电子表格来更新自定义函数。
  • 您想为自定义菜单中的多个功能使用一个功能名称,例如myFunction()
  • 当从自定义菜单运行函数时,您希望将每个值作为参数提供给函数。
  • 示例情况如下。
    • “A”到“I”列中有值。
    • 自定义菜单中有 9 个功能。打开电子表格时会创建自定义菜单。函数名对应每个列名。
    • 单击“A”列的功能时,会激活“A”列的值。
    • 当“I”列复制到“H”列时,新功能将添加到自定义菜单中。

我理解如上。如果我的理解是正确的,这个答案怎么样?请认为这只是几个可能的答案之一。

问题和解决方法:

很遗憾,在当前阶段,当使用addItem 方法将功能添加到自定义菜单时,无法使用该参数。并且当自定义菜单中的功能之一运行时,无法检索有关已运行的功能名称的信息。这样,您的目标无法直接实现。所以需要使用变通方法。

当我看到你的问题时,为了你的目标,我认为this thread 很有用。在 google.script.run 中,要求能够在脚本编辑器中直接运行该函数,该函数包含在this 中。但是在自定义菜单中,当函数包含在this中时,即使在脚本编辑器中无法直接运行该函数,也可以运行该函数。仅在 GAS 端运行该函数时,即使无法通过脚本编辑器直接运行该函数,也可以运行该函数。我认为这种情况可以用于解决方法。

修改后的脚本:

当您的脚本通过包含此解决方法进行修改时,它变为如下。请将其复制并粘贴到电子表格的容器绑定脚本中,其中第一行的标题(“Col1”、“Col2”、、、)和第二行的值。当您运行脚本时,请打开电子表格。这样,添加了自定义菜单。并且当通过复制添加新列时,附加列也会添加到自定义菜单中。并且在自定义菜单中的函数运行时,该列对应的值被激活。

发件人:

function onOpen(e) {
  var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var headers = ss.getRange(1, 1, 1, ss.getLastColumn()).getValues()[0];
  var ui = SpreadsheetApp.getUi();
  var menu = ui.createMenu('Custom Menu')
  .addItem('First item', 'menuItem1')
  .addSeparator();
  var subMenu = ui.createMenu('Sub-menu');
  for (var i = 0; i < headers.length; i++) {
    var dynamicMenu = headers[i];
    subMenu.addItem(dynamicMenu,'dynamicItem');
  }
  menu.addSubMenu(subMenu).addToUi();
}

function onEdit(e) {
  onOpen(e);
}

function menuItem1() {
  SpreadsheetApp.getUi()
  .alert('You clicked the first menu item!');
}

function dynamicItem() {
  SpreadsheetApp.getUi()
  .alert('You clicked the dynamic menu item!');
}

收件人:

function installFunctions() {
  var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var headers = ss.getRange(1, 1, 1, ss.getLastColumn()).getValues()[0];
  var ui = SpreadsheetApp.getUi();
  var menu = ui.createMenu('Custom Menu')
  .addItem('First item', 'menuItem1')
  .addSeparator();
  var subMenu = ui.createMenu('Sub-menu');
  for (var i = 0; i < headers.length; i++) {
    var dynamicMenu = headers[i];
    this[dynamicMenu] = dynamicItem(i); // Added
    subMenu.addItem(dynamicMenu,dynamicMenu); // Modified
  }
  menu.addSubMenu(subMenu).addToUi();
}

function dynamicItem(i) { // Added
  return function() {
    var sheet = SpreadsheetApp.getActiveSheet();
    sheet.getRange(2, i + 1, sheet.getLastRow() - 1, 1).activate();
  }
}

installFunctions(); // Added

function onOpen() {} // Modified: This can be used as the simple trigger.

function onEdit() {} // Modified: This can be used as the simple trigger.

function onChange() {} // Added: Please install OnChange event trigger to this function.
  • 在使用此脚本之前,请先将 OnChange 事件触发器安装到onChange() 的函数中。这样,当列被删除时,自定义菜单就会更新。
  • function onEdit() {}function onChange() {}的函数用于运行onOpen();

结果:

注意:

  • 为了动态创建自定义菜单,该脚本需要在函数运行的初始阶段运行。所以需要把它像onOpen();一样放在全局中。
  • 在此解决方法中,当函数运行时,onOpen 每次都会运行。所以当列数很大时,处理成本会很高。所以请注意这一点。
  • 这是一个简单的示例脚本,用于解释解决方法的一种方法。因此,请根据您的情况进行修改。

参考资料:

【讨论】:

  • @SourabhChoraria 我更新了脚本。因为我注意到当电子表格打开时,onOpen() 运行了 2 次。所以我修改了这个。你能确认一下吗?
  • 我一直在试验您的解决方案,但无法重现。一旦我离开“onOpen”函数,全局动态函数(this[dynamicMenu])就会丢失。菜单在另一个上下文中运行,显然只识别全局静态函数。
  • @Atmadata 感谢您的回复。我愿意支持你。但是,很遗憾,我无法理解您目前的情况。这是因为我的英语水平不好。我对此深表歉意。因此,您可以通过包含复制问题的详细信息将其作为新问题发布。这样,它将帮助包括我在内的用户思考解决方案。当你能合作解决你的问题时,我很高兴。
  • 感谢您的快速回复。您可以发布此电子表格的链接,以便我了解它是如何工作的吗?我还添加了这个功能请求:issuetracker.google.com/issues/36753036
  • @Atmadata 一旦我离开“onOpen”函数,全局动态函数(this[dynamicMenu])就会丢失。我相信this 可以解释/解决你的问题问题。
【解决方案2】:

试试这个:

function onOpen(e) {
    var ui = SpreadsheetApp.getUi(); 
    var menuCount = 0; 
   var menu = ui.createMenu('Manage Timetable');
    menu.addItem('First item', 'menuItem1')
    menu.addSeparator()

    var menuItemArray =     SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Лист2').getDataRange().getValues();  
    for (menuCount=0;menuCount < menuItemArray.length;menuCount++) {
        var menuItemArrayClass = [] 
        menuItemArrayClass =  menuItemArray [menuCount];
        menu.addSubMenu(ui.createMenu('Manage Classes')
            .addItem(menuItemArrayClass + ' Schedule Timetable', 'runBatch1'))
        menu.addToUi();
    }     
}

如果你从 1 开始你会丢失第一行,所以我把它改成了 0。

现在你还需要动态分配脚本,但我不知道它们在你的工作表中是如何设置的,所以我保持那部分不变。

也不确定是否每次都需要添加“管理课程”菜单,但我保留了它以防万一。

试试这个,因为它看起来更像你想要的:

function onOpen(e) {
  var ui = SpreadsheetApp.getUi(); 
  var menuCount = 0; 
  var menu = ui.createMenu('Manage Timetable')
  .addItem('First item', 'menuItem1')
  .addSeparator();
  var subMenu = ui.createMenu('Manage Classes');

  var menuItemArray =     SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Лист2').getDataRange().getValues();  
  for (menuCount=0;menuCount < menuItemArray.length;menuCount++) {
    var menuItemArrayClass = [] 
    menuItemArrayClass =  menuItemArray [menuCount];
    subMenu.addItem(menuItemArrayClass + ' Schedule Timetable', 'runBatch1');      
  }     

  menu.addSubMenu(subMenu).addToUi();
}

【讨论】:

  • 知道如何分配动态functionName吗?目前,我认为 runBatch1 是针对每个动态生成的菜单项的静态函数。
  • @SourabhChoraria 您可以将表示函数名称的任何字符串传递给.addItem()。详情请看我的回答。
  • @Sourabh Choraria 我想了解您的目标。 1.您想在自定义菜单中添加一个函数名为myFunction的函数。 2. 您想通过提供参数从自定义菜单运行myFunction。 3. 论据随着时间而改变。我可以这样理解。但我不确定我的理解是否正确。我能问一下你的目标的详细信息吗?
  • @Tanaike - 你的第二点,是的。我希望能够在函数中传递参数,更重要的是,调用一个通用函数(接受参数)而不是编写更多函数。
  • @Sourabh Choraria 感谢您的回复。从您的解释中,我可以理解您的目标。因此,我将其发布为答案。你能确认一下吗?
【解决方案3】:

假设您想将相关标题和函数名称分别存储在 Sheet1 列 A 和 B 中,您可以简单地调用 .getValues() 并遍历该数据。

/** 
 * SHEET1 VALUES
 * --------------------------------------
 *          Column A        Column B
 * Row 1    Class One       functionOne
 * Row 2    Class Two       functionTwo
 * Row 3    Class Three     functionThree
 */

function onOpen(e) {
  // Initialize the menu & submenu
  var ui = SpreadsheetApp.getUi(); 
  var menu = ui.createMenu('Menu Name').addItem('First label', 'menuItem1').addSeparator();
  var subMenu = ui.createMenu('Submenu Name');

  // Get the submenu item data from Sheet1
  // Values in column A represent the captions that will appear in the submenu
  // Values in column B represent the name of the function to be called
  var values = SpreadsheetApp.getActive().getSheetByName('Sheet1').getRange('A:B').getValues();
  for (var rowIndex = 0; rowIndex < values.length; ++rowIndex) {
    var rowData = values[rowIndex];
    var caption = rowData[0]; // Column A value
    var functionName = rowData[1]; // Column B value
    if (caption != '' && functionName != '') { // Exclude empty strings
      subMenu.addItem(caption, functionName);
    }
  }     

  // Add the submenu to the menu, and finally to the UI
  menu.addSubMenu(subMenu).addToUi();
}

function menuItem1() { Browser.msgBox('menuItem1'); }
function functionOne() { Browser.msgBox('functionOne'); }
function functionTwo() { Browser.msgBox('functionTwo'); }
function functionThree() { Browser.msgBox('functionThree'); }

在原始帖子中,发生错误是因为 .addSubMenu() 被调用值 [9p1] 而不是 Menu object。此外,根据我对问题的理解,我认为菜单 item 旨在在循环中创建,而不是每个类的单独子菜单。所以这是两个需要解决的问题。

我还假设您不想为每个菜单项运行runBatch1,因为这会破坏创建不同菜单项的目的。根据documentation,添加菜单项的方法需要两个字符串:

  1. caption – 菜单项的标签。
  2. functionName – 用户选择项目时要调用的函数的名称。

因此,您可以在拨打submenu.addItem() 时替换任何字符串。

var caption = "Caption";
var functionName = "functionName";
var subMenu = ui.createMenu('Submenu Name');
subMenu.addItem(caption, functionName);     

其他场景

项目对象数组

或者,您可以定义要包含的各种子菜单项的数组(或对象映射),而不是将它们存储在电子表格中。在这里,我使用了一个对象数组来清楚地定义标题和函数名称值。

function onOpen(e) {
  // Initialize the menu & submenu
  var ui = SpreadsheetApp.getUi(); 
  var menu = ui.createMenu('Menu Name').addItem('First label', 'menuItem1').addSeparator();
  var subMenu = ui.createMenu('Submenu Name');

  // Array of items to include in the submenu
  var items = [
    { caption: 'Class One', functionName: 'functionOne' },
    { caption: 'Class Two', functionName: 'functionTwo' },
    { caption: 'Class Three', functionName: 'functionThree' }
  ];
  for (var i = 0; i < items.length; ++i) {
    var item = items[i];
    subMenu.addItem(item.caption, item.functionName);

  }     

  // Add the submenu to the menu, and finally to the UI
  menu.addSubMenu(subMenu).addToUi();
}

function menuItem1() { Browser.msgBox('menuItem1'); }
function functionOne() { Browser.msgBox('functionOne'); }
function functionTwo() { Browser.msgBox('functionTwo'); }
function functionThree() { Browser.msgBox('functionThree'); }

字幕字符串操作

由于该方法接受任何字符串,因此您可以执行任何字符串操作,从而生成有效的函数名称。在此示例中,我仅将字幕存储在数组中,并根据字幕值动态生成函数名称。 (您也可以从 A 列中提取标题名称并应用相同的操作。)

function onOpen(e) {
  // Initialize the menu & submenu
  var ui = SpreadsheetApp.getUi(); 
  var menu = ui.createMenu('Menu Name').addItem('First label', 'menuItem1').addSeparator();
  var subMenu = ui.createMenu('Submenu Name');

  // Array of captions to include in the submenu.
  // Will generate function names from this.
  var captions = ['Class One', 'Class Two', 'Class Three'];
  for (var i = 0; i < captions.length; ++i) {
    var caption = captions[i];
    subMenu.addItem(caption, 'function' +  caption.split(' ')[1]);
  }     

  // Add the submenu to the menu, and finally to the UI
  menu.addSubMenu(subMenu).addToUi();
}

function menuItem1() { Browser.msgBox('menuItem1'); }
function functionOne() { Browser.msgBox('functionOne'); }
function functionTwo() { Browser.msgBox('functionTwo'); }
function functionThree() { Browser.msgBox('functionThree'); }

【讨论】:

  • 即使使用这种方法,我们也需要首先创建那么多函数。我更希望的是,如果我们可以将它作为函数中的参数传递 - 例如:比如说,我们有一个通用函数 - myFunction(option) 其中 option 将是一个在调用动态函数的时间。在这种情况下,这也可能是 for 循环中使用的变量 i
  • 显然,myFunction(option) 不起作用,因为 subMenu.addItem 只需要一个字符串,字符串输入。
  • @SourabhChoraria 不幸的是,您不能将参数传递给自定义菜单调用的函数。
  • 我同意;我自己无法弄清楚这一点的另一个原因是希望有一些更具创新性的东西,甚至是一种解决方法,因为必须运行一个传递动态参数的通用函数——我能想到的一种奇怪的方法是,如果有一个自定义菜单函数生成的函数 ID,以及我们是否可以进一步解读。同样,我自己也想不出解决方案,所以想在这个问题上发布一个赏金。
  • @SourabhChoraria 是的,这与您发布的问题不同。如果您有关于您要解决的问题的详细信息,您可以在 SO 或 apps script community 上发布问题。很多常见的情况都可以通过使用绑定脚本可用的special methods来解决。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多