Soal 1Dengan saveButton.addEventListener("click", save());, kapan save dipanggil?
Event — addEventListener dan Propagation
Menangani klik dengan addEventListener dan event.target, mendelegasikannya lewat bubbling, serta memakai stopPropagation, once, dan removeEventListener.
Assignment ke textContent hanya mengubah element sekali, yaitu saat baris itu dijalankan. Kode yang menambah jumlah setiap kali tombol diklik tidak bisa ditulis hanya dengan baris-baris yang berjalan dari atas ke bawah, karena kapan tombol akan diklik tidak bisa diketahui dari kodenya.
Artikel ini membahas addEventListener, yang memanggil function saat terjadi klik, dan cara menangani klik pada child element dari parent element-nya.
Memanggil Function Saat Diklik — addEventListener
Misalkan tombol "Tambah ke keranjang" di halaman produk harus menambah jumlah barang di keranjang sebanyak 1 dan menampilkannya setiap kali diklik. Kalau baris yang mengubah jumlah itu kamu tulis begitu saja, baris itu hanya berjalan sekali saat halaman dibuka, dan jumlahnya tidak berubah saat tombol diklik.
Untuk memanggil function saat event (kejadian di halaman, seperti klik atau input) terjadi, daftarkan nama event dan function-nya dengan addEventListener("click", function). Function yang didaftarkan ini disebut event listener (selanjutnya disebut listener). Di console kamu tidak bisa mengklik dengan mouse, jadi klik dipicu dengan method click, seperti cartButton.click().
document.body.innerHTML = `<button id="add-to-cart">Tambah ke keranjang</button><p>Keranjang: <span id="cart-count">0</span> barang</p>`;
const cartButton = document.querySelector("#add-to-cart");
const cartCount = document.querySelector("#cart-count");
// Function yang dipanggil saat diklik (di sini baru didefinisikan)
function addToCart() {
cartCount.textContent = Number(cartCount.textContent) + 1;
}
// Berikan function-nya sendiri untuk mendaftarkannya (addToCart tidak dipanggil di baris ini)
cartButton.addEventListener("click", addToCart);
console.log(cartCount.textContent); // 0
// Setiap kali klik dipicu, addToCart yang didaftarkan dipanggil
cartButton.click();
cartButton.click();
console.log(cartCount.textContent); // 2
Di halaman sungguhan, kamu tidak perlu menulis click(): browser memanggil addToCart begitu pengguna mengklik tombolnya. Yang ditanggapi hanya klik yang terjadi setelah pendaftaran, jadi klik sebelum baris pendaftaran tidak ikut dihitung.
Dengan (), Function Berjalan Sekali di Baris Pendaftaran
Kalau kamu menulis addEventListener("click", addToCart()), addToCart dipanggil sekali di baris pendaftaran, dan yang diberikan adalah return value-nya, yaitu undefined. Tidak ada error yang muncul, jadi sulit menemukan kenapa jumlahnya tidak bertambah saat diklik. Saat mendaftarkan, berikan nama function-nya saja tanpa ().
Menangani Klik Child di Parent Element — Bubbling dan Delegation
Berikutnya, saat sebuah baris di daftar pesanan diklik, kamu ingin membaca nomor pesanan di baris itu. Kalau baris-barisnya kamu kumpulkan dengan querySelectorAll lalu kamu pasangi listener satu per satu dengan forEach, hanya baris yang ada di halaman saat itu yang mendapat listener. Baris yang ditambahkan belakangan dengan append tidak bereaksi apa-apa saat diklik.
Karena ada bubbling (mekanisme ketika event yang terjadi di sebuah element diteruskan ke parent element-nya, lalu ke parent dari parent itu, dan seterusnya), listener yang didaftarkan di ul sebagai parent juga dipanggil saat li diklik. Browser memberikan event object (object yang berisi informasi tentang event yang terjadi) ke listener sebagai argument. Di kode di bawah, argument itu diberi nama event, dan target-nya menunjuk element tempat klik itu awalnya terjadi.
document.body.innerHTML = `<section id="order-panel"><ul id="order-list"><li data-id="A-101">Pesanan A-101</li><li data-id="A-102">Pesanan A-102</li></ul></section>`;
const orderList = document.querySelector("#order-list");
const orderPanel = document.querySelector("#order-panel");
// Daftarkan di ul sebagai parent dan di section yang membungkusnya, bukan di li
orderList.addEventListener("click", (event) => {
console.log(`Diterima di ul: ${event.target.dataset.id}`);
});
orderPanel.addEventListener("click", (event) => {
console.log(`Diterima di section: ${event.target.dataset.id}`);
});
// Picu klik di li A-102
document.querySelector('[data-id="A-102"]').click();
// Diterima di ul: A-102
// Diterima di section: A-102
event.target.dataset.idadalah A-102
event.target.dataset.idadalah A-102
- Klik tidak diteruskan ke li yang sejajar
event.targetadalah li ini- Dari sini klik mulai diteruskan ke luar
Cara mendaftarkan hanya 1 listener di parent element lalu memakai event.target untuk mengenali child yang diklik disebut event delegation. Klik pada li ditangani oleh ul setelah diteruskan ke sana, jadi listener yang sama tetap dipanggil untuk li yang ditambahkan dengan append setelah pendaftaran. event.target adalah element paling dalam yang diklik, jadi kalau di dalam li ada span, yang ditunjuk adalah span itu.
Menahan Klik Agar Tidak Sampai ke Parent — stopPropagation
Bayangkan baris pesanan yang membuka detailnya saat diklik, dan di dalamnya ada tombol "Hapus" untuk menghapus pesanan itu. Tombol itu adalah child dari baris, jadi klik pada "Hapus" juga diteruskan ke baris lewat bubbling, dan detail pesanan ikut terbuka padahal pesanannya sedang dihapus.
Event yang diteruskan ke element di luarnya disebut propagation. Untuk menghentikannya, panggil stopPropagation (method yang menghentikan propagation di element yang listener-nya sedang berjalan) di dalam listener tombol, seperti event.stopPropagation(). Setelah dihentikan, listener yang didaftarkan di element luar, seperti baris atau daftar, tidak dipanggil.
document.body.innerHTML = `<ul><li id="order-row">Pesanan A-201 <button id="favorite">Favorit</button><button id="delete">Hapus</button></li></ul>`;
const orderRow = document.querySelector("#order-row");
const favoriteButton = document.querySelector("#favorite");
const deleteButton = document.querySelector("#delete");
// Klik pada baris membuka detailnya
orderRow.addEventListener("click", () => console.log("Buka detail"));
// Favorit: detail boleh ikut terbuka, jadi klik dibiarkan sampai ke baris
favoriteButton.addEventListener("click", () => console.log("Ditambahkan ke favorit"));
favoriteButton.click(); // Ditambahkan ke favorit, Buka detail (2 baris)
// Hapus: panggil stopPropagation() supaya klik tidak sampai ke baris
deleteButton.addEventListener("click", (event) => {
event.stopPropagation();
console.log("Pesanan dihapus");
});
deleteButton.click(); // Pesanan dihapus
Di listener hapus, "Pesanan dihapus" tetap tampil meskipun ditulis setelah stopPropagation(). Yang dihentikan adalah penerusan event ke element luar, bukan sisa baris di listener yang memanggilnya.
Klik yang Dihentikan Juga Tidak Sampai ke ul yang Memakai Delegation
stopPropagation() tidak hanya menahan event agar tidak sampai ke baris yang langsung membungkus tombol, tetapi juga ke semua element di luarnya. Meskipun ul daftar punya listener delegation yang mencatat aksi pengguna, klik pada tombol hapus tidak akan tercatat. Hentikan hanya klik yang memang tidak perlu ditangani oleh element luar mana pun.
Melepas Listener — once dan removeEventListener
Kalau pengguna mengklik tombol "Buat pesanan" 2 kali berturut-turut, listener yang didaftarkan juga dipanggil 2 kali, dan pesanan yang sama terkirim 2 kali. Setelah pengiriman pertama diterima, kamu perlu memastikan listener tidak dipanggil lagi meskipun tombol yang sama diklik.
Kalau kamu memberikan { once: true } sebagai argument ketiga addEventListener, listener otomatis dilepas setelah dipanggil sekali. Untuk melepasnya kapan pun kamu mau, panggil removeEventListener (method yang menerima nama event dan function, mencari listener terdaftar yang === dengan function itu, lalu melepasnya).
document.body.innerHTML = `<button id="confirm">Buat pesanan</button>`;
const confirmButton = document.querySelector("#confirm");
const sendOrder = () => console.log("Pesanan dikirim");
// { once: true } — otomatis dilepas setelah dipanggil sekali
confirmButton.addEventListener("click", sendOrder, { once: true });
confirmButton.click(); // Pesanan dikirim
confirmButton.click(); // (tidak ada yang tampil)
// Memberikan sendOrder yang didaftarkan akan melepas listener itu
confirmButton.addEventListener("click", sendOrder);
confirmButton.removeEventListener("click", sendOrder);
confirmButton.click(); // (tidak ada yang tampil)
// Arrow function yang baru ditulis adalah function lain meskipun isinya sama, jadi tidak ada yang dilepas
confirmButton.addEventListener("click", () => console.log("Pesanan dikirim"));
confirmButton.removeEventListener("click", () => console.log("Pesanan dikirim"));
confirmButton.click(); // Pesanan dikirim
() => … membuat function baru setiap kali ditulis, jadi meskipun isinya sama, hasil perbandingannya dengan function yang didaftarkan memakai === adalah false. Simpan dulu listener yang nanti akan dilepas di variabel seperti sendOrder, lalu daftarkan variabel itu. Tabel di bawah merangkum penulisan terkait event yang dibahas di artikel ini.
| Cara menulis | Fungsinya | Perhatikan |
|---|---|---|
| addEventListener("click", fn) | Memanggil fn setiap klik | Berikan fn tanpa () |
| event.target | Element yang diklik | Paling dalam, meski di parent |
| Listener di parent (delegation) | Menangani klik semua child | Child baru juga tertangani |
| event.stopPropagation() | Menahan penerusan ke luar | Delegation pun tak dipanggil |
| { once: true } | Melepas setelah 1 panggilan | Argument ke-3 addEventListener |
| removeEventListener("click", fn) | Melepas listener | Berikan function yang sama |
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Di dalam listener yang didaftarkan di ul, apa event.target saat kamu memanggil click() pada li di dalam ul itu?
Soal 3Setelah mendaftarkan () => send(), apa yang terjadi kalau kamu memanggil removeEventListener("click", () => send())?