Komponenty instruktażowe – pole wyboru

Podsumowanie

Wartość <howto-checkbox> reprezentuje opcję logiczną w formularzu. Najczęstszym 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 jest zastosowany atrybut disabled, pole wyboru ustawia wartość aria-disabled="true", usuwa atrybut tabindex i przywraca fokus na dokument, jeśli pole wyboru jest obecne w polu activeElement.

Pole wyboru jest sparowane z elementem howto-label, dzięki czemu ma ono nazwę ułatwienia 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. To dobre miejsce na skonfigurowanie początkowych detektorów zdarzeń role, tabindex, stanu wewnętrznego i instalacji.

    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 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ć takie same. Metoda ustawiania właściwości dla zaznaczonego pola obsługuje wartości prawda/fałszywe i odzwierciedla je zgodnie ze stanem atrybutu. Więcej informacji znajdziesz w sekcji na temat unikania ponownego przesyłania.

    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 dowolny z atrybutów w tablicy zaobserwowany atrybut ulegnie zmianie. Możesz tam radzić 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 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 zmienia 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);
})();