Kodeloka
  1. Kodeloka
  2. Kelas JavaScript
  3. Materi

4 · DOM & Peramban · Modul 13 dari 22

Manipulasi DOM yang Aman, Siklus Event, dan Event Delegation

Memilih dan memodifikasi elemen DOM menggunakan API modern (querySelector, classList, dataset, createElement, replaceChildren), memahami perambatan event (bubbling & capturing), serta mengoptimalkan kinerja dengan pola Event Delegation.

1. Memilih Elemen di Pohon DOM (querySelector & querySelectorAll)

Ketika peramban mengurai HTML menjadi pohon **DOM (Document Object Model)**, JavaScript dapat mencari elemen menggunakan selektor CSS apa pun melalui dua metode utama:

  1. document.querySelector(selektor): Mengembalikan satu elemen pertama yang cocok dengan selektor CSS tersebut, atau null jika tidak ditemukan.
  2. document.querySelectorAll(selektor): Mengembalikan NodeList statis berisi seluruh elemen yang cocok (dapat langsung diiterasi dengan for...of, atau diubah menjadi Array dengan Array.from(nodeList) jika Anda butuh .map() / .filter()).
// Mencari satu elemen berdasarkan ID atau atribut
const formulir = document.querySelector("#form-kontak");
const tombolAktif = document.querySelector('button[aria-current="page"]');

// Mencari di dalam sub-pohon elemen tertentu (lebih cepat & terfokus!)
const semuaInputWajib = formulir.querySelectorAll("input[required], textarea[required]");

for (const input of semuaInputWajib) {
  console.log(input.name);
}

2. Membaca dan Mengubah Elemen, Kelas, dan Atribut data-*

A. Mengubah Teks: Gunakan textContent, Hindari innerHTML!

  • elemen.textContent = teksBaru: Mengatur isi teks murni secara cepat dan 100% aman dari celah injeksi skrip (XSS) karena karakter <script> akan diperlakukan sebagai teks biasa, bukan dijalankan sebagai tag HTML!
  • elemen.innerHTML = htmlString: Mengurai ulang string sebagai tag HTML. Jangan pernah memasukkan data yang berasal dari pengguna atau URL ke dalam innerHTML!

B. Mengelola Kelas CSS dengan classList dan Data dengan dataset

const kartu = document.querySelector(".kartu-modul");

// 1. Mengelola class tanpa merusak class lain yang sudah ada di elemen tersebut
kartu.classList.add("kartu--terpilih");
kartu.classList.remove("kartu--redup");
kartu.classList.toggle("kartu--terbuka", statusTerbuka); // Parameter ke-2 memaksa tambah/hapus berdasarkan boolean!

// 2. Membaca dan menulis atribut data-* (mis. data-modul-id="10" <-> dataset.modulId)
const idModul = Number(kartu.dataset.modulId);
kartu.dataset.statusBaca = "selesai"; // Menghasilkan atribut data-status-baca="selesai" di HTML

3. Membuat, Menyisipkan, dan Mengganti Elemen Secara Efisien

Untuk merakit elemen baru secara aman dan cepat:

function buatItemModul({ id, judul, tingkat }) {
  const li = document.createElement("li");
  li.className = "item-modul";
  li.dataset.id = String(id);

  const tautan = document.createElement("a");
  tautan.href = `#modul-${id}`;
  tautan.textContent = judul;

  const lencana = document.createElement("span");
  lencana.className = "lencana";
  lencana.textContent = tingkat;

  // .append() dapat menerima beberapa elemen sekaligus maupun teks biasa!
  li.append(tautan, " ", lencana);
  return li;
}

// Merender daftar 50 item sekaligus ke dalam <ul> dengan satu kali pembaruan DOM:
const wadahDaftar = document.querySelector("#daftar-modul");
const elemenBaru = daftarData.map(buatItemModul);

// replaceChildren() mengosongkan isi lama sekaligus memasukkan seluruh elemen baru dalam satu langkah!
wadahDaftar.replaceChildren(...elemenBaru);
💡 Keunggulan elemen.replaceChildren(...anakBaru)

