Commandes de formulaire plus performantes

Arthur Evans

Publié le 8 août 2019

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

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

  • L'entrée est automatiquement ajoutée à la liste des contrôles du formulaire.
  • La valeur de l'entrée est automatiquement envoyée avec le formulaire.
  • L'entrée participe à la validation du formulaire . Vous pouvez styliser l'entrée à l'aide des pseudo-classes :valid et :invalid.
  • Le système est notifié lorsque le formulaire est réinitialisé, lorsqu'il est rechargé ou lorsque le navigateur tente de remplir automatiquement les champs du formulaire.

Les commandes de formulaire personnalisées ne disposent généralement que de quelques-unes de ces fonctionnalités. Les développeurs peuvent contourner certaines limites de JavaScript, par exemple en ajoutant un <input> masqué à un formulaire pour participer à l'envoi du formulaire. Toutefois, d'autres fonctionnalités ne peuvent pas être répliquées en JavaScript seul.

Deux fonctionnalités Web permettent de créer plus facilement des commandes de formulaire personnalisées et de supprimer les limites des commandes personnalisées :

  • 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 Form-Associated Custom Elements permet aux éléments personnalisés d'agir davantage comme des contrôles de formulaire intégrés.

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

API basée sur les événements

Browser Support

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

Source

L'événement formdata est une API de bas niveau qui permet à tout code JavaScript de participer à la soumission de formulaire.

  • Ajoutez un écouteur d'événements formdata au formulaire avec lequel vous souhaitez interagir.
  • Lorsqu'un utilisateur clique sur "Envoyer", le formulaire déclenche un événement formdata, qui inclut un objet FormData contenant toutes les données envoyées.
  • Chaque formdata écouteur a la possibilité d'ajouter ou de modifier les données avant la soumission 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);
});

É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 étapes du cycle de vie du formulaire. Les éléments personnalisés associés au formulaire visent à combler le fossé entre les widgets personnalisés et les contrôles intégrés. 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 à l'intérieur d'un <form>, il est automatiquement associé au formulaire, comme un contrôle fourni par le navigateur.
  • L'élément peut être libellé à l'aide d'un élément <label>.
  • Cet élément peut définir une valeur qui est automatiquement soumise avec le formulaire.
  • L'élément peut définir un indicateur pour indiquer s'il comporte ou non une entrée valide. Si l'un des champs du formulaire contient des données invalides, le formulaire ne peut pas être soumis.
  • 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éinitialisé à son état par défaut.
  • L'élément est compatible avec les pseudoclasses CSS standards pour les commandes de formulaire, telles que :disabled et :invalid.

Ce document ne couvre pas tout, mais il décrit les bases nécessaires pour intégrer votre élément personnalisé à un formulaire.

Définir un élément personnalisé associé au 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 à la classe de votre élément personnalisé. Cela indique au navigateur de traiter l'élément comme un 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 contrôles de formulaire, comme setFormValue() et setValidity().
  • Ajoutez les propriétés et méthodes communes prises en charge par les contrôles 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.
// 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 donne accès aux API de contrôle de formulaire. La plus élémentaire de ces méthodes est setFormValue(), qui définit la valeur actuelle du contrôle.

La méthode setFormValue() peut accepter 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 dispositif de saisie de carte de crédit peut transmettre un numéro de carte, une date d'expiration et un code de vérification.

Pour définir une valeur :

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 "internals".

// 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, tout comme un contrôle de formulaire intégré.

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 moment 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 le changement d'l'état disabled de l'élément, 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.

Par exemple, l'élément peut désactiver des éléments dans son Shadow DOM lorsqu'il est désactivé.

void formResetCallback()

Appelée après la réinitialisation du formulaire. L'élément doit se réinitialiser dans 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. Avec une case à cocher, cela revient à 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. L'argument mode est "restore".
  • Lorsqu'une valeur est définie par les fonctionnalités d'assistance à la saisie du navigateur, comme la saisie semi-automatique des formulaires. L'argument mode est "autocomplete".

Le type du premier argument dépend de la façon dont la méthode setFormValue() a été appelée.

Restaurer l'état du formulaire

Dans certaines circonstances, par exemple lorsque l'utilisateur revient à une page ou redémarre le navigateur, celui-ci 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 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 pouvant être envoyée du contrôle. Le paramètre facultatif state 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 : une chaîne, un objet File ou FormData.

Le paramètre state est utile lorsque vous ne pouvez pas restaurer l'état d'un contrôle en fonction de la seule valeur. Par exemple, supposons que vous créez un sélecteur de couleurs avec plusieurs modes : une palette ou une roue chromatique RVB. La valeur pouvant être envoyée correspond à la couleur sélectionnée sous une forme canonique, telle que "#7fff00". Pour rétablir le contrôle dans un état spécifique, vous devez également savoir dans quel mode il se trouve, de sorte que l'état pourrait ressembler à "palette/#7fff00".

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

Votre code devra 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 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 entrée numérique), la valeur est probablement suffisante pour restaurer le contrôle à son état précédent. 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;
}

Détection de caractéristiques

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 publié pour ces deux fonctionnalités. Dans les deux cas, vous pouvez ajouter un élément de formulaire masqué pour propager la valeur du contrôle au formulaire.

Il est probable que de nombreuses fonctionnalités avancées des éléments personnalisés associés à un formulaire soient difficiles, voire 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
}

L'événement formdata vous permet d'ajouter les données de votre formulaire au processus d'envoi, sans avoir à créer d'élément <input> masqué. L'API d'éléments personnalisés associés aux formulaires vous permet de fournir un nouvel ensemble de fonctionnalités pour les contrôles de formulaire personnalisés qui fonctionnent comme des contrôles de formulaire intégrés.