您可以混合使用 Apps Script 程式碼和 HTML,以最少的力氣產生動態網頁。如果您使用混合程式碼和 HTML 的模板語言 (例如 PHP、ASP 或 JSP),應該會覺得語法很熟悉。
小程式
Apps Script 範本可包含三個特殊標記,稱為「小程式」。在程式碼片段中,您可以編寫任何可在一般 Apps Script 檔案中運作的程式碼:程式碼片段可以呼叫其他程式碼檔案中定義的函式、參照全域變數,或使用任何 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>
列印指令碼片段
使用 <?= ... ?>
語法的印刷指令碼,會使用內容逃逸機制,將程式碼的結果輸出至網頁。
內容轉義是指 Apps Script 會追蹤網頁上輸出的內容,例如 HTML 屬性、用戶端 script
標記或其他位置,並自動加入轉義字元,以防範跨網站指令碼 (XSS) 攻擊。
在這個範例中,第一個列印指令碼會直接輸出字串;接著是設定陣列和迴圈的標準指令碼,最後是輸出陣列內容的另一個列印指令碼。
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!」
強制列印指令碼片段
使用 <?!= ... ?>
語法的強制列印指令碼,就像是列印指令碼,但不會避免上下文轉義。
如果指令碼允許不受信任的使用者輸入內容,就必須使用內容轉義。相反地,如果指令碼片段的輸出內容刻意包含 HTML 或指令碼,且您想依照指定內容插入,就必須強制列印。
一般來說,除非您確定需要以原樣列印 HTML 或 JavaScript,否則請使用列印指令碼,而非強制列印指令碼。
指令碼片段中的 Apps Script 程式碼
指令碼片段不限於執行一般 JavaScript,您也可以使用下列任一技巧,讓範本存取 Apps Script 資料。
不過,請注意,由於範本程式碼會在網頁提供給使用者前執行,因此這些技巧只能將初始內容提供給網頁。如要透過互動方式從網頁存取 Apps Script 資料,請改用 google.script.run
API。
從範本呼叫 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>
直接呼叫 Apps Script API
您也可以直接在程式片段中使用 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>
LOG (已評估)
(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._ =
(不含內容轉義) 附加腳本小程式外 (例如 b
標記) 的 HTML 內容,而腳本小程式則會以 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. */ ?>