HowTo-Komponenten – HowTo-Kästchen

Ein <howto-checkbox> steht für eine boolesche Option in einem Formular. Die am häufigsten verwendete Art von Kästchen ist ein zweifacher Typ, bei dem der Nutzer zwischen zwei Optionen wechseln kann – aktiviert und deaktiviert.

Das Element versucht, die Attribute role="checkbox" und tabindex="0" beim ersten Erstellen selbst anzuwenden. Mithilfe des Attributs role können Hilfstechnologien wie Screenreader dem Nutzer mitteilen, um welche Art von Steuerelement es sich handelt. Das Attribut tabindex sorgt dafür, dass das Element der TAB-Reihenfolge zugeordnet wird, sodass es per Tastatur zugänglich und bedienbar ist. Weitere Informationen zu diesen beiden Themen finden Sie unter Was kann ARIA tun? 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 das Verwenden von innerHTML, da zusätzliche Kosten für die HTML-Analyse 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 die anfänglichen role-, tabindex- und internen Status-Listener sowie Installationsereignis-Listener festlegen.

    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 Methode _upgradeProperty() sucht nach Instanzattributen und führt sie durch die richtigen Klassen-Setter. Weitere Informationen finden Sie im Abschnitt Verzögerte Eigenschaften.

      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/falsche Werte und überträgt sie auf den Status des Attributs. Weitere Informationen finden Sie im Abschnitt Widerruf 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);
})();