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 nhiều của chúng tôi. Nếu bạn đã sử dụng ngôn ngữ tạo mẫu kết hợp mã và HTML, chẳng hạn như PHP, ASP hoặc NetzDG, bạn nên cảm thấy cú pháp này quen thuộc.

Tập lệnh

Mẫu Apps Script có thể chứa ba thẻ đặc biệt, được gọi là tập lệnh. Bên trong một tập lệnh, bạn có thể viết bất kỳ mã nào hoạt động trong tập lệnh Apps Script thông thường tệp: tập lệnh có thể gọi các hàm được xác định trong các tệp mã khác, tham chiếu 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 cảnh báo rằng chúng không thể được được gọi bằng các hàm được xác định trong tệp mã hoặ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, nội dung của Thẻ <?= ... ?> (một tập lệnh in) sẽ xuất hiện trong 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ư JavaScript hoặc Apps Script phía máy khách các hàm mà bạn gọi 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>

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ối HTML cơ bản. Hàm hiển thị ở đây sẽ tạo ra một Đối tượng HtmlTemplate từ HTML sau đó gọi tệp evaluate() để thực thi tập lệnh và chuyển đổi mẫu thành một HtmlOutput đối tượng mà tập lệnh có thể phân phát cho người dùng.

Tập lệnh chuẩn

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

In tập lệnh (sử dụng cú pháp <?= ... ?>) cho ra kết quả của mã vào trang bằng cách sử dụng thoát theo ngữ cảnh.

Việc thoát theo ngữ cảnh có nghĩa là Apps Script theo dõi ngữ cảnh của đầu ra trên trang – bên trong 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 để chống lạ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 đầu ra trực tiếp một chuỗi; đó là tiếp theo là một tập lệnh tiêu 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>

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 hoạt động như thể chúng được đưa vào tập lệnh. Ví dụ: chỉ tập lệnh <?= 'Hello, world!'; 'abc' ?> in dòng chữ "Hello, world!"

Buộc in tập lệnh

Tập lệnh buộc in (sử dụng cú pháp <?!= ... ?>) giống như in các tập lệnh ngoại trừ việc chúng tránh thoát theo 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. Theo trái lại, bạn sẽ cần buộc in nếu đầu ra tập lệnh của bạn có chủ đích chứa HTML hoặc tập lệnh mà bạn muốn chèn một cách chính xác như đã chỉ định.

Theo quy tắc chung, hãy sử dụng tập lệnh in thay vì tập lệnh in buộc in trừ khi 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

Các tập lệnh không bị hạn chế chạy JavaScript thông thường; bạn cũng có thể sử dụng bất kỳ trong ba kỹ thuật sau đây để cấp cho các mẫu của bạn quyền truy cập vào Apps Script .

Tuy nhiên, hãy nhớ rằng mã mẫu sẽ thực thi trước khi trang được phân phát cho người dùng, các kỹ thuật này chỉ có thể đưa nội dung ban đầu lên một trang. Để truy cập dữ liệu Apps Script từ một trang theo cách tương tác, hãy sử dụng 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 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 chúng dưới dạng thuộc tính của đối tượng HtmlTemplate. Một lần một lần nữa, ví dụ này sẽ đạ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 chưa được thực thi trực tiếp; thay vào đó, máy chủ sẽ biến đổi mẫu thành mã, sau đó thực thi mã kết quả đó.

Nếu không rõ cách mẫu diễn giải tập lệnh của bạn, hai trong phần Lớp học HtmlTemplate có thể giúp bạn và 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 bạn ghi nhật ký sau đó dán mã vào trình chỉnh sửa tập lệnh, bạn có thể chạy mã và gỡ lỗi như bình thường Mã Apps Script.

Đây là mẫu đơn giản 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 (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() tương tự như getCode(), nhưng trả về mã được đánh giá dưới dạng nhận xét xuất hiện bên cạnh mẫu gốc.

Xem qua đoạn 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à mã Đối tượng output được tạo bằng phương thức HtmlService.initTemplate(). Phương thức này không được ghi lại vì chỉ có các mẫu cần sử dụng. output là đối tượng HtmlOutput đặc biệt có 2 các thuộc tính có tên khác thường, __$, là cách viết tắt để gọi append()appendUntrusted().

output còn có một thuộc tính đặc biệt khác là $out, tham chiếu đến một thuộc tính thông thường Đối tượng HtmlOutput không sở hữu các thuộc tính đặc biệt này. Mẫu sẽ trả về đối tượng chuẩn ở cuối mã.

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

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 nhận xét

Vì mã được đánh giá giữ lại số dòng, nên có thể nhận xét bên trong các tập lệnh để nhận xét các tập lệnh khác và thậm chí cả mã HTML. Các ví dụ về một số tác dụng bất ngờ của phần bình luận:

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