บริการ 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 รายการพร้อมกัน คุณจะไม่มีทางทราบว่าฟังก์ชันใดทำงานก่อน ผลลัพธ์อาจแตกต่างกันในแต่ละครั้งที่คุณโหลดหน้าเว็บ ในกรณีนี้ Success Handler และ Failure Handler จะช่วยควบคุมโฟลว์ของโค้ด

google.script.run API อนุญาตให้มีการเรียกฟังก์ชันเซิร์ฟเวอร์พร้อมกัน 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 จะส่งค่านั้นไปยังฟังก์ชัน callback เป็นพารามิเตอร์

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

ตัวอย่างนี้จะประมวลผลแบบฟอร์ม รวมถึงช่องป้อนข้อมูลไฟล์ โดยไม่ต้องโหลดหน้าเว็บซ้ำ จากนั้นจะอัปโหลดไฟล์ไปยัง Google ไดรฟ์ แล้วพิมพ์ URL ของไฟล์ในหน้าฝั่งไคลเอ็นต์ ภายใน onsubmit handler คีย์เวิร์ด 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 ชีต หรือฟอร์มได้โดยเรียกใช้เมธอด 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 service Document.setCursor(position) และ Document.setSelection(range)