Thẻ mẫu mới của HTML

Tiêu chuẩn hoá việc tạo mẫu phía máy khách

Giới thiệu

Khái niệm tạo mẫu không phải là mới đối với việc phát triển web. Trên thực tế, các ngôn ngữ/công cụ tạo mẫu phía máy chủ như Django (Python), ERB/Haml (Ruby) và Smarty (PHP) đã xuất hiện từ lâu. Tuy nhiên, trong vài năm qua, chúng ta đã thấy sự bùng nổ của các khung MVC. Tất cả đều có một chút khác biệt, nhưng hầu hết đều có chung một cơ chế để hiển thị lớp trình bày (còn gọi là thành phần hiển thị da): mẫu.

Hãy đối mặt với thực tế. Mẫu rất tuyệt vời. Hãy cứ hỏi xung quanh. Ngay cả định nghĩa của nó cũng khiến bạn cảm thấy ấm áp và dễ chịu:

"…không cần tạo lại mỗi lần…" Tôi không biết bạn thế nào, nhưng tôi rất thích việc tránh làm thêm việc. Vậy tại sao nền tảng web lại thiếu tính năng hỗ trợ gốc cho một vấn đề mà nhà phát triển rõ ràng quan tâm?

Quy cách Mẫu HTML WhatWG là câu trả lời. Tệp này xác định một phần tử <template> mới mô tả phương pháp dựa trên DOM tiêu chuẩn để tạo mẫu phía máy khách. Mẫu cho phép bạn khai báo các mảnh mã đánh dấu được phân tích cú pháp dưới dạng HTML, không được sử dụng khi tải trang nhưng có thể được tạo bản sao sau đó trong thời gian chạy. Trích lời Rafael Weinstein:

Đây là nơi để đặt một lượng lớn HTML mà bạn không muốn trình duyệt can thiệp vào…vì bất kỳ lý do gì.

Rafael Weinstein (tác giả thông số kỹ thuật)

Phát hiện tính năng

Để phát hiện tính năng <template>, hãy tạo phần tử DOM và kiểm tra để đảm bảo thuộc tính .content tồn tại:

function supportsTemplate() {
    return 'content' in document.createElement('template');
}

if (supportsTemplate()) {
    // Good to go!
} else {
    // Use old templating techniques or libraries.
}

Khai báo nội dung mẫu

Phần tử <template> HTML đại diện cho một mẫu trong mã đánh dấu. Tệp này chứa "nội dung mẫu"; về cơ bản là các phần DOM không hoạt động có thể sao chép. Hãy coi các mẫu là các phần của giàn giáo mà bạn có thể sử dụng (và sử dụng lại) trong suốt thời gian hoạt động của ứng dụng.

Để tạo nội dung mẫu, hãy khai báo một số mã đánh dấu và gói nội dung đó trong phần tử <template>:

<template id="mytemplate">
    <img src="" alt="great image">
    <div class="comment"></div>
</template>

Các trụ cột

Việc gói nội dung trong <template> cung cấp cho chúng ta một số thuộc tính quan trọng.

  1. Nội dung của tệp này không hoạt động cho đến khi được kích hoạt. Về cơ bản, thẻ của bạn là DOM ẩn và không hiển thị.

  2. Mọi nội dung trong một mẫu sẽ không có tác dụng phụ. Tập lệnh không chạy, hình ảnh không tải, âm thanh không phát,… cho đến khi mẫu được sử dụng.

  3. Nội dung được coi là không có trong tài liệu. Việc sử dụng document.getElementById() hoặc querySelector() trong trang chính sẽ không trả về các nút con của mẫu.

  4. Bạn có thể đặt mẫu ở bất kỳ vị trí nào bên trong <head>, <body> hoặc <frameset> và có thể chứa bất kỳ loại nội dung nào được phép trong các phần tử đó. Xin lưu ý rằng "bất cứ nơi nào" có nghĩa là bạn có thể sử dụng <template> một cách an toàn ở những nơi mà trình phân tích cú pháp HTML không cho phép…tất cả ngoại trừ mô hình nội dung con. Bạn cũng có thể đặt thành phần con này làm phần tử con của <table> hoặc <select>:

<table>
  <tr>
    <template id="cells-to-repeat">
      <td>some content</td>
    </template>
  </tr>
</table>

Kích hoạt mẫu

