Commandes de formulaire plus performantes

Grâce à un nouvel événement et aux API d'éléments personnalisés, participer aux formulaires est désormais beaucoup plus facile.

Arthur Evans

De nombreux développeurs créent des commandes de formulaire personnalisées, soit pour fournir des commandes qui ne sont pas intégrées au navigateur, soit pour personnaliser l'apparence au-delà de ce qui est possible avec les commandes de formulaire intégrées.

Toutefois, il peut être difficile de reproduire les fonctionnalités des éléments de contrôle de formulaire HTML intégrés. Voici quelques-unes des fonctionnalités automatiquement attribuées à un élément <input> lorsque vous l'ajoutez à un formulaire:

  • La saisie est automatiquement ajoutée à la liste des commandes du formulaire.
  • La valeur de la saisie est automatiquement envoyée avec le formulaire.
  • L'entrée participe à la validation du formulaire. Vous pouvez styliser l'entrée à l'aide des pseudoclasses :valid et :invalid.
  • La saisie est notifiée lorsque le formulaire est réinitialisé, qu'il est actualisé ou que le navigateur tente de le remplir automatiquement.

Les commandes de formulaire personnalisées ne comportent généralement que peu de ces fonctionnalités. Les développeurs peuvent contourner certaines des limites de JavaScript, par exemple en ajoutant un <input> masqué à un formulaire pour participer à l'envoi du formulaire. Mais d'autres fonctionnalités ne peuvent pas être reproduites en JavaScript seul.

Deux nouvelles fonctionnalités Web facilitent la création de commandes de formulaire personnalisées et éliminent les limites des commandes personnalisées actuelles:

  • L'événement formdata permet à un objet JavaScript arbitraire de participer à l'envoi du formulaire. Vous pouvez ainsi ajouter des données de formulaire sans utiliser de <input> masqué.
  • L'API des éléments personnalisés associés à un formulaire permet aux éléments personnalisés de se comporter davantage comme des commandes de formulaire intégrées.

Ces deux fonctionnalités peuvent être utilisées pour créer de nouveaux types de commandes plus efficaces.

API basée sur les événements

L'événement formdata est une API de bas niveau qui permet à n'importe quel code JavaScript de participer à l'envoi de formulaires. Le mécanisme fonctionne comme suit:

  1. Vous ajoutez un écouteur d'événements formdata au formulaire avec lequel vous souhaitez interagir.
  2. Lorsqu'un utilisateur clique sur le bouton d'envoi, le formulaire déclenche un événement formdata, qui inclut un objet FormData contenant toutes les données envoyées.
  3. Chaque écouteur formdata a la possibilité d'ajouter ou de modifier les données avant l'envoi du formulaire.

Voici un exemple d'envoi d'une seule valeur dans un écouteur d'événements formdata:

const form = document.querySelector('form');
// FormData event is sent on <form> submission, before transmission.
// The event has a formData property
form.addEventListener('formdata', ({formData}) => {
  // https://developer.mozilla.org/docs/Web/API/FormData
  formData.append('my-input', myInputValue);
});

Essayez-le avec notre exemple sur Glitch. Veillez à l'exécuter sur Chrome 77 ou une version ultérieure pour voir l'API en action.

Compatibilité du navigateur

Navigateurs pris en charge

  • Chrome: 5.
  • Edge: 12.
  • Firefox: 4.
  • Safari: 5.

Source

Éléments personnalisés associés à un formulaire

Vous pouvez utiliser l'API basée sur les événements avec n'importe quel type de composant, mais elle ne vous permet d'interagir qu'avec le processus d'envoi.

Les commandes de formulaire standardisées participent à de nombreuses parties du cycle de vie du formulaire en plus de l'envoi. Les éléments personnalisés associés à un formulaire visent à combler l'écart entre les widgets personnalisés et les commandes intégrées. Les éléments personnalisés associés à un formulaire correspondent à de nombreuses fonctionnalités des éléments de formulaire standardisés:

  • Lorsque vous placez un élément personnalisé associé à un formulaire dans un <form>, il est automatiquement associé au formulaire, comme une commande fournie par le navigateur.
  • L'élément peut être libellé à l'aide d'un élément <label>.
  • L'élément peut définir une valeur qui est automatiquement envoyée avec le formulaire.
  • L'élément peut définir un indicateur indiquant s'il dispose ou non d'une entrée valide. Si l'une des commandes du formulaire contient une entrée non valide, le formulaire ne peut pas être envoyé.
  • L'élément peut fournir des rappels pour différentes parties du cycle de vie du formulaire, par exemple lorsque le formulaire est désactivé ou rétabli à son état par défaut.
  • L'élément est compatible avec les pseudoclasses CSS standards pour les commandes de formulaire, comme :disabled et :invalid.

C'est beaucoup de fonctionnalités ! Cet article ne les couvrira pas tous, mais décrit les principes de base nécessaires pour intégrer votre élément personnalisé à un formulaire.

