Soal 1Tepat setelah const li = document.createElement("li");, di mana posisi li di halaman?
Membuat, Mengubah, dan Menghapus Element — createElement / classList / remove
Membahas createElement dan append untuk menambahkan element, remove untuk menghapusnya, classList dan dataset untuk class dan atribut, serta textContent untuk menampilkan input sebagai teks.
Kalau setiap perubahan, seperti menambah satu item ke daftar, menandai item selesai, atau menghapus satu item, kamu lakukan dengan meng-assign innerHTML, li yang seharusnya tetap juga ikut dibuat ulang. Kalau input pengguna di-assign dengan cara yang sama, tag yang terselip di dalamnya, seperti <a>, juga ikut menjadi element.
Artikel ini membahas cara membuat dan menghapus element, classList dan dataset untuk mengubah class dan atribut, serta cara memasukkan input sebagai teks apa adanya.
Membuat dan Menambahkan Element — createElement dan append
Anggap kamu ingin menambahkan "Kirim invoice" ke akhir daftar tugas. Kalau kamu menyambung string ke innerHTML lalu meng-assign-nya kembali, semua li yang sudah ada juga dibuat ulang sebagai element baru. li yang sebelumnya kamu simpan di variabel lewat querySelector masih menunjuk element lama yang sudah tidak ada di halaman, jadi mengubah teks lewat variabel itu tidak mengubah tampilan.
createElement (method yang menerima nama tag dan membuat element baru yang belum ada di mana pun di DOM tree) dipanggil seperti document.createElement("li"). Element yang dibuat baru menjadi bagian halaman setelah ditambahkan ke parent element dengan append (method yang menambahkan element yang diberikan sebagai child terakhir dari element yang memanggilnya).
document.body.innerHTML = `<ul id="tasks"><li>Buat penawaran harga</li><li>Bagikan notulen rapat</li></ul>`;
const taskList = document.querySelector("#tasks");
// Buat element li dan isi teksnya (belum ada di halaman)
const newTask = document.createElement("li");
newTask.textContent = "Kirim invoice";
console.log(newTask.outerHTML); // <li>Kirim invoice</li>
console.log(document.querySelectorAll("li").length); // 2
// Setelah ditambahkan sebagai child terakhir ul, li menjadi bagian halaman
taskList.append(newTask);
console.log(document.querySelectorAll("li").length); // 3
console.log(taskList.outerHTML);
// <ul id="tasks"><li>Buat penawaran harga</li><li>Bagikan notulen rapat</li><li>Kirim invoice</li></ul>
Variabel newTask menunjuk element li yang sama, baik sebelum maupun sesudah append. Jadi kalau kamu meng-assign newTask.textContent setelah memanggil append, teks li ketiga di halaman yang berubah.
Menandai Tugas Selesai — classList dan dataset
Berikutnya, kamu ingin menambahkan class done ke tugas yang sudah selesai supaya CSS menampilkannya dengan coretan. Atribut class li itu berisi satu string, task urgent, jadi kalau kamu menimpanya dengan done, class task yang dipakai CSS untuk mengatur tampilan baris juga ikut hilang.
classList (object untuk mengelola class sebuah element per kata) punya add untuk menambah kata, remove untuk membuangnya, dan toggle yang menambahkannya kalau belum ada atau membuangnya kalau sudah ada. Atribut selain class ditulis dengan setAttribute(nama, nilai), sedangkan atribut yang diawali data- ditulis lewat dataset (object untuk membaca dan menulis atribut data berdasarkan namanya).
document.body.innerHTML = `<ul><li class="task urgent" data-id="T-1">Buat penawaran harga</li><li class="task urgent" data-id="T-2">Kirim invoice</li></ul>`;
const quote = document.querySelector('[data-id="T-1"]');
const invoice = document.querySelector('[data-id="T-2"]');
// classList menambah dan membuang per kata (class lain tetap ada)
quote.classList.add("done");
console.log(quote.outerHTML); // <li class="task urgent done" data-id="T-1">Buat penawaran harga</li>
quote.classList.toggle("urgent"); // Sudah selesai, jadi buang urgent (karena ada, toggle membuangnya)
console.log(quote.outerHTML); // <li class="task done" data-id="T-1">Buat penawaran harga</li>
// setAttribute mengganti seluruh value-nya (kalau dipakai untuk class, task ikut hilang)
invoice.setAttribute("class", "done");
console.log(invoice.outerHTML); // <li class="done" data-id="T-2">Kirim invoice</li>
// Atribut yang diawali data- ditulis lewat dataset (doneAt menjadi data-done-at)
invoice.dataset.doneAt = "9/10";
console.log(invoice.outerHTML);
// <li class="done" data-id="T-2" data-done-at="9/10">Kirim invoice</li>
Nama yang dipakai di dataset adalah nama atribut tanpa data-, dengan huruf setelah setiap tanda hubung yang tersisa dijadikan huruf kapital. data-id dibaca dan ditulis sebagai dataset.id, data-done-at sebagai dataset.doneAt, dan saat di-assign, atributnya ditulis ke HTML dengan nama aslinya.
dataset Tidak Menerima Nama dengan Tanda Hubung
Kalau kamu membaca dengan tanda hubung yang masih ada, seperti dataset["done-at"], hasilnya undefined, bukan error, jadi salah tulisnya mudah terlewat. Kalau di-assign dengan cara itu, baris tersebut melempar error. Nama error-nya SyntaxError, tapi berbeda dengan kesalahan sintaks di kode, error ini terjadi saat kode dijalankan, jadi baris-baris sebelumnya tetap dijalankan.
Menghapus Element — remove dan Referensi yang Tersisa di Variabel
Sekarang misalkan kamu ingin mengeluarkan tugas yang sudah selesai dari daftar. Kalau daftarnya kamu tulis ulang dengan innerHTML, class dan atribut data yang ditambahkan di bagian sebelumnya juga harus ditulis ulang ke string HTML, dan kalau ada satu saja yang terlewat, tugas yang tersisa kehilangan tanda selesainya.
remove (method yang melepas element pemanggilnya dari parent dan menghapusnya dari DOM tree) dipanggil tanpa argument pada element yang ingin dihapus, seperti doneItem.remove(). classList.remove("done") di bagian sebelumnya hanya membuang satu kata dari class; method itu berbeda dengan method ini, yang menghapus element-nya sendiri.
document.body.innerHTML = `<ul id="tasks"><li data-id="T-1">Buat penawaran harga</li><li data-id="T-2" class="done">Kirim invoice</li><li data-id="T-3">Bagikan notulen rapat</li></ul>`;
const tasks = document.querySelectorAll("li");
// Cari li yang sudah selesai, lalu hapus dari DOM tree
const doneItem = document.querySelector(".done");
doneItem.remove();
console.log(document.querySelectorAll("li").length); // 2
// Element yang dihapus dan NodeList yang diambil sebelumnya tetap ada di variabelnya
console.log(doneItem.textContent); // Kirim invoice
console.log(tasks.length); // 3 (element yang ditemukan saat itu)
// Masih ada di variabel, jadi append bisa mengembalikannya ke akhir daftar
document.querySelector("#tasks").append(doneItem);
console.log(document.querySelectorAll("li").length); // 3
querySelectorAll hanya mengembalikan element yang ditemukan saat dipanggil; perubahan DOM setelahnya tidak diikuti. doneItem yang sudah dihapus juga tetap ada di variabelnya, jadi teksnya masih bisa dibaca, atau element-nya dikembalikan ke daftar dengan append.
Menampilkan Input sebagai Teks — textContent dan innerHTML
Bayangkan kolom ulasan produk yang menampilkan komentar kiriman pengguna. Komentar itu disimpan, dan string yang sama juga tampil di halaman pengunjung lain. Kalau komentarnya berisi tag a (tag untuk membuat link, dengan tujuan link ditulis di atribut href), meng-assign-nya ke innerHTML membuat link palsu yang dipasang pengirimnya tampil sebagai link sungguhan.
String yang di-assign ke innerHTML di-parse sebagai HTML, dan tag di dalamnya menjadi element. String yang di-assign ke textContent tidak di-parse, jadi < pun masuk sebagai karakter biasa. Memasukkan input ke innerHTML bukan cuma membuka jalan untuk link palsu, tapi juga bisa menjadi penyebab XSS (celah keamanan di mana kode yang diselipkan pengguna ke input dijalankan di halaman pengunjung lain).
document.body.innerHTML = `<p class="review"></p>`;
const review = document.querySelector(".review");
// String yang diketik pengguna di kolom ulasan (terselip link palsu)
const reviewText = '<a href="https://example.com/login">Login ulang</a> untuk melanjutkan';
// Kalau dimasukkan ke innerHTML, tag a menjadi element a sungguhan
review.innerHTML = reviewText;
console.log(document.querySelectorAll("a").length); // 1
// Kalau dimasukkan ke textContent, tag-nya juga masuk sebagai teks biasa
review.textContent = reviewText;
console.log(document.querySelectorAll("a").length); // 0
console.log(review.outerHTML);
// <p class="review"><a href="https://example.com/login">Login ulang</a> untuk melanjutkan</p>
- Teks di dalamnya "Login ulang"
- Kalau diklik, pindah ke situs lain
- Menyusul setelah element a
<a href="…">Login ulang</a> untuk melanjutkan- Tidak ada element a
< dan > di outerHTML adalah cara HTML menulis < dan > sebagai teks. Kalau dibaca lagi dengan textContent, hasilnya kembali menjadi karakter aslinya. Tabel di bawah merangkum cara mengubah element yang dibahas di artikel ini, dikelompokkan berdasarkan bagian yang diubah.
| Yang diubah | Cara menulis | Perhatikan |
|---|---|---|
| Element itu sendiri | createElement / append / remove | Belum di halaman sebelum append |
| class | classList.add / remove / toggle | setAttribute mengganti seluruh value |
| Atribut berawalan data- | dataset.doneAt = "9/10" | Tanpa data-, huruf setelah - jadi kapital |
| Atribut lainnya | setAttribute("href", "/tasks/T-2") | Ditambah, atau value-nya diganti |
| Isi teks | textContent | Input pengguna masuk ke sini |
| Isi HTML | innerHTML | Hanya HTML yang kamu tulis sendiri |
Kode di onerror milik img Tetap Berjalan
<script> yang dimasukkan ke innerHTML tidak dijalankan, tapi kalau tag img, yang menampilkan gambar, ditulis sebagai <img src="x" onerror="…">, onerror (atribut berisi kode yang dijalankan saat gambar gagal dimuat) tetap berjalan. Jadi, meskipun <script> tidak dijalankan, input tetap tidak aman dimasukkan ke innerHTML.
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Kalau class sebuah li adalah task urgent, apa class-nya setelah setAttribute("class", "done")?
Soal 3Setelah p.textContent = "<b>Baru</b>";, ada berapa element b di dalam p?