บริการ HTML: HTML แบบเทมเพลต

จัดทุกอย่างให้เป็นระเบียบอยู่เสมอด้วยคอลเล็กชัน บันทึกและจัดหมวดหมู่เนื้อหาตามค่ากำหนดของคุณ

คุณสามารถใช้โค้ด Apps Script และ HTML สร้างหน้าเว็บแบบไดนามิกได้อย่างง่ายๆ หากคุณเคยใช้ภาษาที่ขัดต่อกันซึ่งมีทั้งโค้ดและ HTML ผสมกัน เช่น PHP, ASP หรือ JSP ไวยากรณ์ควรคุ้นเคย

สคริปต์

เทมเพลต Apps Script จะมีแท็กพิเศษ 3 แท็ก เรียกว่า Scriptlet ใน JavaScript ให้คุณเขียนโค้ดใดก็ได้ที่จะทํางานในไฟล์ Apps Script ทั่วไป ดังนี้ letlet สามารถเรียกใช้ฟังก์ชันที่กําหนดไว้ในไฟล์โค้ดอื่นๆ อ้างอิงตัวแปรร่วม หรือใช้ 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() เพื่อเรียกใช้ Scriptlet และแปลงเทมเพลตเป็นออบเจ็กต์ 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>

การพิมพ์สคริปต์

การพิมพ์สเปรตชีตซึ่งใช้ไวยากรณ์ <?= ... ?> จะแสดงผลของโค้ดออกมาในหน้าเว็บโดยใช้ Escape ตามบริบท

การ Escape ตามบริบทหมายความว่า 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>

โปรดทราบว่าสคริปต์สคริปต์เอาต์พุตจะแสดงค่าของข้อความแรกเท่านั้น ส่วนข้อความที่เหลืออยู่จะทํางานเสมือนว่าเป็นส่วนหนึ่งของสคริปต์มาตรฐาน ยกตัวอย่างเช่น สคริปต์ <?= 'Hello, world!'; 'abc' ?> พิมพ์ "สวัสดีทั่วโลก!"

สคริปต์การพิมพ์ระดับสูง

สคริปต์ที่บังคับให้พิมพ์ซึ่งใช้ไวยากรณ์ <?!= ... ?> มีลักษณะเหมือนกันกับการพิมพ์สคริปต์ย่อย ยกเว้นเพื่อหลีกเลี่ยงการ Escape ตามบริบท

การ Escape ตามบริบทเป็นสิ่งสําคัญ หากสคริปต์ของคุณอนุญาตอินพุตที่ไม่น่าเชื่อถือจากผู้ใช้ ในทางตรงกันข้าม คุณจะต้องบังคับให้พิมพ์หากเอาต์พุตของสคริปต์มีจงใจใส่ HTML หรือสคริปต์ที่คุณต้องการแทรกตามที่ระบุไว้ทุกประการ

ตามกฎทั่วไป ให้ใช้สคริปต์เอกสารฉบับพิมพ์แทนการพิมพ์สคริปต์เอกสาร เว้นแต่จะทราบว่าคุณต้องพิมพ์ HTML หรือ JavaScript โดยไม่เปลี่ยนแปลง

โค้ด Apps Script ใน Scriptlet

สคริปต์จะไม่จํากัดเฉพาะการเรียกใช้ JavaScript ตามปกติ และคุณยังสามารถใช้เทคนิคใดๆ ต่อไปนี้ใน 3 เทคนิคเพื่อให้สิทธิ์เข้าถึงเทมเพลตข้อมูล Apps Script ของคุณ

อย่างไรก็ตาม โปรดทราบว่าเนื่องจากโค้ดเทมเพลตทํางานก่อนที่จะมีการแสดงหน้าเว็บต่อผู้ใช้ เทคนิคเหล่านี้จึงส่งได้เฉพาะเนื้อหาเริ่มต้นในหน้าเว็บเท่านั้น หากต้องการเข้าถึงข้อมูล Apps Script จากหน้าแบบอินเทอร์แอกทีฟ ให้ใช้ google.script.run API แทน

การเรียกใช้ฟังก์ชันของ 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 ในletlet โดยตรงได้เช่นกัน ตัวอย่างนี้จะมีผลลัพธ์เหมือนกับตัวอย่างก่อนหน้านี้โดยการโหลดข้อมูลในเทมเพลตแทนที่จะเป็นฟังก์ชันแยกต่างหาก

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>

บันทึก (ประเมิน)

(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('');
})();

getCodeWithความคิดเห็น()

getCodeWithComments() คล้ายกับ getCode() แต่แสดงผลโค้ดที่ประเมินเป็นความคิดเห็นที่ปรากฏข้างกันกับเทมเพลตต้นฉบับ

สํารวจโค้ดที่ประเมิน

สิ่งแรกที่คุณจะเห็นในตัวอย่างโค้ดที่ประเมินคือออบเจ็กต์ output โดยนัยซึ่งสร้างโดยเมธอด HtmlService.initTemplate() วิธีการนี้ไม่มีเอกสารประกอบ เนื่องจากมีเพียงเทมเพลตเท่านั้นที่ต้องใช้วิธีการนี้ output เป็นออบเจ็กต์ HtmlOutput แบบพิเศษที่มีพร็อพเพอร์ตี้ที่มักไม่มีชื่ออยู่ 2 พร็อพเพอร์ตี้ ได้แก่ _ และ _$ ซึ่งย่อมาจากการโทร append() และ appendUntrusted()

output มีพร็อพเพอร์ตี้พิเศษอีก 1 รายการ นั่นคือ $out ซึ่งอ้างอิงถึงออบเจ็กต์ HtmlOutput ปกติที่ไม่มีพร็อพเพอร์ตี้พิเศษเหล่านี้ เทมเพลตจะแสดงผลออบเจ็กต์ปกตินั้นในตอนท้ายของโค้ด

เมื่อเข้าใจไวยากรณ์นี้แล้ว โค้ดที่เหลือก็ควรทําตามได้ง่าย เนื้อหา HTML นอกสคริปต์ (เช่น แท็ก b) จะต่อท้ายโดยใช้ output._ = (โดยไม่มีการ Escape ตามบริบท) และข้อมูลโค้ดจะต่อท้ายเป็น JavaScript (โดยที่มีการ Escape ตามบริบทหรือไม่ ขึ้นอยู่กับประเภทของสคริปต์สคริปต์)

โปรดทราบว่าโค้ดที่ประเมินจะเก็บรักษาหมายเลขบรรทัดจากเทมเพลต หากคุณได้รับข้อผิดพลาดขณะเรียกใช้โค้ดที่ประเมิน บรรทัดนั้นจะสอดคล้องกับเนื้อหาที่เทียบเท่าในเทมเพลต

ลําดับชั้นของความคิดเห็น

เนื่องจากโค้ดที่ประเมินจะเก็บรักษาหมายเลขบรรทัดไว้ ดังนั้นความคิดเห็นภายในสคริปต์จึงอาจแสดงความคิดเห็นในสคริปต์อื่นๆ รวมถึงโค้ด 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. */ ?>