บริการ 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() เบราว์เซอร์จะไปยังโค้ดบรรทัดถัดไปทันทีโดยไม่รอการตอบกลับ ซึ่งหมายความว่าการเรียกใช้ฟังก์ชันของเซิร์ฟเวอร์อาจไม่ดำเนินการตามลำดับที่คุณคาดหวังไว้ หากคุณเรียกใช้ฟังก์ชัน 2 ครั้งพร้อมกัน ก็จะไม่มีทางรู้ว่าฟังก์ชันใดจะทำงานก่อน ผลลัพธ์อาจแตกต่างกันไปในแต่ละครั้งที่คุณโหลดหน้าเว็บ ในสถานการณ์นี้เครื่องจัดการสำเร็จและเครื่องจัดการความล้มเหลวจะช่วยควบคุมการทำงานของโค้ด

google.script.run API อนุญาตให้มีการเรียกฟังก์ชันของเซิร์ฟเวอร์พร้อมกัน 10 ครั้ง หากคุณเรียกใช้เป็นครั้งที่ 11 ในขณะที่จำนวน 10 ยังคงทำงานอยู่ ฟังก์ชันของเซิร์ฟเวอร์จะล่าช้าจนกว่าหนึ่งใน 10 ตำแหน่งนี้จะว่าง ในทางปฏิบัติ คุณไม่ควรคำนึงถึงข้อจำกัดนี้ โดยเฉพาะอย่างยิ่งเนื่องจากเบราว์เซอร์ส่วนใหญ่จำกัดจำนวนคำขอที่ทำงานพร้อมกันในเซิร์ฟเวอร์เดียวกันอยู่แล้วโดยจะน้อยกว่า 10 คำขอ เช่น ใน Firefox ขีดจํากัดคือ 6 ในทำนองเดียวกัน เบราว์เซอร์ส่วนใหญ่จะหน่วงเวลาคำขอที่เกินมา ของเซิร์ฟเวอร์จนกว่าคำขอที่มีอยู่รายการหนึ่งจะเสร็จสมบูรณ์

พารามิเตอร์และค่าที่ส่งคืน

คุณสามารถเรียกใช้ฟังก์ชันของเซิร์ฟเวอร์ด้วยพารามิเตอร์จากไคลเอ็นต์ได้ ในทำนองเดียวกัน ฟังก์ชันของเซิร์ฟเวอร์จะแสดงผลค่าให้กับไคลเอ็นต์เป็นพารามิเตอร์ที่ส่งไปยังเครื่องจัดการสำเร็จได้

พารามิเตอร์ทางกฎหมายและค่าที่แสดงผลคือ JavaScript แบบพื้นฐาน เช่น Number, Boolean, String หรือ null รวมถึงออบเจ็กต์และอาร์เรย์ JavaScript ที่ประกอบด้วยไพรม์ อ็อบเจกต์ และอาร์เรย์ องค์ประกอบ form ภายในหน้าเว็บถือว่าเป็นพารามิเตอร์ทางกฎหมายด้วย แต่ต้องเป็นพารามิเตอร์เดียวของฟังก์ชัน และไม่ใช่ค่าที่ถูกต้องตามกฎหมายสำหรับแสดงผล คำขอล้มเหลวหากคุณพยายามส่ง Date, Function, องค์ประกอบ DOM นอกเหนือจาก form หรือประเภทที่ไม่อนุญาตอื่นๆ รวมถึงประเภทที่ไม่อนุญาตภายในออบเจ็กต์หรืออาร์เรย์ นอกจากนี้ ออบเจ็กต์ที่สร้างการอ้างอิงแบบวนจะล้มเหลวและฟิลด์ที่ไม่ได้กำหนดภายในอาร์เรย์จะกลายเป็น null

โปรดทราบว่าออบเจ็กต์ที่ส่งผ่านไปยังเซิร์ฟเวอร์จะกลายเป็นสำเนาของต้นฉบับ หากฟังก์ชันของเซิร์ฟเวอร์ได้รับออบเจ็กต์และเปลี่ยนแปลงพร็อพเพอร์ตี้ พร็อพเพอร์ตี้ในไคลเอ็นต์จะไม่ได้รับผลกระทบ

ตัวจัดการความสำเร็จ

เนื่องจากโค้ดฝั่งไคลเอ็นต์ไปยังบรรทัดถัดไปโดยไม่ต้องรอให้การเรียกใช้เซิร์ฟเวอร์เสร็จสิ้น withSuccessHandler(function) จึงช่วยให้คุณระบุฟังก์ชัน Callback ฝั่งไคลเอ็นต์ที่จะเรียกใช้เมื่อเซิร์ฟเวอร์ตอบสนองได้ หากฟังก์ชันเซิร์ฟเวอร์แสดงผลค่า API จะส่งค่าไปยังฟังก์ชันใหม่เป็นพารามิเตอร์

