บริการ HTML: สื่อสารกับฟังก์ชันของเซิร์ฟเวอร์

google.script.run คือ JavaScript API ฝั่งไคลเอ็นต์แบบไม่พร้อมกันซึ่งอนุญาตให้หน้าบริการ HTML เรียกใช้ฟังก์ชัน Apps Script ฝั่งเซิร์ฟเวอร์ได้ ตัวอย่างต่อไปนี้แสดงฟังก์ชันพื้นฐานของ google.script.run ซึ่งก็คือการเรียกใช้ฟังก์ชันในเซิร์ฟเวอร์จาก JavaScript ฝั่งไคลเอ็นต์

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

พารามิเตอร์และค่าการแสดงผล

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

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

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

เครื่องจัดการความสำเร็จ

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

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

โค้ด.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) หรือส่งตัวแฮนเดิลความล้มเหลวที่ไม่ได้ดำเนินการใดๆ

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

โค้ด.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

โค้ด.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 ที่ไม่ถูกต้องในกรณีที่มีข้อยกเว้น

โค้ด.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() เลย

โค้ด.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)