Commandes de formulaire plus performantes

Avec un nouvel événement et des 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.

Cependant, il peut être difficile de répliquer les fonctionnalités des commandes de formulaire HTML intégrées. 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é, actualisé ou que le navigateur tente de le remplir automatiquement.

Les commandes de formulaire personnalisées offrent généralement peu de fonctionnalités. Les développeurs peuvent contourner certaines limites de JavaScript, par exemple ajouter un <input> masqué à un formulaire pour participer à son envoi. 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 suppriment 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 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 au 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 au formulaire correspondent à de nombreuses caractéristiques des éléments 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.

Cela représente beaucoup de fonctionnalités ! Cet article n'aborde pas tous ces points, mais décrit les principes de base nécessaires pour intégrer votre élément personnalisé dans 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, telles que 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 l'élément enregistré, vous pouvez l'utiliser partout où vous utiliseriez une commande de formulaire fournie 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 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 à associer 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 à un état par défaut. Pour les éléments <input>, cela implique généralement de définir la propriété value de sorte 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 au redémarrage du navigateur). Dans ce cas, l'argument mode est "restore".
  • Lorsque les fonctionnalités d'aide à la saisie du navigateur, telles que le remplissage automatique de 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 où 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 illustré 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: palette ou roue chromatique RVB. La valeur pouvant être envoyée 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 entrée 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 présente de nombreuses caractéristiques des éléments personnalisés associés au formulaire. Veillez à l'exécuter sur Chrome 77 ou 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 d'é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.