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

Bạn có thể kết hợp mã Apps Script và HTML để tạo các trang động mà không cần tốn nhiều công sức. Nếu bạn đã sử dụng một 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.

Tập lệnh nhỏ

Mẫu Apps Script có thể chứa 3 thẻ đặc biệt, gọi là tập lệnh nhỏ. Bên trong tập lệnh con, bạn có thể viết bất kỳ mã nào hoạt động trong tệp Apps Script thông thường: tập lệnh con có thể gọi các hàm được xác định trong các tệp mã khác, tham chiếu 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 tập lệnh, với lưu ý rằng các hàm và biến đó không thể được gọi bằng 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ụ bên dưới vào trình chỉnh sửa tập lệnh, thì nội dung của thẻ <?= ... ?> (tập lệnh in) sẽ xuất hiện ở dạng in nghiêng. Mã in nghiêng đó chạy trên máy chủ trước khi trang được phân phát cho người dùng. Vì mã tập lệnh 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 trên mỗi trang; không giống như các hàm JavaScript phía máy khách hoặc Apps Script mà bạn gọi thông qua google.script.run, tập lệnh 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 theo mẫu khác với các ví dụ để tạo và phân phát HTML cơ bản. Hàm được hiển thị ở đây tạo một đối tượng HtmlTemplate từ tệp HTML, sau đó gọi phương thức evaluate() để thực thi các tập lệnh nhỏ và chuyển đổi mẫu thành đối tượng HtmlOutput mà tập lệnh có thể phân phát cho người dùng.

Tập lệnh chuẩn

Các tập lệnh chuẩn sử dụng cú pháp <? ... ?> sẽ thực thi mã mà không cần xuất nội dung một cách rõ ràng sang trang. Tuy nhiên, như ví dụ này cho thấy, kết quả của mã bên trong tập lệnh nhỏ vẫn có thể ảnh hưởng đến nội dung HTML bên ngoài tập lệnh nhỏ:

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>

In tập lệnh nhỏ

Việc in tập lệnh con sử dụng cú pháp <?= ... ?> sẽ xuất kết quả của mã vào trang bằng cách sử dụng tính năng thoát theo ngữ cảnh.

Tự thoát theo ngữ cảnh có nghĩa là Apps Script theo dõi ngữ cảnh của kết quả 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 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, tập lệnh in đầu tiên sẽ trực tiếp xuất ra một chuỗi; theo sau là một tập lệnh chuẩn thiết lập một mảng và một vòng lặp, theo sau là một tập lệnh 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 tập lệnh in chỉ xuất ra giá trị của câu lệnh đầu tiên; mọi câu lệnh còn lại sẽ hoạt động như thể chúng nằm trong một tập lệnh chuẩn. Ví dụ: tập lệnh <?= 'Hello, world!'; 'abc' ?> chỉ in "Hello, world!" (Xin chào thế giới!)

Buộc in tập lệnh nhỏ

Các tập lệnh buộc in sử dụng cú pháp <?!= ... ?> cũng giống như các tập lệnh in, ngoại trừ việc các tập lệnh này tránh thoát khỏi ngữ cảnh.

Việc thoát theo ngữ cảnh rất quan trọng nếu tập lệnh của bạn cho phép hoạt động đầu vào không đáng tin cậy của người dùng. Ngược lại, bạn sẽ cần buộc in nếu đầu ra của tập lệnh con 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 tập lệnh in thay vì tập lệnh in buộc, trừ phi bạn biết rằng bạn cần in HTML hoặc JavaScript không thay đổi.

Mã Apps Script trong tập lệnh nhỏ

Tập lệnh con 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ố ba 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 trang. Để truy cập vào dữ liệu Apps Script từ một trang theo cách tương tác, hãy sử dụng API google.script.run.

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

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

Bạn cũng có thể sử dụng mã Apps Script ngay trong tập lệnh. 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 đối tượng HtmlTemplate. Một lần nữa, ví dụ này sẽ mang lại 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

Bạn có thể gặp khó khăn khi gỡ lỗi mẫu 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 rõ cách mẫu diễn giải tập lệnh, 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.

getCode()

getCode() trả về một chuỗi chứa mã mà máy chủ tạo từ mẫu. Nếu ghi lại mã rồi dán vào trình soạn thảo tập lệnh, bạn có thể chạy và gỡ lỗi mã đó như mã Apps Script thông thường.

Dưới đây là mẫu đơn giản hiển thị lại danh sách sản phẩm của Google, sau đó 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('');
})();

getCodeWithComments()

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 cạnh mẫu ban đầu.

Xem xét mã đã đánh giá

Điều đầu tiên bạn sẽ nhận thấy trong cả hai mẫu mã được đánh giá 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 nhận vì chỉ có các mẫu cần sử dụng phương thức này. output là một đối tượng HtmlOutput đặc biệt có hai thuộc tính được đặt tên không theo quy tắc thông thường là __$. Đây là viết tắt của lệnh gọi append()appendUntrusted().

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 sở hữu các thuộc tính đặc biệt này. Mẫu sẽ trả về đối tượng thông thường đó ở cuối mã.

Giờ đây, khi bạn đã hiểu cú pháp này, phần còn lại của mã sẽ khá dễ theo dõi. Nội dung HTML bên ngoài tập lệnh con (chẳng hạn như thẻ b) được thêm vào bằng output._ = (không có tính năng thoát khỏi ngữ cảnh) và tập lệnh con được thêm vào dưới dạng JavaScript (có hoặc không có tính năng thoát khỏi ngữ cảnh, tuỳ thuộc vào loại tập lệnh con).

Xin lưu ý rằng mã được đánh giá giữ lại số dòng trong mẫu. Nếu bạn gặp lỗi trong khi chạy mã được đánh giá, dòng này sẽ tương ứng với nội dung tương đương trong mẫu.

Hệ phân cấp bình luận

Vì mã được đánh giá giữ lại số dòng, nên các nhận xét bên trong tập lệnh có thể nhận xét các tập lệnh 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 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. */ ?>