คุณผสมโค้ด Apps Script กับ HTML เพื่อสร้างหน้าเว็บแบบไดนามิกได้โดยใช้ความพยายามเพียงเล็กน้อย หากคุณเคยใช้ภาษาเทมเพลตที่ผสมโค้ดและ HTML เช่น PHP, ASP หรือ JSP คุณน่าจะคุ้นเคยกับไวยากรณ์นี้
สคริปต์ย่อย
เทมเพลต Apps Script อาจมีแท็กพิเศษ 3 รายการที่เรียกว่าสคริปต์ย่อย คุณสามารถเขียนโค้ดใดก็ได้ที่ใช้งานได้ในไฟล์ Apps Script ปกติภายในสคริปต์ย่อย โดยสคริปต์ย่อยสามารถเรียกใช้ฟังก์ชันที่กําหนดไว้ในไฟล์โค้ดอื่นๆ อ้างอิงตัวแปรส่วนกลาง หรือใช้ Apps Script API ใดก็ได้ คุณยังกําหนดฟังก์ชันและตัวแปรภายในสคริปต์เล็ตได้ด้วย แต่ข้อควรระวังคือฟังก์ชันที่กําหนดไว้ในไฟล์โค้ดหรือเทมเพลตอื่นๆ จะเรียกใช้ไม่ได้
หากคุณวางตัวอย่างด้านล่างลงในเครื่องมือแก้ไขสคริปต์ เนื้อหาของแท็ก <?= ... ?>
(สคริปต์การพิมพ์) จะปรากฏเป็นเอียง โค้ดตัวเอียงดังกล่าวจะทํางานบนเซิร์ฟเวอร์ก่อนที่หน้าเว็บจะแสดงต่อผู้ใช้ เนื่องจากโค้ดสคริปต์เลตจะทำงานก่อนที่หน้าเว็บจะแสดง โค้ดดังกล่าวจึงทำงานได้เพียงครั้งเดียวต่อหน้า ต่างจากฟังก์ชัน JavaScript ฝั่งไคลเอ็นต์หรือ Apps Script ที่คุณเรียกผ่าน google.script.run
ซึ่งสคริปต์เลตจะทำงานไม่ได้อีกหลังจากหน้าเว็บโหลด
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
ที่สคริปต์จะแสดงต่อผู้ใช้ได้
สคริปต์มาตรฐาน
สคริปต์เล็ตมาตรฐานซึ่งใช้ไวยากรณ์ <? ... ?>
จะเรียกใช้โค้ดโดยไม่แสดงเนื้อหาไปยังหน้าเว็บอย่างชัดเจน อย่างไรก็ตาม ดังที่ตัวอย่างนี้แสดง ผลลัพธ์ของโค้ดภายในสคริปต์เล็ตจะยังคงส่งผลต่อเนื้อหา 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 ปกติเท่านั้น คุณยังใช้เทคนิคใดก็ได้จาก 3 เทคนิคต่อไปนี้เพื่อให้เทมเพลตเข้าถึงข้อมูล 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>
การแก้ไขข้อบกพร่องของเทมเพลต
การแก้ไขข้อบกพร่องของเทมเพลตอาจเป็นเรื่องยาก เนื่องจากระบบจะไม่เรียกใช้โค้ดที่คุณเขียนโดยตรง แต่เซิร์ฟเวอร์จะเปลี่ยนรูปแบบเทมเพลตเป็นโค้ด แล้วเรียกใช้โค้ดที่ได้
หากไม่แน่ใจว่าเทมเพลตตีความสคริปต์เล็ตของคุณอย่างไร วิธีการแก้ไขข้อบกพร่อง 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 (EVALUATED)
(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. */ ?>