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 im Browser integriert sind, oder das Erscheinungsbild über die Möglichkeiten der integrierten Formularsteuerelemente hinaus anzupassen.

Es kann jedoch schwierig sein, die Funktionen der integrierten HTML-Formularsteuerelemente zu replizieren. Wenn Sie ein <input>-Element einem Formular hinzufügen, werden ihm automatisch einige Funktionen zugewiesen:

  • 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 die Eingabe mit den Pseudoklassen :valid und :invalid gestalten.
  • Die Eingabe wird benachrichtigt, wenn das Formular zurückgesetzt, neu geladen oder 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 ausgeblendetes <input> hinzufügen, um an der Formulareinreichung teilzunehmen. Andere Funktionen können jedoch nicht nur mit JavaScript repliziert 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 API für benutzerdefinierte Elemente, die mit Formularen verknüpft sind, können benutzerdefinierte Elemente eher wie integrierte Formularsteuerelemente funktionieren.

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 kann die Daten vor dem Senden des Formulars ergänzen oder ändern.

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

Probiert das anhand unseres Beispiels in Glitch aus. Sie müssen Chrome 77 oder höher verwenden, damit die API funktioniert.

Browserkompatibilität

Unterstützte Browser

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

Quelle

Mit dem Formular verknüpfte benutzerdefinierte Elemente

Sie können die ereignisbasierte API mit jeder Art von Komponente verwenden, aber sie ermöglicht nur die Interaktion mit dem Einreichungsprozess.

Standardisierte Formularsteuerelemente sind neben dem Senden an vielen Stellen im Formularlebenszyklus beteiligt. Mit formularzugeordneten benutzerdefinierten Elementen soll die Lücke zwischen benutzerdefinierten Widgets und integrierten Steuerelementen geschlossen werden. Mit Formularen verknüpfte benutzerdefinierte Elemente haben viele der Funktionen standardisierter Formularelemente:

  • Wenn Sie ein formularbezogenes benutzerdefiniertes Element in ein <form> einfügen, wird es automatisch mit dem Formular verknüpft, ähnlich wie ein vom Browser bereitgestelltes Steuerelement.
  • 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 Formularelemente 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 viele Funktionen. In diesem Artikel werden nicht alle Optionen behandelt, sondern nur die Grundlagen, die Sie für die Einbindung eines benutzerdefinierten Elements in ein Formular benötigen.

Benutzerdefiniertes Element für ein Formular definieren

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

  • Fügen Sie der benutzerdefinierten Elementklasse ein statisches formAssociated-Attribut hinzu. Dadurch wird dem Browser mitgeteilt, das Element als Formularkontrollelement 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 gängigen Eigenschaften und Methoden hinzu, die von Formularelementen unterstützt werden, z. B. name, value und validity.

So passen diese Elemente in eine grundlegende Definition eines 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 browsereigenes Formularkontrollelement 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 APIs für Formularelemente 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. Mit einem FormData-Objekt können Sie mehrere Werte übergeben. So kann ein Eingabekontrollelement für Kreditkarten beispielsweise eine Kartennummer, ein Ablaufdatum und einen Bestätigungscode übergeben.

So legen Sie einen einfachen 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() auf dem internen 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 genauso stylen wie ein integriertes Formularkontrollelement.

Lebenszyklus-Callbacks

Eine mit einem Formular verknüpfte benutzerdefinierte Element-API enthält eine Reihe zusätzlicher Lebenszyklus-Callbacks, die mit dem Formularlebenszyklus verknüpft sind. Die Callbacks sind optional: Implementieren Sie einen Callback nur, wenn Ihr Element an diesem Punkt im Lebenszyklus etwas tun muss.

void formAssociatedCallback(form)

Wird aufgerufen, wenn der Browser das Element einem Formularelement zuordnet oder die Verknüpfung des Elements mit 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)

Sie werden in einem der folgenden beiden Fälle angerufen:

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

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

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 den Zustand zu versetzen, in dem der Nutzer es verlassen hat.

Bei einem mit einem Formular verknüpften benutzerdefinierten Element stammt der wiederhergestellte Status aus den Werten, die Sie an die setFormValue()-Methode ü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 Wert der Einstellung, die eingereicht werden kann. Der optionale Parameter state ist eine interne Darstellung des Zustands der Steuerelemente. Er kann Daten enthalten, die nicht an den Server gesendet werden. Der Parameter state kann dieselben Typen wie der Parameter value haben: String, File oder FormData-Objekt.

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 einreichbare Wert ist die ausgewählte Farbe in kanonischer Form, z. B. "#7fff00". Wenn Sie die Steuerung jedoch in einen bestimmten Zustand zurückversetzen möchten, müssen Sie auch wissen, in welchem Modus sie sich befand. Der Status könnte also "palette/#7fff00" lauten.

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 currently doesn't handle autofill for form-associated
  // custom elements. In the autofill case, you might need to handle
  // a raw value.
}

Bei einfacheren Steuerelementen (z. B. einer Zahleneingabe) reicht der Wert wahrscheinlich aus, um das Steuerelement in seinen vorherigen Zustand zu versetzen. Wenn Sie state beim Aufruf 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. Verwenden Sie Chrome 77 oder höher, um die API in Aktion zu sehen.

Funktionserkennung

Mithilfe der Funktionserkennung können Sie feststellen, ob das formdata-Ereignis und die mit dem Formular verknüpften benutzerdefinierten Elemente verfügbar sind. Für keine der beiden Funktionen sind derzeit Polyfills verfügbar. In beiden Fällen können Sie ein ausgeblendetes Formularelement hinzufügen, um den Wert des Steuerelements an das Formular weiterzugeben. Viele der erweiterten Funktionen von formularbezogenen benutzerdefinierten Elementen 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
}

Fazit

Das Ereignis formdata und die formularbezogenen benutzerdefinierten 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 API für benutzerdefinierte Elemente, die mit Formularen verknüpft sind, bietet neue Funktionen zum Erstellen benutzerdefinierter Formularsteuerelemente, die wie integrierte Formularsteuerelemente funktionieren.

Hero-Image von Oudom Pravat auf Unsplash