Funktionsfähigere Steuerelemente für Formulare

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

Arthur Evans

Viele Entwickler erstellen benutzerdefinierte Formularsteuerelemente, um entweder Steuerelemente bereitzustellen, die nicht in den Browser integriert sind, oder um das Erscheinungsbild der integrierten Formularsteuerelemente zu ändern.

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 der Liste der Steuerelemente des Formulars hinzugefügt.
  • Der Eingabewert 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, das Formular neu geladen oder versucht wird, Formulareinträge automatisch auszufüllen.

In der Regel gibt es nur wenige dieser Funktionen für benutzerdefinierte Formularsteuerelemente. Entwickler können einige der Einschränkungen in JavaScript umgehen, z. B. ein ausgeblendetes <input> zu einem Formular hinzufügen, um an der Formularübermittlung teilzunehmen. Andere Funktionen können jedoch einfach nicht in JavaScript repliziert werden.

Mit zwei neuen Webfunktionen ist es einfacher, benutzerdefinierte Formularsteuerelemente zu erstellen. Dabei entfallen die Einschränkungen der aktuellen benutzerdefinierten Steuerelemente:

  • Mit dem formdata-Ereignis kann ein beliebiges JavaScript-Objekt an der Formularübermittlung teilnehmen. Sie können also Formulardaten hinzufügen, ohne ein verstecktes <input> zu verwenden.
  • Mit der API für verknüpfte benutzerdefinierte Elemente können benutzerdefinierte Elemente wie integrierte Formularsteuerelemente funktionieren.

Mit diesen beiden Funktionen lassen sich neue Arten von Steuerelementen erstellen, die besser funktionieren.

Ereignisbasierte API

Das formdata-Ereignis ist eine Low-Level-API, mit der jeder JavaScript-Code an der Formularübermittlung 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 eingereichten Daten enthält.
  3. Jeder formdata-Listener kann die Daten ergänzen oder ändern, bevor das Formular gesendet wird.

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

Probiere es mit unserem Beispiel auf Glitch aus. Führen Sie sie unter Chrome 77 oder höher aus, um die API in Aktion zu sehen.

Browserkompatibilität

Unterstützte Browser

  • 5
  • 12
  • 4
  • 5

Quelle

Mit Formular verknüpfte benutzerdefinierte Elemente

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

Standardisierte Formularsteuerelemente sind neben dem Senden in vielen Teilen des Formularlebenszyklus aktiv. Mit Formular verknüpfte benutzerdefinierte Elemente schließen die Lücke zwischen benutzerdefinierten Widgets und integrierten Steuerelementen. Mit Formular verknüpfte benutzerdefinierte Elemente stimmen mit vielen der Merkmale standardisierter Formularelemente überein:

  • Wenn Sie ein mit dem Formular verknüpftes benutzerdefiniertes Element in einem <form> platzieren, wird es wie ein vom Browser bereitgestelltes Steuerelement automatisch mit dem Formular verknüpft.
  • Das Element kann mithilfe eines <label>-Elements mit Labels versehen werden.
  • Mit dem Element kann ein Wert festgelegt werden, der automatisch mit dem Formular gesendet wird.
  • Das Element kann ein Flag festlegen, das angibt, ob es über eine gültige Eingabe verfügt. Enthält eines der Formularsteuerelemente eine ungültige Eingabe, 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 seinen Standardzustand zurückgesetzt wird.
  • Das Element unterstützt die standardmäßigen CSS-Pseudoklassen für Formularsteuerelemente wie :disabled und :invalid.

Das sind viele Funktionen! In diesem Artikel werden nicht alle behandelt, sondern nur die Grundlagen erläutert, die Sie benötigen, um Ihr benutzerdefiniertes Element in ein Formular zu integrieren.

Ein mit dem Formular verknüpftes benutzerdefiniertes Element definieren

Wenn Sie ein benutzerdefiniertes Element in ein benutzerdefiniertes Element umwandeln möchten, das mit einem Formular verknüpft ist, sind einige zusätzliche Schritte erforderlich:

  • Fügen Sie der Klasse Ihres benutzerdefinierten Elements eine statische formAssociated-Eigenschaft hinzu. Dadurch wird der Browser angewiesen, das Element wie eine Formularsteuerung 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 allgemeine Eigenschaften und Methoden hinzu, die von Formularsteuerelementen unterstützt werden, z. B. name, value und validity.

So passen diese Elemente in eine grundlegende Definition eines benutzerdefinierten Elements ein:

// 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 Formularsteuerungs-APIs bietet. Die einfachste Methode ist die Methode setFormValue(), mit der der aktuelle Wert des Steuerelements festgelegt wird.

Die Methode setFormValue() kann einen von drei Werttypen annehmen:

  • Ein Stringwert.
  • Ein File-Objekt.
  • Ein FormData-Objekt. Sie können ein FormData-Objekt verwenden, um mehrere Werte zu übergeben. Bei der Steuerung der Kreditkarteneingabe kann beispielsweise eine Kartennummer, ein Ablaufdatum und ein Bestätigungscode übergeben werden.

