Kontrol formulir yang lebih andal

Dengan acara baru dan API elemen kustom, berpartisipasi dalam formulir kini jauh lebih mudah.

Arthur Evans

Banyak developer membuat kontrol formulir kustom, baik untuk memberikan kontrol yang tidak disertakan pada browser, atau untuk menyesuaikan tampilan dan nuansa di luar yang dapat dilakukan dengan kontrol formulir bawaan.

Namun, hal ini mungkin sulit untuk mereplikasi fitur kontrol formulir HTML bawaan. Pertimbangkan beberapa fitur yang otomatis didapatkan elemen <input> saat Anda menambahkannya ke formulir:

  • Input akan otomatis ditambahkan ke daftar kontrol formulir.
  • Nilai input akan otomatis dikirimkan bersama formulir.
  • Input berpartisipasi dalam validasi formulir. Anda dapat menata gaya input menggunakan pseudoclass :valid dan :invalid.
  • Input akan diberi tahu saat formulir direset, saat formulir dimuat ulang, atau saat browser mencoba mengisi otomatis entri formulir.

Kontrol formulir kustom biasanya memiliki beberapa fitur tersebut. Developer dapat mengatasi beberapa batasan dalam JavaScript, seperti menambahkan <input> tersembunyi ke formulir untuk berpartisipasi dalam pengiriman formulir. Tetapi fitur lain tidak bisa direplikasi di JavaScript saja.

Dua fitur web baru memudahkan pembuatan kontrol formulir kustom, dan menghilangkan batasan kontrol kustom saat ini:

  • Peristiwa formdata memungkinkan objek JavaScript arbitrer berpartisipasi dalam pengiriman formulir, sehingga Anda dapat menambahkan data formulir tanpa menggunakan <input> tersembunyi.
  • API elemen kustom yang terkait dengan Formulir memungkinkan elemen kustom berfungsi lebih seperti kontrol formulir bawaan.

Kedua fitur ini dapat digunakan untuk membuat jenis kontrol baru yang berfungsi lebih baik.

API berbasis peristiwa

Peristiwa formdata adalah API tingkat rendah yang memungkinkan kode JavaScript berpartisipasi dalam pengiriman formulir. Mekanismenya bekerja seperti ini:

  1. Anda menambahkan pemroses peristiwa formdata ke formulir yang ingin Anda gunakan.
  2. Saat pengguna mengklik tombol kirim, formulir akan mengaktifkan peristiwa formdata, yang mencakup objek FormData yang menyimpan semua data yang dikirim.
  3. Setiap pemroses formdata berkesempatan untuk menambahkan atau mengubah data sebelum formulir dikirimkan.

Berikut adalah contoh pengiriman satu nilai dalam pemroses peristiwa 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);
});

Coba ini menggunakan contoh kami di Glitch. Pastikan untuk menjalankannya di Chrome 77 atau yang lebih baru untuk melihat cara kerja API.

Kompatibilitas browser

Dukungan Browser

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

Sumber

Elemen kustom yang terkait formulir

Anda dapat menggunakan API berbasis peristiwa dengan jenis komponen apa pun, tetapi API ini hanya memungkinkan Anda berinteraksi dengan proses pengiriman.

Kontrol formulir standar berpartisipasi di banyak bagian siklus proses formulir selain pengiriman. Elemen kustom yang terkait dengan formulir bertujuan untuk menjembatani kesenjangan antara widget kustom dan kontrol bawaan. Elemen kustom yang terkait dengan formulir cocok dengan banyak fitur elemen formulir standar:

  • Jika Anda menempatkan elemen kustom terkait formulir di dalam <form>, elemen kustom tersebut akan otomatis dikaitkan dengan formulir, seperti kontrol yang disediakan browser.
  • Elemen ini dapat diberi label menggunakan elemen <label>.
  • Elemen dapat menetapkan nilai yang otomatis dikirimkan bersama formulir.
  • Elemen dapat menetapkan flag yang menunjukkan apakah elemen tersebut memiliki input yang valid atau tidak. Jika salah satu kontrol formulir memiliki input yang tidak valid, formulir tidak dapat dikirim.
  • Elemen ini dapat memberikan callback untuk berbagai bagian siklus proses formulir—seperti saat formulir dinonaktifkan atau direset ke status default-nya.
  • Elemen ini mendukung pseudoclass CSS standar untuk kontrol formulir, seperti :disabled dan :invalid.

