שירות HTML: תקשורת עם פונקציות שרת

google.script.run הוא ממשק API אסינכרוני של JavaScript בצד הלקוח, שמאפשר לדפי שירות 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(), הדפדפן ממשיך מיד לשורת הקוד הבאה בלי לחכות לתגובה. כלומר, יכול להיות שהקריאות לפונקציות השרת לא יבוצעו לפי הסדר שציפיתם לו. כשמבצעים שתי קריאות לפונקציות בו-זמנית, אין דרך לדעת איזו פונקציה תפעל ראשונה; התוצאה עשויה להשתנות בכל פעם שטוענים את הדף. במצב כזה, רכיבי handler של הצלחה ורכיבי handler של כשלים עוזרים לשלוט בזרימת הקוד.

ה-API של google.script.run מאפשר 10 קריאות בו-זמנית לפונקציות שרת. אם מבצעים קריאה 11 בזמן ש-10 עדיין פועלים, פונקציית השרת תתעכב עד שאחד מ-10 המקומות יתפנה. בפועל, לעיתים רחוקות צריך לחשוב על ההגבלה הזו, במיוחד כי רוב הדפדפנים כבר מגבילים את מספר הבקשות בו-זמנית לאותו שרת במספר נמוך מ-10. למשל, ב-Firefox, המגבלה היא 6. באופן דומה, רוב הדפדפנים משהים בקשות עודפות לשרת עד שאחת מהבקשות הקיימות מסתיימת.

פרמטרים וערכים מוחזרים

אפשר להפעיל פונקציית שרת עם פרמטרים מהלקוח. באופן דומה, פונקציית שרת יכולה להחזיר ערך ללקוח כפרמטר שמועבר אל handler הצלחה.

פרמטרים משפטיים וערכי החזרה הם רכיבי JavaScript כמו Number, Boolean, String או null, וגם אובייקטים ומערכים של JavaScript שמורכבים מפרימיטיבים, אובייקטים ומערכים. גם רכיב form בדף הוא חוקי כפרמטר, אבל הוא חייב להיות הפרמטר היחיד של הפונקציה, והוא לא חוקי כערך החזרה. בקשות נכשלות אם מנסים להעביר רכיב Date, Function, DOM מלבד form או סוג אסור אחר, כולל סוגים אסורים בתוך אובייקטים או מערכים. גם אובייקטים שיוצרים הפניות מעגליות ייכשלו, ושדות לא מוגדרים בתוך מערכים הופכים ל-null.

שימו לב שאובייקט שמועבר לשרת הופך לעותק של האובייקט המקורי. אם פונקציית שרת מקבלת אובייקט ומשנה את המאפיינים שלו, המאפיינים של הלקוח לא מושפעים.

רכיבי handler של הצלחות

מכיוון שהקוד בצד הלקוח ממשיך לשורה הבאה בלי לחכות לסיום קריאה לשרת, withSuccessHandler(function) מאפשר לציין פונקציית קריאה חוזרת בצד הלקוח שתרוץ כשהשרת מגיב. אם פונקציית השרת מחזירה ערך, ה-API מעביר את הערך לפונקציה החדשה כפרמטר.

בדוגמה הבאה מוצגת התראה של הדפדפן כשהשרת מגיב. שימו לב שדוגמת הקוד הזו דורשת הרשאה כי הפונקציה בצד השרת ניגשת לחשבון Gmail שלכם. הדרך הפשוטה ביותר לאשר את הסקריפט היא להריץ את הפונקציה getUnreadEmails() באופן ידני מעורך הסקריפטים פעם אחת לפני טעינת הדף. לחלופין, כאשר פורסים את אפליקציית האינטרנט, אפשר להפעיל אותה בתור 'המשתמש שניגש לאפליקציית האינטרנט'. במקרה כזה תתבקשו לקבל הרשאה כשתטען את האפליקציה.

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) את ה-handler של הכשלים במקום את ה-handler, כך שהאובייקט Error (אם קיים) מועבר כארגומנט.

