Skip to content

Membuat Aplikasi Pencarian Langsung (Live Search) dengan HTMX

Published: at 00.03
<div class="search-container">
  <input
    type="text"
    name="search"
    hx-post="/search"
    hx-trigger="keyup changed delay:500ms"
    hx-target="#search-results"
    hx-indicator=".htmx-indicator"
    placeholder="Cari..."
  />
  <div class="htmx-indicator">Mencari...</div>
</div>

<div id="search-results"></div>
@app.route('/search', methods=['POST'])
def search():
    query = request.form.get('search')
    results = perform_search(query)  # Fungsi untuk melakukan pencarian di database atau sumber data lainnya

    if not results:
        return '<p>Tidak ada hasil yang ditemukan.</p>'

    return render_template('search_results.html', results=results)

Penjelasan:

  1. Kita menambahkan elemen <div> dengan kelas “search-container” untuk membungkus input pencarian dan indikator loading.
  2. Atribut hx-indicator=".htmx-indicator" digunakan untuk menampilkan indikator loading saat permintaan pencarian sedang berlangsung.
  3. Pada sisi server, endpoint /search menerima kueri pencarian dari permintaan POST.
  4. Fungsi perform_search() digunakan untuk melakukan pencarian berdasarkan kueri yang diterima. Fungsi ini dapat melibatkan logika pencarian yang kompleks, seperti pencarian teks penuh, pencarian berbasis kategori, atau pengurutan hasil berdasarkan relevansi.
  5. Jika tidak ada hasil yang ditemukan, kita mengembalikan pesan “Tidak ada hasil yang ditemukan” dalam format HTML.
  6. Jika ada hasil pencarian, kita merender template search_results.html dengan meneruskan hasil pencarian ke template tersebut.

Template search_results.html dapat berisi logika untuk menampilkan hasil pencarian dengan format yang lebih kaya, seperti menampilkan gambar, deskripsi, atau informasi tambahan lainnya.

14.2 Membangun Aplikasi Daftar Tugas (To-Do List) dengan HTMX

<div id="todo-app">
  <form hx-post="/add-task">
    <input type="text" name="task" placeholder="Tugas baru..." required />
    <button type="submit">Tambah</button>
  </form>

  <div id="task-list">
    {% for task in tasks %}
    <div class="task" id="task-{{ task.id }}">
      <input
        type="checkbox"
        hx-put="/toggle-task/{{ task.id }}"
        {%
        if
        task.completed
        %}checked{%
        endif
        %}
      />
      <span
        class="task-name"
        {%
        if
        task.completed
        %}style="text-decoration: line-through;"
        {%
        endif
        %}
        >{{ task.name }}</span
      >
      <button
        hx-delete="/delete-task/{{ task.id }}"
        hx-target="#task-{{ task.id }}"
      >
        Hapus
      </button>
    </div>
    {% endfor %}
  </div>
</div>
@app.route('/add-task', methods=['POST'])
def add_task():
    name = request.form.get('task')
    new_task = Task(name=name, completed=False)
    db.session.add(new_task)
    db.session.commit()

    return render_template('task_list.html', tasks=Task.query.all())

@app.route('/toggle-task/<int:task_id>', methods=['PUT'])
def toggle_task(task_id):
    task = Task.query.get_or_404(task_id)
    task.completed = not task.completed
    db.session.commit()

    return ''

@app.route('/delete-task/<int:task_id>', methods=['DELETE'])
def delete_task(task_id):
    task = Task.query.get_or_404(task_id)
    db.session.delete(task)
    db.session.commit()

    return ''

Penjelasan:

  1. Aplikasi daftar tugas ditampilkan dalam elemen <div> dengan id “todo-app”.
  2. Formulir untuk menambahkan tugas baru menggunakan atribut hx-post="/add-task" untuk mengirimkan permintaan POST saat formulir dikirim.
  3. Daftar tugas ditampilkan menggunakan perulangan di dalam elemen <div> dengan id “task-list”.
  4. Setiap tugas memiliki checkbox untuk menandai tugas sebagai selesai atau belum selesai. Atribut hx-put="/toggle-task/{{ task.id }}" digunakan untuk mengirimkan permintaan PUT saat checkbox diklik.
  5. Nama tugas ditampilkan dengan tampilan yang berbeda tergantung pada status tugas (selesai atau belum selesai) menggunakan kondisional dalam templat.
  6. Tombol “Hapus” menggunakan atribut hx-delete="/delete-task/{{ task.id }}" untuk mengirimkan permintaan DELETE saat tombol diklik. Atribut hx-target="#task-{{ task.id }}" digunakan untuk menghapus elemen tugas yang sesuai setelah penghapusan berhasil.
  7. Pada sisi server, endpoint /add-task menerima data tugas baru dari permintaan POST, membuat instance Task baru, dan menyimpannya ke database. Kemudian, daftar tugas yang diperbarui dikirimkan kembali dalam respons.
  8. Endpoint /toggle-task/<int:task_id> menerima ID tugas dan memperbarui status tugas (selesai atau belum selesai) di database.
  9. Endpoint /delete-task/<int:task_id> menerima ID tugas dan menghapus tugas yang sesuai dari database.

