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

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

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

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>

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

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

בריחה לפי הקשר מאפשרת ל-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>

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

אילוץ הדפסה של סקריפטים

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

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

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

אפשר גם להשתמש בקוד של 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>

לוגו (מוערך)

(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 (עם או בלי escaping לפי הקשר, בהתאם לסוג הסקריפט).

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

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

מכיוון שקוד מוערך שומר על מספרי שורות, יכול להיות שהערות בתוך סקריפטים יכולים להוסיף הערות לסקריפטים אחרים ואפילו לקוד 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. */ ?>