כברירת מחדל, אם לא מציינים handler של כשלים, הכשלים נרשמים במסוף JavaScript. כדי לשנות את זה, קוראים ל-withFailureHandler(null) או מספקים handler של כשל שלא עושה כלום.

התחביר של מטפלים בכשלים כמעט זהה לתחביר של גורמים שמטפלים בכשלים, כפי שמוצג בדוגמה הזו.

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>

אובייקטים של משתמש

אפשר להשתמש שוב באותו handler של הצלחה או כישלון בקריאות מרובות לשרת על ידי קריאה ל-withUserObject(object) כדי לציין אובייקט שיועבר ל-handler כפרמטר שני. להבדיל בין "אובייקט המשתמש" הזה לבין המחלקה User, אפשר להגיב להקשר שבו הלקוח יצר קשר עם השרת. מכיוון שאובייקטים של משתמשים לא נשלחים לשרת, הם יכולים להיות כמעט כל דבר, כולל פונקציות, רכיבי DOM וכו', בלי הגבלות על פרמטרים וערכי החזרה לקריאות שרת. עם זאת, אי אפשר ליצור אובייקטים של משתמשים באמצעות האופרטור new.

בדוגמה הזו, לחיצה על אחד משני הלחצנים תעדכן את הלחצן בערך מהשרת, אבל הלחצן השני יישאר ללא שינוי למרות שיש להם handler אחד זהה להצלחה. בתוך ה-handler של 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 Drive ואז מדפיסה את כתובת ה-URL של הקובץ בדף בצד הלקוח. בתוך ה-handler של 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 בתור כלי לבניית 'סקריפט מריץ'. אם מוסיפים handler של הצלחה, handler של כשלים או אובייקט משתמש להרצת סקריפט, לא משנים את ההרצה הקיימת, אלא מקבלים חזרה רכיב חדש של הרצה בסקריפטים עם התנהגות חדשה.

אפשר להשתמש בכל שילוב ובכל סדר של withSuccessHandler(), withFailureHandler() ו-withUserObject(). אפשר גם לקרוא לכל אחת מפונקציות השינוי בהפעלה של סקריפט שכבר הוגדר לו ערך. הערך החדש מבטל את הערך הקודם.

בדוגמה הזו מוגדר handler משותף לכשלים לכל שלוש הקריאות לשרת, אבל שני handlers נפרדים להצלחה:

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 Docs, ב-Sheets או ב-Forms, על-ידי קריאה ל-methods google.script.host setWidth(width) או setHeight(height) בקוד בצד הלקוח. (כדי להגדיר את הגודל הראשוני של תיבת דו-שיח, משתמשים בשיטות HtmlOutput setWidth(width) ו-setHeight(height)). שימו לב שתיבות דו-שיח לא ממורכזות מחדש בחלון ההורה כשמשנים את הגודל, ואי אפשר לשנות את הגודל של סרגלי הצד.

סגירה של תיבות דו-שיח וסרגלי צד ב- Google Workspace

אם אתם משתמשים בשירות ה-HTML כדי להציג תיבת דו-שיח או סרגל צד ב-Google Docs , Sheets או Forms, לא תוכלו לסגור את הממשק על ידי שליחת קריאה ל-window.close(). במקום זאת, צריך לבצע קריאה ל-google.script.host.close(). לדוגמה, תוכלו לעיין בסעיף הצגת HTML כ Google Workspace ממשק משתמש.

העברת המיקוד של הדפדפן אל Google Workspace

כדי להעביר את המיקוד בדפדפן של המשתמש מתיבת דו-שיח או סרגל צד חזרה לעורך Google Docs, Sheets או Forms, פשוט קוראים לשיטה google.script.host.editor.focus(). השיטה הזו שימושית במיוחד בשילוב עם ה-methods של שירות המסמך Document.setCursor(position) ו-Document.setSelection(range).