HowTo-Komponenten – HowTo-Kästchen

Zusammenfassung

<howto-checkbox> steht für eine boolesche Option in einem Formular. Der häufigste Typ von Checkbox ist ein Dual-Typ, mit dem der Nutzer zwischen zwei Optionen wechseln kann: „Aktiviert“ und „Deaktiviert“.

Das Element versucht, die Attribute role="checkbox" und tabindex="0" selbst anzuwenden, wenn es zum ersten Mal erstellt wird. Das role-Attribut hilft Hilfstechnologien wie Screenreadern, dem Nutzer mitzuteilen, um welche Art von Steuerelement es sich handelt. Das tabindex-Attribut nimmt das Element in die Tab-Reihenfolge auf, sodass es mit der 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 boolesches Attribut checked hinzugefügt und eine entsprechende checked-Property auf true gesetzt. Außerdem wird für das Element je nach Status das Attribut aria-checked auf "true" oder "false" festgelegt. Durch Klicken auf das Kästchen mit der Maus oder durch Drücken der Leertaste wird der Status geändert.

Das Kästchen unterstützt auch den Status disabled. Wenn entweder das Attribut disabled auf „true“ gesetzt oder das Attribut disabled angewendet wird, wird das Attribut aria-disabled="true" für das Kontrollkästchen festgelegt, das Attribut tabindex entfernt und der Fokus auf das Dokument zurückgesetzt, wenn das Kontrollkä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() {

Definieren Sie Tastencodes, um die Verarbeitung von Tastaturereignissen zu erleichtern.

  const KEYCODE = {
    SPACE: 32,
  };

Das Klonen von Inhalten aus einem <template>-Element ist leistungsfähiger als die Verwendung von „innerHTML“, da zusätzliche HTML-Parsing-Kosten 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 immer 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 untergeordneten Elemente des Light-DOM bearbeiten, 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 anfänglichen role, tabindex und internen Status festlegen und Event-Listener installieren.

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

Ein Nutzer kann ein Attribut für eine Instanz eines Elements festlegen, bevor sein Prototyp mit dieser Klasse verbunden wurde. Die _upgradeProperty()-Methode prüft alle Instanzeigenschaften und führt sie durch die entsprechenden Klassensetter aus. 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 Aufräumarbeiten wie das Freigeben von Referenzen und das Entfernen von Event-Listenern durchführen.

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

Properties und die entsprechenden Attribute sollten sich gegenseitig widerspiegeln. Der Property-Setter für „checked“ verarbeitet „truthy“- und „falsy“-Werte und gibt diese im Status des Attributs wieder. Weitere Informationen finden Sie im Abschnitt Reentrancy 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 sich eines der Attribute im Array „observedAttributes“ ändert. Hier können Sie 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);

Das Attribut tabindex bietet keine Möglichkeit, die Fokussierbarkeit eines Elements vollständig zu entfernen. Elemente mit tabindex=-1 können weiterhin mit einer Maus oder durch Aufrufen von focus() fokussiert werden. Wenn Sie sichergehen möchten, dass ein Element deaktiviert und nicht fokussierbar ist, entfernen Sie das Attribut tabindex.

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

Wenn der Fokus derzeit auf diesem Element liegt, können Sie ihn durch Aufrufen der Methode HTMLElement.blur() entfernen.

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

    _onKeyUp(event) {

Verarbeiten Sie keine Tastenkombinationen mit Modifikator, die in der Regel von unterstützenden Technologien verwendet werden.

      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 geprüften Setter auf und ändert seinen Status. Da _toggleChecked() nur durch eine Nutzeraktion ausgelöst wird, wird auch ein Änderungsereignis gesendet. Dieses Ereignis wird weitergegeben, um das native Verhalten von <input type=checkbox> zu imitieren.

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