HTML 服務:範本 HTML

您可以混合使用 Apps Script 程式碼和 HTML,輕鬆製作動態網頁。如果您曾使用混合程式碼和 HTML 的範本語言 (例如 PHP、ASP 或 JSP),應該會覺得語法很熟悉。

Scriptlet

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

如果將下例貼入指令碼編輯器,<?= ... ?> 標記的內容 (列印小指令碼) 會以斜體顯示。該斜體程式碼會在網頁提供給使用者之前,於伺服器上執行。由於腳本程式碼會在網頁放送前執行,因此每個網頁只能執行一次;與透過 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() 方法來執行 scriptlet,並將範本轉換為 HtmlOutput 物件,供指令碼提供給使用者。

標準指令碼

標準 scriptlet 使用 <? ... ?> 語法執行程式碼,但不會明確將內容輸出至網頁。不過,如這個範例所示,指令碼片段內程式碼的結果仍會影響指令碼片段外的 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 會直接輸出字串;後面接著標準 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' ?> 只會列印「Hello, world!」。

強制列印 scriptlet

強制列印 scriptlet (使用 <?!= ... ?> 語法) 與列印 scriptlet 類似,但會避免內容逸出。

如果指令碼允許不受信任的使用者輸入內容,請務必進行情境逸出。相反地,如果指令碼片段的輸出內容刻意包含 HTML 或指令碼,且您想完全按照指定方式插入,就必須強制列印。

一般來說,請使用列印小程式,而非強制列印小程式,除非您知道需要列印未經變更的 HTML 或 JavaScript。

腳本程式碼片段中的 Apps Script 程式碼

指令碼片段不限於執行一般 JavaScript,您也可以使用下列三種技術,讓範本存取 Google 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 物件。範本會在程式碼結尾傳回該一般物件。

瞭解這項語法後,其餘程式碼應該就相當容易理解。系統會使用 output._ = (不含情境逸出),附加腳本片段以外的 HTML 內容 (例如 b 標記),並以 JavaScript 附加腳本片段 (含或不含情境逸出,視腳本片段類型而定)。

請注意,評估的程式碼會保留範本中的行號。如果在執行評估的程式碼時發生錯誤,該行會對應至範本中的同等內容。

留言階層

由於評估的程式碼會保留行號,因此 scriptlet 內的註解可能會註解掉其他 scriptlet,甚至是 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. */ ?>