Dịch vụ HTML: HTML có mẫu

Bạn có thể sử dụng mẫu để kết hợp mã Google Apps Script và HTML nhằm tạo các trang động với nỗ lực tối thiểu. Nếu đã từng sử dụng các ngôn ngữ tạo mẫu kết hợp mã và HTML, chẳng hạn như PHP, ASP hoặc JSP, thì cú pháp này sẽ quen thuộc với bạn.

Scriptlet

Mẫu Apps Script có thể chứa 3 thẻ đặc biệt gọi là scriptlet. Trong một scriptlet, bạn có thể viết bất kỳ mã nào hoạt động trong tệp Apps Script thông thường: scriptlet có thể gọi các hàm được xác định trong các tệp mã khác, tham chiếu đến các biến toàn cục hoặc sử dụng bất kỳ API Apps Script nào. Bạn thậm chí có thể xác định các hàm và biến trong scriptlet, với lưu ý rằng các hàm và biến này không thể được gọi bởi các hàm được xác định trong tệp mã hoặc các mẫu khác.

Nếu bạn dán ví dụ sau vào trình chỉnh sửa tập lệnh, nội dung của thẻ <?= ... ?> (một scriptlet in) sẽ xuất hiện ở dạng in nghiêng. Mã này chạy trên máy chủ trước khi trang được phân phát cho người dùng. Vì mã scriptlet thực thi trước khi trang được phân phát, nên mã này chỉ có thể chạy một lần cho mỗi trang. Không giống như các hàm JavaScript hoặc Apps Script phía máy khách mà bạn gọi thông qua google.script.run, scriptlet không thể thực thi lại sau khi trang tải.

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>

Xin lưu ý rằng hàm doGet cho HTML được tạo mẫu khác với các ví dụ về cách tạo và phân phát chế độ HTML cơ bản. Hàm được hiển thị ở đây tạo một HtmlTemplate đối tượng từ tệp HTML, sau đó gọi phương thức evaluate của đối tượng đó để thực thi các scriptlet và chuyển đổi mẫu thành một đối tượng HtmlOutput mà tập lệnh có thể phân phát cho người dùng.

Scriptlet chuẩn

Scriptlet chuẩn sử dụng cú pháp <? ... ?>, thực thi mã mà không xuất nội dung một cách rõ ràng ra trang. Tuy nhiên, như ví dụ này cho thấy, kết quả của mã bên trong một scriptlet vẫn có thể ảnh hưởng đến nội dung HTML bên ngoài 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 is always served!</p>
    <? } else  { ?>
      <p>This is never served.</p>
    <? } ?>
  </body>
</html>

Scriptlet in sử dụng cú pháp <?= ... ?>, xuất kết quả của mã vào trang bằng cách thoát theo ngữ cảnh.

Thoát theo ngữ cảnh có nghĩa là Apps Script theo dõi ngữ cảnh của kết quả đầu ra trên trang (bên trong một thuộc tính HTML, bên trong thẻ script phía máy khách hoặc ở bất kỳ nơi nào khác) và tự động thêm các ký tự thoát để bảo vệ khỏi các cuộc tấn công tập lệnh trên nhiều trang web (XSS).

Trong ví dụ này, scriptlet in đầu tiên xuất trực tiếp một chuỗi; sau đó là một scriptlet chuẩn thiết lập một mảng và một vòng lặp, tiếp theo là một scriptlet in khác để xuất nội dung của mảng.

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>

Xin lưu ý rằng một scriptlet in chỉ xuất giá trị của câu lệnh đầu tiên; mọi câu lệnh còn lại đều hoạt động như thể chúng được chứa trong một scriptlet chuẩn. Vì vậy, ví dụ: scriptlet <?= 'Hello, world!'; 'abc' ?> chỉ in "Hello, world!"

Scriptlet in bắt buộc

Scriptlet in bắt buộc sử dụng cú pháp <?!= ... ?>, giống như scriptlet in ngoại trừ việc chúng tránh thoát theo ngữ cảnh.

Thoát theo ngữ cảnh là rất quan trọng nếu tập lệnh của bạn cho phép hoạt động đầu vào của người dùng không đáng tin cậy. Ngược lại, bạn cần phải in bắt buộc nếu kết quả đầu ra của scriptlet cố ý chứa HTML hoặc tập lệnh mà bạn muốn chèn chính xác như đã chỉ định.

Theo nguyên tắc chung, hãy sử dụng scriptlet in thay vì scriptlet in bắt buộc, trừ phi bạn biết rằng mình cần in HTML hoặc JavaScript mà không thay đổi.

