שירות 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).

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

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>

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

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

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

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

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

קוד Apps Script ב-scriptlets

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

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

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

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. */ ?>