คุณสามารถผสมโค้ด Apps Script กับ HTML เพื่อสร้างหน้าแบบไดนามิกโดยใช้ความพยายามเพียงเล็กน้อย หากคุณใช้ภาษาการสร้างเทมเพลตที่ผสมโค้ดและ HTML เช่น PHP, ASP หรือ JSP ไวยากรณ์ควรจะคุ้นเคย
Scriptlet
เทมเพลต Apps Script มีแท็กพิเศษ 3 แท็กที่เรียกว่า Scriptlet ภายใน สคริปต์เล็ต คุณสามารถเขียนโค้ดใดก็ได้ที่ใช้ได้ในไฟล์ Apps Script ปกติ โดยสคริปต์เล็ตจะเรียกฟังก์ชันที่กำหนดไว้ในไฟล์โค้ดอื่นๆ อ้างอิง ตัวแปรส่วนกลาง หรือใช้ API ของ Apps Script ใดก็ได้ คุณยังกำหนด ฟังก์ชันและตัวแปรภายใน 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 will always be served!</p> <? } else { ?> <p>This will never be served.</p> <? } ?> </body> </html>
การพิมพ์สคริปต์ขนาดเล็ก
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>
การเรียกใช้ Apps Script API โดยตรง
นอกจากนี้ คุณยังใช้โค้ด 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 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. */ ?>