Mã Apps Script trong scriptlet

Scriptlet không bị giới hạn ở việc chạy JavaScript thông thường; bạn cũng có thể sử dụng bất kỳ kỹ thuật nào trong số 3 kỹ thuật sau để cấp cho mẫu quyền truy cập vào dữ liệu Apps Script.

Tuy nhiên, hãy nhớ rằng vì mã mẫu thực thi trước khi trang được phân phát cho người dùng, nên các kỹ thuật này chỉ có thể cung cấp nội dung ban đầu cho một trang. Để truy cập dữ liệu Apps Script từ một trang một cách tương tác, hãy sử dụng google.script.run API thay thế.

Gọi hàm Apps Script từ một mẫu

Scriptlet có thể gọi bất kỳ hàm nào được xác định trong tệp mã hoặc thư viện Apps Script. Ví dụ này cho thấy một cách để lấy dữ liệu từ bảng tính vào một mẫu, sau đó tạo một bảng HTML từ dữ liệu đó.

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>

Gọi trực tiếp API Apps Script

Bạn cũng có thể sử dụng trực tiếp mã Apps Script trong scriptlet. Ví dụ này đạt được kết quả tương tự như ví dụ trước bằng cách tải dữ liệu trong chính mẫu thay vì thông qua một hàm riêng biệt.

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>

Đẩy biến vào mẫu

Cuối cùng, bạn có thể đẩy các biến vào một mẫu bằng cách chỉ định các biến đó làm thuộc tính của HtmlTemplate đối tượng. Một lần nữa, ví dụ này đạt được kết quả tương tự như các ví dụ trước.

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>

Gỡ lỗi mẫu

Mẫu có thể khó gỡ lỗi vì mã bạn viết không được thực thi trực tiếp. Thay vào đó, máy chủ sẽ chuyển đổi mẫu của bạn thành mã, sau đó thực thi mã kết quả đó.

Nếu bạn không biết rõ cách mẫu diễn giải các scriptlet, thì hai phương thức gỡ lỗi trong lớp HtmlTemplate có thể giúp bạn hiểu rõ hơn về những gì đang diễn ra.

Hàm getCode

Hàm getCode function trả về một chuỗi chứa mã mà máy chủ tạo từ mẫu. Nếu bạn ghi nhật ký mã, sau đó dán mã đó vào trình chỉnh sửa tập lệnh, bạn có thể chạy và gỡ lỗi mã đó như mã Apps Script thông thường.

Sau đây là mẫu hiển thị lại danh sách các sản phẩm của Google, tiếp theo là kết quả của 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 (ĐÃ ĐÁNH GIÁ)

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

Hàm getCodeWithComments

Hàm getCodeWithComments tương tự như getCode(), nhưng trả về mã đã đánh giá dưới dạng nhận xét xuất hiện song song với mẫu ban đầu.

Xem qua mã đã đánh giá

Điều đầu tiên bạn sẽ nhận thấy trong bất kỳ mẫu mã đã đánh giá nào là đối tượng output ngầm ẩn do phương thức HtmlService.initTemplate tạo. Phương thức này không được ghi lại vì chỉ bản thân mẫu mới cần sử dụng phương thức này. output là một đối tượng HtmlOutput đặc biệt có 2 thuộc tính được đặt tên bất thường là __$, đây là cách viết tắt để gọi appendappendUntrusted.

output có thêm một thuộc tính đặc biệt là $out, thuộc tính này đề cập đến một đối tượng HtmlOutput thông thường không có các thuộc tính đặc biệt này. Mẫu trả về đối tượng thông thường đó ở cuối mã.

Bây giờ bạn đã hiểu cú pháp này, bạn có thể làm theo phần còn lại của mã. Nội dung HTML bên ngoài scriptlet (như thẻ b) được thêm vào bằng output._ = (không thoát theo ngữ cảnh), và scriptlet được thêm vào dưới dạng JavaScript (có hoặc không thoát theo ngữ cảnh, tuỳ thuộc vào loại scriptlet).

Mã đã đánh giá giữ nguyên số dòng từ mẫu. Nếu bạn gặp lỗi khi chạy mã đã đánh giá, thì dòng đó sẽ tương ứng với nội dung tương đương trong mẫu.

Hệ thống phân cấp nhận xét

Vì mã đã đánh giá giữ nguyên số dòng, nên các nhận xét bên trong scriptlet có thể nhận xét về các scriptlet khác và thậm chí cả mã HTML. Các ví dụ này cho thấy một số hiệu ứng đáng ngạc nhiên của nhận xét:

<? 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 prints 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. */ ?>