HTML 服務:範本 HTML

您可以在最輕鬆的情況下,混合使用 Apps Script 程式碼和 HTML 產生動態網頁。如果您使用的範本語言混合了程式碼與 HTML,例如 PHP、ASP 或 JSP,則該語法應該不陌生。

腳本

Apps Script 範本可包含三個稱為 Scriptlet 的特殊標記。在指令碼小程式中,您可以編寫任何可在一般 Apps Script 檔案中運作的程式碼:Scriptlet 可以呼叫其他程式碼檔案中定義的函式、參照全域變數,或使用任何 Apps Script API。您甚至可以在指令碼小程式內定義函式和變數,但請注意,這些函式和變數無法由程式碼檔案或其他範本中定義的函式呼叫。

如果您將以下範例貼到指令碼編輯器中,<?= ... ?> 標記 (列印指令碼小程式) 的內容會以斜體顯示。該斜體程式碼會在「將頁面提供給使用者之前」在伺服器上執行。由於 Scriptlet 程式碼是在頁面提供前執行,因此在每個頁面只能執行一次;不同於透過 google.script.run 呼叫的用戶端 JavaScript 或 Apps Script 函式,指令碼程式在頁面載入後無法再次執行。

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>

請注意,範本式 HTML 的 doGet() 函式與建立及提供基本 HTML 的範例不同。此處顯示的函式會從 HTML 檔案產生 HtmlTemplate 物件,然後呼叫其 evaluate() 方法來執行指令碼小程式,並將範本轉換成 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,會使用結構定義逸出功能將程式碼的結果輸出到網頁中。

內容逸出是指,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' ?> 這個指令碼小程式只會列印「Hello, world!」

強制列印指令碼

使用語法 <?!= ... ?> 的強制列印 Scriptlet 類似於列印指令碼,但會避免進行情境逸出。

如果您的指令碼允許不受信任的使用者輸入內容,就一定要進行內容逸出。相較之下,如果指令碼小程式的輸出刻意包含您想要完全插入的 HTML 或指令碼,則您必須強制列印。

一般而言,除非您知道要列印 HTML 或 JavaScript 要保持不變,否則請使用輸出指令碼,而非強制列印指令碼。

採用 Scriptlet 中的 Apps Script 程式碼

不限於一般 JavaScript 的指令碼執行;您也可以使用下列三種技術,為範本提供 Apps Script 資料的存取權。

不過請記住,由於範本程式碼是在頁面呈現給使用者之前執行,因此這些技術只能將初始內容提供給網頁。如要從頁面以互動方式存取 Apps Script 資料,請改用 google.script.run API。

從範本呼叫 Apps Script 函式

Scriptlet 可以呼叫 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

您也可以直接在 Scriptlet 中使用 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() 類似,但會傳回經過評估的程式碼,做為註解,並與原始範本並列顯示。

逐步說明經過評估的程式碼

在任一經過評估的程式碼範例中,您首先會注意到,這是由 HtmlService.initTemplate() 方法建立的隱含 output 物件。這個方法未經記錄,因為只有範本本身需要用到。output 是特殊的 HtmlOutput 物件,有兩個不常見的屬性 __$,這兩個屬性是呼叫 append()appendUntrusted() 的簡寫。

output 還有一個特殊屬性 $out,也就是沒有這些特殊屬性的一般 HtmlOutput 物件。範本會在程式碼的結尾傳回該一般物件。

現在您已瞭解這個語法,程式碼的其餘部分應該很容易就能操作。超出指令碼小程式 (例如 b 標記) 以外的 HTML 內容,系統會使用 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. */ ?>