Служба HTML: связь с функциями сервера

google.script.run — это асинхронный клиентский JavaScript API, который позволяет страницам службы HTML вызывать серверные функции Apps Script. В следующем примере показаны самые основные функции google.script.runвызов функции на сервере из клиентского JavaScript.

Код.gs

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

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

Индекс.html

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

Если вы развернете этот скрипт как веб-приложение и посетите его URL-адрес, вы ничего не увидите, но если вы просмотрите журналы, вы увидите, что была вызвана серверная функция doSomething() .

Клиентские вызовы серверных функций являются асинхронными: после того, как браузер запрашивает у сервера выполнение функции doSomething() , браузер немедленно переходит к следующей строке кода, не дожидаясь ответа. Это означает, что вызовы серверных функций могут выполняться не в том порядке, в котором вы ожидаете. Если вы одновременно вызываете две функции, невозможно узнать, какая из них запустится первой; результат может отличаться каждый раз, когда вы загружаете страницу. В этой ситуации обработчики успеха и обработчики ошибок помогают контролировать поток вашего кода.

API google.script.run допускает 10 одновременных вызовов функций сервера. Если вы сделаете 11-й вызов, пока 10 еще работают, функция сервера будет отложена до тех пор, пока не освободится одно из 10 мест. На практике вам редко придется думать об этом ограничении, тем более что большинство браузеров уже ограничивают количество одновременных запросов к одному и тому же серверу числом ниже 10. В Firefox, например, ограничение составляет 6. Большинство браузеров аналогичным образом задерживают избыточные запросы к серверу, пока один из существующих запросов не будет завершен.

Параметры и возвращаемые значения

Вы можете вызвать функцию сервера с параметрами от клиента. Точно так же серверная функция может вернуть значение клиенту в качестве параметра, переданного обработчику успеха .

Допустимые параметры и возвращаемые значения — это примитивы JavaScript, такие как Number , Boolean , String или null , а также объекты и массивы JavaScript, состоящие из примитивов, объектов и массивов. Элемент form на странице также допустим в качестве параметра, но он должен быть единственным параметром функции и недопустим в качестве возвращаемого значения. Запросы не выполняются, если вы пытаетесь передать элемент Date , Function , DOM помимо form или другого запрещенного типа, включая запрещенные типы внутри объектов или массивов. Объекты, создающие циклические ссылки, также не будут работать, а неопределенные поля в массивах станут null .

Обратите внимание, что объект, переданный на сервер, становится копией оригинала. Если серверная функция получает объект и изменяет его свойства, это не влияет на свойства клиента.

Обработчики успеха

Поскольку код на стороне клиента переходит к следующей строке, не дожидаясь завершения вызова сервера, withSuccessHandler(function) позволяет указать функцию обратного вызова на стороне клиента, которая будет выполняться при ответе сервера. Если серверная функция возвращает значение, API передает это значение новой функции в качестве параметра.

В следующем примере отображается предупреждение браузера, когда сервер отвечает. Обратите внимание, что для этого примера кода требуется авторизация, поскольку серверная функция обращается к вашей учетной записи Gmail. Самый простой способ авторизовать скрипт — запустить getUnreadEmails() вручную из редактора скриптов один раз перед загрузкой страницы. В качестве альтернативы, когда вы развертываете веб-приложение , вы можете выбрать его выполнение в качестве «пользователя, получающего доступ к веб-приложению», и в этом случае вам будет предложено авторизоваться при загрузке приложения.

Код.gs

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

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

Индекс.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) или укажите обработчик сбоя, который ничего не делает.

Как показывает этот пример, синтаксис обработчиков ошибок почти идентичен обработчикам успехов.

Код.gs

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

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

Индекс.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>

Пользовательские объекты

Вы можете повторно использовать один и тот же обработчик успеха или неудачи для нескольких обращений к серверу, вызвав withUserObject(object) , чтобы указать объект, который будет передан обработчику в качестве второго параметра. Этот «пользовательский объект» — не путать с классом User — позволяет вам реагировать на контекст, в котором клиент связался с сервером. Поскольку пользовательские объекты не отправляются на сервер, они могут быть практически любыми, включая функции, элементы DOM и т. д., без ограничений на параметры и возвращаемые значения для вызовов сервера. Однако пользовательские объекты не могут быть объектами, созданными с помощью оператора new .

В этом примере нажатие любой из двух кнопок обновит эту кнопку значением с сервера, в то время как другая кнопка останется без изменений, даже если они используют один обработчик успеха. Внутри обработчика onclick ключевое слово this относится к самой button .

Код.gs

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

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

Индекс.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 Диск, а затем печатает URL-адрес файла на клиентской странице. Внутри обработчика onsubmit ключевое слово this относится к самой форме. Обратите внимание, что при загрузке всех форм на странице действие отправки по умолчанию отключено с помощью preventFormSubmit . Это предотвращает перенаправление страницы на неточный URL-адрес в случае исключения.

Код.gs

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

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

Индекс.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() совершенно невидимы для клиента.

Код.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
  }
};

Индекс.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 Docs, Sheets или Forms можно изменить, вызвав методы google.script.host setWidth(width) или setHeight(height) в коде на стороне клиента. (Чтобы установить начальный размер диалогового окна, используйте методы HtmlOutput setWidth(width) и setHeight(height) .) Обратите внимание, что диалоговые окна не центрируются в родительском окне при изменении размера, и невозможно изменить размер боковых панелей .

Закрытие диалогов и боковых панелей в Google Workspace

Если вы используете службу HTML для отображения диалогового окна или боковой панели в Google Docs, Sheets или Forms, вы не можете закрыть интерфейс, вызвав window.close() . Вместо этого вы должны вызвать google.script.host.close() . Пример см. в разделе об использовании HTML в качестве пользовательского интерфейса Google Workspace .

Перемещение фокуса браузера в Google Workspace

Чтобы переключить фокус в браузере пользователя с диалогового окна или боковой панели обратно на редактор Google Docs, Sheets или Forms, просто вызовите метод google.script.host.editor.focus() . Этот метод особенно полезен в сочетании с методами службы Document.setCursor(position) и Document.setSelection(range) .