Soal 1Kalau kamu mengetik 3 karakter di kolom teks lalu menekan Tab untuk pindah, berapa kali input dan change terjadi?
Form — Membaca value dan Menangani submit
Membaca kolom dengan value dan checked, menanggapi event input dan change, membatalkan submit dengan preventDefault, dan mengumpulkan kolom dengan FormData.
Klik bisa ditangani dengan addEventListener, tapi di form kamu juga perlu membaca value yang diketik di kolom-kolomnya dan mengeceknya sebelum dikirim. Saat tombol kirim diklik, browser memuat halaman baru, jadi kamu juga butuh cara untuk menahan form agar tidak terkirim sebelum pengecekan selesai.
Artikel ini membahas value, untuk membaca isi kolom, dan event submit, yang terjadi saat form dikirim.
Membaca Isi Kolom — value dan checked
Misalkan kamu ingin membaca angka yang diisi di kolom jumlah pada form pesanan (bagian halaman yang mengelompokkan kolom input dan tombol kirim di dalam element form), lalu menghitung total harganya. Element input tidak punya tag penutup dan tidak bisa berisi child element maupun teks, jadi membaca textContent-nya hanya menghasilkan string kosong.
Kolom input bisa berupa element input (jenis kolomnya ditentukan oleh atribut type) atau element textarea untuk teks beberapa baris. Isi kolom dibaca lewat value (property untuk membaca dan menulis isi kolom saat ini sebagai string), seperti quantity.value, sedangkan status centang checkbox dibaca dengan checked. Atribut value yang ditulis di HTML adalah value awal saat halaman dibuka, dan tidak berubah meskipun kolomnya diisi.
document.body.innerHTML = `<form id="order-form"><input id="quantity" type="number" value="1"><input id="gift-wrap" type="checkbox"></form>`;
const quantity = document.querySelector("#quantity");
const giftWrap = document.querySelector("#gift-wrap");
// Anggap pengguna mengetik 3 dan mencentang kotak bungkus kado
quantity.value = "3";
giftWrap.checked = true;
// value tetap string meskipun kolomnya bertipe number, jadi ubah ke angka dulu sebelum menghitung
console.log(quantity.value + 1); // 31
console.log(Number(quantity.value) * 1200); // 3600
console.log(giftWrap.checked); // true
// outerHTML tetap menampilkan atribut value yang ditulis di HTML
console.log(quantity.outerHTML); // <input id="quantity" type="number" value="1">
Begitu juga dengan checked: mencentang kotak tidak menambahkan atribut checked ke HTML. Kalau kolom jumlah dikosongkan, value menjadi string kosong, dan Number("") mengembalikan 0. Jadi setelah diubah ke angka, kamu tidak bisa membedakan kolom yang kosong dengan kolom yang diisi 0.
Menghitung Ulang Setiap Kali Diketik — Event input dan change
Di halaman penulisan blog internal perusahaan, ada jumlah karakter seperti "16 / 40" di bawah kolom judul, dan angkanya harus diperbarui setiap kali pengguna mengetik. Kalau value hanya dibaca sekali saat halaman dibuka, karakter yang diketik setelahnya tidak akan pernah ikut terhitung.
Kolom input memicu event input setiap kali 1 karakter diketik, dan event change saat value-nya berubah lalu fokus keluar dari kolom. Di console kamu tidak bisa mengetik, jadi event dengan nama yang sama dipicu dengan dispatchEvent (method yang memicu event yang diberikan di element tersebut). Event yang akan dipicu dibuat dengan menyebutkan namanya, seperti new Event("input").
document.body.innerHTML = `<form id="post-form"><input id="post-title"><p id="title-count">0 / 40</p></form>`;
const postTitle = document.querySelector("#post-title");
const titleCount = document.querySelector("#title-count");
// input: hitung ulang jumlah karakter setiap kali diketik
postTitle.addEventListener("input", () => {
titleCount.textContent = `${postTitle.value.length} / 40`;
});
// change: simpan draf saat value-nya berubah dan fokus keluar dari kolom
postTitle.addEventListener("change", () => console.log("Draf disimpan"));
// Hanya meng-assign value
postTitle.value = "Laporan mingguan";
console.log(titleCount.textContent); // 0 / 40
// Memicu event akan memanggil listener yang didaftarkan dengan nama yang sama
postTitle.dispatchEvent(new Event("input"));
console.log(titleCount.textContent); // 16 / 40
postTitle.dispatchEvent(new Event("change")); // Draf disimpan
Listener input dipanggil untuk setiap karakter, jadi pekerjaan yang tidak perlu dilakukan di setiap ketikan, seperti menyimpan draf, sebaiknya didaftarkan di change, yang terjadi saat pengguna selesai mengetik. Tabel di bawah menunjukkan event mana, input atau change, yang terjadi untuk setiap aksi pengguna.
| Aksi | Event input | Event change |
|---|---|---|
| Mengetik 1 karakter di kolom teks | Terjadi tiap karakter | Tidak terjadi |
| Keluar dari kolom (klik luar/Tab) | Tidak terjadi | Terjadi jika value berubah |
| Mengklik checkbox | Terjadi | Terjadi |
Membatalkan Pengiriman — submit dan preventDefault
Form pendaftaran newsletter harus menampilkan pesan error dan menahan pengiriman kalau alamat email tidak berisi @. Saat tombol kirim diklik, browser mengirim isi form dan berpindah ke halaman baru. Jadi, meskipun listener sudah menaruh pesan error di halaman, pesan itu ikut hilang bersama halamannya.
Tepat sebelum form dikirim, event submit terjadi di element form. Kalau kamu memanggil preventDefault (method yang membatalkan aksi yang akan dilakukan browser setelah event itu) di listener-nya, form tidak dikirim. Berhasil tidaknya pembatalan bisa dicek lewat defaultPrevented milik event (property yang menjadi true kalau pembatalannya berhasil).
document.body.innerHTML = `<form id="newsletter-form"><input id="newsletter-email"><button>Berlangganan</button><p id="email-error"></p></form>`;
const newsletterForm = document.querySelector("#newsletter-form");
const emailInput = document.querySelector("#newsletter-email");
const emailError = document.querySelector("#email-error");
// Dipanggil tepat sebelum pengiriman. Pengiriman dibatalkan hanya kalau tidak ada @
newsletterForm.addEventListener("submit", (event) => {
if (!emailInput.value.includes("@")) {
event.preventDefault();
emailError.textContent = "Alamat email tidak berisi @";
}
});
// Sebagai pengganti pengiriman sungguhan, buat lalu picu event submit yang bisa dibatalkan
emailInput.value = "budi.example.com";
const submitEvent = new Event("submit", { cancelable: true });
newsletterForm.dispatchEvent(submitEvent);
console.log(submitEvent.defaultPrevented); // true (preventDefault dipanggil)
console.log(emailError.textContent); // Alamat email tidak berisi @
Kolom kiri dan kanan memakai input yang sama; bedanya hanya apakah preventDefault() dipanggil atau tidak. Event submit terjadi di element form, bukan di tombol kirim, jadi daftarkan listener-nya di form. Listener yang sama juga menangani pengiriman lewat tombol Enter di kolom input.
click() pada Tombol Kirim Benar-Benar Mengirim Form
Element button di dalam form menjadi tombol kirim kalau type-nya tidak ditulis. Kalau click() dipanggil padanya tanpa dibatalkan, form benar-benar terkirim, dan hasilnya tidak terlihat sampai halaman dimuat ulang. Event submit yang dipicu dengan dispatchEvent tidak benar-benar mengirim form, tapi tanpa { cancelable: true }, preventDefault() tidak berpengaruh.
Mengumpulkan Isi Semua Kolom Sekaligus — FormData
Berikutnya, misalkan ada form alamat pengiriman. Kamu ingin mengumpulkan nama penerima, kode pos, dan pilihan titip paket di depan pintu ke dalam 1 object. Kalau kamu memakai querySelector dan value untuk setiap kolom, setiap ada kolom baru, baris kode untuk membacanya juga harus ditambah. Saat form dikirim, atribut name setiap kolom (nama untuk value kolom itu) dikirim berpasangan dengan value-nya.
FormData (object yang mengumpulkan pasangan name dan value dari kolom-kolom di dalam form) dibuat dengan new FormData(form), dan value tiap kolom dibaca berdasarkan name-nya, seperti get("zip"). Kalau FormData diberikan ke Object.fromEntries (kebalikan dari Object.entries, yaitu method yang membuat object dari deretan pasangan [name, value]), hasilnya 1 object yang berisi semua kolom.
document.body.innerHTML = `<form id="shipping-form">
<input id="address-search" value="Shibuya">
<input name="recipient" value="Siti Rahayu">
<input name="zip" value="150-0001">
<input name="dropOff" type="checkbox">
<input name="notify" type="checkbox" checked>
</form>`;
const shippingForm = document.querySelector("#shipping-form");
// Kumpulkan pasangan name dan value dari kolom-kolom di dalam form
const shippingData = new FormData(shippingForm);
console.log(shippingData.get("zip")); // 150-0001
console.log(shippingData.get("dropOff")); // null
// Buat object dari pasangan name dan value
const shipping = Object.fromEntries(shippingData);
console.log(JSON.stringify(shipping));
// {"recipient":"Siti Rahayu","zip":"150-0001","notify":"on"}
| Kolom | Kondisi | Value di FormData |
|---|---|---|
| Kolom cari alamat | Tanpa atribut name | Tidak masuk |
| recipient dan zip | Ada name; kolom teks | Siti Rahayu dan 150-0001 |
| dropOff | Ada name; tidak dicentang | Tidak masuk; get hasilnya null |
| notify | Ada name; dicentang | on (tanpa atribut value) |
Sama seperti Map, pasangan [name, value] di FormData bisa diambil satu per satu secara berurutan, jadi FormData bisa langsung diberikan ke Object.fromEntries. Selama kolom baru diberi atribut name, kamu tidak perlu menambah baris kode untuk membacanya.
Menampilkan FormData Secara Langsung Menghasilkan {}
Console latihan mengubah object menjadi string JSON sebelum menampilkannya, jadi kalau FormData diberikan langsung, seperti console.log(shippingData), yang tampil adalah {} meskipun value kolom-kolomnya sudah terkumpul. Cek isinya dengan membacanya lewat get, atau ubah dulu menjadi object dengan Object.fromEntries.
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Apa yang dibatalkan kalau kamu memanggil event.preventDefault() di listener submit sebuah form?
Soal 3Untuk checkbox yang tidak dicentang dengan name="dropOff", apa hasil get("dropOff") pada FormData?