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:

MethodPosisi 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):

text
1
1
1
2
— Belanja pagi — | Kopi Gayo 250g | Gula Aren | Teh Melati 100g
Total item: 4

Keluaran dicatat dari eksekusi nyata di lingkungan jsdom dan identik dengan console browser. Mari kita bedah:

  1. Baris 6–9 (dua koleksi sebelum perubahan): statis dan hidup dibuat saat keranjang masih berisi satu item, sehingga keduanya sama-sama bernilai 1. Pembuatan koleksi lebih dulu ini syarat percobaan live vs static yang jujur.
  2. Baris 12–13 (createElement + textContent): elemen li baru dibuat dengan tag persis seperti yang dikirim, lalu diisi teks. Pada titik ini elemen belum terlihat di halaman sama sekali.
  3. Baris 16–18 (append dan bukti live vs static): setelah daftar.append(item), angka kedua koleksi terbelah: statis.length tetap 1 karena NodeList dari querySelectorAll adalah cuplikan saat dibuat; hidup.length menjadi 2 karena HTMLCollection dari getElementsByTagName selalu mencerminkan kondisi dokumen terkini.
  4. Baris 21–28 (prepend dan insertBefore): pembuka ditempel di posisi anak pertama, sedangkan sisipan ditempel tepat sebelum daftar.lastElementChild — yaitu sebelum “Teh Melati 100g”. Argumen kedua insertBefore adalah elemen acuan, bukan indeks.
  5. Baris 30 (Array.from atas children): children berupa HTMLCollection yang tidak punya map, jadi kita membungkusnya dengan Array.from dari section Collections sebelum memetakan isi tiap item. Urutan keluaran menegaskan posisi akhir semua elemen.
  6. Baris 33–35 (createTextNode): node teks eksplisit dibuat dengan document.createTextNode lalu ditempelkan ke <p> — hasilnya sama dengan textContent, tetapi cara ini berguna saat kita menyusun campuran node dan teks secara terpisah. <p> kemudian ditempel ke document.body.
  7. Baris 38–39 (insertAdjacentHTML): string HTML di-parsing dan disisipkan tepat setelah elemen daftar (bukan di dalamnya), lalu ditemukan lagi lewat #total. Karena method ini mem-parsing markup, aturan amannya sama dengan innerHTML — 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:

PosisiTempat penyisipan
beforebeginsebelum elemen acuan (di luar)
afterbegindi dalam acuan, sebagai anak pertama
beforeenddi dalam acuan, sebagai anak terakhir
afterendsetelah 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:

js append menerima beberapa node 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:

  • querySelectorAll menetapkan isinya sekali saja ketika dipanggil. Penambahan item pada baris 16 tidak menyentuh statis — nilainya membeku di angka 1.
  • getElementsByTagName tidak menyimpan daftar, melainkan pegangan ke dokumen. Setiap penambahan li otomatis terhitung — angkanya naik ke 2 seketika.

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
javascript
// 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);