Funktionsfähigere Steuerelemente für Formulare

Arthur Evans

Veröffentlicht am 8. August 2019

Viele Entwickler erstellen benutzerdefinierte Formularsteuerelemente, entweder um Steuerelemente bereitzustellen, die nicht im Browser integriert sind, oder um das Erscheinungsbild über das hinaus anzupassen, was mit den integrierten Formularsteuerelementen möglich ist.

Es kann jedoch schwierig sein, die Funktionen der integrierten HTML-Formularsteuerelemente zu replizieren. Hier einige Funktionen, 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 Wert der Eingabe wird automatisch mit dem Formular gesendet.
  • Die Eingabe wird bei der Formularvalidierung berücksichtigt. Sie können das Eingabefeld mithilfe der Pseudoklassen :valid und :invalid formatieren.
  • 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 haben in der Regel nur wenige dieser Funktionen. Entwickler können einige der Einschränkungen in JavaScript umgehen, z. B. indem sie einem Formular ein verborgenes <input> hinzufügen, um am Senden des Formulars teilzunehmen. Andere Funktionen lassen sich jedoch nicht allein in JavaScript nachbilden.

Zwei Webfunktionen erleichtern das Erstellen benutzerdefinierter Formularsteuerelemente und beseitigen die Einschränkungen benutzerdefinierter Steuerelemente:

  • Mit dem Ereignis formdata kann ein beliebiges JavaScript-Objekt an der Formularübermittlung teilnehmen. So können Sie Formulardaten hinzufügen, ohne ein verborgenes <input> zu verwenden.
  • Mit der API für benutzerdefinierte Elemente, die mit Formularen verknüpft sind, können sich benutzerdefinierte Elemente eher wie integrierte Formularsteuerelemente verhalten.

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

Ereignisbasierte API

Browser Support

  • Chrome: 5.
  • Edge: 12.
  • Firefox: 4.
  • Safari: 5.

Source

Das formdata-Ereignis ist eine Low-Level-API, die es beliebigem JavaScript-Code ermöglicht, an der Formularübermittlung teilzunehmen.

  • Fügen Sie dem Formular, mit dem Sie interagieren möchten, einen formdata-Event-Listener hinzu.
  • Wenn ein Benutzer auf Absenden klickt, löst das Formular ein formdata-Ereignis aus, das ein FormData-Objekt enthält, das alle zu übermittelnden Daten enthält.
  • Jeder formdata-Listener erhält die Möglichkeit, die Daten zu ergänzen oder zu ändern, bevor das Formular abgeschickt wird.

Hier ist ein Beispiel für das Senden eines einzelnen Werts in einem formdata-Ereignis-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);
});

Formularbezogene benutzerdefinierte Elemente

Sie können die ereignisbasierte API mit jeder Art von Komponente verwenden, sie erlaubt Ihnen jedoch nur die Interaktion mit dem Übermittlungsprozess.

Standardisierte Formularsteuerelemente kommen in vielen Phasen des Formularlebenszyklus zum Einsatz. Formularbezogene benutzerdefinierte Elemente sollen die Lücke zwischen benutzerdefinierten Widgets und integrierten Steuerelementen schließen. Formularbezogene benutzerdefinierte Elemente weisen viele der Merkmale standardisierter Formularelemente auf:

  • Wenn Sie ein formularbezogenes benutzerdefiniertes Element innerhalb eines <form> platzieren, wird es automatisch dem Formular zugeordnet, wie ein vom Browser bereitgestelltes Steuerelement.
  • Das Element kann mit einem <label>-Element beschriftet werden.
  • Das Element kann einen Wert festlegen, der automatisch mit dem Formular übermittelt wird.
  • Das Element kann ein Flag setzen, das angibt, ob es gültige Eingaben hat oder nicht. Wenn eines der Formularfelder ungültige Eingaben enthält, kann das Formular nicht abgeschickt werden.
  • Das Element kann Rückruffunktionen für verschiedene Teile des Formularlebenszyklus bereitstellen, beispielsweise wenn das Formular deaktiviert oder auf seinen Standardzustand zurückgesetzt wird.
  • Das Element unterstützt die Standard-CSS-Pseudoklassen für Formularsteuerelemente, wie z. B. :disabled und :invalid.

In diesem Dokument wird nicht alles abgedeckt, aber die Grundlagen beschrieben, die für die Integration Ihres benutzerdefinierten Elements in ein Formular erforderlich sind.

Ein benutzerdefiniertes Formularelement definieren

Um ein benutzerdefiniertes Element in ein formularbezogenes benutzerdefiniertes Element umzuwandeln, sind einige zusätzliche Schritte erforderlich:

  • Fügen Sie Ihrer benutzerdefinierten Elementklasse eine statische formAssociated-Eigenschaft hinzu. Dies weist den Browser an, das Element wie ein Formularfeld zu behandeln.
  • Rufen Sie die Methode attachInternals() des Elements 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 unterstützt werden, wie name, value und validity.

So fügen sich diese Elemente in eine grundlegende benutzerdefinierte Elementdefinition ein:

