Thành phần trong hướng dẫn – hộp đánh dấu cách làm

Tóm tắt

<howto-checkbox> đại diện cho một tuỳ chọn boolean trong biểu mẫu. Loại hộp đánh dấu phổ biến nhất là loại kép, cho phép người dùng bật/tắt giữa hai lựa chọn – đã đánh dấu và chưa đánh dấu.

Phần tử này cố gắng tự áp dụng các thuộc tính role="checkbox"tabindex="0" khi được tạo lần đầu tiên. Thuộc tính role giúp công nghệ hỗ trợ như trình đọc màn hình cho người dùng biết đây là loại thành phần điều khiển nào. Thuộc tính tabindex chọn phần tử này vào thứ tự thẻ, giúp phần tử này có thể lấy tiêu điểm và thao tác được bằng bàn phím. Để tìm hiểu thêm về hai chủ đề này, hãy xem bài viết ARIA có thể làm gì?Sử dụng tabindex.

Khi bạn đánh dấu vào hộp đánh dấu, hộp đánh dấu này sẽ thêm một thuộc tính boolean checked và đặt thuộc tính checked tương ứng thành true. Ngoài ra, phần tử này đặt thuộc tính aria-checked thành "true" hoặc "false", tuỳ thuộc vào trạng thái của phần tử. Nhấp vào hộp đánh dấu bằng chuột hoặc thanh dấu cách để bật/tắt các trạng thái đã đánh dấu này.

Hộp đánh dấu cũng hỗ trợ trạng thái disabled. Nếu thuộc tính disabled được đặt thành true hoặc thuộc tính disabled được áp dụng, hộp đánh dấu sẽ đặt aria-disabled="true", xoá thuộc tính tabindex và trả về tiêu điểm vào tài liệu nếu hộp đánh dấu là activeElement hiện tại.

Hộp đánh dấu được ghép nối với phần tử howto-label để đảm bảo phần tử đó có tên dễ đọc.

Tài liệu tham khảo

Bản minh hoạ

Xem bản minh hoạ trực tiếp trên GitHub

Ví dụ về cách sử dụng

<style>
  howto-checkbox {
    vertical-align: middle;
  }
  howto-label {
    vertical-align: middle;
    display: inline-block;
    font-weight: bold;
    font-family: sans-serif;
    font-size: 20px;
    margin-left: 8px;
  }
</style>

<howto-checkbox id="join-checkbox"></howto-checkbox>
<howto-label for="join-checkbox">Join Newsletter</howto-label>

