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

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

Scriptlet

เทมเพลตของ Apps Script จะมีแท็กพิเศษที่เรียกว่า Scriptlet ได้ 3 แท็ก ภายใน คุณสามารถเขียนโค้ดใดๆ ก็ตามที่ทำงานในสคริปต์ Apps ปกติได้ file: Scriptlets สามารถเรียกใช้ฟังก์ชันที่กำหนดไว้ในไฟล์โค้ดอื่นๆ, การอ้างอิง ตัวแปรร่วม หรือใช้ Apps Script API ใดก็ได้ คุณยังกำหนด ฟังก์ชันและตัวแปรภายใน Scriptlets โดยข้อควรระวังว่า เรียกโดยฟังก์ชันที่กำหนดไว้ในไฟล์โค้ดหรือเทมเพลตอื่นๆ

หากคุณวางตัวอย่างด้านล่างลงในโปรแกรมแก้ไขสคริปต์ เนื้อหาของ แท็ก <?= ... ?> (สคริปต์เล็ตการพิมพ์) จะปรากฏใน ตัวเอียง โค้ดตัวเอียงนั้นทำงานบนเซิร์ฟเวอร์ก่อนที่หน้าเว็บจะแสดง ให้แก่ผู้ใช้ เนื่องจากโค้ด Scriptlet ทำงานก่อนที่หน้าเว็บจะแสดง สามารถเรียกใช้ได้ 1 ครั้งต่อ 1 หน้าเว็บเท่านั้น ต่างจาก 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 มาตรฐาน ซึ่งใช้ไวยากรณ์ <? ... ?> เรียกใช้โค้ดโดยไม่มี แสดงเนื้อหาในหน้าเว็บอย่างชัดเจน แต่ตามตัวอย่างนี้ ผลลัพธ์ ของโค้ดภายใน Scriptlet ยังคงส่งผลกระทบต่อเนื้อหา HTML ภายนอก Scriptlet:

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)

ในตัวอย่างนี้ Scriptlet การพิมพ์แรกจะแสดงผลสตริงโดยตรง นี่คือ ตามด้วย Scriptlet มาตรฐานที่ตั้งค่าอาร์เรย์และลูป ตามด้วย Scriptlet การพิมพ์อีกตัวหนึ่งเพื่อแสดงเนื้อหาของอาร์เรย์

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 ตัวอย่างเช่น Scriptlet <?= 'Hello, world!'; 'abc' ?> เท่านั้น พิมพ์คำว่า "สวัสดีทุกคน"

การบังคับพิมพ์ Scriptlet

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

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

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

โค้ด Apps Script ใน Scriptlet

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>

การเรียกใช้ 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>

เทมเพลตการแก้ไขข้อบกพร่อง

เทมเพลตอาจแก้ไขข้อบกพร่องได้ยากเนื่องจากโค้ดที่คุณเขียนไม่ได้เรียกใช้ directly; เซิร์ฟเวอร์จะแปลงเทมเพลตของคุณเป็นโค้ด แล้วจึงเรียกใช้ โค้ดที่ได้

ถ้าไม่ชัดเจนว่าเทมเพลตตีความสคริปต์เพลตของคุณอย่างไร วิธีการแก้ไขข้อบกพร่องใน ชั้นเรียน 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('');
})();

getCodeWithComments()

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

แนะนำโค้ดที่ประเมิน

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

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

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

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

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

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