Usługa HTML: komunikacja z funkcjami serwera

google.script.run to asynchroniczny interfejs API JavaScript działający po stronie klienta, który umożliwia stronom usługi HTML wywoływanie funkcji Apps Script po stronie serwera. Poniższy przykład pokazuje najprostszą funkcję google.script.run – wywoływanie funkcji na serwerze z położenia kodu JavaScript po stronie klienta.

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>

Jeśli wdrożysz ten skrypt jako aplikację internetową i otworzysz jego adres URL, nic nie będzie widoczne, ale jeśli wyświetlisz logi, zobaczysz, że wywołano funkcję serwera doSomething().

Wywołania funkcji po stronie serwera po stronie klienta są asynchroniczne: gdy przeglądarka poprosi serwer o wykonanie funkcji doSomething(), natychmiast przechodzi do następnego wiersza kodu bez oczekiwania na odpowiedź. Oznacza to, że wywołania funkcji serwera mogą nie być wykonywane w oczekiwanej kolejności. Jeśli wywołasz 2 funkcje w tym samym czasie, nie możesz wiedzieć, która z nich zostanie wykonana jako pierwsza. Wynik może się różnić przy każdym wczytaniu strony. W takiej sytuacji obsługa sukcesuobsługa niepowodzenia pomagają kontrolować przepływ kodu.

Interfejs API google.script.run umożliwia 10 jednoczesnych wywołań funkcji serwera. Jeśli wykonasz 11. wywołanie, gdy 10 jest nadal uruchomionych, funkcja serwera zostanie opóźniona do czasu, aż jedno z 10 miejsc zostanie zwolnione. W praktyce rzadko trzeba się martwić o to ograniczenie, zwłaszcza że większość przeglądarek ogranicza już liczbę jednoczesnych żądań do tego samego serwera do liczby mniejszej niż 10. Na przykład w Firefoksie limit wynosi 6. Większość przeglądarek opóźnia również żądania wysyłane do serwera, dopóki nie zostanie zakończone jedno z dotychczasowych żądań.

Parametry i wartości zwracane

Możesz wywołać funkcję serwera z parametrami z klienta. Podobnie funkcja serwera może zwrócić wartość klientowi jako parametr przekazany do obsługi sukcesu.

Prawidłowe parametry i zwracane wartości to typy proste JavaScriptu, takie jak Number, Boolean, String lub null, a także obiekty i tablice JavaScriptu, które składają się z typów prostych, obiektów i tablic. Element form na stronie może też być parametrem, ale musi być jedynym parametrem funkcji. Nie może być wartością zwracaną. Żądania kończą się niepowodzeniem, jeśli próbujesz przekazać element Date, Function lub DOM oprócz form albo innego zabronionego typu, w tym niedozwolone typy wewnątrz obiektów lub tablic. Nie uda się też utworzyć obiektów, które tworzą pętle odniesienia, a niezdefiniowane pola w tablicach staną się null.

Pamiętaj, że obiekt przekazany na serwer staje się kopią oryginału. Jeśli funkcja serwera otrzyma obiekt i zmieni jego właściwości, nie wpłynie to na właściwości na kliencie.

Obsługa zdarzeń powodzenia

Ponieważ kod po stronie klienta przechodzi do następnego wiersza bez oczekiwania na wykonanie wywołania serwera, withSuccessHandler(function) umożliwia określenie funkcji wywołania zwrotnego po stronie klienta, która ma być wykonywana po odpowiedzi serwera. Jeśli funkcja serwera zwraca wartość, interfejs API przekazuje ją nowej funkcji jako parametr.

Poniższy przykład pokazuje alert przeglądarki, gdy serwer odpowiada. Ten przykładowy kod wymaga autoryzacji, bo funkcja po stronie serwera uzyskuje dostęp do Twojego konta Gmail. Najprostszym sposobem autoryzacji skryptu jest ręczne uruchomienie funkcji getUnreadEmails() w edytorze skryptów przed załadowaniem strony. Możesz też wdrażać aplikację internetową jako „użytkownik uzyskujący dostęp do aplikacji internetowej”. W tym przypadku podczas wczytywania aplikacji zostanie wyświetlony monit o autoryzacji.

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>

Moduły obsługi błędów

Jeśli serwer nie odpowiada lub zgłasza błąd, funkcja withFailureHandler(function) umożliwia podanie modułu obsługi błędów zamiast modułu obsługi powodzenia. Argumentem jest obiekt Error (jeśli występuje).

Domyślnie, jeśli nie określisz modułu obsługi błędów, błędy będą rejestrowane w konsoli JavaScriptu. Aby zastąpić to zachowanie, wywołaj funkcję withFailureHandler(null) lub podaj element obsługi niepowodzenia, który nic nie robi.

Składnia metod obsługi błędów jest niemal identyczna jak w przypadku metod obsługi powodzenia, jak pokazuje ten przykład.

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>

Obiekty użytkownika