Dengan pendekatan ini, aplikasi daftar tugas menjadi lebih interaktif dan responsif. Pengguna dapat menambahkan tugas baru, menandai tugas sebagai selesai, dan menghapus tugas tanpa memuat ulang seluruh halaman.

14.3 Mengimplementasikan Fitur Endless Scrolling dengan HTMX

<div
  id="post-list"
  hx-get="/load-posts"
  hx-trigger="revealed"
  hx-swap="afterend"
  hx-target="#post-list"
>
  {% for post in posts %}
  <div class="post">
    <h3>{{ post.title }}</h3>
    <p>{{ post.content }}</p>
    <img src="{{ post.image_url }}" alt="{{ post.title }}" />
    <a href="/posts/{{ post.id }}">Baca Selengkapnya</a>
  </div>
  {% endfor %}
</div>
@app.route('/load-posts')
def load_posts():
    offset = int(request.args.get('offset', 0))
    limit = 10
    posts = Post.query.order_by(Post.id.desc()).offset(offset).limit(limit).all()

    if not posts:
        return '', 404

    return render_template('post_list.html', posts=posts)

Penjelasan:

  1. Daftar posting awal ditampilkan dalam elemen <div> dengan id “post-list”.
  2. Atribut hx-get="/load-posts" digunakan untuk mengirimkan permintaan GET ke endpoint /load-posts saat pemicu revealed terpenuhi.
  3. Atribut hx-swap="afterend" menentukan bahwa respons akan disisipkan setelah elemen #post-list.
  4. Atribut hx-target="#post-list" digunakan untuk memastikan bahwa respons akan disisipkan ke dalam elemen #post-list.
  5. Setiap posting dalam daftar ditampilkan dengan informasi seperti judul, konten, gambar, dan tautan untuk membaca selengkapnya.
  6. Pada sisi server, endpoint /load-posts menerima parameter offset dari permintaan GET untuk menentukan offset saat mengambil posting berikutnya dari database.
  7. Posting diambil dari database dengan menggunakan query yang sesuai, termasuk pengurutan berdasarkan ID posting secara menurun (posting terbaru di atas) dan pembatasan jumlah posting yang diambil (limit).
  8. Jika tidak ada posting yang ditemukan (mencapai akhir daftar), endpoint mengembalikan respons dengan kode status 404.
  9. Jika ada posting yang ditemukan, template post_list.html di-render dengan posting yang diambil dan dikembalikan sebagai respons.

Dengan menggunakan atribut hx-trigger="revealed", permintaan untuk memuat posting berikutnya akan dikirimkan secara otomatis saat pengguna mencapai bagian bawah daftar posting. Respons yang diterima akan disisipkan setelah daftar posting yang ada, memberikan efek endless scrolling yang mulus.

14.4 Membuat Fitur Autocomplete dengan HTMX

<div class="autocomplete">
  <input
    type="text"
    name="search"
    hx-post="/autocomplete"
    hx-trigger="keyup changed delay:300ms"
    hx-target="#suggestions"
    placeholder="Cari..."
  />
  <div id="suggestions"></div>
</div>
@app.route('/autocomplete', methods=['POST'])
def autocomplete():
    query = request.form.get('search')
    suggestions = search_suggestions(query)  # Fungsi untuk mencari saran berdasarkan input pengguna

    if not suggestions:
        return '<p>Tidak ada saran.</p>'

    return render_template('suggestions.html', suggestions=suggestions)

Template suggestions.html:

<ul>
  {% for suggestion in suggestions %}
  <li hx-get="/load-data/{{ suggestion.id }}">{{ suggestion.name }}</li>
  {% endfor %}
</ul>

Penjelasan:

  1. Elemen <div> dengan kelas “autocomplete” digunakan untuk membungkus input pencarian dan daftar saran.
  2. Atribut hx-post="/autocomplete" digunakan untuk mengirimkan permintaan POST ke endpoint /autocomplete saat pemicu keyup dan changed terpenuhi, dengan jeda 300ms.
  3. Atribut hx-target="#suggestions" menentukan bahwa respons akan ditampilkan dalam elemen dengan id “suggestions”.
  4. Pada sisi server, endpoint /autocomplete menerima input pengguna dari permintaan POST.
  5. Fungsi search_suggestions() digunakan untuk mencari saran berdasarkan input pengguna. Fungsi ini dapat melibatkan logika pencarian yang kompleks, seperti pencarian berbasis prefiks, pencarian berbasis similaritas, atau pengambilan saran dari sumber data eksternal.
  6. Jika tidak ada saran yang ditemukan, endpoint mengembalikan pesan “Tidak ada saran” dalam format HTML.
  7. Jika ada saran yang ditemukan, template suggestions.html di-render dengan saran yang ditemukan dan dikembalikan sebagai respons.
  8. Dalam template suggestions.html, setiap saran ditampilkan sebagai elemen <li> dengan atribut hx-get yang mengarah ke endpoint /load-data/{{ suggestion.id }}. Ini memungkinkan pemuatan data tambahan terkait saran saat pengguna mengklik saran tertentu.

Dengan pendekatan ini, fitur autocomplete menjadi lebih kaya dan interaktif. Saat pengguna mengetikkan input, saran yang relevan akan ditampilkan secara real-time. Pengguna dapat mengklik saran untuk memuat data tambahan terkait saran tersebut tanpa memuat ulang seluruh halaman.