Belajar dengan membaca secara berurutan

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">
Dua value pada Input quantity
Atribut valuevalue="1"ditulis di HTMLTetap 1 meskisudah diketikouterHTMLberisi value="1"Property valueInput saat ini"3"Berubah setiapkali diketikDibaca denganquantity.value
"3" yang di-assign hanya masuk ke property value; atribut value tetap 1. Baca isi kolom dari property value.

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.

Untuk pemesanan rental mobil, hitung biayanya dari jumlah hari dan perlu tidaknya kursi anak. Kolom jumlah hari rentalDays (sudah diisi) dan checkbox childSeat sudah dideklarasikan.

① Tampilkan tipe dari value kolom jumlah hari.

② Ubah jumlah hari ke angka, lalu tampilkan biayanya dengan tarif 6.000 yen per hari.

③ Kalau kotaknya dicentang, tambahkan 500 yen per hari, lalu tampilkan total biaya untuk seluruh masa sewa.

(Kalau berjalan dengan benar, penjelasan akan muncul.)

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

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
Assignment Saja Tidak Mengubah Jumlah Karakter
postTitle.value ="Laporan mingguan"Event inputtidak terjadiListener tidakdipanggilTetap0 / 40dispatchEvent(…)setelah assignmentEvent inputterjadiListener membacavalue.lengthBerubah menjadi16 / 40
Assignment ke value tidak memicu event input, jadi jumlahnya tetap 0 / 40. Kalau event dipicu dengan dispatchEvent, listener-nya dipanggil.

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.

AksiEvent inputEvent change
Mengetik 1 karakter di kolom teksTerjadi tiap karakterTidak terjadi
Keluar dari kolom (klik luar/Tab)Tidak terjadiTerjadi jika value berubah
Mengklik checkboxTerjadiTerjadi

Di layar checkout, tampilkan kolom alamat hanya saat "Kirim ke alamat lain" dicentang. Checkbox otherAddress dan kolom alamat addressFields sudah dideklarasikan.

① Daftarkan listener yang membuang class hidden dari kolom alamat saat kotaknya dicentang.

② Sebelum memicu klik, tampilkan HTML kolom alamat.

③ Picu klik pada checkbox, lalu tampilkan status centangnya dan HTML kolom alamat.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

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 @
Hasil yang Berbeda Tergantung @ dan preventDefault
Listener submitdipanggilTanpa @:budi.example.comAda @:budi@example.comTanpa @:budi.example.compreventDefault()dan pesan errorpreventDefault()tidak dipanggilpreventDefault()lupa ditulisTidak dikirim;error tetap adaBrowsermengirimnyaTerkirim; pesanerror ikut hilang
Memanggil preventDefault() saat tidak ada @ membuat pesan error tetap ada di halaman. Kalau lupa memanggilnya, browser berpindah halaman dan pesan error-nya ikut hilang.

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.

Cek apakah password baru sudah cukup panjang. passwordForm, newPassword, dan passwordError sudah dideklarasikan.

① Daftarkan listener yang membatalkan pengiriman dan mengisi pesan error kalau password kurang dari 8 karakter, dan mengosongkan pesan itu kalau tidak.

② Isi password dengan "cat2026", picu event submit yang bisa dibatalkan, lalu tampilkan apakah pengirimannya dibatalkan.

③ Ganti isinya dengan "cat2026!", buat event baru lalu picu, lalu tampilkan apakah pengirimannya dibatalkan dan HTML kolom error.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

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"}
KolomKondisiValue di FormData
Kolom cari alamatTanpa atribut nameTidak masuk
recipient dan zipAda name; kolom teksSiti Rahayu dan 150-0001
dropOffAda name; tidak dicentangTidak masuk; get hasilnya null
notifyAda name; dicentangon (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.

Di form lamaran sebuah situs lowongan kerja, cek isi yang diisi sebelum dikirim. Form applyForm sudah dideklarasikan.

① Di listener submit, batalkan pengiriman, kumpulkan value kolom-kolomnya ke dalam object, lalu tampilkan sebagai string JSON.

② Di listener yang sama, baca value checkbox persetujuan menerima email, lalu tampilkan "Tanpa persetujuan" kalau value-nya tidak ada.

③ Picu event submit yang bisa dibatalkan.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Kalau kamu mengetik 3 karakter di kolom teks lalu menekan Tab untuk pindah, berapa kali input dan change terjadi?

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?