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

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

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

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

העברת המיקוד בדפדפן פנימה

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