# HTMX Conventions
This document outlines the standardized HTMX patterns and conventions used in ThrillWiki.
## Request Attributes
### hx-get / hx-post / hx-put / hx-delete
Use the appropriate HTTP method for the action:
```html
```
### hx-target
Specify where the response should be inserted:
```html
Content
```
### hx-swap
Control how content is inserted:
```html
...
...
...
...
```
### hx-trigger
Define when requests are made:
```html
...
Load More
...
```
## Response Headers
### Server-Side Response Helpers
Use the standardized HTMX utility functions in views:
```python
from apps.core.htmx_utils import (
htmx_success,
htmx_error,
htmx_redirect,
htmx_modal_close,
htmx_refresh,
)
def create_park(request):
# On success
return htmx_success('Park created successfully!')
# On error
return htmx_error('Validation failed', status=422)
# Redirect
return htmx_redirect('/parks/')
# Close modal with refresh
return htmx_modal_close(
message='Park saved!',
refresh_target='#parks-list'
)
```
### HX-Trigger
Trigger client-side events from the server:
```python
# Single event
response['HX-Trigger'] = 'parkCreated'
# Event with data
response['HX-Trigger'] = json.dumps({
'showToast': {
'type': 'success',
'message': 'Park created!'
}
})
# Multiple events
response['HX-Trigger'] = json.dumps({
'closeModal': True,
'refreshList': {'target': '#parks-list'},
'showToast': {'type': 'success', 'message': 'Done!'}
})
```
### HX-Redirect
Perform client-side navigation:
```python
response['HX-Redirect'] = '/parks/'
```
### HX-Refresh
Trigger a full page refresh:
```python
response['HX-Refresh'] = 'true'
```
### HX-Retarget / HX-Reswap
Override target and swap method:
```python
response['HX-Retarget'] = '#different-target'
response['HX-Reswap'] = 'outerHTML'
```
## Partial Templates
### Convention
For each full template, create a corresponding partial:
```
templates/
parks/
list.html # Full page
list_partial.html # Just the content (for HTMX)
detail.html
detail_partial.html
```
### Usage with Decorator
```python
from apps.core.htmx_utils import htmx_partial
@htmx_partial('parks/list.html')
def park_list(request):
parks = Park.objects.all()
return ({'parks': parks},) # Returns context tuple
```
### Manual Detection
```python
from apps.core.htmx_utils import is_htmx_request
def park_list(request):
parks = Park.objects.all()
if is_htmx_request(request):
return render(request, 'parks/list_partial.html', {'parks': parks})
return render(request, 'parks/list.html', {'parks': parks})
```
## Loading States
### Indicator Attribute
```html
```
### CSS Classes
HTMX automatically adds classes during requests:
```css
/* Added to element making request */
.htmx-request {
opacity: 0.5;
pointer-events: none;
}
/* Added to indicator elements */
.htmx-indicator {
display: none;
}
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
display: inline-block;
}
```
### Skeleton Screens
Use skeleton components for better UX:
```html
{% include 'components/skeletons/card_grid_skeleton.html' with cards=6 %}
```
## Form Handling
### Standard Form Pattern
```html
```
### Inline Validation
```html
```
### Server Response
```python
from apps.core.htmx_utils import htmx_validation_response
def validate_name(request):
name = request.POST.get('name', '')
if not name:
return htmx_validation_response('name', errors=['Name is required'])
if Park.objects.filter(name=name).exists():
return htmx_validation_response('name', errors=['Name already exists'])
return htmx_validation_response('name', success_message='Name is available')
```
## Pagination
### Load More Pattern
```html
{% for park in parks %}
{% include 'parks/_card.html' %}
{% endfor %}
{% if page_obj.has_next %}
{% endif %}
```
### Infinite Scroll
```html
{% if page_obj.has_next %}
Loading...
{% endif %}
```
### Traditional Pagination
```html
{% include 'components/pagination.html' with
page_obj=page_obj
hx_target='#parks-list'
hx_swap='innerHTML'
hx_push_url='true'
%}
```
## Modal Integration
### Opening Modal with Content
```html
```
### Closing Modal from Server
```python
def save_park(request, pk):
park = get_object_or_404(Park, pk=pk)
form = ParkForm(request.POST, instance=park)
if form.is_valid():
form.save()
return htmx_modal_close(
message='Park updated successfully!',
refresh_target='#park-detail'
)
return render(request, 'parks/_edit_form.html', {'form': form})
```
## Out-of-Band Updates
### Multiple Element Updates
```html