Selama ini kita memanggil event.preventDefault() tanpa pernah membahasnya secara khusus — di handler submit, pemanggilan itu sudah menjadi kebiasaan yang wajib. Sekarang waktunya memahami mesinnya: browser mengikuti setiap interaksi dengan rencana bawaan (default behavior) yang sudah ditentukan sebelum kode kita ikut bicara. Link akan bernavigasi, tombol submit akan mengirim form, kotak centang akan berubah status. JavaScript punya satu kata untuk menolak rencana itu, dan satu kata lagi untuk keperluan lain yang sering tertukar: stopPropagation.

Dua method ini bekerja di dua lapisan yang berbeda. preventDefault() membatalkan aksi bawaan browser untuk event tersebut, sedangkan stopPropagation() menghentikan perjalanan event merambat ke elemen lain (lihat kembali chapter Event Bubbling). Keduanya tidak saling menggantikan — dan chapter ini membedah keduanya dengan uji nyata di halaman toko Budi.

Dua Jenis “Henti” yang Berbeda #

preventDefault()stopPropagation()
Yang dihentikanaksi bawaan browserperambatan event ke elemen lain
Listener lain tetap berjalanyatidak
Contoh dampaknyalink tidak berpindah halamanlistener di pembungkus tidak merasakan klik

Satu bantuan kecil dari API: setelah preventDefault() dipanggil, properti event.defaultPrevented bernilai true — listener lain bisa membaca flag itu untuk tahu bahwa aksi bawaan sudah dibatalkan. Tidak ada padanan serupa untuk stopPropagation().

Perlu dicatat pula bahwa pembatalan hanya bekerja pada event yang memang bisa dibatalkan. Setiap object event membawa flag cancelable: click, submit, dan keydown umumnya bernilai true, sedangkan event seperti input dan scroll tidak bisa dibatalkan. Memanggil preventDefault() pada event yang tidak bisa dibatalkan tidak menghasilkan galat — dia hanya tidak berpengaruh apa pun, dan defaultPrevented tetap false.

Dua perilaku bawaan yang paling sering kita batal: navigasi link dan pengiriman form. Halaman toko Budi punya keduanya — kartu buku dengan tautan pratinjau, dan form langganan buletin:

html
<div id="kartu-produk">
  <a id="tautan-preview" href="/pratinjau/kisah-dari-aceh">Pratinjau Buku</a>
</div>

<form id="form-langganan">
  <input id="email-langganan" name="email" type="email" placeholder="email untuk buletin">
  <button type="submit">Berlangganan</button>
</form>
// hentikan-bawaan.js — preventDefault menghentikan aksi bawaan browser
const tautan = document.querySelector("#tautan-preview");
const kartu = document.querySelector("#kartu-produk");
const form = document.querySelector("#form-langganan");

// 1. link: cegah navigasi, tampilkan pratinjau sendiri
tautan.addEventListener("click", function (event) {
  event.preventDefault();
  console.log("Pratinjau dibuka sebagai panel, bukan halaman baru.");
  console.log("defaultPrevented:", event.defaultPrevented);
});

// 2. pembungkus tetap menerima event yang sama
kartu.addEventListener("click", function () {
  console.log("Listener kartu ikut berjalan — perambatan tidak dihentikan.");
});

// 3. form: cegah reload, catat email
form.addEventListener("submit", function (event) {
  event.preventDefault();
  const email = form.querySelector("#email-langganan").value;
  console.log("Buletin dikirim ke:", email);
});

Pengujian: alamat [email protected] diisi ke form, tautan pratinjau diklik, lalu form dikirim. Di lingkungan uji jsdom navigasi memang tidak pernah terjadi — yang kita buktikan di sini adalah status pembatalan dan urutan listener, dan keduanya dicatat persis seperti console browser.

Output yang Dihasilkan (console browser):

text
Pratinjau dibuka sebagai panel, bukan halaman baru.
defaultPrevented: true
Listener kartu ikut berjalan — perambatan tidak dihentikan.
Buletin dikirim ke: [email protected]

