שירות HTML: HTML בתבנית

אפשר לשלב קוד של Apps Script ו-HTML כדי ליצור דפים דינמיים במאמץ מינימלי. אם השתמשתם בשפת תבניות שמשלבת קוד ו-HTML, כמו PHP,‏ ASP או JSP, התחביר אמור להיות מוכר לכם.

קובצי סקריפטים

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

אם מדביקים את הדוגמה הבאה בעורך הסקריפטים, התוכן של התג <?= ... ?> (סקריפטט להדפסה) יופיע בכתב נטוי. הקוד הנטוי הזה פועל בשרת לפני שהדף מוצג למשתמש. מאחר שקוד הסקריפטלט מופעל לפני שהדף מוצג, הוא יכול לפעול רק פעם אחת לכל דף. בניגוד ל-JavaScript בצד הלקוח או לפונקציות של Apps Script שמפעילים דרך google.script.run, סקריפטלטים לא יכולים לפעול שוב אחרי שהדף נטען.

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    Hello, World! The time is <?= new Date() ?>.
  </body>
</html>

חשוב לדעת שהפונקציה doGet() ל-HTML לפי תבנית שונה מהדוגמאות ליצירה והצגה של HTML בסיסי. הפונקציה שמוצגת כאן יוצרת אובייקט HtmlTemplate מקובץ ה-HTML, ואז קוראת לשיטה evaluate() שלו כדי להריץ את הסקריפטים הקטנים ולהמיר את התבנית לאובייקט HtmlOutput שהסקריפט יכול להציג למשתמש.

סקריפטים רגילים

סקריפטים רגילים, שמשתמשים ב-syntax <? ... ?>, מריצים קוד בלי להפיק תוכן לדף באופן מפורש. עם זאת, כפי שמוצג בדוגמה הזו, התוצאה של הקוד בתוך סקריפטון עדיין יכולה להשפיע על תוכן ה-HTML מחוץ לסקריפטון:

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <? if (true) { ?>
      <p>This will always be served!</p>
    <? } else  { ?>
      <p>This will never be served.</p>
    <? } ?>
  </body>
</html>

הדפסת סקריפטים קצרים

הדפסת סקריפטים קצרים שמשתמשים בתחביר <?= ... ?>, מפיקה את התוצאות של הקוד שלהם בדף באמצעות בריחה לפי הקשר.

בריחה לפי הקשר פירושה ש-Apps Script עוקב אחרי ההקשר של הפלט בדף – בתוך מאפיין HTML, בתוך תג script בצד הלקוח או בכל מקום אחר – ומוסיף באופן אוטומטי תווים לבריחה כדי להגן מפני התקפות של סקריפטים חוצי-אתרים (XSS).

בדוגמה הזו, ה-scriptlet הראשון להדפסה מפיק מחרוזת ישירות. אחריו מופיע סקריפטיט סטנדרטי שמגדיר מערך ולולאה, ואחריו סקריפט הדפסה נוסף לפלט של תוכן המערך.

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <?= 'My favorite Google products:' ?>
    <? var data = ['Gmail', 'Docs', 'Android'];
      for (var i = 0; i < data.length; i++) { ?>
        <b><?= data[i] ?></b>
    <? } ?>
  </body>
</html>

שימו לב שסקריפט להדפסה מפיק רק את הערך של ההצהרה הראשונה שלו. כל ההצהרות הנותרות פועלות כאילו הן נכללו בסקריפט רגיל. כך, לדוגמה, הסקריפט <?= 'Hello, world!'; 'abc' ?> מדפיס רק "שלום, עולם!"

הדפסה בכפייה של סקריפטים קצרים

סקריפטים לצורך הדפסה בכפייה, שמשתמשים בתחביר <?!= ... ?>, דומים לסקריפטים לצורך הדפסה, מלבד העובדה שהם נמנעים מהימלטה לפי הקשר.

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

ככלל, מומלץ להשתמש בסקריפטים להדפסה במקום בסקריפטים להדפסה בכפייה, אלא אם אתם יודעים שאתם צריכים להדפיס HTML או JavaScript ללא שינוי.

קוד Apps Script ב-סקריפטים

סקריפטים קצרים לא מוגבלים להרצת JavaScript רגילה. אפשר גם להשתמש באחת משלוש השיטות הבאות כדי לתת לתבניות גישה לנתונים של Apps Script.

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

קריאה לפונקציות של Apps Script מתבנית

סקריפטים קטנים יכולים להפעיל כל פונקציה שמוגדרת בספרייה או בקובץ קוד של Apps Script. בדוגמה הזו מוצגת דרך אחת לשלוף נתונים מגיליון אלקטרוני לתבנית, ואז לבנות טבלת HTML מהנתונים.

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

