menengahdomeventMenengah3 mnt baca

DOM: Event Delegation

Teknik satu listener untuk banyak elemen dengan memanfaatkan event bubbling. Solusi wajib untuk list dan tabel dinamis: hemat memori, otomatis berlaku untuk elemen yang dibuat belakangan, plus pemahaman bubbling vs capturing.

Masalah: Seribu Listener untuk Seribu Item

Pendekatan naif menangani klik tiap item list adalah memasang listener ke masing-masing:

javascript
// BURUK untuk 1000 item: 1000 listener di memori!
document.querySelectorAll("li").forEach(li => {
  li.addEventListener("click", hapusItem);
});

Selain boros, ada bug fatal: item yang ditambahkan BELAKANGAN (mis. tugas baru di todo list) tidak punya listener sama sekali karena dipasang sebelum ia lahir. Solusinya adalah membalik logika: pasang SATU listener di parent, dan biarkan event "menggelembung" naik dari anak ke parent.

Bubbling: Cara Event Berjalan

Saat user mengklik <li> di dalam <ul>, browser tidak hanya memberi tahu <li> itu. Event berjalan dalam tiga fase:

  1. Capturing: turun dari window ke elemen target.
  2. Target: tiba di elemen yang diklik.
  3. Bubbling: naik kembali dari target ke window.

Secara default listener bekerja di fase bubbling. Artinya klik pada <li> akan "terdengar" juga oleh <ul>, <body>, sampai document. Inilah yang dimanfaatkan delegation.

javascript
// e.target = elemen yang DIKLIK (li)
// e.currentTarget = elemen pemilik LISTENER (ul)
document.getElementById("daftar").addEventListener("click", (e) => {
  console.log("Diklik:", e.target.tagName);
  console.log("Listener di:", e.currentTarget.id);
});

Solusi: Satu Listener di Parent

Contoh sederhana memakai playground modul ini (ada <ul id="daftar"> berisi li A dan B, plus tombol tambah):

javascript
const ul = document.getElementById("daftar");

ul.addEventListener("click", (e) => {
  if (e.target.tagName === "LI") {
    e.target.remove(); // klik item mana pun -> hapus
  }
});

Pengecekan e.target.tagName === "LI" penting: tanpa itu, klik di area kosong <ul> ikut terpicu. Listener ini otomatis berlaku untuk item A, B, MAUPUN item baru yang ditambahkan tombol "Tambah", karena event mereka semua menggelembung ke <ul> yang sama.

Contoh realistis: todo list dengan tombol hapus per item, memakai closest agar klik pada ikon di dalam tombol tetap terdeteksi.

javascript
const ul = document.getElementById("daftar");

ul.addEventListener("click", (e) => {
  const tombolHapus = e.target.closest(".hapus");
  if (tombolHapus) {
    tombolHapus.closest("li").remove();
    return;
  }
  const item = e.target.closest("li");
  if (item) item.classList.toggle("selesai");
});

Satu listener menangani dua aksi berbeda (hapus dan toggle selesai) untuk item yang jumlahnya tak terbatas, termasuk yang belum lahir saat halaman dimuat.

Kapan Delegation Wajib Dipakai?

  • List/tabel dinamis: todo, chat, hasil pencarian, keranjang belanja.
  • Elemen yang dibuat setelah halaman dimuat (dari API, dari input user).
  • Jumlah elemen besar (ratusan/ribuan): satu listener jauh lebih hemat memori.

Kapan TIDAK perlu? Form kecil dengan segelintir tombol statis: pasang listener langsung saja, lebih mudah dibaca.

stopPropagation dan Capturing

Kadang kamu ingin menghentikan gelembung agar parent tidak ikut bereaksi:

javascript
document.getElementById("modal").addEventListener("click", (e) => {
  e.stopPropagation(); // klik di dalam modal tidak menutup modal
});
document.body.addEventListener("click", tutupModal);

Dan kalau butuh menangkap event di fase TURUN (capturing), pakai opsi ketiga:

javascript
el.addEventListener("click", handler, true); // true = fase capturing

Jebakan Umum

  • Lupa memfilter e.target: listener parent bereaksi terhadap SEMUA klik di dalamnya, termasuk klik di padding kosong. Selalu cek dengan closest() atau tagName.
  • e.target bisa berupa elemen anak TERDALAM (mis. <span> di dalam <button>). Jangan bandingkan langsung dengan elemen yang diharapkan; gunakan e.target.closest("button").
  • Memakai delegation untuk event yang TIDAK menggelembung (focus, blur, scroll pada elemen). Untuk focus/blur pakai versi gelembungnya: focusin/focusout.

Ringkasan

Event delegation = satu listener di parent + manfaatkan bubbling + filter via e.target/closest(). Pola ini wajib untuk konten dinamis: hemat memori, otomatis mencakup elemen baru, dan menjadi standar industri untuk list, tabel, dan menu. Ingat bedanya: e.target adalah yang diklik, e.currentTarget adalah pemilik listener.

Live Playground
export default function App(): JSX.Element {
  return <h1>Hello world</h1>
}

Edit kode di kiri, preview kanan ter-update otomatis.

Tantangan

Todo Delegasi

Di #daftar yang sudah ada, tambahkan 3 item via JS. Pasang SATU delegation listener di #daftar: klik item men-toggle class "selesai" (bukan hapus). Delegation adalah pola yang dipakai aplikasi chat dan feed nyata untuk menangani ribuan pesan tanpa memasang ribuan listener: satu listener di wadah cukup untuk semuanya, termasuk pesan yang baru masuk.

<!doctype html>
<html>
<head>
<meta charset="utf-8" />

</head>
<body>
<h1>Halo JS</h1>
<p>Buka console preview untuk melihat output.</p>
<script src="index.js"></script>
</body>
</html>