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

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

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

Если вы используете службу HTML для отображения диалогового окна или боковой панели в Документах, Таблицах или Формах Google, вы не можете закрыть интерфейс, вызвав 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) .