Layanan HTML: Berkomunikasi dengan Fungsi Server

google.script.run adalah JavaScript API sisi klien asinkron yang memungkinkan halaman layanan HTML memanggil fungsi Apps Script sisi server. Contoh berikut menunjukkan fungsi paling dasar dari google.script.run, yaitu memanggil fungsi di server dari JavaScript sisi klien.

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>

Jika Anda men-deploy skrip ini sebagai aplikasi web dan mengunjungi URL-nya, Anda tidak akan melihat apa pun, tetapi jika Anda melihat log, Anda akan melihat bahwa fungsi server doSomething() dipanggil.

Panggilan sisi klien ke fungsi sisi server bersifat asinkron: setelah browser meminta server menjalankan fungsi doSomething(), browser segera melanjutkan ke baris kode berikutnya tanpa menunggu respons. Artinya, panggilan fungsi server mungkin tidak dijalankan sesuai urutan yang Anda harapkan. Jika Anda melakukan dua panggilan fungsi secara bersamaan, tidak ada cara untuk mengetahui fungsi mana yang akan dijalankan terlebih dahulu; hasilnya mungkin berbeda setiap kali Anda memuat halaman. Dalam situasi ini, pengendali keberhasilan dan pengendali kegagalan akan membantu mengontrol alur kode Anda.

google.script.run API memungkinkan 10 panggilan serentak ke fungsi server. Jika Anda melakukan panggilan ke-11 saat 10 spot masih berjalan, fungsi server akan ditunda hingga salah satu dari 10 spot dikosongkan. Dalam praktiknya, Anda seharusnya tidak perlu memikirkan pembatasan ini, terutama karena sebagian besar browser sudah membatasi jumlah permintaan serentak ke server yang sama dengan jumlah yang lebih rendah dari 10. Di Firefox, misalnya, batasnya adalah 6. Sebagian besar browser juga menunda kelebihan permintaan server hingga salah satu permintaan yang ada selesai.

Parameter dan nilai yang ditampilkan

Anda dapat memanggil fungsi server dengan parameter dari klien. Demikian pula, fungsi server dapat menampilkan nilai ke klien sebagai parameter yang diteruskan ke pengendali keberhasilan.

Parameter hukum dan nilai yang ditampilkan adalah dasar JavaScript seperti Number, Boolean, String, atau null, serta objek dan array JavaScript yang terdiri dari primitif, objek, dan array. Elemen form di dalam halaman juga sah sebagai parameter, tetapi harus menjadi satu-satunya parameter fungsi, dan tidak sah sebagai nilai yang ditampilkan. Permintaan akan gagal jika Anda mencoba meneruskan Date, Function, elemen DOM selain form, atau jenis yang dilarang lainnya, termasuk jenis yang dilarang di dalam objek atau array. Objek yang membuat referensi melingkar juga akan gagal, dan kolom yang tidak ditentukan dalam array akan menjadi null.

Perhatikan bahwa objek yang diteruskan ke server akan menjadi salinan aslinya. Jika fungsi server menerima objek dan mengubah propertinya, properti pada klien tidak akan terpengaruh.

Pengendali keberhasilan

Karena kode sisi klien berlanjut ke baris berikutnya tanpa menunggu panggilan server selesai, withSuccessHandler(function) memungkinkan Anda menentukan fungsi callback sisi klien yang akan dijalankan saat server merespons. Jika fungsi server menampilkan nilai, API akan meneruskan nilai tersebut ke fungsi baru sebagai parameter.

Contoh berikut menampilkan peringatan browser saat server merespons. Perhatikan bahwa contoh kode ini memerlukan otorisasi karena fungsi sisi server mengakses akun Gmail Anda. Cara termudah untuk memberikan otorisasi pada skrip adalah dengan menjalankan fungsi getUnreadEmails() secara manual dari editor skrip satu kali sebelum Anda memuat halaman. Atau, saat men-deploy aplikasi web, Anda dapat memilih untuk menjalankannya sebagai “pengguna yang mengakses aplikasi web”, dalam hal ini Anda akan diminta untuk melakukan otorisasi saat memuat aplikasi.

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>

Pengendali kegagalan

Jika server gagal merespons atau menampilkan error, withFailureHandler(function) memungkinkan Anda menentukan pengendali kegagalan, bukan pengendali yang berhasil, dengan objek Error (jika ada) yang diteruskan sebagai argumen.

Secara default, jika Anda tidak menentukan pengendali kegagalan, kegagalan akan dicatat ke konsol JavaScript. Untuk menggantinya, panggil withFailureHandler(null) atau berikan pengendali kegagalan yang tidak melakukan apa pun.

Sintaksis untuk pengendali kegagalan hampir identik dengan pengendali yang berhasil, seperti yang ditunjukkan dalam contoh ini.

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>

Objek pengguna

