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:
- Shows toast notifications for different HTTP status codes
- Handles timeouts (30 second default)
- Handles network errors
- 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
- Always specify
hx-swapeven for default behavior (clarity) - Use meaningful target IDs following naming conventions
- Include loading indicators for all async operations
- Handle errors gracefully with user-friendly messages
- Debounce search/filter inputs to reduce server load
- Use
hx-push-urlfor URL changes that should be bookmarkable - Provide fallback for JavaScript-disabled browsers where possible
Security Considerations
- CSRF tokens are automatically included via
hx-headersin base.html - All HTMX endpoints should validate permissions
- Use Django's
@require_http_methodsdecorator - 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).