ตัวอย่างต่อไปนี้จะแสดงการแจ้งเตือนของเบราว์เซอร์เมื่อเซิร์ฟเวอร์ตอบสนอง โปรดทราบว่าตัวอย่างโค้ดนี้ต้องมีการให้สิทธิ์ เนื่องจากฟังก์ชันฝั่งเซิร์ฟเวอร์กำลังเข้าถึงบัญชี Gmail ของคุณ วิธีที่ง่ายที่สุดในการให้สิทธิ์สคริปต์คือเรียกใช้ฟังก์ชัน getUnreadEmails() ด้วยตนเองจากเครื่องมือแก้ไขสคริปต์ 1 ครั้งก่อนที่จะโหลดหน้า หรือเมื่อคุณทำให้เว็บแอปใช้งานได้ คุณจะเลือกเรียกใช้เป็น "ผู้ใช้ที่เข้าถึงเว็บแอป" ได้ ซึ่งในกรณีนี้ก็จะได้รับข้อความแจ้งให้ให้สิทธิ์เมื่อโหลดแอป

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) อนุญาตให้คุณระบุเครื่องจัดการความล้มเหลวแทนเครื่องจัดการสำเร็จ โดยการส่งออบเจ็กต์ Error (หากมี) เป็นอาร์กิวเมนต์

โดยค่าเริ่มต้น หากคุณไม่ได้ระบุตัวแฮนเดิลที่ล้มเหลว ระบบจะบันทึกความล้มเหลวลงในคอนโซล JavaScript หากต้องการลบล้างค่านี้ ให้เรียกใช้ withFailureHandler(null) หรือระบุตัวแฮนเดิลความล้มเหลวที่ไม่ดำเนินการใดๆ

ไวยากรณ์สำหรับเครื่องจัดการความล้มเหลวเกือบจะเหมือนกับเครื่องจัดการความสำเร็จตามที่แสดงในตัวอย่างนี้

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>

ออบเจ็กต์ผู้ใช้

คุณนำเครื่องจัดการที่สำเร็จหรือล้มเหลวเดิมมาใช้ซ้ำสำหรับการเรียกไปยังเซิร์ฟเวอร์หลายครั้งได้โดยการเรียกใช้ withUserObject(object) เพื่อระบุออบเจ็กต์ที่จะส่งไปยังเครื่องจัดการเป็นพารามิเตอร์ที่ 2 "ออบเจ็กต์ผู้ใช้" นี้ (ไม่ใช่ความสับสนกับคลาส User) นี้จะช่วยให้คุณตอบสนองต่อบริบทที่ไคลเอ็นต์ติดต่อกับเซิร์ฟเวอร์ได้ เนื่องจากออบเจ็กต์ผู้ใช้ไม่ได้ส่งไปยังเซิร์ฟเวอร์ จึงอาจเป็นได้เกือบทุกอย่าง ซึ่งรวมถึงฟังก์ชัน องค์ประกอบ DOM ฯลฯ โดยไม่มีข้อจำกัดเกี่ยวกับพารามิเตอร์และแสดงผลค่าสำหรับการเรียกเซิร์ฟเวอร์ แต่ออบเจ็กต์ผู้ใช้จะเป็นออบเจ็กต์ที่สร้างขึ้นด้วยโอเปอเรเตอร์ new ไม่ได้

ในตัวอย่างนี้ การคลิกปุ่มใดปุ่มหนึ่งจาก 2 ปุ่มจะอัปเดตปุ่มนั้นด้วยค่าจากเซิร์ฟเวอร์และปล่อยอีกปุ่มหนึ่งไว้ตามเดิม แม้ว่าปุ่มเหล่านั้นจะแชร์เครื่องจัดการสำเร็จเพียงปุ่มเดียว ภายในเครื่องจัดการ 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() คุณยังเรียกใช้ฟังก์ชันการแก้ไขใดๆ ในโปรแกรมเรียกใช้สคริปต์ที่มีการตั้งค่าไว้แล้วได้ด้วย ค่าใหม่จะลบล้างค่าก่อนหน้าเท่านั้น

ตัวอย่างนี้ตั้งค่าเครื่องจัดการความล้มเหลวที่พบบ่อยสำหรับการเรียกใช้เซิร์ฟเวอร์ทั้ง 3 รายการ แต่มีเครื่องจัดการความสำเร็จ 2 รายการแยกกัน ได้แก่

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 เอกสาร ชีต หรือฟอร์มจะปรับขนาดได้โดยเรียกใช้เมธอด 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 เอกสาร ชีต หรือฟอร์ม ให้เรียกใช้เมธอด google.script.host.editor.focus() วิธีการนี้มีประโยชน์อย่างยิ่งเมื่อใช้ร่วมกับเมธอดบริการเอกสาร Document.setCursor(position) และ Document.setSelection(range)