Bardziej zaawansowane elementy sterujące formularzem

Dzięki nowemu zdarzeniu i interfejsom API elementów niestandardowych korzystanie z formularzy stało się o wiele łatwiejsze.

Arthur Evans

Wielu programistów tworzy niestandardowe elementy sterujące, które nie są wbudowane w przeglądarkę, lub umożliwiają dostosowanie wyglądu i stylu strony za pomocą wbudowanych opcji.

Odtwarzanie funkcji wbudowanych elementów sterujących formularza HTML może być jednak trudne. Zastanów się nad niektórymi funkcjami, które element <input> otrzymuje automatycznie po dodaniu do formularza:

  • Dane wejściowe zostaną automatycznie dodane do listy elementów sterujących formularza.
  • Zawartość formularza jest przesyłana automatycznie.
  • Dane wejściowe są uwzględniane podczas weryfikacji formularza. Styl danych wejściowych możesz zmienić za pomocą pseudoklas :valid i :invalid.
  • Pojawi się powiadomienie, gdy formularz zostanie zresetowany, ponownie wczytany lub gdy przeglądarka spróbuje automatycznie uzupełnić wpisy.

Niestandardowe elementy sterujące formularza zwykle mają niewiele z tych funkcji. Deweloperzy mogą obejść niektóre ograniczenia języka JavaScript, np. dodać do formularza ukrytego atrybutu <input>, aby umożliwić jego przesłanie. Jednak innych funkcji po prostu nie da się powielać w samym kodzie JavaScript.

Dwie nowe funkcje internetowe ułatwiają tworzenie niestandardowych elementów sterujących formularza i usuwają ograniczenia dotychczasowych opcji:

  • Zdarzenie formdata umożliwia dowolnemu obiektowi JavaScript udział w przesyłaniu formularza, dzięki czemu możesz dodawać dane do formularzy bez korzystania z ukrytego elementu <input>.
  • Interfejs API elementów niestandardowych powiązanych z formularzem pozwala elementom niestandardowym w bardziej podobny sposób działać jak wbudowane elementy sterujące formularza.

Dzięki tym dwóm funkcjom można tworzyć nowe rodzaje ustawień, które działają lepiej.

Interfejs API oparty na zdarzeniach

Zdarzenie formdata to interfejs API niskiego poziomu, który umożliwia udział dowolnego kodu JavaScript w przesłaniu formularza. Mechanizm działa w następujący sposób:

  1. Dodajesz detektor zdarzeń formdata do formularza, z którym chcesz wejść w interakcję.
  2. Gdy użytkownik kliknie przycisk przesyłania, formularz wywoła zdarzenie formdata, które zawiera obiekt FormData przechowujący wszystkie przesyłane dane.
  3. Każdy detektor formdata ma możliwość dodania lub zmodyfikowania danych przed przesłaniem formularza.

Oto przykład wysłania pojedynczej wartości za pomocą detektora zdarzeń formdata:

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

Wypróbuj tę funkcję na przykładzie Glitch. Aby zobaczyć, jak działa ten interfejs API, uruchom go w Chrome w wersji 77 lub nowszej.

Zgodność z przeglądarką

Obsługa przeglądarek

  • 5
  • 12
  • 4
  • 5

Źródło

Elementy niestandardowe powiązane z formularzem

Interfejsu API opartego na zdarzeniach możesz używać z dowolnym komponentem, ale umożliwia on tylko interakcję z procesem przesyłania.

Poza przesłaniem formularza ustandaryzowane elementy sterujące formularza biorą udział w wielu częściach jego cyklu życia. Elementy niestandardowe powiązane z formularzem mają na celu wypełnienie luki między niestandardowymi widżetami a wbudowanymi elementami sterującymi. Elementy niestandardowe powiązane z formularzem pasują do wielu funkcji standardowych elementów formularzy:

  • Gdy umieścisz element niestandardowy powiązany z formularzem w elemencie <form>, zostanie on automatycznie powiązany z formularzem, podobnie jak element sterujący obsługiwany przez przeglądarkę.
  • Element można oznaczyć etykietą za pomocą elementu <label>.
  • Element może ustawiać wartość, która jest automatycznie przesyłana z formularzem.
  • Element może ustawić flagę wskazującą, czy zawiera prawidłowe dane wejściowe. Jeśli jedno z elementów sterujących formularza zawiera nieprawidłowe dane, nie można go przesłać.
  • Ten element może udostępniać wywołania zwrotne różnych części cyklu życia formularza, np. gdy formularz jest wyłączony lub przywrócony do stanu domyślnego.
  • Ten element obsługuje standardowe pseudoklasy CSS dla elementów sterujących formularza, takie jak :disabled i :invalid.

