Files
thrillwiki_django_no_react/backend/templates/htmx/README.md

7.2 KiB

HTMX Templates and Patterns

This directory contains HTMX-specific templates and components for ThrillWiki.

Overview

HTMX is used throughout ThrillWiki for dynamic content updates without full page reloads. This guide documents the standardized patterns and conventions.

Directory Structure

htmx/
├── components/           # Reusable HTMX components
│   ├── confirm_dialog.html    # Confirmation modal
│   ├── error_message.html     # Error display
│   ├── filter_badge.html      # Filter tag/badge
│   ├── inline_edit_field.html # Inline editing
│   ├── loading_indicator.html # Loading spinners
│   └── success_toast.html     # Success notification
├── partials/             # HTMX response partials
└── README.md             # This documentation

Swap Strategies

Use consistent swap strategies across the application:

Strategy Use Case Example
innerHTML Replace content inside container List updates, search results
outerHTML Replace entire element Status badges, table rows
beforeend Append items Infinite scroll, new items
afterbegin Prepend items New items at top of list

Examples

<!-- Replace content inside container -->
<div id="results"
     hx-get="/api/search"
     hx-swap="innerHTML">

<!-- Replace entire element (e.g., status badge) -->
<span id="park-status"
      hx-get="/api/park/status"
      hx-swap="outerHTML">

<!-- Infinite scroll - append items -->
<div id="item-list"
     hx-get="/api/items?page=2"
     hx-trigger="revealed"
     hx-swap="beforeend">

Target Naming Conventions

Follow these naming patterns for hx-target:

Pattern Use Case Example
#object-type-id Specific objects #park-123, #ride-456
#section-name Page sections #results, #filters, #stats
#modal-container Modal content #modal-container
this Self-replacement Status badges, inline edits

Examples

<!-- Target specific object -->
<button hx-post="/api/parks/123/status"
        hx-target="#park-123"
        hx-swap="outerHTML">

<!-- Target page section -->
<form hx-post="/api/search"
      hx-target="#results"
      hx-swap="innerHTML">

<!-- Self-replacement -->
<span hx-get="/api/badge"
      hx-target="this"
      hx-swap="outerHTML">

Custom Event Naming

Use these conventions for custom HTMX events:

Pattern Description Example
{model}-status-changed Status updates park-status-changed, ride-status-changed
{model}-created New item created park-created, review-created
{model}-updated Item updated ride-updated, photo-updated
{model}-deleted Item deleted comment-deleted
auth-changed Auth state change User login/logout

Triggering Events

From Django views:

response['HX-Trigger'] = 'park-status-changed'
# or with data
response['HX-Trigger'] = json.dumps({
    'showToast': {'type': 'success', 'message': 'Status updated!'}
})

Listening for events:

<div hx-get="/api/park/header"
     hx-trigger="park-status-changed from:body">

Loading Indicators

Use the standardized loading indicator component:

<!-- Inline (in buttons) -->
<button hx-post="/api/action" hx-indicator="#btn-loading">
    Save
    {% include 'htmx/components/loading_indicator.html' with id='btn-loading' inline=True size='sm' %}
</button>

<!-- Block (below content) -->
<div hx-get="/api/data" hx-indicator="#loading">
    Content
</div>
{% include 'htmx/components/loading_indicator.html' with id='loading' message='Loading...' %}

<!-- Overlay (covers container) -->
<div class="relative" hx-get="/api/data" hx-indicator="#overlay">
    Content
    {% include 'htmx/components/loading_indicator.html' with id='overlay' mode='overlay' %}
</div>

Error Handling

HTMX errors are handled globally in base.html. The system:

  1. Shows toast notifications for different HTTP status codes
  2. Handles timeouts (30 second default)
  3. Handles network errors
  4. Supports retry logic

Custom Error Responses

Return error templates for 4xx/5xx responses:

{% include 'htmx/components/error_message.html' with title='Error' message='Something went wrong.' %}

Toast Notifications via HTMX

Trigger toast notifications from server responses:

from django.http import JsonResponse

def my_view(request):
    response = render(request, 'partial.html')
    response['HX-Trigger'] = json.dumps({
        'showToast': {
            'type': 'success',  # success, error, warning, info
            'message': 'Action completed!',
            'duration': 5000  # optional, in milliseconds
        }
    })
    return response

Form Validation

Use inline HTMX validation for forms:

<input type="text"
       name="username"
       hx-post="/api/validate/username"
       hx-trigger="blur changed delay:500ms"
       hx-target="#username-feedback"
       hx-swap="innerHTML">
<div id="username-feedback"></div>

Validation endpoint returns:

<!-- Success -->
{% include 'forms/partials/field_success.html' with message='Username available' %}

<!-- Error -->
{% include 'forms/partials/field_error.html' with errors=errors %}

Common Patterns

Search with Debounce

<input type="search"
       hx-get="/api/search"
       hx-trigger="keyup changed delay:300ms"
       hx-target="#results"
       hx-indicator="#search-loading">

Modal Content Loading

<button hx-get="/api/park/123/edit"
        hx-target="#modal-container"
        hx-swap="innerHTML"
        @click="$store.modal.open()">
    Edit
</button>

Infinite Scroll

<div id="items">
    {% for item in items %}
        {% include 'item.html' %}
    {% endfor %}

    {% if page_obj.has_next %}
    <div hx-get="?page={{ page_obj.next_page_number }}"
         hx-trigger="revealed"
         hx-swap="outerHTML"
         hx-select="#items > *">
        {% include 'htmx/components/loading_indicator.html' %}
    </div>
    {% endif %}
</div>

Status Badge Refresh

<span id="park-header-badge"
      hx-get="{% url 'parks:park_header_badge' park.slug %}"
      hx-trigger="park-status-changed from:body"
      hx-swap="outerHTML">
    {% include 'components/status_badge.html' with status=park.status %}
</span>

Best Practices

  1. Always specify hx-swap even for default behavior (clarity)
  2. Use meaningful target IDs following naming conventions
  3. Include loading indicators for all async operations
  4. Handle errors gracefully with user-friendly messages
  5. Debounce search/filter inputs to reduce server load
  6. Use hx-push-url for URL changes that should be bookmarkable
  7. Provide fallback for JavaScript-disabled browsers where possible

Security Considerations

  • CSRF tokens are automatically included via hx-headers in base.html
  • All HTMX endpoints should validate permissions
  • Use Django's @require_http_methods decorator
  • Sanitize any user input before rendering

Debugging

Enable HTMX debugging in development:

htmx.logAll();

Check browser DevTools Network tab for HTMX requests (look for HX-Request: true header).