Halaman keranjang belanja toko Budi terdiri atas dua bagian yang dikerjakan orang berbeda: tombol “Tambah Kopi Gayo” yang dikembangkan Siti, dan widget ringkasan keranjang milik tim lain. Cara paling mudah menghubungkan keduanya adalah memanggil function widget langsung dari handler tombol — tetapi cara itu menyisakan ikatan yang rapat: setiap kali tombol baru muncul (tombol di kartu produk, di halaman promo), kodenya harus tahu isi dan nama function widget keranjang. Satu perubahan kecil di widget merambat ke semua pemanggilnya.

Browser menawarkan jalan lain yang sudah kita kenal sepanjang section ini: komunikasi lewat peristiwa. Dengan CustomEvent, kode kita bisa membuat peristiwa dengan nama sendiri, melampirkan data di dalamnya, lalu mengirimkannya ke DOM — dan bagian halaman mana pun yang berminat cukup mendengarkan, tanpa saling mengenal satu sama lain. Chapter ini menutup section Browser Events dengan pola tersebut, diuji lewat jsdom seperti biasa.

Membuat dan Mengirim Peristiwa Sendiri #

Dua langkah menyusun peristiwa buatan sendiri. Pertama, membuatnya: new CustomEvent(nama, { detail }) — argumen pertama bebas berupa nama yang kita pilih, dan properti detail menampung data apa pun yang perlu dibawa: string, angka, array, sampai object bertingkat. Kedua, mengirimnya: dispatchEvent(peristiwa) dipanggil pada elemen tempat peristiwa berangkat. Dari sana, peristiwa berperilaku persis seperti peristiwa bawaan — terdengar oleh listener yang cocok, dan merambat bila opsi bubbles: true disertakan.

Perlu ditegaskan: CustomEvent tidak menggantikan peristiwa bawaan browser. Klik tetap click, ketikan tetap keydown. Peristiwa custom mengisi celah yang tidak disediakan browser — peristiwa tingkat aplikasi seperti “produk ditambahkan”, “pengguna masuk”, atau “keranjang berubah”.

Satu penanda membedakan asal peristiwa: properti isTrusted bernilai true hanya untuk peristiwa yang dikirim browser sebagai hasil interaksi pengguna, dan false untuk peristiwa yang dikirim lewat dispatchEvent. Handler yang kritis — terutama yang memicu aksi penting — bisa memeriksa penanda ini agar hanya menuruti peristiwa asli, bukan peristiwa buatan kode.

Sama seperti pendaftaran biasa, listener peristiwa custom menerima opsi pada argumen ketiga: { once: true } untuk pendengar sekali pakai, capture untuk fase turun, dan lainnya. Peristiwa buatan sendiri diperlakukan identik dengan peristiwa bawaan — termasuk pelepasannya dengan removeEventListener.

Mendengarkan Peristiwa Buatan Sendiri #

Struktur halaman kita sederhana: satu tombol di dalam wadah keranjang:

html
<div id="keranjang">
  <p>Keranjang kosong</p>
  <button id="tambah">Tambah Kopi Gayo</button>
</div>
// event-keranjang.js — CustomEvent dengan detail dan dispatchEvent
const tombolTambah = document.querySelector("#tambah");
const keranjang = document.querySelector("#keranjang");

// widget keranjang mendengarkan peristiwa buatannya sendiri
keranjang.addEventListener("tambah-produk", function (event) {
  console.log(`Keranjang menerima: ${event.detail.produk} x${event.detail.jumlah}`);
});

// tombol memicu peristiwa custom saat diklik
tombolTambah.addEventListener("click", function () {
  const peristiwa = new CustomEvent("tambah-produk", {
    detail: { produk: "Kopi Gayo", jumlah: 2 },
    bubbles: true,
  });
  tombolTambah.dispatchEvent(peristiwa);
});

// peristiwa aplikasi juga bisa diumumkan lewat document
document.addEventListener("user-login", function (event) {
  console.log(`Selamat datang, ${event.detail.nama}!`);
});
document.dispatchEvent(new CustomEvent("user-login", { detail: { nama: "Siti" } }));

Berkas ini menghasilkan keluaran saat dimuat — pengumuman user-login dikirim begitu kode berjalan — dan baris kedua muncul setelah kita menekan tombol #tambah satu kali. Keluaran berikut dicatat dari eksekusi nyata berkas ini di lingkungan DOM jsdom.

Output yang Dihasilkan (console browser):

text
Selamat datang, Siti!
Keranjang menerima: Kopi Gayo x2

