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. Hier sind einige der Funktionen, die einem <input>-Element automatisch hinzugefügt werden, 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 die Eingabe mit den Pseudoklassen :valid und :invalid formatieren.
  • 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 lassen sich benutzerdefinierte Formularsteuerelemente einfacher erstellen und die Einschränkungen der aktuellen benutzerdefinierten Steuerelemente beseitigen:

  • Mit dem Ereignis formdata kann ein beliebiges JavaScript-Objekt an der Formulareinreichung teilnehmen. So können Sie Formulardaten hinzufügen, ohne ein ausgeblendetes <input> 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 Sie neue Steuerelemente erstellen, die besser funktionieren.

Ereignisbasierte API

Das Ereignis formdata ist eine Low-Level-API, mit der jeder JavaScript-Code an der Formulareinreichung teilnehmen kann. So funktioniert der Mechanismus:

  1. Sie fügen dem Formular, mit dem Sie interagieren möchten, einen formdata-Ereignis-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);
});

Probieren Sie das anhand unseres Beispiels auf Glitch aus. Verwenden Sie Chrome 77 oder höher, um die API in Aktion zu sehen.

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 formularzugehörigen 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 Ihres 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() auf dem 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 davon ist die setFormValue()-Methode, mit der der aktuelle Wert des Steuerelements festgelegt wird.

Die setFormValue()-Methode kann einen der drei folgenden Werte haben:

  • Ein Stringwert.
  • Ein File-Objekt.
  • Ein FormData-Objekt. Mit einem FormData-Objekt können Sie mehrere Werte übergeben. So kann beispielsweise ein Eingabekontrollelement für Kreditkarten 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 eines <fieldset> geändert hat, das ein Vorfahr 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 aufgerufen, nachdem das Formular zurückgesetzt wurde. Das Element sollte sich selbst auf einen Standardzustand zurücksetzen. Bei <input>-Elementen bedeutet das in der Regel, dass die value-Eigenschaft so festgelegt werden muss, dass sie mit dem im Markup festgelegten value-Attribut übereinstimmt. Bei einem Kästchen muss die checked-Eigenschaft so festgelegt werden, dass sie mit dem checked-Attribut übereinstimmt.

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 ein Wert durch die Eingabehilfen des Browsers wie das automatische Ausfüllen von Formularen 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 mit einem einzelnen Wertparameter aufrufen, wie in den vorangehenden Beispielen gezeigt, oder mit zwei Parametern:

this.internals_.setFormValue(value, state);

value steht für den einreichbaren Wert des Steuerelements. 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: 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: 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 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;
}

Ein Beispiel

Im folgenden Beispiel werden viele der Funktionen von formularzugeordneten benutzerdefinierten Elementen kombiniert. 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 beide Funktionen sind derzeit keine 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 Ereignis formdata bietet keine neuen Funktionen, sondern eine Benutzeroberfläche, über die Sie Ihre Formulardaten dem Sendevorgang hinzufü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