Dịch vụ HTML: Giao tiếp với các chức năng máy chủ

google.script.run là một API JavaScript phía máy khách không đồng bộ, cho phép các trang dịch vụ HTML gọi các hàm Apps Script phía máy chủ. Ví dụ sau đây cho thấy chức năng cơ bản nhất của google.script.rungọi một hàm trên máy chủ từ JavaScript phía máy khách.

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>

Nếu triển khai tập lệnh này dưới dạng một ứng dụng web và truy cập URL của tập lệnh này, bạn sẽ không thấy bất cứ điều gì, nhưng nếu xem nhật ký, bạn sẽ thấy hàm máy chủ doSomething() đã được gọi.

Các lệnh gọi phía máy khách đến các hàm phía máy chủ không đồng bộ: sau khi trình duyệt yêu cầu máy chủ chạy hàm doSomething(), trình duyệt sẽ tiếp tục ngay lập tức đến dòng mã tiếp theo mà không cần chờ phản hồi. Điều này có nghĩa là các lệnh gọi hàm máy chủ có thể không thực thi theo thứ tự bạn dự kiến. Nếu bạn thực hiện 2 lệnh gọi hàm cùng lúc, không có cách nào để biết hàm nào sẽ chạy trước; kết quả có thể khác nhau mỗi lần bạn tải trang. Trong trường hợp này, trình xử lý thành côngtrình xử lý lỗi giúp kiểm soát luồng mã của bạn.

API google.script.run cho phép 10 lệnh gọi đồng thời đến các hàm máy chủ. Nếu bạn thực hiện lệnh gọi thứ 11 trong khi 10 vị trí vẫn đang chạy, thì chức năng máy chủ sẽ bị trì hoãn cho đến khi một trong 10 vị trí được giải phóng. Trong thực tế, bạn hiếm khi phải suy nghĩ về hạn chế này, đặc biệt vì hầu hết các trình duyệt đã giới hạn số lượng yêu cầu đồng thời đến cùng một máy chủ ở mức thấp hơn 10. Ví dụ: trong Firefox, giới hạn là 6. Tương tự, hầu hết các trình duyệt đều trì hoãn các yêu cầu máy chủ quá mức cho đến khi một trong các yêu cầu hiện có đã hoàn tất.

Tham số và giá trị trả về

Bạn có thể gọi hàm máy chủ bằng các tham số từ ứng dụng. Tương tự, hàm máy chủ có thể trả về một giá trị cho ứng dụng dưới dạng tham số được chuyển đến trình xử lý thành công.

Thông số pháp lý và giá trị trả về là các dữ liệu gốc JavaScript như Number, Boolean, String hoặc null, cũng như các đối tượng và mảng JavaScript bao gồm các dữ liệu gốc, đối tượng và mảng. Phần tử form trong trang cũng hợp pháp dưới dạng tham số, nhưng phải là tham số duy nhất của hàm và không hợp lệ khi là giá trị trả về. Yêu cầu không thành công nếu bạn cố truyền phần tử Date, Function, DOM bên cạnh form hoặc loại bị cấm khác, bao gồm cả các loại bị cấm trong đối tượng hoặc mảng. Các đối tượng tạo tham chiếu vòng tròn cũng sẽ không thành công và các trường không xác định trong mảng sẽ trở thành null.

Lưu ý rằng một đối tượng được chuyển đến máy chủ sẽ trở thành bản sao của đối tượng gốc. Nếu hàm máy chủ nhận được một đối tượng và thay đổi các thuộc tính của đối tượng đó, thì các thuộc tính trên ứng dụng sẽ không bị ảnh hưởng.

Trình xử lý thành công

Vì mã phía máy khách tiếp tục dòng tiếp theo mà không cần chờ lệnh gọi máy chủ hoàn tất, nên withSuccessHandler(function) cho phép bạn chỉ định hàm callback phía máy khách để chạy khi máy chủ phản hồi. Nếu hàm máy chủ trả về một giá trị, thì API sẽ chuyển giá trị đó đến hàm mới dưới dạng tham số.

Ví dụ sau đây hiển thị cảnh báo của trình duyệt khi máy chủ phản hồi. Xin lưu ý rằng mã mẫu này cần được cho phép vì hàm phía máy chủ đang truy cập vào tài khoản Gmail của bạn. Cách đơn giản nhất để uỷ quyền tập lệnh là chạy hàm getUnreadEmails() theo cách thủ công trong trình chỉnh sửa tập lệnh một lần trước khi bạn tải trang. Ngoài ra, khi triển khai ứng dụng web, bạn có thể chọn thực thi hoạt động dưới dạng "người dùng truy cập vào ứng dụng web", trong trường hợp đó, bạn sẽ được nhắc uỷ quyền khi tải ứng dụng.

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>

Trình xử lý lỗi

Trong trường hợp máy chủ không phản hồi hoặc báo lỗi, withFailureHandler(function) sẽ cho phép bạn chỉ định trình xử lý lỗi thay vì trình xử lý thành công, với đối tượng Error (nếu có) được truyền dưới dạng đối số.

Theo mặc định, nếu bạn không chỉ định trình xử lý lỗi, các lỗi sẽ được ghi lại vào bảng điều khiển JavaScript. Để ghi đè giá trị này, hãy gọi withFailureHandler(null) hoặc cung cấp một trình xử lý lỗi mà không làm gì.

Cú pháp cho trình xử lý lỗi gần giống với trình xử lý thành công, như trong ví dụ này.

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>

Đối tượng người dùng