Fitur yang sangat banyak. Artikel ini tidak akan menyinggung semuanya, tetapi akan menjelaskan dasar-dasar yang diperlukan untuk mengintegrasikan elemen khusus Anda dengan formulir.

Menentukan elemen kustom yang terkait formulir

Perlu beberapa langkah tambahan untuk mengubah elemen kustom menjadi elemen kustom terkait formulir:

  • Tambahkan properti formAssociated statis ke class elemen kustom Anda. Tindakan ini akan memberi tahu browser untuk memperlakukan elemen seperti kontrol formulir.
  • Panggil metode attachInternals() pada elemen untuk mendapatkan akses ke metode dan properti tambahan untuk kontrol formulir, seperti setFormValue() dan setValidity().
  • Tambahkan properti dan metode umum yang didukung oleh kontrol formulir, seperti name, value, dan validity.

Berikut cara item tersebut sesuai dengan definisi elemen kustom dasar:

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

Setelah terdaftar, Anda dapat menggunakan elemen ini di mana pun Anda menggunakan kontrol formulir yang disediakan browser:

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

Menetapkan nilai

Metode attachInternals() menampilkan objek ElementInternals yang memberikan akses ke API kontrol formulir. Yang paling dasar adalah metode setFormValue(), yang menetapkan nilai kontrol saat ini.

Metode setFormValue() dapat menggunakan salah satu dari tiga jenis nilai:

  • Nilai string.
  • Objek File.
  • Objek FormData. Anda dapat menggunakan objek FormData untuk meneruskan beberapa nilai (misalnya, kontrol input kartu kredit mungkin meneruskan nomor kartu, tanggal habis masa berlaku, dan kode verifikasi).

Untuk menetapkan nilai sederhana:

this.internals_.setFormValue(this.value_);

Untuk menetapkan beberapa nilai, Anda dapat melakukan hal seperti ini:

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

Validasi masukan

Kontrol Anda juga dapat berpartisipasi dalam validasi formulir dengan memanggil setValidity() pada objek internal.

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

Anda dapat menata gaya elemen kustom terkait formulir dengan :valid dan :invalid {i>pseudoclass<i}, seperti kontrol formulir bawaan.

Callback siklus proses

API elemen kustom yang terkait dengan formulir menyertakan serangkaian callback siklus proses tambahan yang akan dikaitkan dengan siklus proses formulir. Callback bersifat opsional: hanya terapkan callback jika elemen Anda perlu melakukan sesuatu pada titik tersebut dalam siklus proses.

void formAssociatedCallback(form)

Dipanggil saat browser mengaitkan elemen dengan elemen formulir, atau memisahkan elemen dari elemen formulir.

void formDisabledCallback(disabled)

Dipanggil setelah status disabled elemen berubah, baik karena atribut disabled elemen ini ditambahkan atau dihapus; atau karena status disabled berubah di <fieldset> yang merupakan ancestor elemen ini. Parameter disabled mewakili status dinonaktifkan baru untuk elemen. Elemen dapat, misalnya, menonaktifkan elemen dalam shadow DOM-nya bila dinonaktifkan.

void formResetCallback()

