Bab 4: Forms & Controls
Form adalah cara utama user mengirim data. Pahami cara mengakses, memvalidasi, dan mengelola form dengan JavaScript.
4.1 Properti dan Method Form
Form HTML itu kayak formulir kertas — ada kolom-kolom yang harus diisi. JavaScript bisa membaca isian, mengubahnya, bahkan mengisi otomatis. Bedanya: formulir digital ini bisa kita program!
Mengakses Form dan Elemennya
// Akses form
const form = document.forms[0]; // Form pertama di halaman
const form2 = document.forms.myForm; // Form dengan name="myForm"
const form3 = document.forms["my-form"]; // Form dengan name="my-form"
// Akses elemen di dalam form
const input = form.elements.username; // Input dengan name="username"
const email = form.elements["email"]; // Input dengan name="email"
const allInputs = form.elements; // Semua elemen form (collection)Contoh Lengkap
<form name="register">
<input name="username" value="Budi">
<input name="email" value="budi@email.com">
<select name="role">
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
</form>const form = document.forms.register;
// Baca nilai
console.log(form.elements.username.value); // "Budi"
console.log(form.elements.email.value); // "budi@email.com"
console.log(form.elements.role.value); // "user"
// Tulis nilai
form.elements.username.value = "Ani";
form.elements.role.value = "admin"; // Select berubah ke "Admin"Fieldset sebagai Sub-form
<form name="settings">
<fieldset name="appearance">
<input name="theme" value="dark">
<input name="fontSize" value="16">
</fieldset>
</form>const form = document.forms.settings;
const fieldset = form.elements.appearance;
// Fieldset juga punya .elements!
console.log(fieldset.elements.theme.value); // "dark"Backreference: element.form
const input = document.querySelector('input[name="username"]');
console.log(input.form); // Referensi ke <form> parent-nya
// Berguna kalau kamu punya referensi input tapi butuh form-nyaInput Types dan Nilainya
// Text input
input.value = "teks baru";
input.value; // "teks baru"
// Checkbox / Radio
checkbox.checked = true; // boolean!
checkbox.checked; // true/false (BUKAN value!)
// Select
select.value = "option2"; // Set by value
select.selectedIndex = 1; // Set by index
select.options[1].selected = true; // Set by option
// Multiple select
const selected = Array.from(select.selectedOptions).map(opt => opt.value);
// Textarea
textarea.value = "isi baru"; // BUKAN innerHTML!// 1. textarea.value, BUKAN textarea.innerHTML
// innerHTML berisi HTML awal, value berisi isi saat ini
textarea.innerHTML; // ❌ Tidak update saat user ketik
textarea.value; // ✅ Selalu isi terkini
// 2. select.value hanya untuk single select
// Untuk multiple select, pakai selectedOptions
// 3. checkbox.value BUKAN status centang!
checkbox.value; // Nilai atribut value (misal "yes")
checkbox.checked; // ✅ Status centang (true/false)
// 4. form.elements bisa duplikat nama (radio buttons)
// Hasilnya RadioNodeList, bukan satu elemen
form.elements.gender; // RadioNodeList kalau ada beberapa radio dengan name="gender"
form.elements.gender.value; // Nilai radio yang terpilih🎯 Challenge
<form name="order">
<input name="product" value="Laptop">
<input name="quantity" type="number" value="1">
<input name="express" type="checkbox">
<select name="payment">
<option value="transfer">Transfer</option>
<option value="cod">COD</option>
<option value="credit">Kartu Kredit</option>
</select>
<button type="submit">Pesan</button>
</form>
<!-- Tulis JavaScript untuk:
1. Baca semua nilai form dan tampilkan sebagai objek
2. Set quantity jadi 3
3. Centang checkbox express
4. Ubah payment ke "credit"
5. Tampilkan ringkasan pesanan di console
-->4.2 Focusing: focus/blur
Focus itu kayak sorotan lampu panggung — hanya satu aktor yang disorot pada satu waktu. Saat input di-focus, dia siap menerima ketikan. Saat blur (kehilangan focus), dia "istirahat".
Event focus dan blur
const input = document.querySelector("input");
input.addEventListener("focus", function() {
console.log("Input aktif — siap diketik");
this.style.borderColor = "blue";
});
input.addEventListener("blur", function() {
console.log("Input tidak aktif");
this.style.borderColor = "";
// Validasi saat blur (user selesai mengisi)
if (this.value.length < 3) {
this.style.borderColor = "red";
}
});Focus/Blur Secara Programmatic
input.focus(); // Beri focus ke input
input.blur(); // Hapus focus dari input
// Berguna untuk: auto-focus saat halaman load, pindah focus setelah validasitabindex — Membuat Elemen Bisa Di-focus
Secara default, hanya elemen interaktif (input, button, a) yang bisa di-focus. Tambahkan tabindex untuk elemen lain:
<!-- tabindex="0" — bisa di-focus, urutan sesuai DOM -->
<div tabindex="0">Bisa di-focus!</div>
<!-- tabindex="-1" — bisa di-focus via JS, tapi TIDAK via Tab key -->
<div tabindex="-1">Focus via JS saja</div>
<!-- tabindex="1+" — urutan Tab custom (HINDARI, bikin bingung) -->const div = document.querySelector("div[tabindex]");
div.addEventListener("focus", () => console.log("Div di-focus!"));
div.focus(); // Bekerja karena ada tabindexfocusin/focusout — Versi yang Bubble
// focus/blur TIDAK bubble!
// focusin/focusout BUBBLE — bisa pakai event delegation
const form = document.querySelector("form");
// ❌ Tidak bekerja (focus tidak bubble)
form.addEventListener("focus", handler);
// ✅ Bekerja (focusin bubble)
form.addEventListener("focusin", function(event) {
event.target.style.background = "lightyellow";
});
form.addEventListener("focusout", function(event) {
event.target.style.background = "";
// Validasi di sini
});document.activeElement
// Elemen mana yang sedang di-focus?
console.log(document.activeElement); // Elemen yang aktif saat ini
// Kalau tidak ada yang di-focus → document.bodyContoh: Validasi Form Real-time
const form = document.querySelector("form");
form.addEventListener("focusout", function(event) {
const input = event.target;
if (input.tagName !== "INPUT") return;
// Validasi berdasarkan atribut
if (input.required && !input.value.trim()) {
showError(input, "Wajib diisi!");
} else if (input.type === "email" && !input.value.includes("@")) {
showError(input, "Email tidak valid!");
} else {
clearError(input);
}
});
function showError(input, message) {
input.classList.add("error");
let errorDiv = input.nextElementSibling;
if (!errorDiv || !errorDiv.classList.contains("error-msg")) {
errorDiv = document.createElement("div");
errorDiv.className = "error-msg";
input.after(errorDiv);
}
errorDiv.textContent = message;
}
function clearError(input) {
input.classList.remove("error");
const errorDiv = input.nextElementSibling;
if (errorDiv?.classList.contains("error-msg")) {
errorDiv.remove();
}
}// 1. focus/blur TIDAK bubble — pakai focusin/focusout untuk delegation
// 2. alert() menghilangkan focus!
input.addEventListener("blur", function() {
// ❌ Ini menyebabkan infinite loop di beberapa browser
// if (!this.value) alert("Isi dulu!");
// ✅ Pakai cara lain untuk notifikasi
if (!this.value) showError(this, "Isi dulu!");
});
// 3. Jangan paksa focus kembali di blur handler
// Bisa bikin user "terjebak" di satu input
// 4. Mobile: focus pada input memunculkan keyboard
// Hati-hati auto-focus di mobile — bisa mengganggu UX🎯 Challenge
<form id="signup">
<input name="name" placeholder="Nama (min 2 huruf)" required>
<input name="email" placeholder="Email" type="email" required>
<input name="password" placeholder="Password (min 6)" type="password" required>
<button type="submit">Daftar</button>
</form>
<!-- Tulis JavaScript untuk:
1. Validasi setiap input saat blur (focusout)
2. Tampilkan pesan error di bawah input yang salah
3. Input yang valid → border hijau
4. Input yang invalid → border merah + pesan error
5. Auto-focus ke input pertama saat halaman load
6. Pakai event delegation (focusin/focusout di form)
-->4.3 Events: change, input, cut, copy, paste
Kalau focus/blur itu tentang kapan user mulai/selesai mengisi, event di sub-bab ini tentang apa yang terjadi SELAMA user mengisi — setiap ketikan, setiap perubahan.
Event "input" — Setiap Perubahan
const input = document.querySelector("input");
input.addEventListener("input", function() {
// Fire SETIAP kali nilai berubah (ketik, paste, delete, dll)
console.log("Nilai sekarang:", this.value);
});input event fire saat:
- User mengetik karakter
- User menghapus karakter (backspace/delete)
- User paste teks
- User cut teks
- Autocomplete browser mengisi
Event "change" — Setelah Selesai
// Untuk text input: fire saat BLUR (setelah user selesai edit)
input.addEventListener("change", function() {
console.log("Nilai final:", this.value);
});
// Untuk select, checkbox, radio: fire LANGSUNG saat berubah
select.addEventListener("change", function() {
console.log("Pilihan baru:", this.value); // Langsung!
});
checkbox.addEventListener("change", function() {
console.log("Checked:", this.checked); // Langsung!
});Perbedaan input vs change
| input | change | |
|---|---|---|
| Text input | Setiap ketikan | Saat blur (selesai edit) |
| Select | — | Saat pilihan berubah |
| Checkbox | — | Saat di-klik |
| Kapan pakai | Live search, real-time validation | Submit-style validation |
Event cut, copy, paste
input.addEventListener("cut", function(event) {
console.log("Cut:", event.clipboardData.getData("text/plain"));
});
input.addEventListener("copy", function(event) {
console.log("Copy:", window.getSelection().toString());
});
input.addEventListener("paste", function(event) {
// Baca data yang di-paste
const pasted = event.clipboardData.getData("text/plain");
console.log("Paste:", pasted);
// Bisa dicegah!
if (pasted.includes("<script>")) {
event.preventDefault();
alert("Paste tidak diizinkan!");
}
});Contoh: Live Search
const searchInput = document.getElementById("search");
const resultsList = document.getElementById("results");
const items = ["Apel", "Anggur", "Alpukat", "Bayam", "Brokoli", "Cabai"];
searchInput.addEventListener("input", function() {
const query = this.value.toLowerCase();
const filtered = items.filter(item =>
item.toLowerCase().includes(query)
);
resultsList.innerHTML = filtered
.map(item => `<li>${item}</li>`)
.join("");
});Contoh: Character Counter
const textarea = document.querySelector("textarea");
const counter = document.querySelector(".counter");
const maxLength = 280;
textarea.addEventListener("input", function() {
const remaining = maxLength - this.value.length;
counter.textContent = `${remaining} karakter tersisa`;
counter.style.color = remaining < 20 ? "red" : "";
if (remaining < 0) {
this.value = this.value.slice(0, maxLength); // Potong
}
});Contoh: Format Input (Hanya Angka)
const phoneInput = document.getElementById("phone");
phoneInput.addEventListener("input", function() {
// Hapus semua non-digit
this.value = this.value.replace(/\D/g, "");
});
// Atau cegah input non-angka
phoneInput.addEventListener("keydown", function(event) {
// Izinkan: backspace, delete, tab, arrow keys
const allowed = ["Backspace", "Delete", "Tab", "ArrowLeft", "ArrowRight"];
if (allowed.includes(event.key)) return;
// Cegah non-angka
if (!/\d/.test(event.key)) {
event.preventDefault();
}
});// 1. "input" event: event.preventDefault() TIDAK mencegah input!
// Karakter sudah masuk saat event fire
// Untuk mencegah input → pakai "keydown" + preventDefault
// 2. "change" pada text input HANYA fire saat blur
// Kalau butuh real-time → pakai "input"
// 3. clipboardData hanya tersedia di event handler
// Tidak bisa diakses di luar cut/copy/paste event
// 4. input event TIDAK fire saat value diubah via JavaScript
input.value = "baru"; // TIDAK trigger "input" event!
// Kalau perlu trigger manual: input.dispatchEvent(new Event("input"))🎯 Challenge
<div>
<input id="search" placeholder="Cari buah...">
<ul id="fruit-list">
<li>Apel</li><li>Anggur</li><li>Alpukat</li>
<li>Mangga</li><li>Melon</li><li>Manggis</li>
<li>Jeruk</li><li>Jambu</li>
</ul>
</div>
<!-- Tulis JavaScript untuk:
1. Ketik di search → filter list secara real-time (pakai "input" event)
2. Highlight huruf yang cocok dengan <mark> tag
3. Kalau tidak ada hasil → tampilkan "Tidak ditemukan"
4. Kosongkan search → tampilkan semua
-->4.4 Form: Event dan Method Submit
Submit form itu kayak mengirim surat — kamu isi formulir, lalu tekan "Kirim". Tapi sebelum dikirim, kamu bisa cek ulang (validasi) dan batalkan kalau ada yang salah.
Event "submit"
const form = document.querySelector("form");
form.addEventListener("submit", function(event) {
event.preventDefault(); // Cegah reload halaman!
// Validasi
const name = form.elements.name.value;
if (!name) {
alert("Nama wajib diisi!");
return;
}
// Kirim data (misal via fetch)
console.log("Mengirim:", name);
});Cara Submit Terjadi
Submit terpicu oleh:
- Klik
<button type="submit">atau<input type="submit"> - Tekan Enter di dalam input text
// ⚠️ Enter di input → submit form!
// Ini behavior bawaan browser
// Kalau tidak mau → preventDefault di submit eventMethod form.submit()
// Submit form via JavaScript (TANPA trigger event "submit"!)
form.submit();
// ⚠️ form.submit() TIDAK trigger event "submit"
// Jadi validasi di event handler TIDAK jalan!
// Kalau perlu validasi → panggil manual sebelum submit()Contoh: Form dengan Fetch (AJAX)
const form = document.getElementById("contact-form");
form.addEventListener("submit", async function(event) {
event.preventDefault();
const submitBtn = form.querySelector('button[type="submit"]');
submitBtn.disabled = true;
submitBtn.textContent = "Mengirim...";
try {
// Kumpulkan data form
const formData = new FormData(form);
// Kirim ke server
const response = await fetch("/api/contact", {
method: "POST",
body: formData
});
if (response.ok) {
alert("Berhasil dikirim!");
form.reset(); // Kosongkan form
} else {
alert("Gagal mengirim!");
}
} catch (error) {
alert("Error: " + error.message);
} finally {
submitBtn.disabled = false;
submitBtn.textContent = "Kirim";
}
});FormData — Kumpulkan Semua Data Form
const form = document.querySelector("form");
const formData = new FormData(form);
// Baca data
console.log(formData.get("username")); // Nilai input name="username"
console.log(formData.get("email")); // Nilai input name="email"
// Iterasi semua
for (const [key, value] of formData) {
console.log(`${key}: ${value}`);
}
// Convert ke object biasa
const data = Object.fromEntries(formData);
console.log(data); // { username: "Budi", email: "budi@mail.com", ... }
// Tambah data extra
formData.append("timestamp", Date.now());Validasi Lengkap Sebelum Submit
form.addEventListener("submit", function(event) {
event.preventDefault();
const errors = [];
const name = form.elements.name.value.trim();
const email = form.elements.email.value.trim();
const password = form.elements.password.value;
if (!name) errors.push("Nama wajib diisi");
if (name.length < 2) errors.push("Nama minimal 2 karakter");
if (!email) errors.push("Email wajib diisi");
if (!email.includes("@")) errors.push("Email tidak valid");
if (password.length < 6) errors.push("Password minimal 6 karakter");
if (errors.length > 0) {
showErrors(errors);
return;
}
// Semua valid → kirim
submitForm(new FormData(form));
});
function showErrors(errors) {
const errorDiv = document.getElementById("errors");
errorDiv.innerHTML = errors.map(e => `<p class="error">${e}</p>`).join("");
}form.reset()
form.reset(); // Kembalikan semua input ke nilai DEFAULT (bukan kosong!)
// Nilai default = yang ada di atribut value HTML
// Event "reset" juga bisa ditangkap
form.addEventListener("reset", function(event) {
// Bisa dicegah
if (!confirm("Yakin mau reset?")) {
event.preventDefault();
}
});// 1. SELALU preventDefault() di submit handler
// Tanpa ini → halaman reload, data hilang
// 2. form.submit() TIDAK trigger "submit" event
// Validasi tidak jalan! Panggil validasi manual
// 3. Button tanpa type di dalam form = type="submit"!
// <button>Klik</button> → submit form!
// Solusi: <button type="button">Klik</button>
// 4. Enter di input → submit form (behavior bawaan)
// Bisa bikin user tidak sengaja submit
// 5. FormData hanya ambil input yang punya atribut "name"
// <input value="test"> → TIDAK terambil! (tidak ada name)
// <input name="test" value="test"> → ✅ terambil🎯 Challenge
<form id="registration">
<input name="fullname" placeholder="Nama Lengkap">
<input name="email" type="email" placeholder="Email">
<input name="password" type="password" placeholder="Password">
<input name="confirm" type="password" placeholder="Konfirmasi Password">
<select name="role">
<option value="">-- Pilih Role --</option>
<option value="student">Student</option>
<option value="teacher">Teacher</option>
</select>
<button type="submit">Daftar</button>
<button type="reset">Reset</button>
</form>
<div id="output"></div>
<!-- Tulis JavaScript untuk:
1. Validasi saat submit:
- Nama minimal 3 karakter
- Email harus valid (ada @)
- Password minimal 8 karakter
- Confirm harus sama dengan password
- Role harus dipilih
2. Tampilkan semua error sekaligus (jangan satu-satu)
3. Kalau valid → tampilkan data di #output (JANGAN kirim ke server)
4. Pakai FormData untuk kumpulkan data
5. Konfirmasi sebelum reset
-->Ringkasan Bab 4
| Sub-bab | Inti |
|---|---|
| Properti Form | form.elements, .value, .checked, .selectedOptions |
| Focus/Blur | Validasi saat blur, focusin/focusout untuk delegation |
| input/change | input = real-time, change = setelah selesai |
| Submit | preventDefault, FormData, validasi sebelum kirim |
Prinsip utama:
- Selalu
preventDefault()di submit handler - Validasi: real-time (input) + final (submit)
FormDatauntuk kumpulkan data form- Button di dalam form = submit by default (tambahkan
type="button"kalau bukan submit) focusin/focusoutuntuk event delegation (bukan focus/blur)
Next: Bab 5 — Document & Resource Loading (DOMContentLoaded, script loading, resource events)
Sudah paham materi ini?
Tandai sebagai selesai untuk melacak progress-mu.