Funktionsfähigere Steuerelemente für Formulare

Mit einem neuen Ereignis und APIs für benutzerdefinierte Elemente ist die Teilnahme an Formularen jetzt viel einfacher.

Arthur Evans

Viele Entwickler erstellen benutzerdefinierte Formularsteuerelemente, um entweder Steuerelemente zur Verfügung zu stellen, die nicht in den Browser integriert sind, oder um das Erscheinungsbild über die integrierten Formularsteuerelemente hinaus anzupassen.

Es kann jedoch schwierig sein, die Funktionen der integrierten HTML-Formularsteuerelemente zu replizieren. Sehen Sie sich einige der Funktionen an, die ein <input>-Element automatisch erhält, wenn Sie es einem Formular hinzufügen:

  • Die Eingabe wird automatisch zur Liste der Steuerelemente des Formulars hinzugefügt.
  • Der Wert der Eingabe wird automatisch mit dem Formular gesendet.
  • Die Eingabe wird bei der Formularvalidierung berücksichtigt. Sie können die Eingabe mithilfe der Pseudoklassen :valid und :invalid gestalten.
  • Die Eingabe wird benachrichtigt, wenn das Formular zurückgesetzt oder neu geladen wird oder wenn der Browser versucht, Formulareinträge automatisch auszufüllen.

Benutzerdefinierte Formularsteuerelemente verfügen in der Regel über einige dieser Funktionen. Entwickler können einige Einschränkungen in JavaScript umgehen, z. B. ein ausgeblendetes <input>-Element zu einem Formular hinzufügen, um es an der Formulareinreichung zu beteiligen. Andere Funktionen können jedoch nicht allein in JavaScript reproduziert werden.

Mit zwei neuen Webfunktionen ist es einfacher, benutzerdefinierte Formularsteuerelemente zu erstellen und die Einschränkungen aktueller benutzerdefinierter Steuerelemente zu entfernen:

  • Mit dem formdata-Ereignis kann ein beliebiges JavaScript-Objekt an der Formulareinreichung teilnehmen. So können Sie Formulardaten hinzufügen, ohne ein ausgeblendetes <input>-Element zu verwenden.
  • Mit der formularbezogenen API für benutzerdefinierte Elemente können sich benutzerdefinierte Elemente ähnlich wie integrierte Formularsteuerelemente verhalten.

Mit diesen beiden Funktionen können neue Arten von Steuerelementen erstellt werden, die besser funktionieren.

Ereignisbasierte API

Das formdata-Ereignis ist eine Low-Level-API, mit der jeder JavaScript-Code an der Übermittlung von Formularen teilnehmen kann. Der Mechanismus funktioniert so:

  1. Fügen Sie dem Formular, mit dem Sie interagieren möchten, einen formdata-Event-Listener hinzu.
  2. Wenn ein Nutzer auf die Schaltfläche „Senden“ klickt, löst das Formular ein formdata-Ereignis aus, das ein FormData-Objekt enthält, das alle gesendeten Daten enthält.
  3. Jeder formdata-Listener hat die Möglichkeit, die Daten vor dem Senden des Formulars zu ergänzen oder zu ändern.

Hier ist ein Beispiel für das Senden eines einzelnen Werts in einem formdata-Event-Listener:

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);
});

Probiert das anhand unseres Beispiels in Glitch aus. Führen Sie es auf Chrome 77 oder höher aus, um die API in Aktion zu sehen.

Browserkompatibilität

Unterstützte Browser

  • 5
  • 12
  • 4
  • 5

Quelle

Formularbezogene benutzerdefinierte Elemente

Sie können die ereignisbasierte API mit jeder Art von Komponente verwenden, Sie haben jedoch nur die Möglichkeit, mit dem Übermittlungsprozess zu interagieren.

Standardisierte Formularsteuerelemente sind neben dem Senden in vielen Teilen des Lebenszyklus von Formularen Teil. Formularbezogene benutzerdefinierte Elemente zielen darauf ab, die Lücke zwischen benutzerdefinierten Widgets und integrierten Steuerelementen zu schließen. Formularbezogene benutzerdefinierte Elemente entsprechen vielen der Funktionen standardisierter Formularelemente:

  • Wenn Sie ein dem Formular zugeordnetes benutzerdefiniertes Element in einem <form> platzieren, wird es wie ein vom Browser bereitgestelltes Steuerelement automatisch mit dem Formular verknüpft.
  • Das Element kann mit einem <label>-Element gekennzeichnet werden.
  • Das Element kann einen Wert festlegen, der automatisch mit dem Formular gesendet wird.
  • Das Element kann ein Flag setzen, das angibt, ob eine gültige Eingabe vorliegt oder nicht. Wenn eines der Formularsteuerelemente eine ungültige Eingabe enthält, kann das Formular nicht gesendet werden.
  • Das Element kann Callbacks für verschiedene Teile des Formularlebenszyklus bereitstellen, z. B. wenn das Formular deaktiviert oder auf den Standardstatus zurückgesetzt wird.
  • Das Element unterstützt die standardmäßigen CSS-Pseudoklassen für Formularsteuerelemente wie :disabled und :invalid.