Définir un élément personnalisé associé à un formulaire

Pour transformer un élément personnalisé en élément personnalisé associé à un formulaire, vous devez suivre quelques étapes supplémentaires:

  • Ajoutez une propriété formAssociated statique à votre classe d'éléments personnalisés. Cela indique au navigateur de traiter l'élément comme un élément de contrôle de formulaire.
  • Appelez la méthode attachInternals() sur l'élément pour accéder à des méthodes et propriétés supplémentaires pour les commandes de formulaire, comme setFormValue() et setValidity().
  • Ajoutez les propriétés et méthodes courantes compatibles avec les commandes de formulaire, comme name, value et validity.

Voici comment ces éléments s'intègrent dans une définition d'élément personnalisé de base:

// Form-associated custom elements must be autonomous custom elements--
// meaning they must extend HTMLElement, not one of its subclasses.
class MyCounter extends HTMLElement {

  // Identify the element as a form-associated custom element
  static formAssociated = true;

  constructor() {
    super();
    // Get access to the internal form control APIs
    this.internals_ = this.attachInternals();
    // internal value for this control
    this.value_ = 0;
  }

  // Form controls usually expose a "value" property
  get value() { return this.value_; }
  set value(v) { this.value_ = v; }

  // The following properties and methods aren't strictly required,
  // but browser-level form controls provide them. Providing them helps
  // ensure consistency with browser-provided controls.
  get form() { return this.internals_.form; }
  get name() { return this.getAttribute('name'); }
  get type() { return this.localName; }
  get validity() {return this.internals_.validity; }
  get validationMessage() {return this.internals_.validationMessage; }
  get willValidate() {return this.internals_.willValidate; }

  checkValidity() { return this.internals_.checkValidity(); }
  reportValidity() {return this.internals_.reportValidity(); }

  
}
customElements.define('my-counter', MyCounter);

Une fois enregistré, vous pouvez utiliser cet élément partout où vous utiliseriez un contrôle de formulaire fourni par le navigateur:

<form>
  <label>Number of bunnies: <my-counter></my-counter></label>
  <button type="submit">Submit</button>
</form>

Définir une valeur

La méthode attachInternals() renvoie un objet ElementInternals qui permet d'accéder aux API de contrôle de formulaire. La méthode la plus élémentaire est la méthode setFormValue(), qui définit la valeur actuelle du contrôle.

La méthode setFormValue() peut prendre l'un des trois types de valeurs suivants:

  • Valeur de chaîne.
  • Un objet File.
  • Un objet FormData. Vous pouvez utiliser un objet FormData pour transmettre plusieurs valeurs (par exemple, un contrôle de saisie de carte de crédit peut transmettre un numéro de carte, une date d'expiration et un code de validation).

Pour définir une valeur simple:

this.internals_.setFormValue(this.value_);

Pour définir plusieurs valeurs, vous pouvez procéder comme suit:

// Use the control's name as the base name for submitted data
const n = this.getAttribute('name');
const entries = new FormData();
entries.append(n + '-first-name', this.firstName_);
entries.append(n + '-last-name', this.lastName_);
this.internals_.setFormValue(entries);

Validation des entrées

Votre contrôle peut également participer à la validation du formulaire en appelant la méthode setValidity() sur l'objet interne.

// Assume this is called whenever the internal value is updated
onUpdateValue() {
  if (!this.matches(':disabled') && this.hasAttribute('required') &&
      this.value_ < 0) {
    this.internals_.setValidity({customError: true}, 'Value cannot be negative.');
  }
  else {
    this.internals_.setValidity({});
  }
  this.internals.setFormValue(this.value_);
}

Vous pouvez styliser un élément personnalisé associé à un formulaire avec les pseudoclasses :valid et :invalid, comme pour une commande de formulaire intégrée.

Rappels de cycle de vie

Une API d'élément personnalisé associé à un formulaire inclut un ensemble de rappels de cycle de vie supplémentaires pour s'intégrer au cycle de vie du formulaire. Les rappels sont facultatifs: n'implémentez un rappel que si votre élément doit effectuer une action à ce stade du cycle de vie.

void formAssociatedCallback(form)

Appelé lorsque le navigateur associe l'élément à un élément de formulaire ou le dissocie d'un élément de formulaire.

void formDisabledCallback(disabled)

Appelé après que l'état disabled de l'élément a changé, soit parce que l'attribut disabled de cet élément a été ajouté ou supprimé, soit parce que l'état disabled a changé sur un <fieldset> qui est un ancêtre de cet élément. Le paramètre disabled représente le nouvel état désactivé de l'élément. L'élément peut, par exemple, désactiver les éléments de son DOM ombragé lorsqu'il est désactivé.

void formResetCallback()

Appelé après la réinitialisation du formulaire. L'élément doit se réinitialiser sur un état par défaut. Pour les éléments <input>, cela implique généralement de définir la propriété value pour qu'elle corresponde à l'attribut value défini dans le balisage (ou, dans le cas d'une case à cocher, de définir la propriété checked pour qu'elle corresponde à l'attribut checked).

