שירות 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. באופן דומה, רוב הדפדפנים משהים בקשות עודפות לשרת עד שאחת מהבקשות הקיימות מסתיימת.

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

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

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

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

פונקציות טיפול בהצלחה

מכיוון שהקוד בצד הלקוח ממשיך לשורה הבאה בלי להמתין להשלמת קריאה לשרת, אפשר להשתמש ב-withSuccessHandler(function) כדי לציין פונקציית קריאה חוזרת (callback) בצד הלקוח שתופעל כשהשרת יגיב. אם פונקציית השרת מחזירה ערך, ה-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, כך שהאובייקט 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) כדי לציין אובייקט שיוענק לטיפול כפרמטר שני. "אובייקט המשתמש" הזה – שאין לבלבל אותו עם הכיתה User – מאפשר לכם להגיב להקשר שבו הלקוח יצר קשר עם השרת. מכיוון שאובייקטים של משתמשים לא נשלחים לשרת, הם יכולים להיות כמעט כל דבר, כולל פונקציות, רכיבי DOM וכו', ללא ההגבלות על פרמטרים וערכי החזרה של קריאות לשרת. עם זאת, אובייקטים של משתמשים לא יכולים להיות אובייקטים שנוצרו באמצעות האופרטור new.

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

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

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

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 באמצעות קריאה לשיטות 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(). השיטה הזו שימושית במיוחד בשילוב עם ה-methods של שירות המסמך Document.setCursor(position) ו-Document.setSelection(range).