Komponen Petunjuk – kotak centang petunjuk

Ringkasan

<howto-checkbox> merepresentasikan opsi boolean dalam formulir. Jenis kotak centang yang paling umum adalah jenis ganda yang memungkinkan pengguna beralih di antara dua pilihan -- dicentang dan tidak dicentang.

Elemen mencoba menerapkan sendiri atribut role="checkbox" dan tabindex="0" saat pertama kali dibuat. Atribut role membantu teknologi pendukung seperti pembaca layar memberi tahu pengguna tentang jenis kontrol ini. Atribut tabindex mengikutsertakan elemen ke dalam urutan tab, sehingga dapat difokuskan dan dioperasikan dengan keyboard. Untuk mempelajari kedua topik ini lebih lanjut, lihat Apa yang dapat dilakukan ARIA? dan Menggunakan tabindex.

Jika kotak centang ini dicentang, atribut boolean checked akan ditambahkan, dan menetapkan properti checked yang sesuai ke true. Selain itu, elemen tersebut menetapkan atribut aria-checked ke "true" atau "false", bergantung pada statusnya. Mengklik kotak centang dengan mouse, atau tombol spasi, akan menonaktifkan status yang dicentang ini.

Kotak centang ini juga mendukung status disabled. Jika properti disabled ditetapkan ke benar (true) atau atribut disabled diterapkan, kotak centang akan menetapkan aria-disabled="true", menghapus atribut tabindex, dan menampilkan fokus ke dokumen jika kotak centangnya adalah activeElement saat ini.

Kotak centang disambungkan dengan elemen howto-label untuk memastikannya memiliki nama yang dapat diakses.

Referensi

Demo

Lihat demo langsung di GitHub

Contoh penggunaan

<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>

Kode

(function() {

Menentukan kode tombol untuk membantu menangani peristiwa keyboard.

  const KEYCODE = {
    SPACE: 32,
  };

Meng-clone konten dari elemen <template> berperforma lebih baik daripada menggunakan innerHTML karena menghindari biaya penguraian HTML tambahan.

  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'];
    }

Konstruktor elemen dijalankan setiap kali instance baru dibuat. Instance dibuat dengan mengurai HTML, memanggil document.createElement('howto-checkbox'), atau memanggil HowToCheckbox() baru; Konstruktor ini adalah tempat yang baik untuk membuat shadow DOM, meskipun Anda harus menghindari menyentuh atribut atau turunan DOM ringan apa pun karena mungkin belum tersedia.

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

connectedCallback() diaktifkan saat elemen disisipkan ke dalam DOM. Ini adalah tempat yang tepat untuk menetapkan role, tabindex, status internal, dan menginstal pemroses peristiwa awal.

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

Pengguna dapat menetapkan properti pada instance elemen, sebelum prototipenya terhubung ke class ini. Metode _upgradeProperty() akan memeriksa setiap properti instance dan menjalankannya melalui penyetel class yang tepat. Lihat bagian properti lambat untuk mengetahui detail selengkapnya.

      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() diaktifkan saat elemen dihapus dari DOM. Ini adalah tempat yang baik untuk melakukan pekerjaan pembersihan seperti merilis referensi dan menghapus pemroses peristiwa.

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

Properti dan atributnya yang sesuai harus mencerminkan satu sama lain. Penyetel properti untuk dicentang menangani nilai truthy/falsy dan mencerminkannya ke status atribut. Lihat bagian menghindari reentransi untuk detail selengkapnya.

    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() dipanggil saat salah satu atribut dalam arrayObservationAttributes berubah. Ini adalah tempat yang baik untuk menangani efek samping, seperti menyetel atribut 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);

Atribut tabindex tidak menyediakan cara untuk sepenuhnya menghapus fokus dari suatu elemen. Elemen dengan tabindex=-1 masih dapat difokuskan dengan mouse atau dengan memanggil focus(). Untuk memastikan elemen dinonaktifkan dan tidak dapat difokuskan, hapus atribut tabindex.

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

Jika fokus saat ini berada pada elemen ini, hapus fokus dengan memanggil metode HTMLElement.blur()

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

    _onKeyUp(event) {

Tidak menangani pintasan pengubah yang biasanya digunakan oleh teknologi pendukung.

      if (event.altKey)
        return;

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

Penekanan tombol lainnya akan diabaikan dan diteruskan kembali ke browser.

        default:
          return;
      }
    }

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

_toggleChecked() memanggil penyetel yang dicentang dan membalik statusnya. Karena _toggleChecked() hanya disebabkan oleh tindakan pengguna, tindakan ini juga akan mengirimkan peristiwa perubahan. Peristiwa ini ditampilkan sebagai balon untuk meniru perilaku native <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);
})();