Komponenty instruktażowe – pole wyboru

<howto-checkbox> reprezentuje opcję logiczną w formularzu. Najpopularniejszym typem pola wyboru jest pole podwójne, które umożliwia użytkownikowi przełączanie się między 2 opcjami – zaznaczoną i odznaczoną.

Element próbuje samodzielnie zastosować atrybuty role="checkbox" i tabindex="0", gdy jest tworzony po raz pierwszy. Atrybut role pomaga technologiom wspomagającym, takim jak czytnik ekranu, informować użytkownika, jakiego typu jest to pole. Atrybut tabindex włącza element do kolejności kart, dzięki czemu można je zaznaczyć i obsługiwać za pomocą klawiatury. Więcej informacji na te tematy znajdziesz w artykułach Co może ARIA?Używanie atrybutu tabindex.

Gdy pole wyboru jest zaznaczone, dodaje atrybut logiczny checked i ustawia odpowiadającą mu właściwość checked na true. Dodatkowo element ustawia atrybut aria-checked na wartość "true" lub "false" w zależności od stanu. Kliknięcie pola wyboru myszką lub spacją powoduje przełączenie tych stanów.

Pole wyboru obsługuje też stan disabled. Jeśli właściwość disabled ma wartość Prawda lub zastosowano atrybut disabled, pole wyboru ustawia aria-disabled="true", usuwa atrybut tabindex i zwraca fokus do dokumentu, jeśli pole wyboru jest bieżącym activeElement.

Pole wyboru jest połączone z elementem howto-label, aby mieć pewność, że ma on nazwę na potrzeby ułatwień dostępu.

Dokumentacja

Prezentacja

Wyświetl prezentację na żywo w GitHubie

Przykład użycia

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

Kod

(function() {

Definiowanie kodów klawiszy, aby ułatwić obsługę zdarzeń związanych z klawiaturą.

  const KEYCODE = {
    SPACE: 32,
  };

Klonowanie zawartości elementu <template> jest wydajniejsze niż użycie innerHTML, ponieważ pozwala uniknąć dodatkowych kosztów parsowania 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'];
    }

Konstruktor elementu jest uruchamiany za każdym razem, gdy tworzona jest nowa instancja. Identyfikatory są tworzone przez analizowanie kodu HTML, wywołanie document.createElement('howto-checkbox') lub wywołanie new HowToCheckbox(). Konstruktor jest dobrym miejscem do tworzenia schatten DOM, ale nie należy modyfikować żadnych atrybutów ani elementów podrzędnych schatten DOM, ponieważ mogą one nie być jeszcze dostępne.

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

connectedCallback() jest wywoływany, gdy element jest wstawiany do DOM. Jest to dobre miejsce na ustawienie początkowych wartości role, tabindex, stanu wewnętrznego i instalowania odbiorników zdarzeń.

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

Użytkownik może ustawić właściwość w instancji elementu, zanim jego prototyp zostanie połączony z tą klasą. Metoda _upgradeProperty() sprawdza, czy istnieją jakieś właściwości wystąpienia, i przekazuje je do odpowiednich ustawień klasy. Więcej informacji znajdziesz w sekcji Właściwości opóźnione.

      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() jest wywoływany, gdy element zostanie usunięty z DOM. Jest to dobre miejsce na wykonanie prac porządkowych, takich jak zwalnianie odwołań i usuwanie odbiorników zdarzeń.

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

Właściwości i odpowiadające im atrybuty powinny być lustrzanym odbiciem siebie nawzajem. Metoda settera właściwości checked obsługuje wartości prawda/fałsz i odzwierciedla je w stanie atrybutu. Więcej informacji znajdziesz w sekcji Unikanie ponownego wywoływania funkcji.

    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() jest wywoływany, gdy zmieni się którykolwiek z atrybutów w tablicy observedAttributes. Jest to dobre miejsce do obsługi skutków ubocznych, takich jak ustawianie atrybutów 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);

Atrybut tabindex nie umożliwia całkowitego wyłączenia możliwości zaznaczenia elementu. Elementy z atrybutem tabindex=-1 można nadal wybierać za pomocą myszy lub wywołując funkcję focus(). Aby mieć pewność, że element jest wyłączony i nie można go zaznaczyć, usuń atrybut tabindex.

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

Jeśli ten element jest obecnie zaznaczony, odznacz go, wywołując metodę HTMLElement.blur().

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

    _onKeyUp(event) {

Nie obsługuj skrótów klawiszowych, które są zwykle używane przez technologie wspomagające.

      if (event.altKey)
        return;

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

Każde inne naciśnięcie klawisza jest ignorowane i przekazywane z powrotem do przeglądarki.

        default:
          return;
      }
    }

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

_toggleChecked() wywołuje ustawianie pola checked i odwraca jego stan. Ponieważ zdarzenie _toggleChecked() jest wywoływane tylko przez działanie użytkownika, powoduje też wysłanie zdarzenia zmiany. To zdarzenie jest przenoszone, aby naśladować natywny interfejs <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);
})();