Dipanggil setelah formulir direset. Elemen harus mereset dirinya sendiri ke semacam status default. Untuk elemen <input>, hal ini biasanya melibatkan penetapan properti value agar cocok dengan atribut value yang ditetapkan di markup (atau untuk kotak centang, menyetel properti checked agar cocok dengan atribut checked.

void formStateRestoreCallback(state, mode)

Dipanggil dalam salah satu dari dua situasi:

  • Saat browser memulihkan status elemen (misalnya, setelah navigasi, atau saat browser dimulai ulang). Dalam kasus ini, argumen mode adalah "restore".
  • Jika fitur bantuan input browser seperti isi otomatis formulir menetapkan nilai. Dalam kasus ini, argumen mode adalah "autocomplete".

Jenis argumen pertama bergantung pada cara metode setFormValue() dipanggil. Untuk mengetahui detail selengkapnya, lihat Memulihkan status formulir.

Memulihkan status formulir

Dalam beberapa situasi—seperti saat menavigasi kembali ke halaman, atau memulai ulang browser, browser mungkin mencoba memulihkan formulir ke keadaan saat pengguna meninggalkannya.

Untuk elemen kustom terkait formulir, status yang dipulihkan berasal dari nilai yang Anda teruskan ke metode setFormValue(). Anda dapat memanggil metode dengan satu parameter nilai, seperti yang ditunjukkan dalam contoh sebelumnya, atau dengan dua parameter:

this.internals_.setFormValue(value, state);

value merepresentasikan nilai kontrol yang dapat dikirim. Parameter state opsional adalah representasi internal dari status kontrol, yang dapat menyertakan data yang tidak dikirim ke server. Parameter state menggunakan jenis yang sama dengan parameter value—bisa berupa string, File, atau objek FormData.

Parameter state berguna jika Anda tidak dapat memulihkan status kontrol hanya berdasarkan nilai saja. Misalnya, Anda membuat pemilih warna dengan beberapa mode: palet atau roda warna RGB. Nilai yang dapat dikirimkan adalah warna yang dipilih dalam formulir kanonis, seperti "#7fff00". Namun, untuk memulihkan kontrol ke status tertentu, Anda juga perlu mengetahui mode mana yang digunakan, sehingga status mungkin terlihat seperti "palette/#7fff00".

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

Kode Anda harus memulihkan statusnya berdasarkan nilai status yang disimpan.

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

Dalam kasus kontrol yang lebih sederhana (misalnya input angka), nilainya mungkin cukup untuk memulihkan kontrol ke status sebelumnya. Jika Anda menghapus state saat memanggil setFormValue(), nilai akan diteruskan ke formStateRestoreCallback().

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

Contoh yang berfungsi

Contoh berikut menyatukan banyak fitur elemen kustom yang terkait dengan formulir. Pastikan untuk menjalankannya di Chrome 77 atau yang lebih baru untuk melihat cara kerja API.

Deteksi fitur

Anda dapat menggunakan deteksi fitur untuk menentukan apakah peristiwa formdata dan elemen kustom yang terkait formulir tersedia. Saat ini tidak ada polyfill yang dirilis untuk kedua fitur. Dalam kedua kasus tersebut, Anda dapat kembali untuk menambahkan elemen formulir tersembunyi untuk menyebarkan nilai kontrol ke formulir. Banyak fitur yang lebih canggih dari elemen kustom yang terkait dengan formulir mungkin akan sulit atau tidak mungkin di-polyfill.

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

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

Kesimpulan

Elemen kustom terkait formulir dan peristiwa formdata menyediakan alat baru untuk membuat kontrol formulir kustom.

Peristiwa formdata tidak memberi Anda kemampuan baru, tetapi memberi Anda antarmuka untuk menambahkan data formulir ke proses pengiriman, tanpa harus membuat elemen <input> tersembunyi.

API elemen kustom terkait formulir menyediakan serangkaian kemampuan baru untuk membuat kontrol formulir kustom yang berfungsi seperti kontrol formulir bawaan.

Banner besar oleh Oudom Pravat di Unsplash.