(function() {

Xác định các mã phím để giúp xử lý các sự kiện trên bàn phím.

  const KEYCODE = {
    SPACE: 32,
  };

Việc sao chép nội dung từ phần tử <template> hiệu quả hơn so với sử dụng innerHTML vì nó giúp tránh phát sinh thêm chi phí phân tích cú pháp HTML.

  const template = document.createElement('template');

  template.innerHTML = `
    <style>
      :host {
        display: inline-block;
        background: url('../images/unchecked-checkbox.svg') no-repeat;
        background-size: contain;
        width: 24px;
        height: 24px;
      }
      :host([hidden]) {
        display: none;
      }
      :host([checked]) {
        background: url('../images/checked-checkbox.svg') no-repeat;
        background-size: contain;
      }
      :host([disabled]) {
        background:
          url('../images/unchecked-checkbox-disabled.svg') no-repeat;
        background-size: contain;
      }
      :host([checked][disabled]) {
        background:
          url('../images/checked-checkbox-disabled.svg') no-repeat;
        background-size: contain;
      }
    </style>
  `;


  class HowToCheckbox extends HTMLElement {
    static get observedAttributes() {
      return ['checked', 'disabled'];
    }

Hàm khởi tạo của phần tử được chạy bất cứ khi nào một thực thể mới được tạo. Các đối tượng được tạo bằng cách phân tích cú pháp HTML, gọi document.createElement('howto-checkbox') hoặc gọi new HowToCheckbox(); Hàm tạo là nơi phù hợp để tạo DOM bóng, tuy nhiên bạn nên tránh chạm vào bất kỳ thuộc tính nào hoặc các thành phần con DOM sáng vì chúng có thể chưa có sẵn.

    constructor() {
      super();
      this.attachShadow({mode: 'open'});
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }

connectedCallback() kích hoạt khi phần tử được chèn vào DOM. Đây là nơi thích hợp để đặt role, tabindex ban đầu, trạng thái nội bộ và cài đặt trình nghe sự kiện.

    connectedCallback() {
      if (!this.hasAttribute('role'))
        this.setAttribute('role', 'checkbox');
      if (!this.hasAttribute('tabindex'))
        this.setAttribute('tabindex', 0);

Người dùng có thể đặt thuộc tính trên một thực thể của phần tử trước khi nguyên mẫu của phần tử đó được kết nối với lớp này. Phương thức _upgradeProperty() sẽ kiểm tra mọi thuộc tính thực thể và chạy các thuộc tính đó thông qua phương thức setter lớp thích hợp. Hãy xem phần thuộc tính tải lười để biết thêm thông tin chi tiết.

      this._upgradeProperty('checked');
      this._upgradeProperty('disabled');

      this.addEventListener('keyup', this._onKeyUp);
      this.addEventListener('click', this._onClick);
    }

    _upgradeProperty(prop) {
      if (this.hasOwnProperty(prop)) {
        let value = this[prop];
        delete this[prop];
        this[prop] = value;
      }
    }

disconnectedCallback() kích hoạt khi phần tử bị xoá khỏi DOM. Đây là nơi phù hợp để dọn dẹp công việc như phát hành tệp tham chiếu và xoá trình nghe sự kiện.

    disconnectedCallback() {
      this.removeEventListener('keyup', this._onKeyUp);
      this.removeEventListener('click', this._onClick);
    }

Các thuộc tính và thuộc tính tương ứng phải phản ánh lẫn nhau. Phương thức setter của thuộc tính đã đánh dấu xử lý các giá trị đúng/sai và phản ánh các giá trị đó vào trạng thái của thuộc tính. Hãy xem phần tránh tái nhập để biết thêm chi tiết.

    set checked(value) {
      const isChecked = Boolean(value);
      if (isChecked)
        this.setAttribute('checked', '');
      else
        this.removeAttribute('checked');
    }

    get checked() {
      return this.hasAttribute('checked');
    }

    set disabled(value) {
      const isDisabled = Boolean(value);
      if (isDisabled)
        this.setAttribute('disabled', '');
      else
        this.removeAttribute('disabled');
    }

    get disabled() {
      return this.hasAttribute('disabled');
    }

attributeChangedCallback() được gọi khi bất kỳ thuộc tính nào trong mảngObserveAttributes thay đổi. Đây là một nơi phù hợp để xử lý các tác dụng phụ, chẳng hạn như đặt các thuộc tính ARIA.

    attributeChangedCallback(name, oldValue, newValue) {
      const hasValue = newValue !== null;
      switch (name) {
        case 'checked':
          this.setAttribute('aria-checked', hasValue);
          break;
        case 'disabled':
          this.setAttribute('aria-disabled', hasValue);

Thuộc tính tabindex không cung cấp cách để xoá hoàn toàn khả năng lấy tiêu điểm khỏi một phần tử. Bạn vẫn có thể đặt tiêu điểm vào các phần tử có tabindex=-1 bằng chuột hoặc bằng cách gọi focus(). Để đảm bảo một phần tử bị vô hiệu hoá và không thể lấy tiêu điểm, hãy xoá thuộc tính tabindex.

          if (hasValue) {
            this.removeAttribute('tabindex');

Nếu tiêu điểm hiện đang nằm trên phần tử này, hãy bỏ tiêu điểm bằng cách gọi phương thức HTMLElement.blur()

            this.blur();
          } else {
            this.setAttribute('tabindex', '0');
          }
          break;
      }
    }

    _onKeyUp(event) {

Không xử lý các phím tắt thường dùng của công nghệ hỗ trợ.

      if (event.altKey)
        return;

      switch (event.keyCode) {
        case KEYCODE.SPACE:
          event.preventDefault();
          this._toggleChecked();
          break;

Mọi thao tác nhấn phím khác đều bị bỏ qua và được chuyển lại trình duyệt.

        default:
          return;
      }
    }

    _onClick(event) {
      this._toggleChecked();
    }

_toggleChecked() gọi phương thức setter đã kiểm tra và lật trạng thái của phương thức này. Vì _toggleChecked() chỉ do hành động của người dùng gây ra, nên _toggleChecked() cũng sẽ gửi một sự kiện thay đổi. Sự kiện này sẽ nổi lên để mô phỏng hành vi gốc của <input type=checkbox>.

    _toggleChecked() {
      if (this.disabled)
        return;
      this.checked = !this.checked;
      this.dispatchEvent(new CustomEvent('change', {
        detail: {
          checked: this.checked,
        },
        bubbles: true,
      }));
    }
  }

  customElements.define('howto-checkbox', HowToCheckbox);
})();