Bab 2: Pengantar Event
Event = sesuatu yang terjadi. Klik, ketik, scroll, load — browser memberitahu kita, dan kita bisa merespons.
2.1 Pengantar Browser Events
Event itu kayak bel pintu. Seseorang menekan bel (event terjadi), dan kamu merespons dengan membuka pintu (event handler). Kamu tidak perlu terus-terusan mengintip dari jendela — bel yang memberitahu kamu.
Apa Itu Event?
Event adalah sinyal bahwa sesuatu terjadi. Contoh event:
| Event | Kapan Terjadi |
|---|---|
click | Elemen diklik |
dblclick | Elemen di-double-click |
mouseenter | Mouse masuk area elemen |
mouseleave | Mouse keluar area elemen |
keydown | Tombol keyboard ditekan |
keyup | Tombol keyboard dilepas |
submit | Form di-submit |
input | Isi input berubah |
focus | Elemen mendapat fokus |
blur | Elemen kehilangan fokus |
load | Halaman/resource selesai dimuat |
scroll | Halaman/elemen di-scroll |
Cara Memasang Event Handler
Cara 1: HTML attribute (JANGAN pakai ini)
<!-- Tidak recommended — campur HTML dan JS -->
<button onclick="alert('Diklik!')">Klik saya</button>Cara 2: DOM property (Oke, tapi terbatas)
const btn = document.querySelector("button");
btn.onclick = function() {
alert("Diklik!");
};
// ⚠️ Masalah: hanya bisa SATU handler per event
btn.onclick = function() {
alert("Handler kedua"); // Yang pertama HILANG!
};Cara 3: addEventListener (RECOMMENDED ✅)
const btn = document.querySelector("button");
// Bisa banyak handler untuk satu event
btn.addEventListener("click", function() {
alert("Handler 1");
});
btn.addEventListener("click", function() {
alert("Handler 2"); // Keduanya jalan!
});addEventListener Lengkap
element.addEventListener(event, handler, options);
// options (opsional):
// - once: true → handler otomatis dihapus setelah 1x jalan
// - capture: true → tangkap di fase capturing (nanti dibahas)
// - passive: true → handler tidak akan panggil preventDefault()
btn.addEventListener("click", handler, { once: true });
// Klik pertama → handler jalan. Klik kedua → tidak ada efek.Menghapus Event Handler
function handleClick() {
alert("Diklik!");
}
// Pasang
btn.addEventListener("click", handleClick);
// Hapus — HARUS fungsi yang SAMA (referensi sama)
btn.removeEventListener("click", handleClick);
// ⚠️ Ini TIDAK BEKERJA:
btn.addEventListener("click", function() { alert("Halo"); });
btn.removeEventListener("click", function() { alert("Halo"); }); // GAGAL! Beda referensi!Objek Event
Saat event terjadi, browser membuat objek event berisi detail:
btn.addEventListener("click", function(event) {
console.log(event.type); // "click"
console.log(event.target); // Elemen yang diklik
console.log(event.clientX); // Posisi X mouse
console.log(event.clientY); // Posisi Y mouse
console.log(event.currentTarget); // Elemen yang punya handler (= btn)
});Handler sebagai Objek: handleEvent
// Bisa pakai objek dengan method handleEvent
const handler = {
handleEvent(event) {
if (event.type === "click") {
alert("Diklik di " + event.clientX + ", " + event.clientY);
} else if (event.type === "mouseenter") {
event.target.style.background = "yellow";
}
}
};
btn.addEventListener("click", handler);
btn.addEventListener("mouseenter", handler);// 1. addEventListener("click", handler()) ← SALAH! (langsung dipanggil)
btn.addEventListener("click", handler); // ✅ Tanpa ()
// 2. removeEventListener butuh referensi SAMA
// Simpan fungsi di variabel kalau mau bisa dihapus
// 3. this di dalam handler = elemen yang punya handler
btn.addEventListener("click", function() {
console.log(this); // = btn (elemen yang di-addEventListener)
});
// 4. Arrow function: this BUKAN elemen!
btn.addEventListener("click", () => {
console.log(this); // = window (atau undefined di strict mode)
});🎯 Challenge
<button id="counter-btn">Diklik: 0 kali</button>
<button id="reset-btn">Reset</button>
<!-- Tulis JavaScript untuk:
1. Klik counter-btn → angka bertambah, teks update
2. Klik reset-btn → angka kembali ke 0
3. Setelah 10 klik, hapus event listener dari counter-btn (pakai removeEventListener)
4. Tampilkan pesan "Sudah selesai!" setelah listener dihapus
-->2.2 Bubbling dan Capturing
Bayangin kamu teriak di dalam ruangan yang ada di dalam gedung. Suaramu:
- Capturing (menangkap) — suara masuk dari luar gedung → lantai → ruangan → kamu
- Target — suara sampai di kamu (sumber)
- Bubbling (menggelembung) — suara keluar dari kamu → ruangan → lantai → gedung
Event di browser bekerja sama persis!
Bubbling
Saat event terjadi di elemen, handler di elemen itu jalan dulu, lalu naik ke parent, terus ke atas sampai document:
<div id="grandparent">
<div id="parent">
<button id="child">Klik saya</button>
</div>
</div>document.getElementById("grandparent").addEventListener("click", () => {
console.log("3. Grandparent diklik");
});
document.getElementById("parent").addEventListener("click", () => {
console.log("2. Parent diklik");
});
document.getElementById("child").addEventListener("click", () => {
console.log("1. Child diklik");
});
// Klik button → output:
// 1. Child diklik
// 2. Parent diklik
// 3. Grandparent diklikevent.target vs event.currentTarget
document.getElementById("parent").addEventListener("click", function(event) {
console.log("target:", event.target); // Elemen yang BENAR-BENAR diklik
console.log("currentTarget:", event.currentTarget); // Elemen yang punya handler (= this)
});
// Klik button (child) →
// target: <button> (yang diklik)
// currentTarget: <div#parent> (yang punya handler)Menghentikan Bubbling
btn.addEventListener("click", function(event) {
event.stopPropagation(); // Event BERHENTI di sini, tidak naik ke parent
alert("Hanya handler ini yang jalan");
});
// stopImmediatePropagation — hentikan SEMUA handler (termasuk di elemen yang sama)
btn.addEventListener("click", function(event) {
event.stopImmediatePropagation();
alert("Handler 1");
});
btn.addEventListener("click", function(event) {
alert("Handler 2"); // TIDAK JALAN!
});Capturing Phase
Secara default, handler jalan di fase bubbling. Untuk menangkap di fase capturing:
// Fase capturing (jarang dipakai, tapi penting dipahami)
elem.addEventListener("click", handler, { capture: true });
// atau
elem.addEventListener("click", handler, true);Urutan Lengkap Event
1. CAPTURING: document → html → body → div → ... → target
2. TARGET: handler di elemen target
3. BUBBLING: target → ... → div → body → html → document
// Demo urutan
for (const elem of document.querySelectorAll("*")) {
elem.addEventListener("click", (e) => {
console.log(`Capturing: ${elem.tagName}`);
}, true); // capture phase
elem.addEventListener("click", (e) => {
console.log(`Bubbling: ${elem.tagName}`);
}); // bubble phase
}// 1. Tidak semua event bubble!
// focus, blur, load → TIDAK bubble
// focusin, focusout → bubble (alternatifnya)
// 2. stopPropagation() jarang dibutuhkan
// Biasanya ada cara lebih baik (event delegation)
// Hati-hati: bisa break fitur lain yang bergantung pada bubbling
// 3. event.target bisa elemen DALAM yang tidak kamu expect
// Misal klik <b> di dalam <button> → target = <b>, bukan <button>🎯 Challenge
<div id="outer" style="padding:30px; background:lightblue;">
OUTER
<div id="middle" style="padding:30px; background:lightgreen;">
MIDDLE
<div id="inner" style="padding:30px; background:lightyellow;">
INNER
</div>
</div>
</div>
<!-- Tulis JavaScript untuk:
1. Pasang click handler di ketiga div (log nama div yang diklik)
2. Klik INNER → lihat urutan log (bubbling)
3. Tambahkan stopPropagation() di MIDDLE → klik INNER lagi, apa yang berubah?
4. Pasang handler capturing di OUTER → perhatikan urutan baru
-->2.3 Event Delegation
Bayangin kamu punya 100 karyawan. Kamu bisa:
- ❌ Kasih HP ke masing-masing dan telepon satu-satu (100 event listener)
- ✅ Pasang 1 pengumuman di speaker gedung, karyawan yang relevan merespons (1 event listener di parent)
Event delegation = pasang satu handler di parent, lalu cek siapa yang sebenarnya diklik.
Masalah: Banyak Handler
// ❌ BURUK — 100 handler untuk 100 tombol
document.querySelectorAll(".btn").forEach(btn => {
btn.addEventListener("click", function() {
// handle...
});
});Solusi: Event Delegation
// ✅ BAGUS — 1 handler di parent
document.getElementById("button-container").addEventListener("click", function(event) {
const btn = event.target.closest(".btn"); // Cari tombol terdekat
if (!btn) return; // Klik bukan di tombol → abaikan
if (!this.contains(btn)) return; // Safety check
// Handle berdasarkan tombol mana
console.log("Tombol diklik:", btn.dataset.action);
});Contoh Praktis: Menu
<div id="menu">
<button data-action="save">💾 Save</button>
<button data-action="load">📂 Load</button>
<button data-action="search">🔍 Search</button>
</div>const menu = document.getElementById("menu");
const actions = {
save() { alert("Menyimpan..."); },
load() { alert("Memuat..."); },
search() { alert("Mencari..."); }
};
menu.addEventListener("click", function(event) {
const btn = event.target.closest("[data-action]");
if (!btn) return;
const action = btn.dataset.action;
if (actions[action]) {
actions[action]();
}
});Contoh Praktis: Tabel yang Bisa Diurutkan
<table id="data-table">
<thead>
<tr>
<th data-sort="name">Nama ↕</th>
<th data-sort="age">Umur ↕</th>
<th data-sort="city">Kota ↕</th>
</tr>
</thead>
<tbody>
<tr><td>Budi</td><td>25</td><td>Jakarta</td></tr>
<tr><td>Ani</td><td>30</td><td>Bandung</td></tr>
</tbody>
</table>document.getElementById("data-table").addEventListener("click", function(event) {
const th = event.target.closest("th[data-sort]");
if (!th) return;
const column = th.dataset.sort;
console.log("Sort by:", column);
// Logic sorting di sini...
});Pattern: Behavior dengan data-* Attribute
<!-- Deklaratif: behavior ditentukan di HTML -->
<button data-toggle-id="menu">Toggle Menu</button>
<button data-toggle-id="sidebar">Toggle Sidebar</button>
<div id="menu" hidden>Menu content</div>
<div id="sidebar" hidden>Sidebar content</div>// Satu handler untuk semua toggle behavior
document.addEventListener("click", function(event) {
const btn = event.target.closest("[data-toggle-id]");
if (!btn) return;
const targetId = btn.dataset.toggleId;
const target = document.getElementById(targetId);
target.hidden = !target.hidden;
});Kapan Pakai Event Delegation?
| Situasi | Delegation? |
|---|---|
| Banyak elemen serupa (list, tabel) | ✅ Ya |
| Elemen ditambah/dihapus dinamis | ✅ Ya (handler tetap bekerja!) |
| Satu elemen spesifik | ❌ Langsung addEventListener |
| Event yang tidak bubble (focus/blur) | ❌ Pakai focusin/focusout |
// 1. event.target bisa elemen DALAM tombol
// <button><b>Bold text</b></button>
// Klik bold → target = <b>, bukan <button>!
// Solusi: pakai closest()
// 2. Jangan lupa cek contains()
parent.addEventListener("click", function(event) {
const btn = event.target.closest(".btn");
if (!btn) return;
if (!this.contains(btn)) return; // Penting! btn bisa di luar parent
// handle...
});
// 3. CPU overhead minimal, tapi logic bisa jadi kompleks
// Jangan over-delegate — kalau cuma 2-3 tombol, langsung aja🎯 Challenge
<ul id="todo-list">
<li>Belajar DOM <button class="delete">❌</button></li>
<li>Latihan Event <button class="delete">❌</button></li>
<li>Buat Project <button class="delete">❌</button></li>
</ul>
<button id="add-todo">+ Tambah Todo</button>
<!-- Tulis JavaScript dengan EVENT DELEGATION:
1. Klik tombol ❌ → hapus <li> parent-nya
2. Klik "Tambah Todo" → tambah <li> baru (dengan tombol ❌)
3. Todo baru juga harus bisa dihapus (tanpa addEventListener baru!)
4. Semua pakai SATU event listener di #todo-list
-->2.4 Browser Default Actions
Browser punya kebiasaan bawaan untuk event tertentu. Klik link → pindah halaman. Submit form → reload. Klik kanan → muncul menu. Kadang kita mau mencegah kebiasaan ini.
Default Action yang Umum
| Event | Default Action |
|---|---|
Klik <a href> | Navigasi ke URL |
Submit <form> | Kirim data & reload |
| Klik kanan | Muncul context menu |
keydown di input | Karakter muncul |
mousedown pada teks | Mulai seleksi teks |
Mencegah Default Action
// Cara 1: event.preventDefault()
const link = document.querySelector("a");
link.addEventListener("click", function(event) {
event.preventDefault(); // Link tidak navigasi
alert("Link dicegah! URL: " + this.href);
});
// Cara 2: return false (hanya untuk on-event property, BUKAN addEventListener)
link.onclick = function() {
return false; // Sama dengan preventDefault + stopPropagation
};Contoh: Form Validation
<form id="register">
<input type="text" id="username" placeholder="Username (min 3 huruf)">
<button type="submit">Daftar</button>
</form>document.getElementById("register").addEventListener("submit", function(event) {
const username = document.getElementById("username").value;
if (username.length < 3) {
event.preventDefault(); // Form TIDAK di-submit
alert("Username minimal 3 karakter!");
}
});Contoh: Custom Context Menu
document.addEventListener("contextmenu", function(event) {
event.preventDefault(); // Menu bawaan tidak muncul
// Tampilkan menu custom
const menu = document.getElementById("custom-menu");
menu.style.left = event.clientX + "px";
menu.style.top = event.clientY + "px";
menu.hidden = false;
});
// Klik di mana saja → sembunyikan menu
document.addEventListener("click", function() {
document.getElementById("custom-menu").hidden = true;
});Cek Apakah Default Dicegah
elem.addEventListener("click", function(event) {
if (event.defaultPrevented) {
// Seseorang sudah preventDefault sebelumnya (di child)
return;
}
// Handle...
});Option passive
// Untuk scroll/touch event — bilang ke browser: "Saya TIDAK akan preventDefault"
// Browser bisa optimize scrolling (lebih smooth)
document.addEventListener("touchstart", handler, { passive: true });
// Kalau passive:true tapi tetap panggil preventDefault → warning di console// 1. preventDefault() TIDAK menghentikan bubbling!
// Kalau mau keduanya: preventDefault() + stopPropagation()
// 2. Beberapa event tidak bisa dicegah
// Cek event.cancelable sebelum preventDefault
if (event.cancelable) {
event.preventDefault();
}
// 3. Jangan disable context menu di seluruh halaman
// User butuh itu! Hanya disable di area spesifik kalau memang perlu
// 4. return false di addEventListener TIDAK sama dengan preventDefault
elem.addEventListener("click", function() {
return false; // Tidak ada efek! Hanya bekerja di onclick property
});🎯 Challenge
<nav>
<a href="https://google.com" data-confirm="true">Google (konfirmasi dulu)</a>
<a href="https://github.com" data-confirm="true">GitHub (konfirmasi dulu)</a>
<a href="https://javascript.info">JS Info (langsung)</a>
</nav>
<!-- Tulis JavaScript untuk:
1. Link dengan data-confirm="true" → tanya konfirmasi dulu sebelum navigasi
2. Kalau user cancel → preventDefault, tampilkan "Navigasi dibatalkan"
3. Link tanpa data-confirm → biarkan normal
4. Pakai event delegation (satu handler di <nav>)
-->2.5 Dispatching Custom Events
Selama ini kita mendengarkan event yang dibuat browser (klik, ketik, dll). Sekarang kita belajar membuat event sendiri — kayak bikin bel custom yang bisa kamu tekan kapan saja.
Membuat dan Mengirim Event
// 1. Buat event
const myEvent = new Event("hello");
// 2. Kirim (dispatch) ke elemen
const elem = document.getElementById("target");
elem.dispatchEvent(myEvent);
// 3. Handler yang sudah dipasang akan jalan
elem.addEventListener("hello", function(event) {
console.log("Event hello diterima!");
});CustomEvent — Dengan Data Tambahan
// CustomEvent bisa bawa data via detail
const event = new CustomEvent("user-login", {
detail: {
username: "budi",
role: "admin"
},
bubbles: true, // Bisa bubble ke parent
cancelable: true // Bisa di-preventDefault
});
elem.dispatchEvent(event);
// Handler menerima data
elem.addEventListener("user-login", function(event) {
console.log("User:", event.detail.username); // "budi"
console.log("Role:", event.detail.role); // "admin"
});Event Options
new CustomEvent("nama", {
bubbles: true, // Default: false. True = event naik ke parent
cancelable: true, // Default: false. True = bisa preventDefault
detail: { ... } // Data custom apapun
});Cek Apakah Event Dicegah
const event = new CustomEvent("my-event", { cancelable: true });
elem.addEventListener("my-event", function(event) {
event.preventDefault(); // Cegah!
});
const wasNotCancelled = elem.dispatchEvent(event);
// dispatchEvent return false kalau ada yang preventDefault
if (!wasNotCancelled) {
console.log("Event dicegah oleh handler");
} else {
console.log("Event tidak dicegah, lanjutkan aksi");
}Contoh Praktis: Komponen Tab
// Komponen tab mengirim event saat tab berubah
class TabPanel {
constructor(elem) {
this.elem = elem;
this.elem.addEventListener("click", (e) => {
const tab = e.target.closest("[data-tab]");
if (!tab) return;
this.switchTab(tab.dataset.tab);
});
}
switchTab(tabName) {
// Kirim event sebelum switch
const event = new CustomEvent("tab-change", {
detail: { tab: tabName },
bubbles: true,
cancelable: true
});
if (this.elem.dispatchEvent(event)) {
// Tidak dicegah → lakukan switch
console.log("Switching to:", tabName);
}
}
}
// Parent bisa mendengarkan dan mencegah
document.addEventListener("tab-change", function(event) {
if (event.detail.tab === "admin" && !isAdmin) {
event.preventDefault();
alert("Akses ditolak!");
}
});Event dalam Event (Synchronous)
// dispatchEvent itu SYNCHRONOUS — handler langsung jalan
btn.addEventListener("click", function() {
console.log("1. Sebelum dispatch");
elem.dispatchEvent(new Event("custom"));
console.log("3. Setelah dispatch");
});
elem.addEventListener("custom", function() {
console.log("2. Custom handler"); // Jalan di tengah!
});
// Output: 1, 2, 3// 1. Custom event TIDAK bubble secara default
new Event("my-event"); // bubbles: false!
new Event("my-event", { bubbles: true }); // Harus eksplisit
// 2. addEventListener harus dipasang SEBELUM dispatchEvent
// Kalau dispatch dulu, handler belum ada → tidak tertangkap
// 3. Nama event: gunakan kebab-case untuk custom event
// "user-login" ✅ (jelas custom)
// "userLogin" ⚠️ (bisa bingung dengan native event)
// 4. event.isTrusted
// true = dari user (klik asli)
// false = dari script (dispatchEvent)🎯 Challenge
// Buat sistem notifikasi custom:
// 1. Buat fungsi notify(message, type) yang dispatch CustomEvent "notification"
// - detail: { message, type } (type: "success", "error", "info")
// - bubbles: true
// 2. Pasang listener di document yang menangkap event "notification"
// 3. Handler: buat <div class="notification {type}"> dan tampilkan di halaman
// 4. Notifikasi hilang otomatis setelah 3 detik
// 5. Test: notify("Berhasil disimpan!", "success")Ringkasan Bab 2
| Sub-bab | Inti |
|---|---|
| Browser Events | addEventListener (recommended), event object, handler |
| Bubbling & Capturing | Event naik dari target ke document (bubble), turun saat capture |
| Event Delegation | Satu handler di parent, cek target — efisien untuk banyak elemen |
| Default Actions | preventDefault() untuk cegah behavior bawaan browser |
| Custom Events | new CustomEvent() + dispatchEvent() — buat event sendiri |
Prinsip utama:
- Selalu pakai
addEventListener(bukan onclick) - Manfaatkan bubbling dengan event delegation
event.target= yang diklik,event.currentTarget= yang punya handler- Custom event untuk komunikasi antar komponen
Next: Bab 3 — UI Events (mouse, keyboard, scroll — detail interaksi user)
Sudah paham materi ini?
Tandai sebagai selesai untuk melacak progress-mu.