คุณสามารถใช้เทมเพลตเพื่อผสมโค้ด Google Apps Script กับ HTML เพื่อ สร้างหน้าแบบไดนามิกโดยใช้ความพยายามเพียงเล็กน้อย หากคุณเคยใช้ภาษาเทมเพลตที่ผสมโค้ดและ HTML เช่น PHP, ASP หรือ JSP คุณควรจะคุ้นเคยกับไวยากรณ์
Scriptlet
เทมเพลต Apps Script มีแท็กพิเศษ 3 แท็กที่เรียกว่า สคริปต์เล็ต ภายใน Scriptlet คุณสามารถเขียนโค้ดใดก็ได้ที่ใช้ได้ในไฟล์ Apps Script ปกติ โดย Scriptlet สามารถเรียกฟังก์ชันที่กำหนดไว้ในไฟล์โค้ดอื่นๆ อ้างอิงตัวแปรส่วนกลาง หรือใช้ API ของ Apps Script ใดก็ได้ คุณยังกำหนดฟังก์ชันและตัวแปร ภายใน Scriptlet ได้ด้วย โดยมีข้อควรระวังคือฟังก์ชัน ที่กำหนดในไฟล์โค้ดหรือเทมเพลตอื่นๆ จะเรียกใช้ไม่ได้
หากวางตัวอย่างต่อไปนี้ลงในตัวแก้ไขสคริปต์ เนื้อหาของแท็ก
<?= ... ?> (สคริปต์ย่อยสำหรับพิมพ์) จะปรากฏเป็นตัวเอียง
โค้ดนี้จะทํางานบนเซิร์ฟเวอร์ก่อนที่จะแสดงหน้าเว็บ
ต่อผู้ใช้ เนื่องจากโค้ด Scriptlet จะทํางานก่อนที่จะแสดงหน้าเว็บ จึงทํางานได้เพียงครั้งเดียวต่อหน้าเว็บ Scriptlet ต่างจากฟังก์ชัน JavaScript หรือ Apps Script ฝั่งไคลเอ็นต์ที่คุณเรียกใช้ผ่าน google.script.run ตรงที่ Scriptlet ไม่สามารถ
เรียกใช้อีกครั้งหลังจากโหลดหน้าเว็บแล้ว
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 เพื่อเรียกใช้สคริปต์เล็กๆ และแปลงเทมเพลตเป็นออบเจ็กต์ HtmlOutput ที่สคริปต์สามารถแสดงต่อผู้ใช้ได้
Scriptlet มาตรฐาน
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 is always served!</p>
<? } else { ?>
<p>This is never served.</p>
<? } ?>
</body>
</html>
พิมพ์ Scriptlet
Scriptlet การพิมพ์ซึ่งใช้ไวยากรณ์ <?= ... ?> จะแสดงผลลัพธ์ของ
โค้ดลงในหน้าโดยใช้การหลีกตามบริบท
การหลีกเลี่ยงตามบริบทหมายความว่า Apps Script จะติดตามบริบทของเอาต์พุตในหน้าเว็บ ไม่ว่าจะเป็นภายในแอตทริบิวต์ HTML ภายในแท็ก scriptฝั่งไคลเอ็นต์ หรือที่อื่นๆ และจะเพิ่มอักขระหลีกโดยอัตโนมัติเพื่อป้องกันการโจมตีแบบ Cross-Site Scripting (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>
โปรดทราบว่า Scriptlet การพิมพ์จะแสดงผลค่าของคำสั่งแรกเท่านั้น
คำสั่งที่เหลือจะทำงานราวกับว่าอยู่ใน Scriptlet มาตรฐาน
ดังนั้น เช่น สคริปต์ย่อย <?= 'Hello, world!'; 'abc' ?> only
จะพิมพ์ "Hello, world!"
บังคับให้พิมพ์ Scriptlet
สคริปต์ย่อยที่บังคับให้พิมพ์ซึ่งใช้ไวยากรณ์ <?!= ... ?> จะเหมือนกับการพิมพ์สคริปต์ย่อย
ยกเว้นว่าจะหลีกเลี่ยงการหลบหนีตามบริบท
การหลีกเลี่ยงตามบริบทเป็นสิ่งสำคัญหากสคริปต์ของคุณอนุญาตให้ผู้ใช้ที่ไม่น่าเชื่อถือป้อนข้อมูล ในทางตรงกันข้าม คุณจะต้องบังคับพิมพ์หากเอาต์พุตของ Scriptlet มี HTML หรือสคริปต์ที่คุณต้องการแทรกตามที่ระบุไว้ทุกประการ
โดยทั่วไป ให้ใช้ Scriptlet การพิมพ์แทน Scriptlet การพิมพ์แบบบังคับ เว้นแต่คุณจะทราบว่าต้องพิมพ์ HTML หรือ JavaScript โดยไม่มีการเปลี่ยนแปลง
โค้ด Apps Script ใน Scriptlet
Scriptlet ไม่ได้จำกัดเฉพาะการเรียกใช้ JavaScript ปกติ แต่คุณยังใช้เทคนิค 3 อย่างต่อไปนี้เพื่อให้เทมเพลตเข้าถึงข้อมูล Apps Script ได้ด้วย
อย่างไรก็ตาม โปรดทราบว่าเนื่องจากโค้ดเทมเพลตจะทํางานก่อนที่ระบบจะแสดงหน้าเว็บต่อผู้ใช้
เทคนิคเหล่านี้จึงใช้ได้เฉพาะกับการป้อนเนื้อหาเริ่มต้นไปยังหน้าเว็บ หากต้องการเข้าถึงข้อมูล Apps Script จากหน้าเว็บแบบอินเทอร์แอกทีฟ ให้ใช้ API ของ google.script.run แทน
เรียกใช้ฟังก์ชัน 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>
เรียกใช้ API ของ Apps Script โดยตรง
นอกจากนี้ คุณยังใช้โค้ด Apps Script ใน Scriptlet ได้โดยตรงด้วย ตัวอย่างนี้ให้ผลลัพธ์เช่นเดียวกับตัวอย่างก่อนหน้าโดยการโหลดข้อมูลในเทมเพลตเองแทนที่จะผ่านฟังก์ชันแยกต่างหาก
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>
แก้ไขข้อบกพร่องของเทมเพลต
การแก้ไขข้อบกพร่องของเทมเพลตอาจเป็นเรื่องยากเนื่องจากโค้ดที่คุณเขียนไม่ได้ ดำเนินการโดยตรง แต่เซิร์ฟเวอร์จะแปลงเทมเพลตเป็นโค้ด จากนั้นจึงเรียกใช้โค้ดที่ได้
หากไม่แน่ใจว่าเทมเพลตตีความสคริปต์ของคุณอย่างไร วิธีการแก้ไขข้อบกพร่อง 2 วิธีในคลาส 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() แต่จะแสดงผลโค้ดที่ประเมินแล้วเป็นความคิดเห็น
ซึ่งปรากฏข้างๆ เทมเพลตเดิม
ดูโค้ดที่ประเมินแล้ว
สิ่งแรกที่คุณจะเห็นในตัวอย่างโค้ดที่ประเมินแล้วคือออบเจ็กต์ output ที่สร้างขึ้นโดยเมธอด HtmlService.initTemplate วิธีนี้
ไม่มีในเอกสารประกอบเนื่องจากมีเพียงเทมเพลตเท่านั้นที่ต้องใช้วิธีนี้ output คือออบเจ็กต์ HtmlOutput พิเศษที่มีพร็อพเพอร์ตี้ชื่อแปลก 2 รายการ ได้แก่ _ และ _$ ซึ่งเป็นคำย่อสำหรับการเรียก append และ appendUntrusted
output มีพร็อพเพอร์ตี้พิเศษอีก 1 รายการคือ $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 prints 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. */ ?>