HTML 服务:与服务器功能通信

google.script.run 是一种异步客户端 JavaScript API,允许 HTML 服务页面调用服务器端 Apps 脚本函数。以下示例展示了 google.script.run 最基本的功能 - 通过客户端 JavaScript 在服务器上调用函数

Code.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function doSomething() {
  Logger.log('I was called!');
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      google.script.run.doSomething();
    </script>
  </head>
</html>

如果您将此脚本部署为 Web 应用并访问其网址,则不会看到任何内容,但如果查看日志,则会发现已调用服务器函数 doSomething()

客户端对服务器端函数的调用是异步进行的:在浏览器请求服务器运行函数 doSomething() 之后,浏览器会立即继续执行下一行代码,而不会等待响应。这意味着,服务器函数调用可能无法按您预期的顺序执行。如果您同时进行两个函数调用,则无法确定首先运行哪个函数;每次加载页面时,结果都可能有所不同。在这种情况下,成功处理程序失败处理程序有助于控制代码的流。

google.script.run API 允许对服务器函数进行 10 次并发调用。如果您在 10 个位置仍在运行时发出第 11 次调用,服务器函数将延迟,直到 10 个空档中的一个被释放。在实践中,您很少需要考虑这一限制,尤其是因为大多数浏览器已经将对同一服务器的并发请求数量限制在 10 以下。例如,在 Firefox 中,上限为 6 个。同样,大多数浏览器都会将多余的服务器请求延迟到某个现有请求完成为止。

参数和返回值

您可以使用客户端中的参数调用服务器函数。同样,服务器函数可以将值作为传递给成功处理程序的参数向客户端返回值。

合法参数和返回值包括 NumberBooleanStringnull 等 JavaScript 原语,以及由基元、对象和数组组成的 JavaScript 对象和数组。页面中的 form 元素也可以作为参数合法,但它必须是函数的唯一参数,而不能作为返回值。如果您尝试传递 DateFunction、除 form 之外的 DOM 元素或其他被禁止的类型(包括对象或数组内禁止的类型),请求将会失败。创建循环引用的对象也会失败,并且数组中未定义的字段会变为 null

请注意,传递到服务器的对象将成为原始文件的副本。如果服务器函数接收对象并更改其属性,则客户端上的属性不会受到影响。

成功处理程序

由于客户端代码会继续执行下一行,而不会等待服务器调用完成,因此 withSuccessHandler(function) 可让您指定在服务器响应时要运行的客户端回调函数。如果服务器函数返回一个值,该 API 会将该值作为参数传递给新函数。

以下示例在服务器响应时显示浏览器提醒。请注意,此代码示例需要授权,因为服务器端函数正在访问您的 Gmail 帐号。对脚本授权的最简单方法是在加载页面之前,通过脚本编辑器手动运行一次 getUnreadEmails() 函数。或者,在部署 Web 应用时,您可以选择以“访问 Web 应用的用户”的身份执行该应用,在这种情况下,当您加载应用时,系统会提示您授权。

Code.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function getUnreadEmails() {
  return GmailApp.getInboxUnreadCount();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function onSuccess(numUnread) {
        var div = document.getElementById('output');
        div.innerHTML = 'You have ' + numUnread
            + ' unread messages in your Gmail inbox.';
      }

      google.script.run.withSuccessHandler(onSuccess)
          .getUnreadEmails();
    </script>
  </head>
  <body>
    <div id="output"></div>
  </body>
</html>

失败处理程序

如果服务器无法响应或抛出错误,您可以使用 withFailureHandler(function) 通过将 Error 对象(如有)作为参数传递来指定失败处理程序,而不是成功处理程序。

默认情况下,如果您未指定失败处理程序,则失败处理程序会记录到 JavaScript 控制台中。如需替换此设置,请调用 withFailureHandler(null) 或提供一个不执行任何操作的故障处理程序。

如本例所示,失败处理程序的语法与成功处理程序的语法几乎完全相同。

Code.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function getUnreadEmails() {
  // 'got' instead of 'get' will throw an error.
  return GmailApp.gotInboxUnreadCount();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function onFailure(error) {
        var div = document.getElementById('output');
        div.innerHTML = "ERROR: " + error.message;
      }

      google.script.run.withFailureHandler(onFailure)
          .getUnreadEmails();
    </script>
  </head>
  <body>
    <div id="output"></div>
  </body>
</html>

User 对象

如需为多次服务器调用重复使用同一个成功或失败处理程序,您可以调用 withUserObject(object) 指定一个将作为第二个参数传递给处理程序的对象。通过这个“用户对象”(不要与 User 类混淆),您可以响应客户端与服务器联系的上下文。由于用户对象不会被发送到服务器,因此它们几乎可以是任何内容,包括函数、DOM 元素等等,而对服务器调用的参数和返回值没有限制。但是,用户对象不能是使用 new 运算符构建的对象。

在此示例中,点击两个按钮中的任何一个都会使用来自服务器的值更新该按钮,同时让另一个按钮保持不变,即使这两个按钮共用一个成功处理程序也是如此。在 onclick 处理脚本内,关键字 this 是指 button 本身。

Code.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function getEmail() {
  return Session.getActiveUser().getEmail();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function updateButton(email, button) {
        button.value = 'Clicked by ' + email;
      }
    </script>
  </head>
  <body>
    <input type="button" value="Not Clicked"
      onclick="google.script.run
          .withSuccessHandler(updateButton)
          .withUserObject(this)
          .getEmail()" />
    <input type="button" value="Not Clicked"
      onclick="google.script.run
          .withSuccessHandler(updateButton)
          .withUserObject(this)
          .getEmail()" />
  </body>
</html>

表单

如果您使用 form 元素作为参数来调用服务器函数,表单会变成一个以字段名称为键、以字段值作为值的对象。所有值都将转换为字符串,文件输入字段的内容除外,它们会成为 Blob 对象。

本示例在不重新加载页面的情况下处理了一个表单(包括一个文件输入字段);它会将文件上传到 Google 云端硬盘,然后在客户端页面中输出该文件的网址。在 onsubmit 处理脚本中,关键字 this 是指表单本身。请注意,加载页面中的所有表单时,preventFormSubmit 会停用默认的提交操作。这样可以防止网页在发生异常时重定向到不准确的网址。

Code.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function processForm(formObject) {
  var formBlob = formObject.myFile;
  var driveFile = DriveApp.createFile(formBlob);
  return driveFile.getUrl();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      // Prevent forms from submitting.
      function preventFormSubmit() {
        var forms = document.querySelectorAll('form');
        for (var i = 0; i < forms.length; i++) {
          forms[i].addEventListener('submit', function(event) {
            event.preventDefault();
          });
        }
      }
      window.addEventListener('load', preventFormSubmit);

      function handleFormSubmit(formObject) {
        google.script.run.withSuccessHandler(updateUrl).processForm(formObject);
      }
      function updateUrl(url) {
        var div = document.getElementById('output');
        div.innerHTML = '<a href="' + url + '">Got it!</a>';
      }
    </script>
  </head>
  <body>
    <form id="myForm" onsubmit="handleFormSubmit(this)">
      <input name="myFile" type="file" />
      <input type="submit" value="Submit" />
    </form>
    <div id="output"></div>
 </body>
</html>

脚本运行程序

您可以将 google.script.run 视为“脚本运行程序”的构建器。如果向脚本运行程序添加成功处理程序、失败处理程序或用户对象,则不会更改现有的运行程序,而是会得到具有新行为的新脚本运行程序。

您可以使用 withSuccessHandler()withFailureHandler()withUserObject() 的任意组合及任意顺序。您还可以在已设置值的脚本运行程序上调用任何修改函数。新值只会替换先前的值。

此示例为所有三个服务器调用设置了通用失败处理程序,但设置了两个单独的成功处理程序:

var myRunner = google.script.run.withFailureHandler(onFailure);
var myRunner1 = myRunner.withSuccessHandler(onSuccess);
var myRunner2 = myRunner.withSuccessHandler(onDifferentSuccess);

myRunner1.doSomething();
myRunner1.doSomethingElse();
myRunner2.doSomething();

私有函数

名称以下划线结尾的服务器函数被视为私有函数。google.script 无法调用这些函数,并且它们的名称绝不会发送到客户端。因此,您可以使用它们来隐藏需要在服务器上保密的实现细节。google.script 也无法查看中的函数以及未在脚本顶层声明的函数。

在此示例中,函数 getBankBalance() 在客户端代码中可用;即使您不调用函数,检查源代码的用户也可以发现函数的名称。不过,deepSecret_()obj.objectMethod() 函数对客户端完全不可见。

Code.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function getBankBalance() {
  var email = Session.getActiveUser().getEmail()
  return deepSecret_(email);
}

function deepSecret_(email) {
 // Do some secret calculations
 return email + ' has $1,000,000 in the bank.';
}

var obj = {
  objectMethod: function() {
    // More secret calculations
  }
};

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <script>
      function onSuccess(balance) {
        var div = document.getElementById('output');
        div.innerHTML = balance;
      }

      google.script.run.withSuccessHandler(onSuccess)
          .getBankBalance();
    </script>
  </head>
  <body>
    <div id="output">No result yet...</div>
  </body>
</html>

调整应用内对话框大小 Google Workspace

您可以在客户端代码中调用 google.script.host 方法 setWidth(width)setHeight(height) 来调整 Google 文档、表格或表单中自定义对话框的大小。(如需设置对话框的初始大小,请使用 HtmlOutput 方法 setWidth(width)setHeight(height)。)请注意,调整对话框大小时,对话框不会在父窗口中重新居中,并且无法调整边栏的大小。

正在关闭“ Google Workspace”中的对话框和边栏

如果您使用 HTML 服务在 Google 文档、表格或表单中显示对话框或边栏,则无法通过调用 window.close() 来关闭界面。必须改为调用 google.script.host.close()。如需查看示例,请参阅将 HTML 作为 Google Workspace 界面提供部分。

将浏览器焦点移到 Google Workspace

如需将用户浏览器中的焦点从对话框或边栏切换回 Google 文档、表格或表单编辑器,只需调用 google.script.host.editor.focus() 方法即可。此方法与文档服务方法 Document.setCursor(position)Document.setSelection(range) 结合使用时特别有用。