htmx memberi kabar di setiap langkah request-nya lewat event DOM: sebelum request dikirim, sesudah balasan ditukar, saat server menolak, dan seterusnya. Kamu bisa mendengarkan event itu untuk menjalankan JavaScript kecil tepat di saat yang pas. Bayangin notifikasi WhatsApp dari Laundry Bu Rini: "cucian diterima", "sedang dicuci", "siap diambil". Kamu gak perlu menelepon untuk bertanya, cukup bereaksi saat pesannya datang. Event htmx adalah pesan-pesan itu, dan addEventListener atau hx-on adalah HP yang menerimanya.
🧪 Tebakan cepat: Order Bu Tini dikirim dan berhasil. Mana yang dikirim htmx lebih dulu:
htmx:afterSwapatauhtmx:afterRequest? Jawabannya ada di bagian "Urutan event satu request".
Event itu apa
Event htmx adalah event DOM biasa, sama seperti click. htmx mengirimnya ke sebuah elemen, lalu event itu naik (bubble) ke induknya sampai document. Namanya diawali htmx: dan ditulis camelCase, misalnya htmx:afterRequest. htmx juga mengirim versi huruf kecil dengan tanda hubung, htmx:after-request, untuk dipakai di atribut HTML. Detailnya ada di event.detail: elt (elemen yang mengirim), target, xhr (request-nya, misalnya xhr.status), dan successful (true untuk balasan 2xx).
Urutan event satu request
Ini urutan nyata dari htmx 2.0.11 saat form Terima cucian mengirim POST /order dengan target #papan-order:
| Event | Dikirim ke | Kapan |
|---|---|---|
htmx:configRequest |
form | request sedang disusun, parameter dan header masih bisa diubah |
htmx:beforeRequest |
form | tepat sebelum dikirim. event.preventDefault() di sini membatalkannya |
htmx:beforeSwap |
#papan-order |
balasan tiba, belum ditukar |
htmx:afterSwap |
#papan-order |
balasan sudah ditukar ke halaman |
htmx:afterRequest |
form | request selesai |
htmx:afterSettle |
#papan-order |
20 ms kemudian, sesudah atribut baru dipasang |
htmx:afterSwap datang sebelum htmx:afterRequest. Itu jawaban tebakannya. Kalau server membalas 4xx atau 5xx, urutannya beforeRequest, beforeSwap, htmx:responseError, lalu afterRequest dengan successful: false. Gak ada afterSwap dan afterSettle, karena gak ada yang ditukar.
hx-on: handler langsung di atribut
<form id="terima" hx-post="/order" hx-target="#papan-order" hx-swap="beforeend"
hx-on::before-request="console.log('Mengirim', event.detail.requestConfig.path)"
hx-on::after-request="console.log('Selesai', event.detail.xhr.status, event.detail.successful)">
Console sesudah Terima cucian diklik:
Mengirim /order
Selesai 200 true
hx-on::before-requestadalah singkatan darihx-on:htmx:before-request. Di atribut, nama event-nya pakai huruf kecil dan tanda hubung.- Di dalam handler ada
eventdanthis(elemen yang punya atribut itu). hx-on::before-request="if (!this.nama.value) event.preventDefault()"membatalkan request: gak ada yang dikirim.
addEventListener di body
document.body.addEventListener('htmx:afterSwap', (event) => {
console.log('Ditukar:', event.detail.target.id) // Ditukar: papan-order
})
document.body.addEventListener('htmx:responseError', (event) => {
console.log('Gagal:', event.detail.xhr.status) // Gagal: 500
})
Karena event naik ke atas, satu listener di body mendengar event dari semua elemen, termasuk elemen yang baru ditambahkan lewat swap. htmx:responseError cocok untuk memberi tahu pengguna bahwa server menolak, supaya klik gak terasa mati.
HX-Trigger: server yang memicu event
Server bisa menambahkan header di balasannya: HX-Trigger: orderBaru. Begitu balasan tiba, sebelum swap, htmx memicu event orderBaru di form yang mengirim, dan event itu naik ke body. Di Laravel: return response($html)->header('HX-Trigger', 'orderBaru');.
<span id="jumlah-hari-ini" hx-get="/jumlah" hx-trigger="orderBaru from:body">3 order hari ini</span>
- Penghitung ini mendengar
orderBarudibody, lalu mengirimGET /jumlahsendiri dan menjadi "4 order hari ini". - Tanpa
from:body, span ini gak mendengar apa-apa: event-nya naik dari form kebody, gak lewat span. - Bisa membawa data:
HX-Trigger: {"orderBaru": {"no": 15}}membuatevent.detail.nobernilai 15. - Bedanya dengan OOB: OOB mengirim HTML barunya. HX-Trigger cuma mengirim sinyal, dan tiap pendengar mengambil isinya sendiri.
⚠️ Jebakan yang sering ketemu
- camelCase atau satu titik dua di atribut. Nama atribut HTML jadi huruf kecil, jadi
hx-on::afterRequestmenungguhtmx:afterrequest, yang gak pernah ada. Tulishx-on::after-request. hx-on::after-swapdi form yang target-nya di tempat lain.afterSwapdikirim ke target, bukan ke form, jadi handler-nya gak jalan. Pasang di target, atau dengarkan dibody.- Listener di elemen yang nanti ditukar. Elemennya hilang, listener-nya ikut hilang. Pasang di
body. - HX-Trigger tanpa
from:body, atau nama yang beda huruf.orderBarudanorder-baruitu dua event berbeda. Kirim HX-Trigger cuma di balasan sukses: htmx tetap memicunya walau statusnya 422. - Di latihan: tombolnya butuh
hx-get="/api/data"danhx-on::before-requestyang isinya gak kosong, misalnyaconsole.log('Mengirim request...').
TL;DR: htmx memicu event DOM di tiap langkah: htmx:beforeRequest sebelum request dikirim (bisa dibatalkan dengan event.preventDefault()), htmx:afterSwap sesudah balasan ditukar, lalu htmx:afterRequest, dan htmx:responseError untuk 4xx atau 5xx. Request event dikirim ke elemen pengirim, swap event ke target. Dengarkan lewat hx-on::after-request="..." (huruf kecil, dua titik dua) atau document.body.addEventListener('htmx:afterSwap', ...). Header balasan HX-Trigger: orderBaru memicu event buatanmu di client, dan elemen lain bisa bereaksi dengan hx-trigger="orderBaru from:body".