Pembedahan tiga skenarionya:

  1. Baris 7–11 (tautan pratinjau): preventDefault() membatalkan navigasi ke /pratinjau/kisah-dari-aceh; di browser sungguhan halaman tidak berpindah dan kode kita bebas menampilkan pratinjau dengan caranya sendiri. Flag defaultPrevented di baris 10 membuktikan pembatalan itu tercatat.
  2. Baris 14–16 (pembungkus kartu): baris ketiga keluaran penting untuk pembeda nanti — pembatalan aksi bawaan sama sekali tidak mengganggu perambatan, sehingga listener di #kartu-produk tetap menerima klik yang sama.
  3. Baris 19–23 (form langganan): pola yang sudah akrab dari chapter sebelumnya. Pembatalan di sini menyelamatkan isian: tanpa preventDefault(), halaman akan memuat ulang dan alamat yang baru diketik pengguna menghilang sebelum sempat diproses.

stopPropagation Bukan Pengganti preventDefault #

Sekarang balik arahnya: bagaimana bila yang kita inginkan justru sebaliknya — perambatan berhenti, tetapi aksi bawaan tetap berjalan? Dua kartu berikut menampung dua skenario dengan konfigurasi listener yang identik, kecuali satu method yang dipanggil:

html
<div id="kartu-a"><a id="tautan-a" href="/pratinjau/a">Pratinjau A</a></div>
<div id="kartu-b"><a id="tautan-b" href="/pratinjau/b">Pratinjau B</a></div>
// hentikan-vs-rambat.js — preventDefault dan stopPropagation: dua tujuan berbeda
const kartuA = document.querySelector("#kartu-a");
const kartuB = document.querySelector("#kartu-b");
const tautanA = document.querySelector("#tautan-a");
const tautanB = document.querySelector("#tautan-b");

// pembungkus masing-masing mencatat sampainya event
for (const kartu of [kartuA, kartuB]) {
  kartu.addEventListener("click", function () {
    console.log("[kartu] click sampai di pembungkus");
  });
}

// skenario A — preventDefault: aksi bawaan dibatalkan, perambatan tetap jalan
tautanA.addEventListener("click", function (event) {
  event.preventDefault();
  console.log("[A] defaultPrevented:", event.defaultPrevented);
});

// skenario B — stopPropagation: perambatan dihentikan, aksi bawaan tetap jalan
tautanB.addEventListener("click", function (event) {
  event.stopPropagation();
  console.log("[B] defaultPrevented:", event.defaultPrevented);
});

Masing-masing tautan diklik sekali:

Output yang Dihasilkan (console browser):

text
[A] defaultPrevented: true
[kartu] click sampai di pembungkus
[B] defaultPrevented: false
  1. Skenario A (baris 15–18): klik pada #tautan-a memicu listener tautan lebih dulu, lalu merambat naik ke #kartu-a — baris [kartu] membuktikannya. Navigasi batal, tetapi kartu tetap tahu kliknya.
  2. Skenario B (baris 21–24): stopPropagation() memutus perjalanan event sebelum mencapai #kartu-b, sehingga tidak ada baris [kartu] untuk tautan B. Tetapi perhatikan defaultPrevented: false: aksi bawaan tidak disentuh sama sekali. Di browser sungguhan, tautan B akan tetap berpindah halaman — satu-satunya yang hilang adalah informasi klik di pembungkus.

Kesalahan klasiknya kini terlihat jelas: programmer yang ingin “link tidak berpindah halaman” tetapi menulis stopPropagation() akan mendapat kartu yang diam, lalu keheranan ketika halaman tetap berpindah. Kedua method itu menyelesaikan persoalan berbeda, dan bila kedua kebutuhan muncul bersamaan, panggil keduanya di listener yang sama.

Perilaku Bawaan yang Lain #

Navigasi dan pengiriman bukan satu-satunya rencana bawaan browser. Beberapa yang paling sering ditemui dalam pekerjaan sehari-hari:

  • Menekan Enter di dalam kolom teks mengirim form yang membungkusnya — itulah sebabnya listener submit wajib dipasang di form, bukan di tombol.
  • Tombol Tab memindahkan fokus ke elemen berikutnya; mencetanya untuk pola alur khusus (misalnya dialog) harus mempertimbangkan ulang navigasi keyboard.
  • Klik kanan membuka menu konteks, dan penekanan spasi pada kotak centang mengubah statusnya.
  • Menyeret (drag) sebuah link membawa alamatnya keluar dari halaman.

