Composants HowTo – Case à cocher

Résumé

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

L'élément tente de s'appliquer les attributs role="checkbox" et tabindex="0" lors de sa création. L'attribut role aide les technologies d'assistance telles qu'un lecteur d'écran à indiquer à l'utilisateur de quel type de commande il s'agit. L'attribut tabindex inclut l'élément dans l'ordre des onglets, ce qui le rend sélectionnable et utilisable au clavier. Pour en savoir plus sur ces deux sujets, consultez les pages Que peut 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 la barre d'espace pour activer ou désactiver ces états.

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 rétablit la sélection sur le document si la case à cocher est l'activeElement actuelle.

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

Référence

Démo

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>

Code

(function() {

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

  const KEYCODE = {
    SPACE: 32,
  };

Le clonage du contenu à partir d'un élément <template> est plus performant que l'utilisation de innerHTML, car il évite les coûts d'analyse HTML supplémentaires.

  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. Les instances sont créées en analysant le code HTML, en appelant document.createElement('howto-checkbox') ou en appelant new HowToCheckbox(); Le constructeur est un bon endroit pour créer un DOM ombragé, mais vous devez éviter de toucher les attributs ou les enfants du DOM léger, 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 un bon endroit pour définir l'état interne role, tabindex initial et installer des écouteurs d'événements.

    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() recherche toutes les propriétés d'instance et les exécute via les sétteurs de classe appropriés. Pour en savoir plus, consultez la section Propriétés lazy.

      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 la libération des références et la suppression 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 se refléter les uns les autres. Le setter de propriété pour "checked" gère les valeurs vraies/fausses et les reflète dans l'état de l'attribut. Pour en savoir plus, consultez la section Éviter les entrées multiples.

    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 "observedAttributes" est modifié. C'est un bon endroit pour gérer les effets secondaires, comme la définition d'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 mis au premier plan à l'aide de la souris ou en appelant focus(). Pour vous assurer qu'un élément est désactivé et non sélectionnable, supprimez l'attribut tabindex.

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

Si cet élément est actuellement sélectionné, annulez la sélection 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 checked et inverse son état. Comme _toggleChecked() n'est causé que par une action de l'utilisateur, il envoie également un événement de modification. Cet événement s'affiche dans des 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);
})();