Możesz używać tego samego modułu obsługi sukcesu lub błędu w przypadku wielu wywołań serwera. Aby to zrobić, wywołaj funkcję withUserObject(object), aby określić obiekt, który zostanie przekazany do modułu obsługi jako drugi parametr. Ten „obiekt użytkownika” – nie należy mylić z klasą User – umożliwia reagowanie w kontekście, w którym klient skontaktował się z serwerem. Obiekty użytkownika nie są wysyłane na serwer, więc mogą być dowolne, np. funkcje, elementy DOM itp., bez ograniczeń dotyczących parametrów i wartości zwracanych przez wywołania serwera. Obiekty użytkownika nie mogą być jednak obiektami utworzonymi za pomocą operatora new.

W tym przykładzie kliknięcie jednego z 2 przycisków spowoduje zaktualizowanie tego przycisku za pomocą wartości z serwera, a drugi pozostanie bez zmian, mimo że oba mają ten sam kod obsługi sukcesu. W obiekcie onclick słowo kluczowe this odnosi się do obiektu 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>

Formularze

Jeśli wywołasz funkcję serwera z elementem form jako parametrem, formularz stanie się pojedynczym obiektem z nazwami pól jako kluczami i wartościami pól jako wartościami. Wszystkie wartości są konwertowane na ciągi znaków, z wyjątkiem zawartości pól danych wejściowych pliku, które stają się obiektami Blob.

W tym przykładzie formularz, w tym pole do wprowadzania plików, jest przetwarzany bez ponownego wczytywania strony. Plik jest przesyłany na Dysk Google, a następnie na stronie po stronie klienta jest drukowany adres URL pliku. W obiekcie onsubmit słowo kluczowe this odnosi się do samego formularza. Pamiętaj, że po załadowaniu strony wszystkie formularze mają domyślne działanie przesyłania wyłączone przez preventFormSubmit. Zapobiega to przekierowywaniu strony na nieprawidłowy adres URL w przypadku wyjątku.

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>

Uruchamiający skrypt

google.script.run jest narzędziem do tworzenia skryptu. Jeśli dodasz do niego moduł obsługi powodzenia, moduł obsługi błędów lub obiekt użytkownika, nie zmienisz istniejącego mechanizmu uruchamiania – zamiast tego otrzymasz nowy mechanizm uruchamiający skrypt z nowym działaniem.

Możesz użyć dowolnej kombinacji atrybutów withSuccessHandler(), withFailureHandler()withUserObject() w dowolnej kolejności. Możesz też wywołać dowolną z funkcji modyfikujących w ramach skryptu, który ma już ustawioną wartość. Nowa wartość po prostu zastąpi poprzednią.

W tym przykładzie ustawiono wspólny moduł obsługi błędów dla wszystkich 3 wywołań na serwer, ale 2 oddzielne moduły obsługi powodzenia:

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

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

Funkcje prywatne

Funkcje serwera, których nazwy kończą się podkreśleniem, są uznawane za prywatne. Funkcji tych nie można wywołać za pomocą funkcji google.script, a ich nazwy nigdy nie są wysyłane do klienta. Dzięki temu możesz ukryć szczegóły implementacji, które muszą być utrzymywane w tajnych na serwerze. google.script nie widzi też funkcji w bibliotekach ani w funkcjach, które nie są zadeklarowane na najwyższym poziomie skryptu.

W tym przykładzie w kodzie klienta dostępna jest funkcja getBankBalance(). Użytkownik, który sprawdzi kod źródłowy, może poznać jej nazwę, nawet jeśli jej nie wywołasz. Jednak funkcje deepSecret_()obj.objectMethod() są całkowicie niewidoczne dla klienta.

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>

Zmienianie rozmiaru okien w aplikacji Google Workspace

Rozmiar niestandardowych okienek dialogowych w Dokumentach, Arkuszach lub Formularzach Google można zmienić, wywołując metody google.script.hostsetWidth(width) lub setHeight(height) w kodzie po stronie klienta. (Aby ustawić początkowy rozmiar okna dialogowego, użyj metod HtmlOutput setWidth(width) i setHeight(height)). Pamiętaj, że po zmianie rozmiaru okna dialogowego nie jest ono ponownie wyśrodkowywane w oknie nadrzędnym, a nie można zmieniać rozmiaru pasków bocznych.

Zamknij okna dialogowe i paski boczne w  Google Workspace

Jeśli używasz usługi HTML do wyświetlania okna dialogowego lub paska bocznego w Dokumentach, Arkuszach lub Formularzach Google, nie możesz zamknąć interfejsu, wywołując funkcję window.close(). Zamiast tego musisz zadzwonić na numer google.script.host.close(). Przykład znajdziesz w sekcji o wyświetlaniu kodu HTML jako Google Workspace interfejsu użytkownika.

Przenoszenie aktywnego obszaru w przeglądarce Google Workspace

Aby w przeglądarce użytkownika przełączyć fokus z okna dialogowego lub paska bocznego z powrotem na edytor Dokumentów, Arkuszy lub Formularzy Google, po prostu wywołaj metodę google.script.host.editor.focus(). Ta metoda jest szczególnie przydatna w połączeniu z metodami usługi Dokumenty Document.setCursor(position)Document.setSelection(range).