menengahdombrowserMenengah3 mnt baca

DOM: Navigasi (Traversal)

Bergerak di pohon DOM tanpa id: parentElement, children, sibling, closest, dan querySelector ber-scoped. Skill wajib saat menangani event di dalam kartu, baris tabel, atau komponen yang digandakan.

Kenapa Tidak Selalu Pakai getElementById?

getElementById butuh id unik. Tapi bagaimana kalau kamu punya 50 kartu produk dengan struktur identik? Memberi 50 id berbeda tidak praktis, dan id yang digandakan justru melanggar aturan HTML. Di sinilah traversal berperan: bergerak RELATIF dari elemen yang sudah kamu pegang. "Dari tombol yang diklik ini, naik ke kartu pemiliknya, lalu ambil harga di dalamnya." Itulah pola yang dipakai semua list dinamis di dunia nyata.

Keluarga Elemen: Naik, Turun, Menyamping

javascript
el.parentElement;           // satu tingkat ke atas
el.children;                // semua anak (HTMLCollection)
el.firstElementChild;       // anak pertama
el.lastElementChild;        // anak terakhir
el.nextElementSibling;      // saudara kandung berikutnya
el.previousElementSibling;  // saudara kandung sebelumnya

Versi ber-akhiran Element sengaja mengabaikan text node (spasi dan enter di HTML). Versi lama tanpa kata Element (childNodes, nextSibling) ikut menghitung text node dan sering membingungkan pemula.

Contoh sederhana memakai playground modul ini (ada <div id="kartu"> berisi h3, p.harga, dan button#tombol):

javascript
const tombol = document.getElementById("tombol");
const kartu = tombol.parentElement; // naik ke #kartu
console.log("Parent:", kartu.id);   // "kartu"
console.log("Anak pertama:", kartu.firstElementChild.tagName); // "H3"
console.log("Harga:", kartu.querySelector(".harga").textContent); // "Rp15000"

closest: Bintang Utamanya

closest(selector) naik ke atas pohon sampai menemukan ancestor yang cocok dengan selector, termasuk elemen itu sendiri. Ini method traversal paling berguna dalam praktik.

javascript
tombol.addEventListener("click", (e) => {
  const baris = e.target.closest("tr"); // <tr> terdekat ke atas
  baris.remove(); // hapus baris tabel tempat tombol berada
});

Rantai parentElement patah saat struktur HTML berubah; closest("tr") tetap bekerja selama tombol masih di dalam baris.

Contoh realistis: banyak kartu produk, satu handler tahu kartu mana yang diklik.

javascript
document.getElementById("katalog").addEventListener("click", (e) => {
  const btnBeli = e.target.closest(".btn-beli");
  if (!btnBeli) return; // klik bukan pada tombol beli, abaikan

  const kartu = btnBeli.closest(".kartu-produk"); // naik ke kartu pemilik
  const nama = kartu.querySelector(".nama").textContent;   // turun ke nama
  const harga = kartu.querySelector(".harga").textContent;  // turun ke harga
  console.log(`Membeli: ${nama} seharga ${harga}`);
});

Perhatikan kombinasi lengkapnya: delegation (listener di parent) + closest (naik ke konteks) + querySelector ber-scoped (turun mengambil data). Tiga teknik tiga modul terakhir bersatu di sini, dan inilah cara aplikasi nyata menangani list dinamis.

Mencari ke Bawah Secara Scoped

querySelector bisa dipanggil dari elemen mana pun, bukan hanya document. Pencarian lalu terbatas di DALAM elemen itu:

javascript
const kartu = document.getElementById("kartu");
kartu.querySelector(".harga"); // hanya mencari di dalam #kartu

Ini mencegah bug klasik: halaman punya banyak .harga, tapi kamu hanya mau harga di kartu yang sedang diproses.

Contoh realistis kedua: validasi satu baris form dinamis.

javascript
function validasiBaris(baris) {
  const input = baris.querySelector("input.jumlah");
  const pesan = baris.querySelector(".error-msg");
  const ok = Number(input.value) > 0;
  pesan.textContent = ok ? "" : "Jumlah harus lebih dari 0";
  input.classList.toggle("error", !ok);
  return ok;
}

// pakai untuk semua baris
document.querySelectorAll("#tabel-belanja tr").forEach(validasiBaris);

Jebakan Umum

  • children adalah HTMLCollection yang "hidup": ia berubah mengikuti DOM. Loop sambil menghapus anak bisa melompati elemen. Solusi: konversi dulu ke array ([...el.children]) atau loop mundur.
  • nextElementSibling bisa null di elemen terakhir. Selalu cek sebelum dipakai.
  • closest me-return null kalau tidak ada ancestor yang cocok. Panggil .querySelector di hasilnya tanpa cek = TypeError. Pola aman: const k = e.target.closest(".kartu"); if (!k) return;.
  • Mengandalkan struktur HTML yang kaku (parentElement.children[2]). Rapuh: satu perubahan markup merusak kode. Pilih closest + querySelector ber-scoped yang tahan perubahan.

Ringkasan

Traversal adalah navigasi relatif di pohon DOM: naik dengan parentElement dan closest(), turun dengan children dan querySelector ber-scoped, menyamping dengan sibling. closest() adalah yang paling berharga karena tahan terhadap perubahan struktur HTML. Kuasai kombinasi delegation + closest + scoped querySelector, karena itulah pola standar menangani komponen yang digandakan seperti kartu produk, baris tabel, dan item chat.

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

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

Tantangan

Tombol Info Kartu

Saat #tombol diklik, gunakan closest untuk dapat #kartu, lalu tampilkan teks h3 dan .harga dalam alert/console: "Produk A - Rp15000". Pola "dari tombol naik ke kartu lalu ambil datanya" adalah cara standar tombol Beli, Hapus, dan Edit bekerja di toko online dan dashboard admin: satu handler melayani ratusan kartu identik tanpa satu pun id unik.

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