Daftar ini tidak perlu dihafal — yang penting kesadarannya: setiap interaksi bawa punya rencana bawaan, dan kita hanya perlu membatalnya saat rencana itu bertentangan dengan perilaku yang kita susun. Untuk kebutuhan yang lebih khusus lagi, browser menyediakan event beforeunload (peringatan sebelum halaman ditutup) dan API lain yang dibahas di section lanjutan; di sinilah batas chapter ini.

Memilih dengan Sadar #

Beberapa keputusan yang layak dijadikan kebiasaan:

  • Form aplikasi: selalu preventDefault() pada submit bila halaman dielola dengan JavaScript — pola yang kita pakai sejak chapter Handling Form Submission.
  • Tombol bantu: gunakan type="button" alih-alih membatalkan klik tombol submit; niatnya lebih jelas bagi pembaca kode.
  • Link berperilaku khusus: preventDefault() pada click, lalu jalankan perilakunya sendiri. Tetap beri atribut href yang benar agar link tetap sah bagi pembuka halaman (browser) dan pembaca layar (screen reader).
  • Pembungkus yang perlu tahu interaksi: pastikan tidak ada stopPropagation di jalur atasnya, atau daftarkan listener pada fase capture bila benar-benar diperlukan (chapter Event Capturing).

Dengan dua method ini, kita kini mengendalikan dua lapisan perilaku event sekaligus: apa yang browser lakukan sebagai reaksi, dan sejauh mana event itu disampaikan ke elemen lain. Bagian penutup section ini melangkah ke sisi lain form — memastikan umpan baliknya sampai ke semua pengguna, termasuk yang mengandalkan pembaca layar.

Berkas Sumber

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

hentikan-bawaan.js

Unduh berkas
javascript
// hentikan-bawaan.js — preventDefault menghentikan aksi bawaan browser
const tautan = document.querySelector("#tautan-preview");
const kartu = document.querySelector("#kartu-produk");
const form = document.querySelector("#form-langganan");

// 1. link: cegah navigasi, tampilkan pratinjau sendiri
tautan.addEventListener("click", function (event) {
  event.preventDefault();
  console.log("Pratinjau dibuka sebagai panel, bukan halaman baru.");
  console.log("defaultPrevented:", event.defaultPrevented);
});

// 2. pembungkus tetap menerima event yang sama
kartu.addEventListener("click", function () {
  console.log("Listener kartu ikut berjalan — perambatan tidak dihentikan.");
});

// 3. form: cegah reload, catat email
form.addEventListener("submit", function (event) {
  event.preventDefault();
  const email = form.querySelector("#email-langganan").value;
  console.log("Buletin dikirim ke:", email);
});

hentikan-vs-rambat.js

Unduh berkas
javascript
// hentikan-vs-rambat.js — preventDefault dan stopPropagation: dua tujuan berbeda
const kartuA = document.querySelector("#kartu-a");
const kartuB = document.querySelector("#kartu-b");
const tautanA = document.querySelector("#tautan-a");
const tautanB = document.querySelector("#tautan-b");

// pembungkus masing-masing mencatat sampainya event
for (const kartu of [kartuA, kartuB]) {
  kartu.addEventListener("click", function () {
    console.log("[kartu] click sampai di pembungkus");
  });
}

// skenario A — preventDefault: aksi bawaan dibatalkan, perambatan tetap jalan
tautanA.addEventListener("click", function (event) {
  event.preventDefault();
  console.log("[A] defaultPrevented:", event.defaultPrevented);
});

// skenario B — stopPropagation: perambatan dihentikan, aksi bawaan tetap jalan
tautanB.addEventListener("click", function (event) {
  event.stopPropagation();
  console.log("[B] defaultPrevented:", event.defaultPrevented);
});