Bardziej zaawansowane elementy sterujące formularzem

Dzięki nowemu zdarzeniu i interfejsom API elementów niestandardowych znacznie ułatwiliśmy wypełnianie formularzy.

Arthur Evans

Wielu deweloperów tworzy niestandardowe elementy sterujące formularza, aby umożliwiać korzystanie z elementów sterujących, które nie są wbudowane w przeglądarkę, lub aby dostosować wygląd i styl ponad to, co jest możliwe dzięki wbudowanym elementom sterującym.

Odtwarzanie funkcji wbudowanych elementów sterujących formularza HTML może być jednak trudne. Weź pod uwagę niektóre funkcje, które element <input> otrzymuje automatycznie po dodaniu go do formularza:

  • To pole zostanie automatycznie dodane do listy elementów sterujących formularza.
  • Wartość wejściowa jest automatycznie przesyłana razem z formularzem.
  • Dane wejściowe są uwzględniane podczas weryfikacji formularza. Możesz określić styl danych wejściowych, używając pseudoklas :valid i :invalid.
  • Dane wejściowe są powiadamiane, gdy formularz zostanie zresetowany, odświeżony lub przeglądarka spróbuje automatycznie uzupełnić wpisy w formularzu.

Elementy sterujące formularza niestandardowego zwykle mają niewiele z tych funkcji. Deweloperzy mogą obejść niektóre ograniczenia występujące w kodzie JavaScriptu, takie jak dodanie do formularza ukrytego <input>, aby umożliwić przesyłanie formularzy. Innych funkcji nie da się jednak powielić w samym JavaScripcie.

Dwie nowe funkcje internetowe ułatwiają tworzenie niestandardowych elementów sterujących formularza i eliminują ograniczenia tych elementów:

  • Zdarzenie formdata umożliwia dowolnemu obiektowi JavaScriptu uczestniczenie w przesyłaniu formularza, więc możesz dodawać dane do formularza bez używania ukrytego obiektu <input>.
  • Interfejs API elementów niestandardowych związanych z formularzem pozwala tym elementom działać bardziej jak wbudowane elementy sterujące formularza.

Dzięki tym 2 funkcjom można tworzyć nowe, lepiej działające ustawienia.

Interfejs API oparty na zdarzeniach

Zdarzenie formdata to niskopoziomowy interfejs API, który umożliwia udział dowolnego kodu JavaScript w przesyłaniu formularza. Mechanizm działa tak:

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

Oto przykład wysyłania pojedynczej wartości w detektorze 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 to na przykładzie w 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

  • Chrome: 5.
  • Krawędź: 12.
  • Firefox: 4.
  • Safari: 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.

Ustandaryzowane elementy sterujące formularza są uwzględniane w wielu częściach cyklu życia formularza poza jego przesyłaniem. Elementy niestandardowe związane z formularzem mają wypełnić lukę między niestandardowymi widżetami a wbudowanymi elementami sterującymi. Powiązane z formularzem elementy niestandardowe pasują do wielu funkcji standardowych elementów formularzy:

  • Gdy umieścisz w elemencie <form> element niestandardowy powiązany z formularzem, zostanie on automatycznie powiązany z formularzem, tak jak w przypadku elementu sterującego w przeglądarce.
  • Element może być oznaczony etykietą za pomocą elementu <label>.
  • Element może ustawić wartość, która jest przesyłana automatycznie w formularzu.
  • Element może ustawić flagę wskazującą, czy zawiera on prawidłowe dane wejściowe. Jeśli jeden z elementów sterujących formularza zawiera nieprawidłowe dane wejściowe, nie można przesłać formularza.
  • Ten element może zawierać wywołania zwrotne do różnych części cyklu życia formularza – na przykład gdy formularz jest wyłączony lub przywrócony do stanu domyślnego.
  • Element obsługuje standardowe pseudoklasy CSS na potrzeby elementów sterujących formularza, np. :disabled i :invalid.

To mnóstwo funkcji! W tym artykule nie omówimy wszystkich z nich, ale opiszemy podstawowe kwestie potrzebne do zintegrowania elementu niestandardowego z formularzem.

Definiowanie elementu niestandardowego powiązanego z formularzem

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

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

Oto jak te elementy 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 ustawienia formularza w przeglądarce:

<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 kontroli formularza. Podstawowa z nich to metoda setFormValue(), która ustawia bieżącą wartość elementu sterującego.

Metoda setFormValue() może przyjmować 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. element sterujący danymi karty kredytowej może przekazywać numer karty, datę ważności i kod weryfikacyjny).