Daripada mengosongkan daftar dengan wadah.innerHTML = "" lalu memanggil appendChild() berulang-ulang di dalam perulangan (yang memicu perhitungan tata letak berulang kali), wadah.replaceChildren(...elemenBaru) (status Baseline Widely Available) mengganti seluruh anak elemen secara atomik dan jauh lebih rapi!

4. Menangani Event, Event Bubbling, dan Pola Event Delegation

Gunakan elemen.addEventListener(tipeEvent, fungsiHandler) untuk merespons interaksi pengguna (seperti "click", "submit", "input", "keydown").

Bagaimana Event Merambat di Pohon DOM? (Capturing & Bubbling)

Ketika pengguna mengeklik sebuah <button> yang berada di dalam <li> → <ul> → <main> → <body>:

  1. Fase Capturing (Turun): Event berjalan turun dari window → document → <body> → <main> → <ul> → <li>.
  2. Fase Target: Event tiba di <button> yang diklik.
  3. Fase Bubbling (Menggelembung Naik — Bawaan): Event merambat naik kembali ke atas melalui <li> → <ul> → <main> → <body> → document!
          [ document ]
            │      ▲
  1. Capture│      │3. Bubble (Secara bawaan addEventListener mendengarkan di fase ini!)
            ▼      │
           [  <ul>  ]  <-- Pasang SATU event listener di sini (Event Delegation!)
            │      ▲
            ▼      │
           [  <li>  ]
            │      ▲
            ▼      │
         [ <button> ]  <-- 2. Target yang diklik pengguna

Kekuatan Event Delegation dengan event.target.closest()

Bayangkan Anda memiliki daftar <ul> yang berisi 200 tombol hapus item, dan item baru dapat ditambahkan secara dinamis kapan saja.

  • Cara Buruk: Memasang 200 addEventListener("click", ...) pada masing-masing tombol (boros memori, dan tombol yang baru ditambahkan belakangan tidak akan memiliki event listener!).
  • **Cara Terbaik (Event Delegation): Pasang cukup SATU addEventListener pada elemen induk <ul>, lalu gunakan event.target.closest("button.tombol-hapus")** untuk mendeteksi tombol mana yang baru saja diklik!
const daftarTugas = document.querySelector("#daftar-tugas");

daftarTugas.addEventListener("click", (event) => {
  // .closest() mencari ke diri sendiri atau ke induk terdekat jika yang terklik adalah <svg> di dalam <button>!
  const tombolHapus = event.target.closest("button[data-aksi='hapus']");

  // Jika klik terjadi di luar tombol hapus, abaikan
  if (!tombolHapus || !daftarTugas.contains(tombolHapus)) return;

  const barisItem = tombolHapus.closest("li");
  const idTugas = barisItem?.dataset.id;

  console.log(`Menghapus tugas #${idTugas}`);
  barisItem?.remove();
});
⚠️ Mengapa Wajib Menggunakan event.target.closest("button") dan Bukan event.target Saja?

Di dalam <button>, sering kali terdapat elemen anak seperti <span> atau ikon <svg>. Saat pengguna mengeklik tepat di atas gambar ikon <svg>, maka event.target adalah elemen <svg> tersebut, bukan <button>! Dengan event.target.closest("button"), pencarian akan naik dari <svg> ke elemen <button> pembungkusnya secara akurat.

Latihan Praktik: Daftar Catatan Belajar Interaktif dengan Event Delegation

Buatlah berkas dom-catatan.html dan catatan.js:

  1. Buat formulir <form id="form-catatan"> dengan input teks untuk menambah catatan baru ke dalam <ul id="daftar-catatan"> (gunakan event.preventDefault() pada event "submit" agar halaman tidak memuat ulang).
  2. Setiap item <li> dibuat menggunakan document.createElement dan textContent, dilengkapi tombol Tandai Selesai (data-aksi="selesai") dan tombol Hapus (data-aksi="hapus").
  3. Gunakan satu event listener pada #daftar-catatan (Event Delegation) untuk menangani kedua tombol aksi tersebut.