Das sind eine Menge Funktionen! In diesem Artikel werden nicht alle behandelt, sondern die Grundlagen beschrieben, die für die Integration Ihres benutzerdefinierten Elements in ein Formular erforderlich sind.

Ein mit einem Formular verknüpftes benutzerdefiniertes Element definieren

Um ein benutzerdefiniertes Element in ein benutzerdefiniertes Element zu einem Formular umzuwandeln, sind einige zusätzliche Schritte erforderlich:

  • Fügen Sie Ihrer benutzerdefinierten Elementklasse ein statisches formAssociated-Attribut hinzu. Dadurch wird der Browser angewiesen, das Element wie ein Formularsteuerelement zu behandeln.
  • Rufen Sie die Methode attachInternals() für das Element auf, um Zugriff auf zusätzliche Methoden und Eigenschaften für Formularsteuerelemente wie setFormValue() und setValidity() zu erhalten.
  • Fügen Sie die allgemeinen Eigenschaften und Methoden hinzu, die von Formularsteuerelementen wie name, value und validity unterstützt werden.

So passen diese Elemente in die Definition eines grundlegenden benutzerdefinierten Elements:

// 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);

Nach der Registrierung können Sie dieses Element überall dort verwenden, wo Sie ein vom Browser bereitgestelltes Formularsteuerelement verwenden würden:

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

Wert festlegen

Die Methode attachInternals() gibt ein ElementInternals-Objekt zurück, das Zugriff auf APIs zur Formularsteuerung bietet. Die einfachste Methode ist die setFormValue()-Methode, mit der der aktuelle Wert des Steuerelements festgelegt wird.

Die Methode setFormValue() kann einen von drei Arten von Werten annehmen:

  • Ein Stringwert.
  • Ein File-Objekt.
  • Ein FormData-Objekt. Du kannst ein FormData-Objekt verwenden, um mehrere Werte zu übergeben. So kann ein Steuerelement zur Eingabe von Kreditkarten beispielsweise eine Kartennummer, ein Ablaufdatum und einen Bestätigungscode übergeben.

So legen Sie einen einfachen Wert fest:

this.internals_.setFormValue(this.value_);

Um mehrere Werte festzulegen, können Sie beispielsweise so vorgehen:

// 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);

Eingabevalidierung

Die Steuerung kann auch an der Formularvalidierung teilnehmen, indem sie die Methode setValidity() für das Internals-Objekt aufruft.

// 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_);
}

Sie können ein benutzerdefiniertes Element, das einem Formular zugeordnet ist, mit den Pseudoklassen :valid und :invalid gestalten, genau wie bei einem integrierten Formularsteuerelement.

Lebenszyklus-Callbacks

Eine API für benutzerdefinierte Elemente, die einem Formular zugeordnet sind, enthält eine Reihe zusätzlicher Lebenszyklus-Callbacks, die mit dem Lebenszyklus des Formulars verknüpft werden. Callbacks sind optional: Implementieren Sie einen Callback nur dann, wenn das Element zu diesem Zeitpunkt im Lebenszyklus eine Aktion ausführen muss.

void formAssociatedCallback(form)

Wird aufgerufen, wenn der Browser das Element einem Formularelement zuordnet oder das Element von einem Formularelement aufhebt.

void formDisabledCallback(disabled)

Wird aufgerufen, nachdem sich der disabled-Status des Elements geändert hat, entweder weil das disabled-Attribut dieses Elements hinzugefügt oder entfernt wurde oder weil sich der disabled-Status für eine <fieldset> geändert hat, die ein Vorgänger dieses Elements ist. Der Parameter disabled steht für den neuen deaktivierten Status des Elements. Das Element kann beispielsweise Elemente in seinem Shadow DOM deaktivieren, wenn es deaktiviert ist.

void formResetCallback()