function getData() {
  return SpreadsheetApp
      .openById('1234567890abcdefghijklmnopqrstuvwxyz')
      .getActiveSheet()
      .getDataRange()
      .getValues();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <? var data = getData(); ?>
    <table>
      <? for (var i = 0; i < data.length; i++) { ?>
        <tr>
          <? for (var j = 0; j < data[i].length; j++) { ?>
            <td><?= data[i][j] ?></td>
          <? } ?>
        </tr>
      <? } ?>
    </table>
  </body>
</html>

קריאה ישירה ל-Apps Script API

אפשר גם להשתמש בקוד Apps Script ישירות ב-scriptlets. בדוגמה הזו מתקבלת אותה תוצאה כמו בדוגמה הקודמת, על ידי טעינת הנתונים בתבנית עצמה ולא דרך פונקציה נפרדת.

Code.gs

function doGet() {
  return HtmlService
      .createTemplateFromFile('Index')
      .evaluate();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <? var data = SpreadsheetApp
        .openById('1234567890abcdefghijklmnopqrstuvwxyz')
        .getActiveSheet()
        .getDataRange()
        .getValues(); ?>
    <table>
      <? for (var i = 0; i < data.length; i++) { ?>
        <tr>
          <? for (var j = 0; j < data[i].length; j++) { ?>
            <td><?= data[i][j] ?></td>
          <? } ?>
        </tr>
      <? } ?>
    </table>
  </body>
</html>

דחיפת משתנים לתבניות

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

Code.gs

function doGet() {
  var t = HtmlService.createTemplateFromFile('Index');
  t.data = SpreadsheetApp
      .openById('1234567890abcdefghijklmnopqrstuvwxyz')
      .getActiveSheet()
      .getDataRange()
      .getValues();
  return t.evaluate();
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <table>
      <? for (var i = 0; i < data.length; i++) { ?>
        <tr>
          <? for (var j = 0; j < data[i].length; j++) { ?>
            <td><?= data[i][j] ?></td>
          <? } ?>
        </tr>
      <? } ?>
    </table>
  </body>
</html>

ניפוי באגים בתבניות

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

אם לא ברור איך התבנית מפרשת את הסקריפטים, שתי שיטות לניפוי באגים בכיתה HtmlTemplate יכולות לעזור לכם להבין טוב יותר מה קורה.

getCode()

הפונקציה getCode() מחזירה מחרוזת שמכילה את הקוד שהשרת יוצר מהתבנית. אם תתעדו את הקוד ואז תדביקו אותו בעורך הסקריפט, תוכלו להריץ אותו ולאתר בו באגים כמו קוד רגיל של Apps Script.

זו התבנית הפשוטה ששוב מציגה רשימה של מוצרי Google, ואחריה את התוצאה של getCode():

Code.gs

function myFunction() {
  Logger.log(HtmlService
      .createTemplateFromFile('Index')
      .getCode());
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <?= 'My favorite Google products:' ?>
    <? var data = ['Gmail', 'Docs', 'Android'];
      for (var i = 0; i < data.length; i++) { ?>
        <b><?= data[i] ?></b>
    <? } ?>
  </body>
</html>

LOG (EVALUATED)

(function() { var output = HtmlService.initTemplate(); output._ =  '<!DOCTYPE html>\n';
  output._ =  '<html>\n' +
    '  <head>\n' +
    '    <base target=\"_top\">\n' +
    '  </head>\n' +
    '  <body>\n' +
    '    '; output._$ =  'My favorite Google products:' ;
  output._ =  '    ';  var data = ['Gmail', 'Docs', 'Android'];
        for (var i = 0; i < data.length; i++) { ;
  output._ =  '        <b>'; output._$ =  data[i] ; output._ =  '</b>\n';
  output._ =  '    ';  } ;
  output._ =  '  </body>\n';
  output._ =  '</html>';
  /* End of user code */
  return output.$out.append('');
})();

getCodeWithComments()

הפונקציה getCodeWithComments() דומה ל-getCode(), אבל מחזירה את הקוד שנבדק כתגובות שמופיעות לצד התבנית המקורית.

מעבר על הקוד שנבדק

הדבר הראשון שרואים בכל אחת מהדוגמאות של הקוד שנבדק הוא האובייקט המשתמע output שנוצר על ידי השיטה HtmlService.initTemplate(). השיטה הזו לא מתועדת כי צריך להשתמש בה רק בתבניות. output הוא אובייקט מיוחד מסוג HtmlOutput עם שני מאפיינים עם שמות לא רגילים, _ ו-_$, שהם קיצור דרך לקריאה ל-append() ול-appendUntrusted().

ל-output יש עוד מאפיין מיוחד אחד, $out, שמתייחס לאובייקט HtmlOutput רגיל שאין לו את המאפיינים המיוחדים האלה. התבנית מחזירה את האובייקט הרגיל הזה בסוף הקוד.

עכשיו, אחרי שהבנתם את התחביר הזה, יהיה לכם קל להבין את שאר הקוד. תוכן HTML מחוץ לסקריפטים קצרים (כמו התג b) מצורף באמצעות output._ = (ללא בריחה מהקשר), וסקריפטים קצרים מצורפים כ-JavaScript (עם או בלי בריחה מהקשר, בהתאם לסוג הסקריפט הקצר).

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

היררכיית התגובות

מאחר שמספרי השורות נשמרים בקוד המוערך, אפשר להוסיף תגובות בתוך סקריפטלים כדי להסיר תגובות מסקריפטלים אחרים ואפילו מקוד HTML. בדוגמאות האלה אפשר לראות כמה השפעות מפתיעות של תגובות:

<? var x; // a comment ?> This sentence won't print because a comment begins inside a scriptlet on the same line.

<? var y; // ?> <?= "This sentence won't print because a comment begins inside a scriptlet on the same line.";
output.append("This sentence will print because it's on the next line, even though it's in the same scriptlet.”) ?>

<? doSomething(); /* ?>
This entire block is commented out,
even if you add a */ in the HTML
or in a <script> */ </script> tag,
<? until you end the comment inside a scriptlet. */ ?>