Komponenty instruktażowe – pole wyboru

Podsumowanie

<howto-checkbox> reprezentuje opcję wartości logicznej w formularzu. Najczęściej jest to pole wyboru dwutypowe, które pozwala użytkownikowi przełączać się między 2 opcjami: zaznaczoną i niezaznaczoną.

Element próbuje samodzielnie zastosować atrybuty role="checkbox" i tabindex="0" przy jego tworzeniu. Atrybut role pomaga technologii wspomagającej osoby z niepełnosprawnością, na przykład czytnika ekranu, informować użytkownika o tym, jaką kontrolę mają. Atrybut tabindex uwzględnia element w kolejności tabulacji, dzięki czemu można go naciskać i działać z klawiaturą. Aby dowiedzieć się więcej na te 2 tematy, przeczytaj artykuły Co może robić ARIA? i Korzystanie z tabindex.

Zaznaczenie tego pola wyboru powoduje dodanie atrybutu wartości logicznej checked i ustawienie odpowiadającej mu właściwości checked na wartość true. Dodatkowo w zależności od stanu elementu ustawia on atrybut aria-checked na "true" lub "false". Kliknięcie pola wyboru za pomocą myszy lub spacji powoduje przełączenie zaznaczonego stanu.

To pole wyboru obsługuje też stan disabled. Jeśli właściwość disabled ma wartość true lub zostanie zastosowany atrybut disabled, pole wyboru ustawia aria-disabled="true", usuwa atrybut tabindex i zwraca zaznaczenie dokumentu, jeśli pole wyboru to bieżący parametr activeElement.

Pole wyboru jest sparowane z elementem howto-label, aby mieć pewność, że ma ono ułatwioną nazwę.

Dokumentacja

Wersja demonstracyjna

Zobacz 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() {

Określaj kody klawiszy ułatwiające obsługę zdarzeń klawiatury.

  const KEYCODE = {
    SPACE: 32,
  };

Klonowanie treści z elementu <template> jest wydajniejsze niż użycie innerHTML, ponieważ pozwala to uniknąć dodatkowych kosztów analizy 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 przy każdym utworzeniu nowej instancji. Instancje są tworzone przez analizowanie kodu HTML, wywoływanie elementu document.createElement('howto-checkbox') lub wywołanie nowego HowToCheckbox(). Konstruktor jest dobrym miejscem do tworzenia obiektu shadow DOM, ale unikaj dotykania żadnych atrybutów i elementów podrzędnych interfejsu Light DOM, ponieważ mogą one jeszcze nie być dostępne.

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

connectedCallback() uruchamia się, gdy element zostanie wstawiony do modelu DOM. Jest to dobre miejsce do ustawienia początkowego stanu role i tabindex, stanu wewnętrznego oraz instalacji detektoró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 wystąpieniu elementu, zanim jego prototyp zostanie połączony z klasą. Metoda _upgradeProperty() sprawdzi właściwości instancji i przeprowadzi je za pomocą odpowiednich ustawień ustawiających klasy. Więcej informacji znajdziesz w sekcji dotyczącej leniwych właściwości.

      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() uruchamia się, gdy element zostanie usunięty z DOM. To świetne miejsce, by zrobić porządek, np. zwolnić pliki referencyjne i usunąć detektory zdarzeń.

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

Właściwości i odpowiadające im atrybuty powinny się replikować. Metoda ustawiająca w przypadku zaznaczonych właściwości obsługuje wartości prawda/fałsz i odzwierciedla ich stan w atrybucie. Więcej informacji znajdziesz w sekcji dotyczącej unikania ponawiania próśb.

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

Funkcja attributeChangedCallback() jest wywoływana, gdy zmieni się dowolny z atrybutów w tablicy zaobserwowanej. To dobre miejsce do radzenia sobie z efektami ubocznymi, takimi 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 pełnego usunięcia możliwości zaznaczenia elementu. Elementy z atrybutem tabindex=-1 nadal można zaznaczyć, używając myszy lub wywołując metodę 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 zaznaczony jest obecnie ten element, cofnij jego zaznaczenie, wywołując metodę HTMLElement.blur()

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

    _onKeyUp(event) {

Nie stosuj skrótów modyfikujących, które zwykle są używane w technologiach wspomagających osoby z niepełnosprawnością.

      if (event.altKey)
        return;

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

Pozostałe naciśnięcie klawisza jest ignorowane i jest przekazywane z powrotem do przeglądarki.

        default:
          return;
      }
    }

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

_toggleChecked() wywołuje zaznaczoną metodę ustawiania i odwraca jej stan. Funkcja _toggleChecked() jest wywoływana tylko przez działanie użytkownika, więc wysyła też zdarzenie zmiany. To zdarzenie wyświetla się jako dymki, aby naśladować natywne działanie usługi <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);
})();