Mari kita telaah:

  1. Baris 6–8 (listener pada #keranjang): widget mendaftarkan dirinya untuk nama peristiwa "tambah-produk" — nama yang tidak ada di daftar peristiwa bawaan, murni ciptaan aplikasi kita.
  2. Baris 12–15 (membuat peristiwa dengan detail): properti detail membawa object { produk, jumlah }. Di dalam listener, data itu terbaca lewat event.detail — baris keluaran kedua membuktikan isinya sampai utuh.
  3. Baris 14 (bubbles: true): peristiwa dikirim dari tombol, sedangkan listener menempel di wadah #keranjang di atasnya. Tanpa opsi ini, peristiwa berhenti di tombol dan tidak pernah sampai — baris kedua keluaran tidak akan muncul.
  4. Baris 16 (dispatchEvent): pengirimannya ke tombolTambah, titik berangkat yang wajar untuk peristiwa “produk ditambahkan dari tombol ini”.
  5. Baris 20–23 (pengumuman lewat document): peristiwa user-login diumumkan dari puncak dokumen agar seluruh halaman bisa mendengarnya. Baris pertama keluaran — yang muncul saat halaman dimuat — membuktikan listener menerima detail.nama dengan benar.

Peristiwa Aplikasi lewat document #

Pola user-login di atas adalah pola pengumuman yang lazim di aplikasi nyata: modul autentikasi mengumumkan bahwa sesi dimulai, dan bagian halaman mana pun — header yang menampilkan nama, widget notifikasi, pencatat analitik — mendengarkan tanpa perlu diarahkan satu per satu. Tidak ada yang memanggil function siapa pun; yang terjadi hanyalah satu pengumuman dan beberapa pendengar sukarela.

Ikatan longgar seperti ini membuat pengerjaan tim terpisah tetap aman. Siti bisa menambah tombol baru di mana pun cukup dengan mengirim peristiwa "tambah-produk" yang sama; widget keranjang tidak perlu diubah sedikit pun. Bila suatu hari bagian lain ingin ikut bereaksi — penghitung stok, misalnya — cukup menambah listener baru untuk nama peristiwa yang sama.

Karena peristiwa custom juga bisa merambat, dua pola dari section ini saling menopang: pengumuman "tambah-produk" yang diberi bubbles: true bisa ditangkap satu listener di document — delegation untuk peristiwa aplikasi. Bila jumlah tombol produk bertambah puluhan, pengumumannya tetap satu dan pendengarnya tetap satu.

Bentuk pengumuman yang sama berguna untuk siklus aplikasi yang lebih luas: "cart-updated" setelah keranjang berubah, "filter-applied" setelah daftar difilter, atau "langkah-selesai" pada alur multi-langkah. Polanya konsisten: satu tempat mengirim, pendengar lain mengurus reaksinya.

Merancang detail yang Informatif #

Properti detail adalah kontrak antara pengirim dan pendengar. Isilah yang cukup lengkap membuat pendengar tidak perlu membaca DOM untuk melengkapi informasi: peristiwa tambah-produk membawa nama produk dan jumlah, bukan sekadar sinyal kosong yang menyuruh listener menebak. Sebaliknya, hindari memasukkan seluruh isi komponen pengirim — kirim data yang berarti bagi penerima, tidak lebih.

Bentuk data di dalam detail juga sebaiknya stabil dari waktu ke waktu: menambah properti baru aman bagi listener lama, mengubah makna properti lama tidak. Bila bentuk data harus berubah, peristiwa dengan nama baru lebih jujur daripada diam-diam mengganti isi peristiwa lama.

Dengan peristiwa custom, seluruh siklus interaksi halaman telah kita bahas: mendengarkan, membaca object event, memahami perjalanannya melalui capture dan bubble, mendelegasikan dengan hemat, membaca papan ketik dan pointer, mengelola form, hingga merancang peristiwa sendiri. Materi section ini adalah fondasi bagi section Forms dan Input yang berikutnya.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

event-keranjang.js

Unduh berkas
javascript
// event-keranjang.js — CustomEvent dengan detail dan dispatchEvent
const tombolTambah = document.querySelector("#tambah");
const keranjang = document.querySelector("#keranjang");

// widget keranjang mendengarkan peristiwa buatannya sendiri
keranjang.addEventListener("tambah-produk", function (event) {
  console.log(`Keranjang menerima: ${event.detail.produk} x${event.detail.jumlah}`);
});

// tombol memicu peristiwa custom saat diklik
tombolTambah.addEventListener("click", function () {
  const peristiwa = new CustomEvent("tambah-produk", {
    detail: { produk: "Kopi Gayo", jumlah: 2 },
    bubbles: true,
  });
  tombolTambah.dispatchEvent(peristiwa);
});

// peristiwa aplikasi juga bisa diumumkan lewat document
document.addEventListener("user-login", function (event) {
  console.log(`Selamat datang, ${event.detail.nama}!`);
});
document.dispatchEvent(new CustomEvent("user-login", { detail: { nama: "Siti" } }));