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

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

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

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

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

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

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

הדפסת scriptlet

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

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

בדוגמה הזו, ה-scriptlet הראשון להדפסה מפיק מחרוזת ישירות; זה ולאחר מכן סקריפט סטנדרטי שמגדיר מערך ולולאה, ואחריו 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. לדוגמה, ה-scriptlet <?= 'Hello, world!'; 'abc' ?> בלבד "שלום, עולם!"

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

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

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

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