Bạn có thể sử dụng lại cùng một trình xử lý thành công hoặc không thành công cho nhiều lệnh gọi đến máy chủ bằng cách gọi withUserObject(object) để chỉ định một đối tượng sẽ được chuyển đến trình xử lý dưới dạng tham số thứ hai. “Đối tượng người dùng” này – đừng nhầm với lớp User – cho phép bạn phản hồi ngữ cảnh mà ứng dụng đã liên lạc với máy chủ. Vì đối tượng người dùng không được gửi đến máy chủ, nên đối tượng đó có thể là bất kỳ thứ gì, bao gồm cả hàm, phần tử DOM, v.v. mà không bị hạn chế về tham số và giá trị trả về đối với lệnh gọi máy chủ. Tuy nhiên, đối tượng người dùng không thể là đối tượng được tạo bằng toán tử new.

Trong ví dụ này, thao tác nhấp vào một trong hai nút sẽ cập nhật nút đó bằng một giá trị từ máy chủ trong khi vẫn giữ nguyên nút còn lại, mặc dù các nút đó dùng chung một trình xử lý thành công. Bên trong trình xử lý onclick, từ khoá this tham chiếu đến chính 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>

Biểu mẫu

Nếu bạn gọi một hàm máy chủ có phần tử form dưới dạng tham số, thì biểu mẫu sẽ trở thành một đối tượng duy nhất với tên trường là khoá và giá trị của trường là giá trị. Tất cả các giá trị đều được chuyển đổi thành chuỗi, ngoại trừ nội dung của các trường nhập tệp sẽ trở thành đối tượng Blob.

Ví dụ này xử lý một biểu mẫu, bao gồm cả trường nhập dữ liệu tệp mà không cần tải lại trang. Ví dụ này sẽ tải tệp lên Google Drive, sau đó in URL của tệp trên trang phía máy khách. Bên trong trình xử lý onsubmit, từ khoá this tham chiếu đến chính biểu mẫu đó. Xin lưu ý rằng khi tải tất cả các biểu mẫu trên trang, preventFormSubmit sẽ vô hiệu hoá thao tác gửi mặc định. Điều này ngăn trang chuyển hướng đến một URL không chính xác trong trường hợp có trường hợp ngoại lệ.

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>

Trình chạy tập lệnh

Bạn có thể xem google.script.run là một trình tạo cho "trình chạy tập lệnh". Nếu thêm trình xử lý thành công, trình xử lý lỗi hoặc đối tượng người dùng vào trình chạy tập lệnh, bạn sẽ không thay đổi trình chạy hiện có; thay vào đó, bạn sẽ nhận lại trình chạy tập lệnh mới với hành vi mới.

Bạn có thể sử dụng tổ hợp bất kỳ và thứ tự bất kỳ trong số withSuccessHandler(), withFailureHandler()withUserObject(). Bạn cũng có thể gọi bất kỳ hàm sửa đổi nào trên trình chạy tập lệnh đã có tập hợp giá trị. Giá trị mới chỉ ghi đè giá trị trước đó.

Ví dụ này đặt một trình xử lý lỗi phổ biến cho cả ba lệnh gọi máy chủ, nhưng có hai trình xử lý thành công riêng biệt:

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

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

Hàm riêng tư

Hàm máy chủ có tên kết thúc bằng dấu gạch dưới được coi là hàm riêng tư. google.script không thể gọi các hàm này và tên của các hàm này không bao giờ được gửi đến ứng dụng. Do đó, bạn có thể sử dụng các hàm đó để ẩn thông tin triển khai cần được giữ bí mật trên máy chủ. google.script cũng không thể thấy các hàm trong thư viện và các hàm không được khai báo ở cấp cao nhất của tập lệnh.

Trong ví dụ này, hàm getBankBalance() có sẵn trong mã ứng dụng khách; người dùng kiểm tra mã nguồn của bạn có thể khám phá tên hàm ngay cả khi bạn không gọi hàm đó. Tuy nhiên, ứng dụng sẽ hoàn toàn không thấy các hàm 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>

Đổi kích thước hộp thoại trong Google Workspace ứng dụng

Bạn có thể đổi kích thước hộp thoại tuỳ chỉnh trong Google Tài liệu, Trang tính hoặc Biểu mẫu bằng cách gọi phương thức google.script.host setWidth(width) hoặc setHeight(height) trong mã phía máy khách. (Để đặt kích thước ban đầu của hộp thoại, hãy dùng phương thức HtmlOutput setWidth(width)setHeight(height).) Xin lưu ý rằng hộp thoại không căn giữa lại trong cửa sổ mẹ khi được đổi kích thước, cũng như không thể đổi kích thước thanh bên.

Đóng hộp thoại và thanh bên trong Google Workspace

Nếu sử dụng dịch vụ HTML để hiển thị hộp thoại hoặc thanh bên trong Google Tài liệu, Trang tính hoặc Biểu mẫu, thì bạn không thể đóng giao diện bằng cách gọi window.close(). Thay vào đó, bạn phải gọi google.script.host.close(). Để biết ví dụ, hãy xem phần trình bày về việc phân phát HTML dưới dạng Google Workspace giao diện người dùng.

Đang di chuyển tiêu điểm của trình duyệt trong Google Workspace

Để chuyển tiêu điểm trong trình duyệt của người dùng từ hộp thoại hoặc thanh bên trở lại trình chỉnh sửa Google Tài liệu, Trang tính hoặc Biểu mẫu, chỉ cần gọi phương thức google.script.host.editor.focus(). Phương thức này đặc biệt hữu ích khi kết hợp với các phương thức Dịch vụ tài liệu Document.setCursor(position)Document.setSelection(range).