Mnóstwo funkcji! W tym artykule nie omówimy wszystkich. Znajdziesz w nim jednak podstawowe informacje potrzebne do integracji elementu niestandardowego z formularzem.

Definiowanie elementu niestandardowego powiązanego z formularzem

Aby przekształcić element niestandardowy w element niestandardowy powiązany z formularzem, wykonaj kilka dodatkowych czynności:

  • Dodaj statyczną właściwość formAssociated do klasy elementu niestandardowego. Dzięki temu przeglądarka będzie traktować element jak element sterujący formularza.
  • Wywołaj w elemencie metodę attachInternals(), aby uzyskać dostęp do dodatkowych metod i właściwości elementów sterujących formularza, takich jak setFormValue() i setValidity().
  • Dodaj typowe właściwości i metody obsługiwane przez elementy sterujące formularza, takie jak name, value czy validity.

Elementy te pasują do podstawowej definicji elementu niestandardowego:

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

Po zarejestrowaniu możesz używać tego elementu wszędzie tam, gdzie używasz sterowania formularzem udostępnianym przez przeglądarkę:

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

Ustawianie wartości

Metoda attachInternals() zwraca obiekt ElementInternals, który zapewnia dostęp do interfejsów API sterowania formularzem. Najbardziej podstawowa z nich jest metoda setFormValue(), która ustawia bieżącą wartość elementu sterującego.

Metoda setFormValue() może mieć jeden z 3 typów wartości:

  • Wartość ciągu znaków.
  • Obiekt File.
  • Obiekt FormData. Za pomocą obiektu FormData możesz przekazywać wiele wartości (np. funkcja wprowadzania danych karty kredytowej może przekazać numer karty, datę ważności i kod weryfikacyjny).

Aby ustawić prostą wartość:

this.internals_.setFormValue(this.value_);

Aby ustawić wiele wartości, wykonaj te czynności:

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

Weryfikacja danych wejściowych

Grupa kontrolna może też uczestniczyć w weryfikacji formularza, wywołując metodę setValidity() w obiekcie wewnętrznym.

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

Styl elementu niestandardowego powiązanego z formularzem możesz określić za pomocą pseudoklas :valid i :invalid (tak jak w przypadku wbudowanego elementu sterującego formularza).

Wywołania zwrotne cyklu życia

Interfejs API elementu niestandardowego powiązanego z formularzem zawiera zestaw dodatkowych wywołań zwrotnych cyklu życia, które można powiązać z cyklem życia formularza. Wywołania zwrotne są opcjonalne – korzystaj z nich tylko wtedy, gdy element musi wykonać jakąś czynność w danym momencie cyklu życia.

void formAssociatedCallback(form)

Wywoływane, gdy przeglądarka powiąże element z elementem formularza lub odłączy go od elementu formularza.

void formDisabledCallback(disabled)

Wywoływane po zmianie stanu elementu disabled z powodu dodania lub usunięcia atrybutu disabled tego elementu albo zmiany stanu disabled elementu <fieldset>, który jest elementem nadrzędnym tego elementu. Parametr disabled wskazuje nowy stan wyłączonego elementu. Ten element może na przykład wyłączać elementy w modelu shadow DOM, gdy jest wyłączony.

void formResetCallback()

Wywoływana po zresetowaniu formularza. Element powinien się zresetować do jakiegoś stanu domyślnego. W przypadku elementów <input> oznacza to zwykle ustawienie właściwości value tak, aby pasowała do atrybutu value określonego w znacznikach (lub w przypadku pola wyboru ustawienie właściwości checked na wartość zgodną z atrybutem checked).