Aby ustawić prostą wartość:

this.internals_.setFormValue(this.value_);

Aby ustawić wiele wartości, wykonaj następujące 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

Element sterujący może również uczestniczyć w weryfikacji formularza, wywołując metodę setValidity() na obiekcie Internals.

// 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 zmienić za pomocą atrybutów :valid i :invalid pseudoklas, tak jak wbudowany element sterujący formularza.

Wywołania zwrotne cyklu życia

Interfejs API elementu niestandardowego powiązanego z formularzem zawiera zestaw dodatkowych wywołań zwrotnych cyklu życia, które są powiązane z cyklem życia formularza. Wywołania zwrotne są opcjonalne: zaimplementuj wywołanie zwrotne tylko wtedy, gdy element musi coś zrobić na tym etapie 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ływana po zmianie stanu elementu disabled, ponieważ atrybut disabled tego elementu został dodany lub usunięty. lub dlatego, że stan disabled zmienił się w elemencie <fieldset>, który jest elementem nadrzędnym tego elementu. Parametr disabled reprezentuje nowy stan wyłączenia elementu. Element może na przykład wyłączać elementy w swoim modelu shadow DOM, jeśli jest wyłączony.

void formResetCallback()

Wywoływane po zresetowaniu formularza. Element powinien się zresetować do jakiegoś stanu domyślnego. W przypadku elementów <input> zazwyczaj wymaga to skonfigurowania właściwości value tak, aby pasowała do atrybutu value ustawionego w znacznikach (lub w przypadku pola wyboru – dostosowanie właściwości checked do atrybutu checked.

void formStateRestoreCallback(state, mode)

Wywołanie w jednej z dwóch sytuacji:

  • Gdy przeglądarka przywróci stan elementu (np. po przejściu nawigacji lub ponownym uruchomieniu przeglądarki). W tym przypadku argument mode to "restore".
  • Gdy funkcje przeglądarki wspomagające dane wejściowe, 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 uruchamiania przeglądarki, przeglądarka może spróbować przywrócić formularz do stanu, w którym został pozostawiony przez użytkownika.

W przypadku elementu niestandardowego powiązanego z formularzem stan przywrócony zależy od wartości przesłanych do metody setFormValue(). Metodę można wywołać z jednym parametrem wartości, jak pokazano we wcześniejszych przykładach, lub za pomocą dwóch parametrów:

this.internals_.setFormValue(value, state);

value reprezentuje wartość elementu sterującego, którą można przesłać. Opcjonalny parametr state to wewnętrzna reprezentacja stanu elementu sterującego, która może zawierać dane, które nie są wysyłane na serwer. Parametr state ma te same typy co parametr value – może być ciągiem znaków, File lub obiektem 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. Wartość, którą można przesłać, to kolor wybrany w postaci kanonicznej, np. "#7fff00". Aby jednak przywrócić kontrolkę do określonego stanu, musisz też wiedzieć, w którym trybie się ona znajdowała, więc stan może wyglądać tak: "palette/#7fff00".

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

Twój kod musiałby przywrócić swój stan 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 (np. w postaci liczby wejściowej) wartość jest prawdopodobnie wystarczająca do przywrócenia elementu sterującego do poprzedniego stanu. Jeśli pominiesz state podczas wywoływania funkcji setFormValue(), wartość zostanie przekazana do formStateRestoreCallback().

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

Dobry przykład

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

Wykrywanie cech

Wykrywanie funkcji pozwala określić, czy zdarzenie formdata i elementy niestandardowe powiązane z formularzem są dostępne. Obecnie dla żadnej z tych funkcji nie są dostępne żadne zasoby polyfill. W obu przypadkach możesz wrócić do dodania ukrytego elementu formularza, aby przekazać wartość ustawienia do formularza. Wiele z bardziej zaawansowanych funkcji powiązanych z formularzem niestandardowych elementów może być trudne lub niemożliwe do wypełnienia.

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 powiązane z formularzem elementy niestandardowe oferują nowe narzędzia do tworzenia niestandardowych elementów sterujących formularza.

Zdarzenie formdata nie zapewnia żadnych nowych możliwości, ale zapewnia interfejs do dodawania danych formularza do procesu przesyłania bez konieczności tworzenia ukrytego elementu <input>.

Interfejs API elementów niestandardowych związanych z formularzem zapewnia nowy zestaw możliwości tworzenia niestandardowych elementów sterujących formularza, które działają jak wbudowane elementy sterujące formularza.

Baner powitalny autorstwa Oudoma Pravata na kanale Unsplash.