Để sử dụng một mẫu, bạn cần kích hoạt mẫu đó. Nếu không, nội dung của thẻ này sẽ không bao giờ hiển thị. Cách đơn giản nhất để thực hiện việc này là tạo một bản sao sâu của .content bằng document.importNode(). Thuộc tính .content là một DocumentFragment chỉ có thể đọc chứa nội dung chính của mẫu.

var t = document.querySelector('#mytemplate');
// Populate the src at runtime.
t.content.querySelector('img').src = 'logo.png';

var clone = document.importNode(t.content, true);
document.body.appendChild(clone);

Sau khi tạo một mẫu, nội dung của mẫu đó sẽ "có hiệu lực". Trong ví dụ cụ thể này, nội dung được nhân bản, yêu cầu hình ảnh được thực hiện và mã đánh dấu cuối cùng được hiển thị.

Bản minh hoạ

Ví dụ: Tập lệnh không hoạt động

Ví dụ này minh hoạ tính chất trơ của nội dung mẫu. <script> chỉ chạy khi nhấn nút, tạo ra mẫu.

<button onclick="useIt()">Use me</button>
<div id="container"></div>
<script>
  function useIt() {
    var content = document.querySelector('template').content;
    // Update something in the template DOM.
    var span = content.querySelector('span');
    span.textContent = parseInt(span.textContent) + 1;
    document.querySelector('#container').appendChild(
      document.importNode(content, true)
    );
  }
</script>

<template>
  <div>Template used: <span>0</span></div>
  <script>alert('Thanks!')</script>
</template>

Ví dụ: Tạo Shadow DOM từ một mẫu

Hầu hết mọi người đính kèm Shadow DOM vào máy chủ lưu trữ bằng cách đặt một chuỗi mã đánh dấu thành .innerHTML:

<div id="host"></div>
<script>
  var shadow = document.querySelector('#host').createShadowRoot();
  shadow.innerHTML = '<span>Host node</span>';
</script>

Vấn đề với phương pháp này là DOM tối càng phức tạp thì bạn càng phải nối chuỗi nhiều hơn. Mọi thứ sẽ trở nên lộn xộn và trẻ sẽ bắt đầu khóc. Phương pháp này cũng là cách XSS ra đời! <template> sẽ giúp bạn giải quyết vấn đề này.

Bạn nên làm việc trực tiếp với DOM bằng cách thêm nội dung mẫu vào gốc bóng:

<template>
<style>
  :host {
    background: #f8f8f8;
    padding: 10px;
    transition: all 400ms ease-in-out;
    box-sizing: border-box;
    border-radius: 5px;
    width: 450px;
    max-width: 100%;
  }
  :host(:hover) {
    background: #ccc;
  }
  div {
    position: relative;
  }
  header {
    padding: 5px;
    border-bottom: 1px solid #aaa;
  }
  h3 {
    margin: 0 !important;
  }
  textarea {
    font-family: inherit;
    width: 100%;
    height: 100px;
    box-sizing: border-box;
    border: 1px solid #aaa;
  }
  footer {
    position: absolute;
    bottom: 10px;
    right: 5px;
  }
</style>
<div>
  <header>
    <h3>Add a Comment
  </header>
  <content select="p"></content>
  <textarea></textarea>
  <footer>
    <button>Post</button>
  </footer>
</div>
</template>

<div id="host">
  <p>Instructions go here</p>
</div>

<script>
  var shadow = document.querySelector('#host').createShadowRoot();
  shadow.appendChild(document.querySelector('template').content);
</script>

Các lỗi thường gặp

Sau đây là một số vấn đề tôi gặp phải khi sử dụng <template> trong thực tế:

  • Nếu bạn đang sử dụng modpagespeed, hãy cẩn thận với lỗi này. Các mẫu xác định <style scoped> cùng dòng có thể được chuyển sang phần đầu bằng các quy tắc viết lại CSS của PageSpeed.
  • Không có cách nào để "hiển thị trước" một mẫu, nghĩa là bạn không thể tải trước các thành phần, xử lý JS, tải CSS ban đầu, v.v. Điều này áp dụng cho cả máy chủ và ứng dụng. Mẫu chỉ hiển thị khi được phát hành.
  • Hãy cẩn thận với các mẫu lồng nhau. Các thành phần này không hoạt động như bạn mong đợi. Ví dụ:

    <template>
      <ul>
        <template>
          <li>Stuff</li>
        </template>
      </ul>
    </template>
    

    Việc kích hoạt mẫu bên ngoài sẽ không kích hoạt các mẫu bên trong. Tức là các mẫu lồng nhau yêu cầu các mẫu con cũng phải được kích hoạt theo cách thủ công.

