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