שירות 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, הדפדפן ממשיך מיד לשורה הבאה של הקוד בלי להמתין לתגובה. המשמעות היא שקריאות לפונקציות בשרת לא יבוצעו בסדר שציפיתם. אם מבצעים שתי קריאות לפונקציה בו-זמנית, אין דרך לדעת איזו פונקציה תפעל קודם. התוצאה עשויה להיות שונה בכל פעם שנטען הדף. במצב כזה, success handlers ו-failure handlers עוזרים לשלוט בזרימת הקוד.

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

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

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

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

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

גורמים שמטפלים בהצלחה

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

רכיבי handler של כשלים

אם השרת לא מגיב או מחזיר שגיאה, אפשר להשתמש ב-withFailureHandler(function) כדי לציין handler של כשל שיפעל במקום handler של הצלחה. אם מתרחשת שגיאה, ה-API מעביר את האובייקט Error כארגומנט ל-failure handler.

כברירת מחדל, אם לא מציינים handler לטיפול בשגיאות, השגיאות נרשמות ביומן בלוח 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>

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

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

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

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

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

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, ב-Google Sheets או ב-Forms על ידי קריאה לשיטות google.script.hostsetWidth(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. השיטה הזו שימושית במיוחד בשילוב עם השיטות Document serviceDocument.setCursor(position) ו-Document.setSelection(range).