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 元素也可以作为参数,但必须是函数的唯一参数,并且不能作为返回值。如果您尝试传递 DateFunctionform 以外的 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 服务方法 Document.setCursor(position)Document.setSelection(range) 结合使用时特别有用。