Add standardized HTMX conventions, interaction patterns, and migration guide for ThrillWiki UX

This commit is contained in:
pacnpal
2025-12-22 16:56:27 -05:00
parent 2e35f8c5d9
commit ae31e889d7
144 changed files with 25792 additions and 4440 deletions

View File

@@ -0,0 +1,273 @@
# 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
```html
<!-- 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
```html
<!-- 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:
```python
response['HX-Trigger'] = 'park-status-changed'
# or with data
response['HX-Trigger'] = json.dumps({
'showToast': {'type': 'success', 'message': 'Status updated!'}
})
```
Listening for events:
```html
<div hx-get="/api/park/header"
hx-trigger="park-status-changed from:body">
```
## Loading Indicators
Use the standardized loading indicator component:
```html
<!-- 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:
```html
{% include 'htmx/components/error_message.html' with title='Error' message='Something went wrong.' %}
```
## Toast Notifications via HTMX
Trigger toast notifications from server responses:
```python
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:
```html
<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:
```html
<!-- 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
```html
<input type="search"
hx-get="/api/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#results"
hx-indicator="#search-loading">
```
### Modal Content Loading
```html
<button hx-get="/api/park/123/edit"
hx-target="#modal-container"
hx-swap="innerHTML"
@click="$store.modal.open()">
Edit
</button>
```
### Infinite Scroll
```html
<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
```html
<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:
```javascript
htmx.logAll();
```
Check browser DevTools Network tab for HTMX requests (look for `HX-Request: true` header).