Commandes de formulaire plus performantes

Avec un nouvel événement et des API d'éléments personnalisés, il est désormais beaucoup plus facile de participer 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 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 caractéristiques auxquelles un élément <input> est automatiquement ajouté lorsque vous l'ajoutez à un formulaire:

  • L'entrée est automatiquement ajoutée à 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.
  • L'entrée est avertie lorsque le formulaire est réinitialisé, lorsqu'il est actualisé ou que le navigateur tente de remplir automatiquement les entrées du formulaire.

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 fonctions ne peuvent tout simplement pas être répliquées 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 d'éléments personnalisés associés à Form 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 qui fonctionnent mieux.

API basée sur des é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 devez ajouter 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 ceci en utilisant 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

  • 5
  • 12
  • 4
  • 5

Source

Éléments personnalisés associés au formulaire

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

Les commandes de formulaire standardisées participent à de nombreuses parties du cycle de vie des formulaires, 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 élément <form>, il est automatiquement associé au formulaire, comme une commande fournie par le navigateur.
  • L'élément peut être étiqueté à 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 contient ou non une entrée valide. Si l'un des contrôles du formulaire comporte 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éinitialisé à son état par défaut.
  • L'élément est compatible avec les pseudo-classes 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ément personnalisé. Cela 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, 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 plus basique est la méthode setFormValue(), qui définit la valeur actuelle de la commande.

La méthode setFormValue() peut utiliser 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, tout 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 du formulaire.

void formDisabledCallback(disabled)

Appelé après que l'état disabled de l'élément change, 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é après la réinitialisation du formulaire. L'élément devrait se réinitialiser à une sorte d'é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 situations 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 la section Restaurer l'état du formulaire.

Restauration de l'état du formulaire

Dans certains cas, par exemple lorsque vous revenez sur une page ou redémarrez le navigateur, il peut essayer de restaurer le formulaire dans l'état dans lequel il l'avait 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);

L'élément value représente la valeur de contrôle pouvant être envoyée. Le paramètre facultatif state 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éiez un sélecteur de couleur avec plusieurs modes: palette ou roue chromatique RVB. La valeur à envoyer correspond à la couleur sélectionnée dans un format canonique, tel que "#7fff00". Toutefois, pour restaurer un état spécifique, vous devez également savoir dans quel mode il se trouvait, de sorte que l'state puisse 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, la saisie d'un nombre), la valeur est probablement suffisante pour restaurer la commande à son état précédent. 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;
}

Exemple pratique

L'exemple suivant rassemble 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 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 actuellement publié pour ces fonctionnalités. Dans les deux cas, vous pouvez ajouter un élément de formulaire masqué pour propager la valeur de la commande dans le formulaire. Bon nombre des fonctionnalités 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 fournissent de nouveaux outils pour créer des commandes de formulaire personnalisées.

L'événement formdata n'offre aucune nouvelle fonctionnalité, mais offre une interface vous permettant 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 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.