// Form-associated custom elements must be autonomous custom elements.
// 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 attachInternals()-Methode gibt ein ElementInternals-Objekt zurück, das Zugriff auf Formularsteuerungs-APIs bietet. Die einfachste Methode ist setFormValue(), mit der der aktuelle Wert des Steuerelements festgelegt wird.

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

  • Ein Stringwert.
  • Ein File-Objekt.
  • Ein FormData-Objekt. Sie können ein FormData-Objekt verwenden, um mehrere Werte zu übergeben. Ein Steuerelement für die Eingabe von Kreditkartendaten kann beispielsweise eine Kartennummer, ein Ablaufdatum und einen Sicherheitscode übergeben.

So legen Sie einen Wert fest:

this.internals_.setFormValue(this.value_);

So legen Sie mehrere Werte fest:

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

Ihr Steuerelement kann auch an der Formularvalidierung teilnehmen, indem Sie die Methode setValidity() für das interne Objekt aufrufen.

// 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 formularbezogenes benutzerdefiniertes Element mit den Pseudoklassen :valid und :invalid gestalten, genau wie ein integriertes Formularsteuerelement.

Lebenszyklus-Callbacks

Eine API für formularbezogene benutzerdefinierte Elemente enthält eine Reihe zusätzlicher Lifecycle-Callbacks, die in den Formular-Lifecycle eingebunden werden. Die Callbacks sind optional. Sie müssen nur dann einen Callback implementieren, wenn Ihr Element zu diesem Zeitpunkt im Lebenszyklus etwas tun muss.

void formAssociatedCallback(form)

Wird aufgerufen, wenn der Browser das Element einem Formularelement zuordnet oder die Zuordnung des Elements zu einem Formularelement aufhebt.

void formDisabledCallback(disabled)

Wird aufgerufen, nachdem sich der disabled-Zustand des Elements geändert hat. Das kann daran liegen, dass das disabled-Attribut dieses Elements hinzugefügt oder entfernt wurde oder dass sich der disabled-Zustand eines <fieldset> geändert hat, das ein übergeordnetes Element dieses Elements ist.

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 in einen Standardzustand zurücksetzen. Bei <input>-Elementen bedeutet dies in der Regel, dass die value-Eigenschaft so eingestellt wird, dass sie mit dem im Markup festgelegten value-Attribut übereinstimmt. Bei einem Kästchen muss die checked-Property auf den Wert des checked-Attributs gesetzt werden.

void formStateRestoreCallback(state, mode)

Wird in einem von zwei Fällen aufgerufen:

  • Wenn der Browser den Status des Elements wiederherstellt, z. B. nach der Navigation oder wenn der Browser neu gestartet wird. Das Argument mode ist "restore".
  • Wenn die Eingabehilfefunktionen des Browsers, z. B. das automatische Ausfüllen von Formularen, einen Wert festlegen. Das Argument mode ist "autocomplete".

Der Typ des ersten Arguments hängt davon ab, wie die setFormValue()-Methode aufgerufen wurde.

Formularstatus wiederherstellen

Unter bestimmten Umständen, z. B. wenn Sie zu einer Seite zurückkehren oder den Browser neu starten, versucht der Browser möglicherweise, das Formular in dem Zustand wiederherzustellen, in dem der Nutzer es verlassen hat.

Bei einem formularbezogenen benutzerdefinierten Element stammt der wiederhergestellte Status 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 Wert, der für das Steuerelement gesendet werden kann. Der optionale Parameter state ist eine interne Darstellung des Status des Steuerelements, die Daten enthalten kann, die nicht an den Server gesendet werden. Für den Parameter state werden dieselben Typen wie für den Parameter value verwendet: ein String, ein File- oder ein FormData-Objekt.

Der Parameter state ist nützlich, wenn Sie den Status eines Steuerelements nicht nur anhand des Werts wiederherstellen können. Angenommen, Sie erstellen eine Farbauswahl mit mehreren Modi: einer Palette oder einem RGB-Farbkreis. Der einreichbare Wert ist die ausgewählte Farbe in einer kanonischen Form, z. B. "#7fff00". Wenn Sie das Steuerelement in einem bestimmten Zustand wiederherstellen möchten, müssen Sie auch wissen, in welchem Modus es sich befand. Der state könnte also so aussehen: "palette/#7fff00".

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

Ihr Code muss den Status basierend auf dem gespeicherten Statuswert 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 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 das Steuerelement in seinen vorherigen Zustand zurückzusetzen. 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;
}

Funktionserkennung

Mit der Funktionserkennung können Sie feststellen, ob das formdata-Ereignis und die formularbezogenen benutzerdefinierten Elemente verfügbar sind. Für beide Funktionen wurden keine Polyfills veröffentlicht. In beiden Fällen können Sie ein ausgeblendetes Formularelement hinzufügen, um den Wert des Steuerelements an das Formular zu übergeben.

Viele der erweiterten Funktionen von benutzerdefinierten Elementen, die mit Formularen verknüpft sind, lassen sich wahrscheinlich nur schwer oder gar nicht polyfillen.

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

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

Das formdata-Ereignis bietet eine Schnittstelle, mit der Sie Ihre Formulardaten dem Übermittlungsprozess hinzufügen können, ohne ein verborgenes <input>-Element erstellen zu müssen. Mit der API für formularbezogene benutzerdefinierte Elemente können Sie benutzerdefinierten Formularsteuerelementen, die wie integrierte Formularsteuerelemente funktionieren, neue Funktionen hinzufügen.