Composants HowTo – Case à cocher

Résumé

Un <howto-checkbox> représente une option booléenne dans un formulaire. Le type de case à cocher le plus courant est double, qui permet à l'utilisateur de basculer entre deux choix : cochée et décochée.

L'élément tente d'appliquer lui-même les attributs role="checkbox" et tabindex="0" lors de sa création initiale. L'attribut role permet à une technologie d'assistance telle qu'un lecteur d'écran d'indiquer à l'utilisateur de quel type de contrôle il s'agit. L'attribut tabindex active l'élément dans l'ordre de tabulation, ce qui le rend sélectionnable et exploitable avec le clavier. Pour en savoir plus sur ces deux sujets, consultez les sections Que peuvent faire ARIA ? et Utiliser tabindex.

Lorsque la case est cochée, un attribut booléen checked est ajouté et une propriété checked correspondante est définie sur true. En outre, l'élément définit un attribut aria-checked sur "true" ou "false", en fonction de son état. Cliquez sur la case à cocher avec la souris ou sur la barre d'espace pour activer ou désactiver ces états cochés.

La case à cocher accepte également un état disabled. Si la propriété disabled est définie sur "true" ou si l'attribut disabled est appliqué, la case à cocher définit aria-disabled="true", supprime l'attribut tabindex et replace le curseur sur le document si la case à cocher est l'élément activeElement actuel.

La case à cocher est associée à un élément howto-label pour garantir qu'elle possède un nom accessible.

Reference

Démonstration

Voir la démonstration en direct sur GitHub

Exemple d'utilisation

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

Coder

(function() {

Définissez des codes de touche pour faciliter la gestion des événements de clavier.

  const KEYCODE = {
    SPACE: 32,
  };

Il est plus efficace de cloner le contenu à partir d'un élément <template> que d'utiliser innerHTML, car cela évite des coûts supplémentaires d'analyse du code 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'];
    }

Le constructeur de l'élément est exécuté chaque fois qu'une instance est créée. Pour créer les instances, vous devez analyser le code HTML, appeler document.createElement('howto-checkbox') ou appeler la méthode new HowToCheckbox(). Le constructeur est un bon endroit pour créer un Shadow DOM, mais évitez de toucher des attributs ou des éléments enfants DOM clairs, car ils ne sont peut-être pas encore disponibles.

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

connectedCallback() se déclenche lorsque l'élément est inséré dans le DOM. C'est l'endroit idéal pour définir l'état interne role et tabindex initiaux, ainsi que pour installer les écouteurs d'événements d'installation.

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

Un utilisateur peut définir une propriété sur une instance d'un élément, avant que son prototype n'ait été connecté à cette classe. La méthode _upgradeProperty() vérifie les propriétés de l'instance et les exécute via les setters de classe appropriés. Pour en savoir plus, consultez la section Propriétés différées.

      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() se déclenche lorsque l'élément est supprimé du DOM. C'est un bon endroit pour effectuer des tâches de nettoyage, comme libérer des références et supprimer des écouteurs d'événements.

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

Les propriétés et leurs attributs correspondants doivent être dupliqués. Le setter de propriété des éléments vérifiés gère les valeurs véridiques/falsifiées et les reflète dans l'état de l'attribut. Pour en savoir plus, consultez la section Éviter la réentrée.

    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() est appelé lorsque l'un des attributs du tableau "ObserverAttributes" est modifié. C'est un bon endroit pour gérer les effets secondaires, comme la définition des attributs 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);

L'attribut tabindex ne permet pas de supprimer complètement la sélection d'un élément. Les éléments avec tabindex=-1 peuvent toujours être sélectionnés avec la souris ou en appelant focus(). Pour vous assurer qu'un élément est désactivé et qu'il n'est pas possible de sélectionner un élément, supprimez l'attribut tabindex.

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

Si cet élément est actuellement sélectionné, désactivez-le en appelant la méthode HTMLElement.blur().

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

    _onKeyUp(event) {

Ne gérez pas les raccourcis de modification généralement utilisés par les technologies d'assistance.

      if (event.altKey)
        return;

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

Toute autre pression sur une touche est ignorée et renvoyée au navigateur.

        default:
          return;
      }
    }

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

_toggleChecked() appelle le setter vérifié et inverse son état. Comme _toggleChecked() n'est causé que par une action de l'utilisateur, un événement de modification est également envoyé. Cet événement apparaît sous forme de bulles pour imiter le comportement natif de <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);
})();