Commandes de formulaire plus performantes

Un nouvel événement et des API Custom Elements facilitent grandement la participation aux formulaires.

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 qu'il est possible de faire avec les commandes de formulaire intégrées.

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

  • Le texte saisi est automatiquement ajouté à la liste des commandes du formulaire.
  • La valeur 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 pseudo-classes :valid et :invalid.
  • Une notification est envoyée lorsque le formulaire est réinitialisé, rechargé ou lorsque le navigateur tente de saisir automatiquement des entrées.

Les commandes de formulaire personnalisées présentent généralement peu de ces fonctionnalités. Les développeurs peuvent contourner certaines limites de JavaScript, comme l'ajout d'un <input> masqué à un formulaire pour participer à l'envoi du formulaire. En revanche, certaines fonctionnalités ne peuvent pas être répliquées uniquement en JavaScript.

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

  • L'événement formdata permet à un objet JavaScript arbitraire de participer à l'envoi de formulaires. 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.

Vous pouvez utiliser ces deux fonctionnalités 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 à tout code JavaScript de participer à l'envoi de formulaire. 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 valeur unique 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 de suivre 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

  • 5
  • 12
  • 4
  • 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 vous permet uniquement d'interagir avec le processus d'envoi.

En plus de l'envoi, les commandes de formulaire standardisées interviennent à de nombreuses étapes de leur cycle de vie. 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 fonctionnalités des éléments de formulaire standardisés:

  • Lorsque vous placez un élément personnalisé associé à un formulaire dans une <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 envoyée automatiquement avec le formulaire.
  • L'élément peut définir un indicateur indiquant si une entrée est valide ou non. Si l'une des commandes du formulaire contient des données incorrectes, 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éinitialisé à son état par défaut.
  • L'élément est compatible avec les pseudo-classes CSS standards pour les commandes de formulaire, telles que :disabled et :invalid.

Cela fait beaucoup de fonctionnalités ! Cet article n'aborde pas toutes ces fonctionnalités, mais décrit les principes de base à connaître 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ément personnalisé. Cette opération indique au navigateur de traiter l'élément comme une commande 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, telles que 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 enregistré, vous pouvez utiliser cet élément partout où vous utilisez 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 fournit un accès aux API de contrôle de formulaire. La méthode la plus élémentaire est setFormValue(), qui définit la valeur actuelle de la commande.

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, une commande 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, procédez 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 commande 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 pseudo-classes :valid et :invalid, comme une commande de formulaire intégrée.

Rappels de cycle de vie

Une API d'élément personnalisé associée à un formulaire inclut un ensemble de rappels de cycle de vie supplémentaires à associer au cycle de vie du formulaire. Les rappels sont facultatifs: ils ne sont implémentés que si l'élément doit effectuer une opération à ce stade du cycle de vie.

void formAssociatedCallback(form)

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

void formDisabledCallback(disabled)

Appelée après la modification de 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. Le paramètre disabled représente le nouvel état désactivé de l'élément. L'élément peut, par exemple, 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 à 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, à définir la propriété checked pour qu'elle corresponde à l'attribut checked.

void formStateRestoreCallback(state, mode)

Cet appel est effectué 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 le remplissage automatique de formulaire, 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 la section Restaurer l'état d'un formulaire.

Restauration de l'état du formulaire

Dans certains cas, par exemple lorsque l'utilisateur revient sur une page ou redémarre le navigateur, il peut essayer de restaurer le formulaire tel que 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);

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

Le paramètre state est utile lorsque vous ne pouvez pas restaurer l'état d'une commande 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 value pouvant être envoyée correspond à la couleur sélectionnée dans une forme canonique telle que "#7fff00". Toutefois, pour restaurer la commande à un état spécifique, vous devez également savoir dans quel mode il se trouvait. L'state 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'une commande plus simple (par exemple, une entrée numérique), la valeur est probablement suffisante pour restaurer l'état précédent de la commande. Si vous omettez state lors de l'appel de setFormValue(), la valeur est transmise à formStateRestoreCallback().

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

Un exemple concret

L'exemple suivant rassemble de nombreuses caractéristiques 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 l'un ou l'autre des éléments géographiques. Dans les deux cas, vous pouvez revenir à l'ajout d'un élément de formulaire masqué pour propager la valeur de la commande dans le formulaire. Bon nombre des fonctionnalités les plus avancées des éléments personnalisés associés à un formulaire seront probablement difficiles, voire impossibles à émuler.

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 offrent de nouveaux outils pour créer des commandes de formulaire personnalisées.

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

L'API des éléments personnalisés associés à un formulaire offre un nouvel ensemble de fonctionnalités permettant de créer des commandes de formulaire personnalisées qui fonctionnent de la même manière que les commandes de formulaire intégrées.

Image principale d'Oudom Pravat sur Unsplash.