Bab 4: Forms & Controls

3 menit baca

Form adalah cara utama user mengirim data. Pahami cara mengakses, memvalidasi, dan mengelola form dengan JavaScript.


4.1 Properti dan Method Form

💡Analogi

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

javascript
// 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

html
<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>
javascript
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

html
<form name="settings">
  <fieldset name="appearance">
    <input name="theme" value="dark">
    <input name="fontSize" value="16">
  </fieldset>
</form>
javascript
const form = document.forms.settings;
const fieldset = form.elements.appearance;

// Fieldset juga punya .elements!
console.log(fieldset.elements.theme.value); // "dark"

Backreference: element.form

javascript
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-nya

Input Types dan Nilainya

javascript
// 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!
⚠️Jebakan!
javascript
// 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

html
<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

💡Analogi

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

javascript
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

javascript
input.focus(); // Beri focus ke input
input.blur();  // Hapus focus dari input

// Berguna untuk: auto-focus saat halaman load, pindah focus setelah validasi

tabindex — Membuat Elemen Bisa Di-focus

Secara default, hanya elemen interaktif (input, button, a) yang bisa di-focus. Tambahkan tabindex untuk elemen lain:

html
<!-- 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) -->
javascript
const div = document.querySelector("div[tabindex]");
div.addEventListener("focus", () => console.log("Div di-focus!"));
div.focus(); // Bekerja karena ada tabindex

focusin/focusout — Versi yang Bubble

javascript
// 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

javascript
// Elemen mana yang sedang di-focus?
console.log(document.activeElement); // Elemen yang aktif saat ini
// Kalau tidak ada yang di-focus → document.body

Contoh: Validasi Form Real-time

javascript
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();
  }
}
⚠️Jebakan!
javascript
// 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

html
<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

💡Analogi

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

javascript
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

javascript
// 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

inputchange
Text inputSetiap ketikanSaat blur (selesai edit)
SelectSaat pilihan berubah
CheckboxSaat di-klik
Kapan pakaiLive search, real-time validationSubmit-style validation

Event cut, copy, paste

javascript
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!");
  }
});
javascript
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

javascript
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)

javascript
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();
  }
});
⚠️Jebakan!
javascript
// 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

html
<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

💡Analogi

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"

javascript
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:

  1. Klik <button type="submit"> atau <input type="submit">
  2. Tekan Enter di dalam input text
javascript
// ⚠️ Enter di input → submit form!
// Ini behavior bawaan browser
// Kalau tidak mau → preventDefault di submit event

Method form.submit()

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

javascript
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

javascript
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

javascript
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()

javascript
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();
  }
});
⚠️Jebakan!
javascript
// 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

html
<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-babInti
Properti Formform.elements, .value, .checked, .selectedOptions
Focus/BlurValidasi saat blur, focusin/focusout untuk delegation
input/changeinput = real-time, change = setelah selesai
SubmitpreventDefault, FormData, validasi sebelum kirim

Prinsip utama:

  • Selalu preventDefault() di submit handler
  • Validasi: real-time (input) + final (submit)
  • FormData untuk kumpulkan data form
  • Button di dalam form = submit by default (tambahkan type="button" kalau bukan submit)
  • focusin/focusout untuk 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.