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
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 sebelumnyaVersi 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):
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.
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.
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:
const kartu = document.getElementById("kartu");
kartu.querySelector(".harga"); // hanya mencari di dalam #kartuIni 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.
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
childrenadalah HTMLCollection yang "hidup": ia berubah mengikuti DOM. Loop sambil menghapus anak bisa melompati elemen. Solusi: konversi dulu ke array ([...el.children]) atau loop mundur.nextElementSiblingbisa null di elemen terakhir. Selalu cek sebelum dipakai.closestme-return null kalau tidak ada ancestor yang cocok. Panggil.querySelectordi 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. Pilihclosest+querySelectorber-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.
export default function App(): JSX.Element { return <h1>Hello world</h1> }
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>