Active search adalah kolom cari yang menampilkan hasil sambil kamu mengetik, tanpa tombol Cari. Namanya macam-macam: active search, live search, instant search, semuanya pola yang sama. Bayangin kasir Laundry Bu Rini mencari nama di buku order. Dia menunggu pelanggan selesai menyebut nama, baru membalik buku. Kalau pelanggan meralat di tengah jalan ("Wu... eh, Wulan"), kasir yang pintar berhenti mencari "Wu" dan langsung mencari "Wulan". Kalau pelanggan bilang "gak jadi", halaman yang terbuka ditutup lagi.
🧪 Tebakan cepat: Kolom cari di papan memakai
hx-trigger="keyup changed delay:500ms". Hasil untuk "wulan" sudah tampil, lalu kamu mengklik tanda × di ujung kolom. Kolomnya kosong. Apa yang terjadi pada daftar hasilnya? Jawabannya ada di bagian "Tombol × dan event search".
Polanya: satu kolom, satu daftar hasil
<input id="cari" name="cari" type="search" placeholder="Nama pelanggan"
hx-get="/cari"
hx-trigger="keyup changed delay:500ms, search"
hx-target="#hasil"
hx-indicator="#mencari"
hx-sync="this:replace">
<span id="mencari" class="htmx-indicator">Mencari...</span>
<ul id="hasil"></ul>
hx-get="/cari": nilai kolom ikut sebagai query,GET /cari?cari=wulan, karenaname-nyacari.hx-trigger: kapan request dikirim. Dibahas di dua bagian berikut.hx-target="#hasil": balasan masuk ke daftar, bukan menimpa kolomnya.hx-indicator="#mencari": "Mencari..." muncul selama request berjalan (pelajaran indikator loading).hx-sync="this:replace": request lama dibatalkan kalau ada yang baru. Dibahas di "Balasan basi dan hx-sync".
Server membalas baris-baris <li> saja, bukan halaman utuh:
<li>#8 Mbak Wulandari, 2 kg: Sudah diambil</li>
<li>#13 Mbak Wulan, 2 kg: Disetrika</li>
Mengetik "wulan": satu request
Dari pelajaran hx-trigger: keyup tanpa apa-apa mengirim satu request per huruf. delay:500ms menunggu 500 ms tanpa ketikan, dan changed mengabaikan tombol yang gak mengubah nilai (Shift, panah). Mengetik wulan dengan jeda 120 ms per huruf:
| Waktu | Kejadian |
|---|---|
| 0 sampai 480 ms | w, u, l, a, n: tiap huruf mengulang hitungan 500 ms |
| 980 ms | GET /cari?cari=wulan, satu-satunya request |
| 1.130 ms | balasan tiba, 2 baris masuk ke #hasil |
- Event-nya boleh
keyupatauinput.inputjuga menangkap teks yang di-paste lewat klik kanan. - Angka jedanya pilihanmu. 300 ms terasa lebih sigap, 500 ms lebih hemat request. Latihan memakai
delay:300ms.
Tombol × dan event search
Kolom type="search" di Chrome, Edge, dan Safari punya tanda × untuk mengosongkannya. Mengklik × gak memicu keyup, karena gak ada tombol keyboard yang dilepas. Jadi kolomnya kosong, tapi daftar hasil "wulan" tetap tampil. Itu jawaban tebakannya: daftar basi, dan pengguna bingung.
Browser yang sama mengirim event search saat × diklik dan saat Enter ditekan. Tambahkan sebagai pemicu kedua:
hx-trigger="keyup changed delay:500ms, search"
- × diklik:
searchlangsung mengirimGET /cari?cari=(tanpa jeda, karenasearchgak punyadelay), server membalas kosong,#hasilikut kosong. - Enter ditekan sebelum 500 ms habis: hitungan yang sedang berjalan dibatalkan, dan request langsung dikirim. Tetap satu request.
- Firefox gak mengirim
search. Kalau kamu butuh semua browser, pakaiinput changed delay:500ms: × memicuinput, jadi daftar ikut kosong 500 ms kemudian.
Balasan basi dan hx-sync
Misalnya server butuh 1.200 ms untuk wu (kata pendek, banyak yang dicocokkan) dan 150 ms untuk wulan. Kamu mengetik wu, berhenti 0,7 detik, lalu meneruskan lan:
| Waktu | Tanpa hx-sync |
Dengan hx-sync="this:replace" |
|---|---|---|
| 620 ms | GET /cari?cari=wu dikirim |
sama |
| 700 sampai 940 ms | l, a, n diketik |
sama |
| 1.440 ms | wulan siap dikirim, tapi wu belum dibalas: wulan antre |
wu dibatalkan, GET /cari?cari=wulan langsung dikirim |
| 1.590 ms | kolom berisi "wulan", daftar masih kosong | hasil wulan tampil, selesai |
| 1.820 ms | hasil wu yang basi tampil (3 baris), baru wulan dikirim |
|
| 1.970 ms | hasil wulan tampil |
- Bawaan htmx: request baru dari elemen yang sama antre di belakang yang masih berjalan. Urutannya gak tertukar, tapi hasil basi sempat tampil dan hasil yang benar telat 380 ms.
hx-sync="this:replace": request yang masih berjalan dibatalkan (abort), yang baru langsung jalan. Hasil basi gak pernah tampil.- Kalau kamu menulis
fetch()sendiri, dua request jalan bareng, dan balasanwuyang lambat bisa tiba sesudahwulanlalu menimpanya. Itu race condition yang klasik.
Isi hx-sync adalah elemen yang dijadikan patokan, titik dua, lalu strateginya:
| Strategi | Kalau sudah ada request berjalan |
|---|---|
replace |
yang lama dibatalkan, yang baru dikirim. Pilihan untuk pencarian |
drop |
yang baru dibuang. Ini bawaan kalau strateginya gak ditulis |
abort |
yang baru dibuang, sama seperti drop. Bedanya, request ini dibatalkan kalau elemen patokannya (misalnya form) mengirim request lain |
queue last |
yang baru antre, cuma yang terakhir yang disimpan |
hx-sync="this" tanpa strategi berarti drop: wulan dibuang, dan daftar berhenti di hasil wu.
throttle: kenapa gak cocok untuk pencarian
hx-trigger="keyup changed throttle:1s" mengirim huruf pertama langsung, lalu membuang ketikan lain selama 1 detik. Mengetik wulan dalam 0,5 detik cuma mengirim cari=w, dan wulan gak pernah terkirim kalau kamu berhenti mengetik. throttle membatasi laju, delay menunggu kamu selesai. Pencarian pakai delay. Latihan kedua memakai delay bersama dua ide dari pelajaran ini: event search dan hx-sync="this:replace".
⚠️ Jebakan yang sering ketemu
keyuptanpadelay. Satu request per huruf. Di latihan,<input>harus punyahx-trigger="keyup changed delay:300ms",hx-get="/api/search", danhx-target="#results".- Lupa
search. Kolom dikosongkan dengan ×, tapi daftar hasil lama tetap tampil. hx-sync="this"tanpa strategi. Bawaannyadrop, jadi ketikan terakhir dibuang. Tulisthis:replace.hx-indicatortanpa#.hx-indicator="search-spinner"mencari tag<search-spinner>. Tulishx-indicator="#search-spinner", dan elemennya harus ber-classhtmx-indicator.
TL;DR: Active search (live search) mengirim kolom cari sambil diketik: hx-get="/cari", hx-target="#hasil", dan hx-trigger="keyup changed delay:500ms, search". keyup (atau input) memicu, changed mengabaikan tombol yang gak mengubah nilai, delay menunggu ketikan terakhir, dan search menangkap tombol × dan Enter di Chrome, Edge, dan Safari. Tanpa search, mengosongkan kolom lewat × membiarkan hasil lama tampil. hx-sync="this:replace" membatalkan request lama yang masih berjalan, jadi hasil basi gak pernah tampil. hx-sync="this" saja berarti drop dan membuang ketikan terakhir. throttle membuang ketikan terakhir juga, jadi untuk pencarian pakai delay.