So legen Sie einen einfachen Wert fest:

this.internals_.setFormValue(this.value_);

So können Sie mehrere Werte festlegen:

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

Das Steuerelement kann auch an der Formularvalidierung teilnehmen, indem es 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 mit dem Formular verknüpftes benutzerdefiniertes Element wie eine integrierte Formularsteuerung mit den Pseudoklassen :valid und :invalid gestalten.

Lebenszyklus-Callbacks

Eine mit dem Formular verknüpfte API für benutzerdefinierte Elemente enthält eine Reihe zusätzlicher Lebenszyklus-Callbacks, die dem Formularlebenszyklus zugeordnet sind. Die Callbacks sind optional. Implementieren Sie nur dann einen Callback, wenn das Element an diesem Punkt im Lebenszyklus eine Aktion ausführen muss.

void formAssociatedCallback(form)

Wird aufgerufen, wenn der Browser das Element mit einem Formularelement verknüpft oder die Verknüpfung zwischen einem Element und 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 eines <fieldset>-Elements geändert hat, das ein Ancestor dieses Elements ist. Der Parameter disabled stellt den neuen deaktivierten Status des Elements dar. Das Element kann beispielsweise Elemente in seinem Shadow-DOM deaktivieren, wenn es deaktiviert ist.

void formResetCallback()

Wird aufgerufen, nachdem das Formular zurückgesetzt wurde. Das Element sollte sich selbst auf einen Standardstatus zurücksetzen. Bei <input>-Elementen muss dafür in der Regel die value-Eigenschaft so festgelegt werden, dass sie mit dem value-Attribut übereinstimmt, das im Markup festgelegt ist. Im Fall eines Kästchens muss die checked-Eigenschaft dem checked-Attribut entsprechen.

void formStateRestoreCallback(state, mode)

Der Aufruf erfolgte in einem von zwei Fällen:

  • Wenn der Browser den Zustand des Elements wiederherstellt, z. B. nach einer Navigation oder beim Neustart des Browsers. Das Argument mode ist in diesem Fall "restore".
  • Wenn durch die eingabehilfenden Funktionen 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 Zurückkehren zu einer Seite oder einem Neustart des Browsers, kann der Browser versuchen, das Formular in dem Zustand wiederherzustellen, in dem der Nutzer es verlassen hat.

Bei einem mit dem Formular verknüpften benutzerdefinierten Element stammt der wiederhergestellte Zustand aus den Werten, die Sie an die Methode setFormValue() übergeben. Sie können die Methode mit einem einzelnen Wertparameter aufrufen, wie in den vorherigen Beispielen gezeigt, oder mit zwei Parametern:

this.internals_.setFormValue(value, state);

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

Der Parameter state ist nützlich, wenn Sie den Status eines Steuerelements nicht allein anhand des Werts wiederherstellen können. Angenommen, Sie erstellen eine Farbauswahl mit mehreren Modi: eine Palette oder ein RGB-Farbrad. Der einstellbare Wert wäre die ausgewählte Farbe in einer kanonischen Form, z. B. "#7fff00". Um das Steuerelement auf einen bestimmten Zustand zurückzusetzen, müssen Sie auch wissen, in welchem Modus es sich befand. Der state könnte dann so aussehen: "palette/#7fff00".

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

Ihr Code müsste seinen Status anhand 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 Status des Steuerelements wiederherzustellen. Wenn Sie beim Aufrufen von setFormValue() state weglassen, wird der Wert an formStateRestoreCallback() übergeben.

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

Ein funktionierendes Beispiel

Im folgenden Beispiel werden viele der Merkmale von formularverknüpften benutzerdefinierten Elementen zusammengefasst. Führen Sie sie unter Chrome 77 oder höher aus, um die API in Aktion zu sehen.

Funktionserkennung

Mit der Funktionserkennung können Sie feststellen, ob das formdata-Ereignis und die mit dem Formular verknüpften benutzerdefinierten Elemente verfügbar sind. Derzeit gibt es für keine der Funktionen Polyfills. In beiden Fällen können Sie auf das Hinzufügen eines ausgeblendeten Formularelements zurückgreifen, um den Wert des Steuerelements an das Formular weiterzugeben. Viele der fortgeschritteneren Funktionen form-bezogener benutzerdefinierter Elemente werden wahrscheinlich nur schwer oder gar nicht polyfill möglich sein.

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 zwar keine neuen Funktionen, bietet Ihnen aber eine Schnittstelle, über die Sie Ihre Formulardaten dem Senden hinzufügen können, ohne ein verstecktes <input>-Element erstellen zu müssen.

Die dem Formular zugeordnete Custom Elements API bietet eine Reihe neuer Funktionen, mit denen Sie benutzerdefinierte Formularsteuerelemente erstellen können, die wie integrierte Formularsteuerelemente funktionieren.

Hero-Image von Oudom Pravat auf Unsplash