Con đường đến một tiêu chuẩn

Đừng quên nguồn gốc của chúng ta. Đã có một chặng đường dài để đến với các mẫu HTML dựa trên tiêu chuẩn. Trong những năm qua, chúng tôi đã đưa ra một số thủ thuật khá thông minh để tạo các mẫu có thể sử dụng lại. Dưới đây là hai lỗi thường gặp mà tôi từng gặp phải. Tôi sẽ đưa các phiên bản này vào bài viết này để so sánh.

Phương thức 1: DOM ngoài màn hình

Một phương pháp mà mọi người đã sử dụng từ lâu là tạo DOM "ngoài màn hình" và ẩn DOM đó khỏi chế độ xem bằng thuộc tính hidden hoặc display:none.

<div id="mytemplate" hidden>
  <img src="logo.png">
  <div class="comment"></div>
</div>

Mặc dù kỹ thuật này hiệu quả, nhưng vẫn có một số nhược điểm. Tóm tắt về kỹ thuật này:

  • Sử dụng DOM – trình duyệt biết DOM. Nó rất giỏi trong việc này. Chúng ta có thể dễ dàng nhân bản lớp này.
  • Không có nội dung nào được kết xuất – việc thêm hidden sẽ ngăn chặn khối này hiển thị.
  • Không trơ – mặc dù nội dung của chúng ta bị ẩn, nhưng vẫn có yêu cầu mạng được tạo cho hình ảnh.
  • Khó khăn trong việc tạo kiểu và giao diện – trang nhúng phải đặt tiền tố cho tất cả các quy tắc CSS bằng #mytemplate để thu hẹp phạm vi kiểu xuống mẫu. Cách này không linh hoạt và không đảm bảo rằng chúng ta sẽ không gặp phải xung đột tên trong tương lai. Ví dụ: chúng ta sẽ gặp sự cố nếu trang nhúng đã có một phần tử có mã nhận dạng đó.

Phương thức 2: Nạp chồng tập lệnh

Một kỹ thuật khác là nạp chồng <script> và thao tác với nội dung của lớp này dưới dạng một chuỗi. John Resig có lẽ là người đầu tiên trình bày điều này vào năm 2008 bằng Tiện ích tạo mẫu vi mô của mình. Hiện tại, có nhiều thư viện khác, bao gồm cả một số thư viện mới như handlebars.js.

Ví dụ:

<script id="mytemplate" type="text/x-handlebars-template">
  <img src="logo.png">
  <div class="comment"></div>
</script>

Tóm tắt về kỹ thuật này:

  • Không có nội dung nào được hiển thị – trình duyệt không hiển thị khối này vì <script>display:none theo mặc định.
  • Inert – trình duyệt không phân tích cú pháp nội dung tập lệnh dưới dạng JS vì loại của tập lệnh được đặt thành một loại khác ngoài "text/javascript".
  • Vấn đề bảo mật – khuyến khích sử dụng .innerHTML. Việc phân tích cú pháp chuỗi trong thời gian chạy của dữ liệu do người dùng cung cấp có thể dễ dàng dẫn đến các lỗ hổng XSS.

Kết luận

Bạn còn nhớ khi jQuery giúp việc làm việc với DOM trở nên đơn giản không? Kết quả là querySelector()/querySelectorAll() được thêm vào nền tảng. Rõ ràng là chiến thắng, phải không? Một thư viện phổ biến việc tìm nạp DOM bằng bộ chọn CSS và các tiêu chuẩn sau đó đã áp dụng phương thức này. Không phải lúc nào cách này cũng hiệu quả, nhưng tôi yêu thích khi nó hoạt động.

Tôi nghĩ <template> cũng là một trường hợp tương tự. Phương thức này chuẩn hoá cách chúng ta tạo mẫu phía máy khách, nhưng quan trọng hơn là không cần phải sử dụng các bản hack năm 2008. Theo tôi, việc giúp toàn bộ quy trình tạo nội dung trên web trở nên hợp lý, dễ bảo trì và có nhiều tính năng hơn luôn là một điều tốt.

Tài nguyên khác