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

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

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

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

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

הדפסת scriptlet

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

המשמעות של בריחה לפי הקשר היא ש-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' ?> מדפיס רק את הטקסט "שלום, עולם!"

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

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

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

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

קוד Apps Script ב-Scriptlet

סקריפטים לא מוגבלים להרצת 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>

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

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

יומן (בוצעה הערכה)

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