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

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 còn mới trong lĩnh vự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 khác nhau một chút, nhưng hầu hết đều có chung một cơ chế để hiển thị các mẫu lớp trình bày (còn gọi là khung hiển thị da).

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ả bản đặc 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 bao bọc 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 ứng dụng sẽ ở trạng thái trơ thực tế 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 tệp 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 trang đó 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 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 sao chép, yêu cầu hình ảnh được thực hiện và đánh dấu cuối cùng được hiển thị.

Bản thu thử

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

Ví dụ này minh hoạ tính trơ của nội dung mẫu. <script> chỉ chạy khi người dùng nhấn nút này để đóng 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 DOM tối từ một mẫu

Hầu hết mọi người đều đí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. Mọi thứ không điều chỉnh theo tỷ lệ, mọi thứ trở nên lộn xộn và trẻ sơ sinh 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

Dưới đây là một số lỗi mà tôi gặp phải khi sử dụng <template> trong môi trường tự nhiên:

  • 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, nhiều mẫu sẽ được chuyển đến phần đầu bằng quy tắc ghi 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

Hãy nhớ chúng ta đến từ đâu. Con đường dẫn đến các mẫu HTML dựa trên tiêu chuẩn là một hành trình dài. 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 pháp 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 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ó gì được hiển thị – việc thêm hidden sẽ ngă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ẽ được lưu trữ nếu trang nhúng đã có phần tử với mã nhận dạng đó.

Phương pháp 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 đối tượng này dưới dạng một chuỗi. John Resig có lẽ là người đầu tiên cho thấy điều này vào năm 2008 bằng Tiện ích tạo mẫu vi mô. 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 kỹ thuật này:

  • Không hiển thị nội dung nào – trình duyệt không hiển thị khối này vì <script>display:none theo mặc định.
  • Không hoạt động – 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ó nhớ thời điểm jQuery thực hiện việc xử lý với DOM 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. Mặc dù không phải lúc nào cũng hoạt động theo cách đó, nhưng tôi rất thích khi thực hiện.

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