Sampai chapter ini, semua elemen yang kita pegang berasal dari HTML yang ditulis lebih dulu. Padahal daftar keranjang Budi tidak bekerja begitu: item baru datang dari data — hasil pilihan pengguna, atau kiriman server — dan halaman harus menambahkan sendiri baris <li> untuk setiap item itu. Kebutuhan inilah yang dijawab babak terakhir pekerjaan DOM: membuat node baru dan menempelkannya ke posisi yang tepat.
Chapter ini membahas sepasang langkah yang selalu terpisah — membuat node, lalu menempelkannya — beserta tiga cara menempel: append di akhir, prepend di awal, dan insertBefore di posisi mana pun. Di ujung chapter, janji dari chapter Selecting Elements dipenuhi: membuktikan perbedaan koleksi live dan static dengan kode yang benar-benar berjalan.
Membuat vs Menempel: Dua Langkah Terpisah #
document.createElement("li") membuat elemen li yang utuh — propertinya bisa diisi, class-nya bisa diatur, teksnya bisa ditulis — tetapi elemen itu belum berada di halaman mana pun. Ia melayang bebas (detached) di memori, tidak dirender, dan tidak ada di pohon dokumen sampai kita menempelkannya secara eksplisit ke sebuah induk.
Pemisahan dua langkah ini memberi kendali penuh: kita bisa menyiapkan elemen yang rumit — banyak class, banyak anak — tanpa membuat halaman berkedip karena berubah sedikit demi sedikit. Penempelan dilakukan method-method berikut:
| Method | Posisi penempelan |
|---|---|
induk.append(node) | anak terakhir dari induk |
induk.prepend(node) | anak pertama dari induk |
induk.insertBefore(node, acuan) | tepat sebelum elemen acuan |
Berkas berikut memakai ketiganya pada keranjang yang berisi satu item awal:
// bangun-daftar.js — membuat elemen baru dan menempelkannya ke dokumen
const daftar = document.querySelector("#keranjang");
// 1. dua koleksi dibuat SEBELUM perubahan (untuk bukti live vs static)
const statis = document.querySelectorAll("#keranjang li");
const hidup = document.getElementsByTagName("li");
console.log(statis.length); // 1
console.log(hidup.length); // 1
// 2. buat elemen baru, beri isi
const item = document.createElement("li");
item.textContent = "Teh Melati 100g";
// 3. append: menambah di akhir
daftar.append(item);
console.log(statis.length); // 1 — snapshot saat dibuat
console.log(hidup.length); // 2 — koleksi live mengikuti dokumen
// 4. prepend: menambah di awal
const pembuka = document.createElement("li");
pembuka.textContent = "— Belanja pagi —";
daftar.prepend(pembuka);
// 5. insertBefore: sisip sebelum elemen acuan
const sisipan = document.createElement("li");
sisipan.textContent = "Gula Aren";
daftar.insertBefore(sisipan, daftar.lastElementChild);
console.log(Array.from(daftar.children).map((li) => li.textContent).join(" | "));
// 6. createTextNode: menempel teks tanpa markup
const catatan = document.createElement("p");
catatan.append(document.createTextNode("3 item ditambahkan hari ini"));
document.body.append(catatan);
// 7. insertAdjacentHTML: sisip markup pada posisi tertentu
daftar.insertAdjacentHTML("afterend", '<p id="total">Total item: 4</p>');
console.log(document.querySelector("#total").textContent);Output yang Dihasilkan (console browser):
1
1
1
2
— Belanja pagi — | Kopi Gayo 250g | Gula Aren | Teh Melati 100g
Total item: 4Keluaran dicatat dari eksekusi nyata di lingkungan jsdom dan identik dengan console browser. Mari kita bedah:
- Baris 6–9 (dua koleksi sebelum perubahan):
statisdanhidupdibuat saat keranjang masih berisi satu item, sehingga keduanya sama-sama bernilai1. Pembuatan koleksi lebih dulu ini syarat percobaan live vs static yang jujur. - Baris 12–13 (
createElement+textContent): elemenlibaru dibuat dengan tag persis seperti yang dikirim, lalu diisi teks. Pada titik ini elemen belum terlihat di halaman sama sekali. - Baris 16–18 (
appenddan bukti live vs static): setelahdaftar.append(item), angka kedua koleksi terbelah:statis.lengthtetap1karena NodeList dariquerySelectorAlladalah cuplikan saat dibuat;hidup.lengthmenjadi2karena HTMLCollection darigetElementsByTagNameselalu mencerminkan kondisi dokumen terkini. - Baris 21–28 (
prependdaninsertBefore):pembukaditempel di posisi anak pertama, sedangkansisipanditempel tepat sebelumdaftar.lastElementChild— yaitu sebelum “Teh Melati 100g”. Argumen keduainsertBeforeadalah elemen acuan, bukan indeks. - Baris 30 (
Array.fromataschildren):childrenberupa HTMLCollection yang tidak punyamap, jadi kita membungkusnya denganArray.fromdari section Collections sebelum memetakan isi tiap item. Urutan keluaran menegaskan posisi akhir semua elemen. - Baris 33–35 (
createTextNode): node teks eksplisit dibuat dengandocument.createTextNodelalu ditempelkan ke<p>— hasilnya sama dengantextContent, tetapi cara ini berguna saat kita menyusun campuran node dan teks secara terpisah.<p>kemudian ditempel kedocument.body. - Baris 38–39 (
insertAdjacentHTML): string HTML di-parsing dan disisipkan tepat setelah elemendaftar(bukan di dalamnya), lalu ditemukan lagi lewat#total. Karena method ini mem-parsing markup, aturan amannya sama denganinnerHTML— bahasan lengkapnya menunggu di chapter Safe DOM Manipulation.
Posisi insertAdjacentHTML #
Method ini menerima dua argumen: posisi relatif dan string markup. Empat posisinya sering tertukar, dan tabel ini merangkumnya dengan acuan daftar pada berkas contoh:
| Posisi | Tempat penyisipan |
|---|---|
beforebegin | sebelum elemen acuan (di luar) |
afterbegin | di dalam acuan, sebagai anak pertama |
beforeend | di dalam acuan, sebagai anak terakhir |
afterend | setelah elemen acuan (di luar) |
Dua posisi tengah (afterbegin dan beforeend) berperilaku seperti prepend dan append — bedanya, isinya string markup yang di-parsing, bukan node yang sudah jadi.
Bila node yang sudah jadi memang tersedia, append bisa menerima beberapa sekaligus:
const daftar = document.querySelector("#keranjang");
const a = document.createElement("li");
const b = document.createElement("li");
a.textContent = "Item A";
b.textContent = "Item B";
daftar.append(a, b);
console.log(daftar.children.length); // 3
Keranjang yang mula-mula berisi satu item kini berisi 3 — kedua node masuk dalam urutan argumen, satu panggilan untuk keduanya. Untuk kebutuhan menyusun banyak node sekaligus tanpa menyentuh dokumen di tengah jalan, susun dulu di fragment — cara lengkapnya menanti di chapter DOM Fragments.
Bukti Live vs Static #
Janji chapter Selecting Elements kini bisa ditepati dengan baris 6–9 dan 16–18 pada berkas contoh. Bacaan keluarannya:
querySelectorAllmenetapkan isinya sekali saja ketika dipanggil. Penambahan item pada baris 16 tidak menyentuhstatis— nilainya membeku di angka1.getElementsByTagNametidak menyimpan daftar, melainkan pegangan ke dokumen. Setiap penambahanliotomatis terhitung — angkanya naik ke2seketika.
Kedua sifat ini sama-sama berguna: koleksi static memberi hasil yang stabil saat kita sedang mengubah dokumen di tengah loop, sedangkan koleksi live cocok ketika kita ingin penghitungan yang selalu segar. Kebiasaan yang aman: pilih static (querySelectorAll) sebagai bawaan, dan gunakan live hanya dengan sengaja.
Kita kini bisa membuat dan menempel elemen apa pun. Pertanyaan berikutnya adalah kebalikannya: bagaimana menghapus elemen yang tidak lagi dibutuhkan — tanpa meninggalkan rongsokan di memori. Itulah bahasan chapter Removing Elements.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
bangun-daftar.js
Unduh berkas// bangun-daftar.js — membuat elemen baru dan menempelkannya ke dokumen
const daftar = document.querySelector("#keranjang");
// 1. dua koleksi dibuat SEBELUM perubahan (untuk bukti live vs static)
const statis = document.querySelectorAll("#keranjang li");
const hidup = document.getElementsByTagName("li");
console.log(statis.length); // 1
console.log(hidup.length); // 1
// 2. buat elemen baru, beri isi
const item = document.createElement("li");
item.textContent = "Teh Melati 100g";
// 3. append: menambah di akhir
daftar.append(item);
console.log(statis.length); // 1 — snapshot saat dibuat
console.log(hidup.length); // 2 — koleksi live mengikuti dokumen
// 4. prepend: menambah di awal
const pembuka = document.createElement("li");
pembuka.textContent = "— Belanja pagi —";
daftar.prepend(pembuka);
// 5. insertBefore: sisip sebelum elemen acuan
const sisipan = document.createElement("li");
sisipan.textContent = "Gula Aren";
daftar.insertBefore(sisipan, daftar.lastElementChild);
console.log(Array.from(daftar.children).map((li) => li.textContent).join(" | "));
// 6. createTextNode: menempel teks tanpa markup
const catatan = document.createElement("p");
catatan.append(document.createTextNode("3 item ditambahkan hari ini"));
document.body.append(catatan);
// 7. insertAdjacentHTML: sisip markup pada posisi tertentu
daftar.insertAdjacentHTML("afterend", '<p id="total">Total item: 4</p>');
console.log(document.querySelector("#total").textContent);