void formStateRestoreCallback(state, mode)

Appelé dans l'une des deux circonstances suivantes:

  • Lorsque le navigateur restaure l'état de l'élément (par exemple, après une navigation ou lorsque le navigateur redémarre). Dans ce cas, l'argument mode est "restore".
  • Lorsque les fonctionnalités d'assistance à la saisie du navigateur, telles que la saisie automatique des formulaires, définissent une valeur. Dans ce cas, l'argument mode est "autocomplete".

Le type du premier argument dépend de la manière dont la méthode setFormValue() a été appelée. Pour en savoir plus, consultez Restaurer l'état du formulaire.

Restaurer l'état du formulaire

Dans certains cas, par exemple lorsque vous revenez sur une page ou redémarrez le navigateur, le navigateur peut essayer de restaurer le formulaire dans l'état dans lequel l'utilisateur l'a laissé.

Pour un élément personnalisé associé à un formulaire, l'état restauré provient de la ou des valeurs que vous transmettez à la méthode setFormValue(). Vous pouvez appeler la méthode avec un seul paramètre de valeur, comme indiqué dans les exemples précédents, ou avec deux paramètres:

this.internals_.setFormValue(value, state);

value représente la valeur à envoyer de la commande. Le paramètre state facultatif est une représentation interne de l'état du contrôle, qui peut inclure des données qui ne sont pas envoyées au serveur. Le paramètre state accepte les mêmes types que le paramètre value. Il peut s'agir d'une chaîne, d'un objet File ou d'un objet FormData.

Le paramètre state est utile lorsque vous ne pouvez pas restaurer l'état d'un contrôle en fonction de la valeur seule. Par exemple, supposons que vous créiez un sélecteur de couleur avec plusieurs modes: une palette ou une roue de couleurs RVB. La valeur à envoyer correspond à la couleur sélectionnée sous une forme canonique, comme "#7fff00". Toutefois, pour restaurer la commande à un état spécifique, vous devez également connaître le mode dans lequel elle se trouvait. L'état peut donc ressembler à "palette/#7fff00".

this.internals_.setFormValue(this.value_,
    this.mode_ + '/' + this.value_);

Votre code doit restaurer son état en fonction de la valeur d'état stockée.

formStateRestoreCallback(state, mode) {
  if (mode == 'restore') {
    // expects a state parameter in the form 'controlMode/value'
    [controlMode, value] = state.split('/');
    this.mode_ = controlMode;
    this.value_ = value;
  }
  // Chrome currently doesn't handle autofill for form-associated
  // custom elements. In the autofill case, you might need to handle
  // a raw value.
}

Dans le cas d'un contrôle plus simple (par exemple, une saisie numérique), la valeur est probablement suffisante pour rétablir l'état précédent du contrôle. Si vous omettez state lorsque vous appelez setFormValue(), la valeur est transmise à formStateRestoreCallback().

formStateRestoreCallback(state, mode) {
  // Simple case, restore the saved value
  this.value_ = state;
}

Exemple concret

L'exemple suivant rassemble de nombreuses fonctionnalités des éléments personnalisés associés à un formulaire. Veillez à l'exécuter sur Chrome 77 ou une version ultérieure pour voir l'API en action.

Détection de fonctionnalités

Vous pouvez utiliser la détection de fonctionnalités pour déterminer si l'événement formdata et les éléments personnalisés associés au formulaire sont disponibles. Aucun polyfill n'est actuellement disponible pour ces deux fonctionnalités. Dans les deux cas, vous pouvez ajouter un élément de formulaire masqué pour propager la valeur de la commande au formulaire. De nombreuses fonctionnalités avancées des éléments personnalisés associés à un formulaire seront probablement difficiles ou impossibles à polyfiller.

if ('FormDataEvent' in window) {
  // formdata event is supported
}

if ('ElementInternals' in window &&
    'setFormValue' in window.ElementInternals.prototype) {
  // Form-associated custom elements are supported
}

Conclusion

L'événement formdata et les éléments personnalisés associés au formulaire fournissent de nouveaux outils pour créer des commandes de formulaire personnalisées.

L'événement formdata ne vous offre aucune nouvelle fonctionnalité, mais il vous fournit une interface permettant d'ajouter vos données de formulaire au processus d'envoi, sans avoir à créer un élément <input> masqué.

L'API des éléments personnalisés associés au formulaire fournit un nouvel ensemble de fonctionnalités permettant de créer des commandes de formulaire personnalisées qui fonctionnent comme des commandes de formulaire intégrées.

Image principale par Oudom Pravat sur Unsplash.