Anda dapat menggunakan kembali pengendali yang berhasil atau gagal untuk beberapa panggilan ke server dengan memanggil withUserObject(object) untuk menentukan objek yang akan diteruskan ke pengendali sebagai parameter kedua. “Objek pengguna” ini — yang tidak sama dengan class User — memungkinkan Anda merespons konteks saat klien menghubungi server. Karena tidak dikirim ke server, objek pengguna dapat berupa hampir semua hal, termasuk fungsi, elemen DOM, dan sebagainya, tanpa batasan pada parameter dan nilai yang ditampilkan untuk panggilan server. Namun, objek pengguna tidak boleh berupa objek yang dibuat dengan operator new.

Dalam contoh ini, mengklik salah satu dari dua tombol akan memperbarui tombol tersebut dengan nilai dari server sementara tombol lainnya tidak berubah, meskipun satu pengendali yang berhasil digunakan bersama. Di dalam pengendali onclick, kata kunci this mengacu pada button itu sendiri.

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>

Formulir

Jika Anda memanggil fungsi server dengan elemen form sebagai parameter, formulir tersebut akan menjadi satu objek dengan nama kolom sebagai kunci dan nilai kolom sebagai nilai. Semua nilai dikonversi menjadi string, kecuali untuk konten kolom input file, yang menjadi objek Blob.

Contoh ini memproses formulir, termasuk kolom input file, tanpa memuat ulang halaman; contoh ini akan mengupload file ke Google Drive, lalu mencetak URL untuk file tersebut di halaman sisi klien. Di dalam pengendali onsubmit, kata kunci this mengacu pada formulir itu sendiri. Perhatikan bahwa setelah memuat semua formulir di halaman, tindakan kirim default dinonaktifkan oleh preventFormSubmit. Tindakan ini akan mencegah halaman dialihkan ke URL yang tidak akurat jika terjadi pengecualian.

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>

Runner skrip

Anda dapat menganggap google.script.run sebagai builder untuk “runner skrip”. Jika menambahkan pengendali sukses, pengendali kegagalan, atau objek pengguna ke runner skrip, Anda tidak akan mengubah runner yang sudah ada. Sebagai gantinya, Anda akan mendapatkan kembali runner skrip baru dengan perilaku baru.

Anda dapat menggunakan kombinasi apa pun dan urutan withSuccessHandler(), withFailureHandler(), dan withUserObject(). Anda juga dapat memanggil salah satu fungsi yang mengubah pada runner skrip yang sudah memiliki kumpulan nilai. Nilai baru hanya menggantikan nilai sebelumnya.

Contoh ini menetapkan pengendali kegagalan umum untuk ketiga panggilan server, tetapi dua pengendali berhasil yang terpisah:

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

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

Fungsi pribadi

Fungsi server yang namanya diakhiri dengan garis bawah akan dianggap pribadi. Fungsi ini tidak dapat dipanggil oleh google.script dan namanya tidak pernah dikirim ke klien. Dengan demikian, Anda dapat menggunakannya untuk menyembunyikan detail implementasi yang perlu dirahasiakan di server. google.script juga tidak dapat melihat fungsi dalam library dan fungsi yang tidak dideklarasikan di tingkat atas skrip.

Dalam contoh ini, fungsi getBankBalance() tersedia dalam kode klien; pengguna yang memeriksa kode sumber dapat menemukan namanya meskipun Anda tidak memanggilnya. Namun, fungsi deepSecret_() dan obj.objectMethod() sama sekali tidak terlihat oleh klien.

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>

Mengubah ukuran dialog di Google Workspace aplikasi

Kotak dialog kustom di Google Dokumen, Spreadsheet, atau Formulir dapat diubah ukurannya dengan memanggil metode google.script.host setWidth(width) atau setHeight(height) dalam kode sisi klien. (Untuk menetapkan ukuran awal dialog, gunakan metode HtmlOutput setWidth(width) dan setHeight(height).) Perhatikan bahwa dialog tidak dipusatkan kembali di jendela induk saat ukurannya diubah, dan ukuran sidebar tidak dapat diubah.

Menutup dialog dan sidebar di Google Workspace

Jika Anda menggunakan layanan HTML untuk menampilkan kotak dialog atau sidebar di Google Dokumen, Spreadsheet, atau Formulir, Anda tidak dapat menutup antarmuka dengan memanggil window.close(). Sebagai gantinya, Anda harus memanggil google.script.host.close(). Misalnya, lihat bagian tentang menayangkan HTML sebagai Google Workspace antarmuka pengguna.

Memindahkan fokus browser dalam Google Workspace

Untuk mengalihkan fokus di browser pengguna dari dialog atau sidebar kembali ke editor Google Dokumen, Spreadsheet, atau Formulir, cukup panggil metode google.script.host.editor.focus(). Metode ini sangat berguna saat dikombinasikan dengan metode Layanan dokumen Document.setCursor(position) dan Document.setSelection(range).