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 tuỳ chọn boolean trong một biểu mẫu. Loại hộp đánh dấu phổ biến nhất là kiểu kép cho phép người dùng chuyển đổi giữa hai lựa chọn – đánh dấu và bỏ đá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" trong lần tạo đầ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 chế độ điều khiển nào. Thuộc tính tabindex chọn phần tử theo thứ tự thẻ, giúp bàn phím có thể lấy tiêu điểm và hoạt động được. Để tìm hiểu thêm về hai chủ đề này, hãy xem phần ARIA có thể làm gì?Sử dụng chỉ mục thẻ.

Khi bạn đánh dấu vào hộp, thao tác này sẽ thêm 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 còn đặ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 phím cách sẽ chuyển đổi 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, thì hộp đánh dấu sẽ đặt aria-disabled="true", xoá thuộc tính tabindex và trả về tiêu điểm về 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 có tên dễ tiếp cận.

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 liên quan đến bàn phím.

  const KEYCODE = {
    SPACE: 32,
  };

Việc sao chép nội dung từ một phần tử <template> hiệu quả hơn so với sử dụng internalHTML 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ử này sẽ chạy bất cứ khi nào một thực thể mới được tạo. Các thực thể được tạo bằng cách phân tích cú pháp HTML, gọi document.createElement('howto- {1})) hoặc gọi new HowToCheckbox() ! Hàm khởi tạo là một nơi phù hợp để tạo DOM bóng, mặc dù bạn nên tránh chạm vào bất kỳ thuộc tính nào hoặc con của 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() sẽ kích hoạt khi phần tử được chèn vào DOM. Đây là một nơi phù hợp để đặt role ban đầu, tabindex, 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 bản sao của một 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 các phương thức setter của lớp thích hợp. Hãy xem phần thuộc tính tải từng phần để 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() sẽ kích hoạt khi phần tử bị xóa khỏi DOM. Đây là nơi phù hợp để dọn dẹp các công việc như phát hành tệp đối 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 thuộc tính cho đã đánh dấu xử lý các giá trị trung thực/giả và phản ánh các giá trị đó cho trạng thái của thuộc tính. Xem phần tránh truy cập lại để 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ảng "{Attributes" thay đổi. Bạn nên xử lý các hiệu ứng phụ, chẳng hạn như đặt 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 đưa ra 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ể lấy tiêu điểm 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àm tâm điểm, hãy xoá thuộc tính tabindex.

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

Nếu tiêu điểm hiện nằm trên phần tử này, hãy huỷ lấy 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 đối tượng sửa đổi thường được công nghệ hỗ trợ sử dụng.

      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 sẽ bị bỏ qua và trả về trình duyệt.

        default:
          return;
      }
    }

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

_toggleChecked() gọi phương thức setter đã đánh dấu và lật trạng thái của phương thức đó. Vì _toggleChecked() chỉ do một hành động của người dùng gây ra, nên thao tác này cũng sẽ gửi một sự kiện thay đổi. Sự kiện này sẽ bắt chước 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);
})();