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

google.script.run — это асинхронный клиентский JavaScript API, позволяющий HTML-страницам-сервисам вызывать серверные функции Apps Script. Следующий пример демонстрирует базовую функциональность 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>

Если вы развернете этот скрипт как веб-приложение и перейдете по его 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 .

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

Специалисты по обеспечению успеха

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

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

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) позволяет указать обработчик ошибок, который будет запущен вместо обработчика успешного выполнения. В случае ошибки API передает объект Error в качестве аргумента обработчику ошибок.

По умолчанию, если вы не укажете обработчик ошибок, ошибки будут записываться в консоль JavaScript. Чтобы переопределить это, вызовите withFailureHandler(null) или укажите обработчик ошибок, который ничего не делает.

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

Code.gs

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

function getUnreadEmails() {
  // 'got' instead of 'get' throws 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>

Объекты пользователя

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

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

Закрыть диалоговые окна и боковые панели в 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: Document.setCursor(position) и Document.setSelection(range) .