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 đã 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 NetzDG, thì cú pháp này sẽ 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 được trong một tệp Apps Script thông thường: 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 các biến chung 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 phân phát trang, 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 hoặc Apps Script phía máy khách 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 hiển thị ở đây sẽ tạo một đối tượng HtmlTemplate từ tệp HTML, sau đó gọi phương thức evaluate() để thực thi tập lệ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

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

Việc in các tập lệnh (sử dụng cú pháp <?= ... ?>) sẽ xuất kết quả mã vào trang bằng cách sử dụng tính năng 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 đầ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ỳ vị trí 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 sẽ trực tiếp xuất ra một chuỗi; theo sau là một tập lệnh chuẩn giúp thiết lập một mảng và một vòng lặp, tiếp theo 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 đều 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ỉ hiện dòng chữ "Hello, world!" (Xin chào thế giới!)

Buộc in tập lệnh

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

Theo quy tắc chung, hãy sử dụng tập lệnh in thay vì buộc in tập lệnh, trừ khi bạn biết rằng mình cần in tập lệnh 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ỳ kỹ thuật nào trong số 3 kỹ thuật sau đây để cấp cho các mẫu của mình quyền truy cập vào dữ liệu của 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 chúng làm thuộc tính của đối tượng HtmlTemplate. Một lần nữa, ví dụ này cho thấy 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ẽ biến đổi mẫu của bạn thành mã, sau đó thực thi mã kết quả đó.

Nếu không rõ mẫu đang diễn giải tập lệnh của bạn như thế nào, thì 2 phương thức gỡ lỗi trong lớp HtmlTemplate có thể giúp bạn hiểu rõ hơn về vấn đề đang xảy 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ý 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.

Dưới đây là mẫu đơn giản cho thấy lại danh sách các sản phẩm của Google, theo 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 (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 song song với 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à đối tượng output ngầm ẩn được tạo bằng phương thức HtmlService.initTemplate(). 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ên không thường là __$, viết tắt để gọi append()appendUntrusted().

output có một thuộc tính đặc biệt khác là $out, tham chiếu đế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ã.

Bây giờ, 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 (như thẻ b) được nối bằ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 đó 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á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. Những ví dụ sau đây cho thấy một số tác dụng bất ngờ của phần ghi chú:

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