void formStateRestoreCallback(state, mode)

Wywołano w jednym z 2 przyczyn:

  • Gdy przeglądarka przywraca stan elementu (na przykład po nawigacji lub po ponownym uruchomieniu). W tym przypadku argument mode to "restore".
  • Gdy funkcje Asystenta wprowadzania w przeglądarce, takie jak autouzupełnianie formularzy, ustawiają wartość. W tym przypadku argument mode to "autocomplete".

Typ pierwszego argumentu zależy od sposobu wywołania metody setFormValue(). Więcej informacji znajdziesz w artykule Przywracanie stanu formularza.

Przywracam stan formularza

W pewnych okolicznościach, np. podczas powrotu na stronę lub ponownego uruchomienia przeglądarki, przeglądarka może próbować przywrócić formularz do stanu, w którym użytkownik go zostawił.

Przywrócenie elementu niestandardowego powiązanego z formularzem pochodzi z wartości przekazywanych do metody setFormValue(). Możesz wywołać tę metodę z jednym parametrem wartości, jak pokazano we wcześniejszych przykładach, lub za pomocą 2 parametrów:

this.internals_.setFormValue(value, state);

value reprezentuje wartość kontroli, którą można przesłać. Opcjonalny parametr state to wewnętrzny stan elementu sterującego, który może zawierać dane, które nie są wysyłane na serwer. Parametr state przyjmuje te same typy co parametr value – może to być ciąg znaków, obiekt File lub obiekt FormData.

Parametr state jest przydatny, gdy na podstawie samej wartości nie można przywrócić stanu elementu sterującego. Załóżmy na przykład, że tworzysz selektor kolorów z wieloma trybami: paletą lub kołem kolorów RGB. Atrybut value, który można przesłać, to wybrany kolor w formie kanonicznej, np. "#7fff00". Aby jednak przywrócić kontrolę do określonego stanu, musisz też wiedzieć, w jakim trybie to nastąpiło, więc state może wyglądać tak: "palette/#7fff00".

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

Kod musiałby zostać przywrócony na podstawie zapisanej wartości stanu.

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

W przypadku prostszego elementu sterującego (na przykład wprowadzania wartości) wartość prawdopodobnie wystarczy do przywrócenia go do poprzedniego stanu. Jeśli pominiesz właściwość state przy wywoływaniu funkcji setFormValue(), wartość zostanie przekazana do formStateRestoreCallback().

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

Praktyczny przykład

Poniższy przykład stanowi połączenie wielu funkcji elementów niestandardowych powiązanych z formularzem. Aby zobaczyć, jak działa ten interfejs API, uruchom go w Chrome w wersji 77 lub nowszej.

Wykrywanie funkcji

Za pomocą wykrywania funkcji możesz określić, czy dostępne są zdarzenie formdata i elementy niestandardowe powiązane z formularzem. Obecnie nie jest dostępny żaden kod polyfill dla żadnej z tych funkcji. W obu przypadkach możesz wrócić do dodawania ukrytego elementu formularza, aby przekazać wartość elementu sterującego do formularza. Wprowadzenie wielu zaawansowanych funkcji elementów niestandardowych powiązanych z formularzem będzie prawdopodobnie trudne lub niemożliwe do wykorzystania polyfill.

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

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

Podsumowanie

Zdarzenie formdata i elementy niestandardowe powiązane z formularzem zapewniają nowe narzędzia do tworzenia niestandardowych elementów sterujących.

Zdarzenie formdata nie daje żadnych nowych możliwości, ale udostępnia interfejs umożliwiający dodawanie danych formularzy do procesu przesyłania bez konieczności tworzenia ukrytego elementu <input>.

Interfejs API elementów niestandardowych powiązanych z formularzem udostępnia nowy zestaw funkcji do tworzenia niestandardowych elementów sterujących, które działają jak wbudowane elementy sterujące formularza.

Baner powitalny autorstwa Oudom Pravat w filmie Unsplash.