2

I'm trying to add a table of contents to Google Sheets: simply want to include a list of all sheets inside the document, as clickable links (got 150+ sheets.)

I got it to work, but it's more complicated than I'd like, and leaves me with questions about custom functions in Google Sheets.

Here's what I have, set of course in Tools › Script editor:

/**
 * Returns all the document's sheet IDs.
 *
 * @return
 * @customfunction
 */
function tocid() {
  var out = new Array()
  var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
    for (var i=0 ; i<sheets.length ; i++)
      out.push( [ sheets[i].getSheetId() ]
    )
  return out 
}

/**
 * Returns all the document's sheet names.
 *
 * @return
 * @customfunction
 */

function toctitle() {
  var out = new Array()
  var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
    for (var i=0 ; i<sheets.length ; i++)
      out.push( [ sheets[i].getSheetName() ]
    )
  return out 
}

Using each formula, I get:

| Sheet_ID   | Sheet_Title       |
|------------|-------------------|
|  349319062 | Table of Contents |
| 1280378086 | many ou much      |
| …          | …                 |

And then I can use the HYPERLINK formula to get links: =hyperlink(concatenate("#gid=",A2), B2).

So it works.

However, I tried to do it all in one pass, like so:

/**
 * Returns a list of all the document's sheets as hyperlinks.
 *
 * @return
 * @customfunction
 *
 * …unfortunately, can't use built-in functions inside of it, it seems. So instead of hyperlinks, it shows the formula for the hyperlinks.
 */
function toclink() {
  var out = new Array()
  var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
    for (var i=0 ; i<sheets.length ; i++)
      out.push( [ "=HYPERLINK(\"#gid=" + sheets[i].getSheetId() + "\", \"" + sheets[i].getName() + "\")" ]
    )
  return out 
}

But, as noted in the function's code comments, it does not work.

My questions are:

  1. Is the fact that you cannot use built-in functions inside of Google Sheets' custom functions / Google Apps scripts actually documented anywhere? (I'd have saved a couple of hours of my life had I known.)

  2. Any way to have the a custom function return clickable hyperlinks?

I suspect using Range might do it, but I'm not comfortable with them (and the fact the above still works makes it less of an incentive to learn). Is there a way to have a custom function just evaluate a formula taken from another column?

NOTE: I don't want to use a macro. I want to use a solution that auto-updates when new sheets get inserted.


I realize there's a similar question here, with a very useful answer. This does not exactly answer my questions, but is a bit easier to use than my current solution.

4

2 回答 2

3

Q1:是不是不能使用内置函数...

如果通过内置函数您的意思是这些,那么不,旨在“在”应用程序“内部”工作的东西肯定不会在用 JavaScript 编写的脚本中可用,除非特别公开为服务/全局变量(它们不是)。

不过,您可以使用严格的内置 Google 服务子集。请参阅本指南以确定哪些可以,哪些不能。

最初,我以为您的字面意思是使用内置函数,但显然并非如此

Q2:有什么方法可以让自定义函数返回可点击的超链接?

不,不使用自定义函数。公式必须通过需要授权且未被列入白名单以与自定义函数一起使用的setFormula() 方法进行设置。

Q3:我想使用一种在插入新工作表时自动更新的解决方案

答案取决于“插入新工作表时”的含义:

  1. 用户手动插入新工作表:使用可安装onChange 触发器

首先,安装一个onChange触发器,该触发器将在触发时运行调用的函数createTableOfContents

function installOnChange() {
    const ss = SpreadsheetApp.getActiveSpreadsheet();

    const builder = ScriptApp
        .newTrigger("createTableOfContents")
        .forSpreadsheet(ss)
        .onChange();

    builder.create();
}

然后,createTableOfContents根据您的喜好声明您的。我创建了一个示例用于演示目的:

const createTableOfContents = () => {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    const sheets = ss.getSheets();

    const tableOfContents = sheets.map(sheet => {
        return [
            sheet.getSheetId(),
            sheet.getSheetName()
        ];
    });

    const withHeaders = [
        ["Sheet Id", "Title"],
        ...tableOfContents
    ];

    const [sh] = sheets;

    const rng = sh.getRange(1, 1, withHeaders.length, withHeaders[0].length);

    rng.setValues(withHeaders).activate();

    const formulas = tableOfContents.map(([id,name]) => {
        return [`=HYPERLINK("#gid=${id}","${name}")`];
    });

    rng.offset(1, 1, tableOfContents.length, 1).setFormulas(formulas);
};

请注意,该示例不处理由于工作表删除而导致的范围缩小,但您可以从此处获取:

在此处输入图像描述

  1. 通过脚本插入工作表:createTableOfContents从插入工作表的函数运行。

我不想使用宏

另一个答案提供的不是 marco。宏构成了用 Google Apps 脚本编写的函数子集,必须在清单中声明并通过键盘快捷键调用。

参考

  1. setFormulas() 参考
  2. offset() 参考

建议归功于 TheMaster:

HYPERLINK您可以使用内置服务中的setRichTextValues()方法和类,而不是设置公式。RichTextValueBuilder所需的更改很小:

const links = tableOfContents.map(([id,name]) => {
  const value = SpreadsheetApp.newRichTextValue();

  return [value
    .setText(name)
    .setLinkUrl(`#gid=${id}`)
    .build()];
});

rng.offset(1, 1, tableOfContents.length, 1).setRichTextValues(links);
于 2020-06-13T05:42:05.240 回答
2

有很多路要走,还有很多你可能想要的不同的东西。

function tableOfContents() {
  const ss=SpreadsheetApp.getActive();
  let sh=ss.getSheetByName('Table Of Contents');
  if(sh) {
    ss.deleteSheet(sh);
    SpreadsheetApp.flush();
  }
  sh=ss.insertSheet('Table of Contents',0);
  sh.clear();  
  let c=[["Table of Contents","","",""],["Item","Name","Link","Hidden"]];
  let shts=ss.getSheets();
  shts.forEach(function(sh,i){
    c.push([i+1,sh.getName(),sh.isSheetHidden()?"":Utilities.formatString('=HYPERLINK("\#gid=%s\","Link")',sh.getSheetId()),sh.isSheetHidden()?"Yes":""]);
  })
  sh.getRange(1,1,c.length,c[0].length).setValues(c).setHorizontalAlignment("center").setFontSize(10);
  sh.getRange(1,1,1,c[0].length).mergeAcross().setFontWeight("bold").setFontSize(12).setBorder(false,false,true,false,false,false);
  sh.getRange(2,1,1,c[0].length).setFontWeight("bold").setFontSize(8).setBorder(true,true,true,true,true,true);
}
于 2020-06-08T17:57:49.597 回答