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

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

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

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

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

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

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

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

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

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

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.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, צריך פשוט לקרוא ל-method google.script.host.editor.focus(). השיטה הזו שימושית במיוחד בשילוב עם השיטות של שירות מסמכים Document.setCursor(position) ו-Document.setSelection(range).