Wird nach dem Zurücksetzen des Formulars aufgerufen. Das Element sollte sich selbst auf einen Standardstatus zurücksetzen. Bei <input>-Elementen muss dazu normalerweise die value-Eigenschaft mit dem value-Attribut im Markup übereinstimmen (oder im Falle eines Kästchens, das checked-Attribut entsprechend dem checked-Attribut festzulegen).

void formStateRestoreCallback(state, mode)

Wird unter einem der folgenden Fälle aufgerufen:

  • Wenn der Browser den Status des Elements wiederherstellt, z. B. nach einer Navigation oder beim Neustart des Browsers Das Argument mode ist in diesem Fall "restore".
  • Wenn durch die Eingabehilfen des Browsers, z. B. das automatische Ausfüllen von Formularen, ein Wert festgelegt wird. Das Argument mode ist in diesem Fall "autocomplete".

Der Typ des ersten Arguments hängt davon ab, wie die Methode setFormValue() aufgerufen wurde. Weitere Informationen finden Sie unter Formularstatus wiederherstellen.

Formularstatus wird wiederhergestellt

Unter bestimmten Umständen, z. B. beim Navigieren zu einer Seite oder beim Neustart des Browsers, versucht der Browser, das Formular in den Zustand zurückzuversetzen, in dem es sich befinden.

Bei einem benutzerdefinierten Element, das dem Formular zugeordnet ist, wird der wiederhergestellte Status aus den Werten übernommen, die Sie an die Methode setFormValue() übergeben. Sie können die Methode wie in den früheren Beispielen gezeigt mit einem einzelnen Wertparameter oder mit zwei Parametern aufrufen:

this.internals_.setFormValue(value, state);

Der value-Wert steht für den eingereichten Wert der Kontrollgruppe. Der optionale Parameter state ist eine interne Darstellung des Status des Steuerelements. Er kann auch Daten enthalten, die nicht an den Server gesendet werden. Der Parameter state hat dieselben Typen wie der Parameter value. Er kann ein String, ein File- oder ein FormData-Objekt sein.

Der Parameter state ist nützlich, wenn sich der Status eines Steuerelements nicht allein anhand des Werts wiederherstellen lässt. Angenommen, Sie erstellen eine Farbauswahl mit mehreren Modi: eine Palette oder ein RGB-Farbrad. Der eingereichte Wert wäre die ausgewählte Farbe in einer kanonischen Form wie "#7fff00". Wenn Sie das Steuerelement auf einen bestimmten Status zurücksetzen möchten, müssen Sie auch wissen, in welchem Modus es sich befand. Daher könnte der state als "palette/#7fff00" lauten.

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

Ihr Code müsste dann seinen Status auf Grundlage des gespeicherten Statuswerts wiederherstellen.

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.
}

Bei einem einfacheren Steuerelement (z. B. einer Zahleneingabe) reicht der Wert wahrscheinlich aus, um den vorherigen Zustand des Steuerelements wiederherzustellen. Wenn Sie state beim Aufrufen von setFormValue() weglassen, wird der Wert an formStateRestoreCallback() übergeben.

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

Funktionierendes Beispiel

Im folgenden Beispiel werden viele Funktionen von formularbezogenen benutzerdefinierten Elementen zusammengefasst. Führen Sie es auf Chrome 77 oder höher aus, um die API in Aktion zu sehen.

Funktionserkennung

Mit der Funktionserkennung lässt sich feststellen, ob das formdata-Ereignis und mit dem Formular verknüpfte benutzerdefinierte Elemente verfügbar sind. Für keine der beiden Funktionen werden derzeit Polyfills veröffentlicht. In beiden Fällen können Sie ein ausgeblendetes Formularelement hinzufügen, um den Wert des Steuerelements im Formular zu übertragen. Viele der erweiterten Funktionen von formularbezogenen benutzerdefinierten Elementen lassen sich wahrscheinlich nur schwer oder gar nicht in Polyfills einfügen.

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

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

Fazit

Das formdata-Ereignis und mit dem Formular verknüpfte benutzerdefinierte Elemente bieten neue Tools zum Erstellen benutzerdefinierter Formularsteuerelemente.

Das formdata-Ereignis bietet Ihnen keine neuen Funktionen, bietet Ihnen aber eine Oberfläche, über die Sie Ihre Formulardaten in den Übermittlungsprozess einfügen können, ohne ein ausgeblendetes <input>-Element erstellen zu müssen.

Die formularzugehörige API für benutzerdefinierte Elemente bietet neue Funktionen zum Erstellen benutzerdefinierter Formularsteuerelemente, die wie integrierte Formularsteuerelemente funktionieren.

Hero-Image von Oudom Pravat auf Unsplash