HowTo-Komponenten – HowTo-Kästchen

Ein <howto-checkbox> steht für eine boolesche Option in einem Formular. Die gängigste Art von Kästchen ist ein Doppelkästchen, mit dem Nutzer zwischen zwei Optionen wechseln können: angeklickt und nicht angeklickt.

Das Element versucht, die Attribute role="checkbox" und tabindex="0" beim ersten Erstellen selbst anzuwenden. Das role-Attribut hilft Hilfstechnologien wie Screenreadern, dem Nutzer mitzuteilen, um welche Art von Steuerelement es sich handelt. Mit dem tabindex-Attribut wird das Element in die Tabulatorreihenfolge aufgenommen, sodass es über die Tastatur fokussiert und bedient werden kann. Weitere Informationen zu diesen beiden Themen finden Sie unter Was kann ARIA? und Tabindex verwenden.

Wenn das Kästchen angeklickt ist, wird ein boolesche checked-Attribut hinzugefügt und eine entsprechende checked-Property auf true gesetzt. Außerdem legt das Element je nach Status das Attribut aria-checked auf "true" oder "false" fest. Wenn Sie mit der Maus oder der Leertaste auf das Kästchen klicken, wird der jeweilige Status aktiviert oder deaktiviert.

Das Kästchen unterstützt auch den Status disabled. Wenn entweder die Eigenschaft disabled auf „wahr“ gesetzt ist oder das Attribut disabled angewendet wird, wird über das Kästchen aria-disabled="true" festgelegt, das Attribut tabindex entfernt und der Fokus wird auf das Dokument zurückgesetzt, wenn das Kästchen das aktuelle activeElement ist.

Das Kästchen ist mit einem howto-label-Element verknüpft, damit es einen barrierefreien Namen hat.

Referenz

Demo

Live-Demo auf GitHub ansehen

Nutzungsbeispiel

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

Code

(function() {

Schlüsselcodes definieren, um die Verarbeitung von Tastaturereignissen zu unterstützen

  const KEYCODE = {
    SPACE: 32,
  };

Das Klonen von Inhalten aus einem <template>-Element ist leistungsfähiger als die Verwendung von innerHTML, da dadurch zusätzliche Kosten für das Parsen von HTML vermieden werden.

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

Der Konstruktor des Elements wird jedes Mal ausgeführt, wenn eine neue Instanz erstellt wird. Instanzen werden entweder durch Parsen von HTML, Aufrufen von document.createElement('howto-checkbox') oder Aufrufen von new HowToCheckbox() erstellt. Der Konstruktor ist ein guter Ort, um Shadow-DOM zu erstellen. Sie sollten jedoch keine Attribute oder Light-DOM-Untergeordneten ändern, da sie möglicherweise noch nicht verfügbar sind.

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

connectedCallback() wird ausgelöst, wenn das Element in das DOM eingefügt wird. Hier können Sie den Anfangsstatus von role, tabindex und den internen Status festlegen und Ereignis-Listener installieren.

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

Ein Nutzer kann eine Property für eine Instanz eines Elements festlegen, bevor der Prototyp mit dieser Klasse verbunden wurde. Die _upgradeProperty()-Methode sucht nach Instanzeigenschaften und führt sie durch die entsprechenden Klassen-Setter. Weitere Informationen finden Sie im Abschnitt Lazy-Properties.

      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() wird ausgelöst, wenn das Element aus dem DOM entfernt wird. Hier können Sie Bereinigungsarbeiten wie das Freigeben von Referenzen und das Entfernen von Ereignis-Listenern ausführen.

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

Unterkünfte und ihre entsprechenden Attribute sollten übereinstimmen. Der Property-Setter für „checked“ verarbeitet wahrheitsgemäße/falschheitsgemäße Werte und überträgt sie auf den Status des Attributs. Weitere Informationen finden Sie im Abschnitt Wiederkehrende Aufrufe vermeiden.

    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() wird aufgerufen, wenn eines der Attribute im Array „observedAttributes“ geändert wird. Hier können Sie auch Nebenwirkungen wie das Festlegen von ARIA-Attributen behandeln.

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

Mit dem Attribut „tabindex“ kann die Fokussierbarkeit eines Elements nicht vollständig entfernt werden. Elemente mit tabindex=-1 können weiterhin mit der Maus oder durch Aufrufen von focus() fokussiert werden. Wenn ein Element deaktiviert und nicht fokussierbar sein soll, entfernen Sie das Attribut „tabindex“.

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

Wenn der Fokus derzeit auf diesem Element liegt, heben Sie den Fokus auf, indem Sie die Methode HTMLElement.blur() aufrufen.

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

    _onKeyUp(event) {

Modifikatorkürzel, die in der Regel von Hilfstechnologien verwendet werden, werden nicht verarbeitet.

      if (event.altKey)
        return;

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

Alle anderen Tastendrücke werden ignoriert und an den Browser zurückgegeben.

        default:
          return;
      }
    }

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

_toggleChecked() ruft den prüfbaren Setter auf und ändert den Status. Da _toggleChecked() nur durch eine Nutzeraktion verursacht wird, wird auch ein Änderungsereignis gesendet. Dieses Ereignis wird gepuffert, um das native Verhalten von <input type=checkbox> nachzuahmen.

    _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);
})();