*with some caveats
<form action="/contacts/{{ color.id }}/edit" method="post">
<fieldset>
<legend>Contact Values</legend>
<p>
<label for="email">Email</label>
<input name="email" type="text" value="{{ color.name }}">
<span class="error">{{ color.errors['name'] }}</span>
</p>
<p>
<button>Save</button>
</fieldset>
</form>
<form action="." method="POST">
{{ form.as_p }}
</form>
class ModelForm(forms.ModelForm):
class Meta:
model = Model
fields = [
"color"
]
+
=
1
2
3
<li>
{% for color in colors %}
<ul>{{ color.name }}</ul>
{% endfor %}
</li>
The philosophy (Locality of Behaviour)
"The primary feature for easy maintenance is locality: Locality is that characteristic of source code that enables a programmer to understand that source by looking at only a small portion of it."
-- Richard Gabriel (https://www.dreamsongs.com/Files/PatternsOfSoftware.pdf)
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
<form action="." method="POST">
{{ form.as_p }}
<button hx-post="{% url 'partials:list_colors' %}"
hx-target="#colors-list"hx-swap="innerHTML" >
Add Color
</button>
</form>
<li id="colors-list">
{% for color in colors %}
<ul>{{ color.name }}</ul>
{% endfor %}
</li>
<li id="colors-list" hx-get="{% url 'partials:list_colors' %}" hx-trigger="every 1s">
{% for color in colors %}
<ul>{{ color.name }}</ul>
{% endfor %}
</li>
Polling Pattern
Server Sent Events / SSE Push
<li id="colors-list" sse-connect="{% url 'partials:sse_updates' sse-swap="ColorUpdate" %}">
{% for color in colors %}
<ul>{{ color.name }}</ul>
{% endfor %}
</li>
Text
All examples with Django-HTMX
https://django-htmx.readthedocs.io/en/latest/installation.html
Attributes | Description |
---|---|
hx-get, hx-post, etc | AJAX requests |
hx-trigger |
Event that triggers a request |
hx-swap | How to swap HTML content into DOM |
hx-target |
Where in the DOM to swap the returned HTML content |
Events | Description |
---|---|
SSE | Server generated events |
Native |
Can be registered with document.body.addEventListener
|
https://htmx.org/reference/#attributes
https://htmx.org/reference/#events
Response Headers | Description |
---|---|
HX-Location | Causes a client-side redirection to a new location |
HX-Push-Url |
Pushes a new URL into the location bar / History |
https://htmx.org/reference/#response_headers
import django_tables2 as tables
from django_tables2.utils import A
class ColorTable(tables.Table):
def extract_url(**kwargs):
if "record" in kwargs:
return reverse(
"color_app:delete_url",
kwargs={"pk": kwargs.get("record").id},
)
return "header"
edit = tables.LinkColumn(
"color_app:edit_color", orderable=False, text="Edit", args=[A("id")]
)
delete = tables.URLColumn(
text="Delete",
orderable=False,
attrs={
"td": {
"style": "text-decoration: underline; color: #0a58ca",
"hx-confirm": "Are you sure?",
"hx-target": "closest tr",
"hx-get": extract_url,
}
},
)
class Meta:
model = Color
fields = ("name", "hex_code")
{% extends "./_base.html" %}
{% load render_table from django_tables2 %}
{% block main %}
<div class="container overflow-hidden">
<div class="row gy-5">
<div class="col">
<h3> Colors Table </h3>
</div>
</div>
<div class="row gy-5">
<div class="col">
{% render_table table %}
</div>
</div>
</div>
{% endblock %}
class RulesetsTableView(SingleTableView):
template_name = "color-table.html"
table_class = ColorTable
def get_queryset(self):
return Colors.objects.all()
https://taskbadger.net/blog/tables.html
<div id="app" class="container" hx-boost="true">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'chat_app:puppies' intent=intent %}">Kittens</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'chat_app:kittens' intent=intent %}">Puppies</a>
</li>
</ul>
</div>
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def chat_message(self, event):
message = event["message"]
ulid_str = event["ulid"]
timestamp = ULID.from_str(ulid_str).timestamp
# dt = datetime.datetime.fromtimestamp(timestamp, tzinfo=pytz.timezone("Australia/Sydney"))
dt = datetime.datetime.fromtimestamp(
timestamp
)
text_data = render_to_string(
"chat/message_partial.html",
{
"author": "Mr Roboto",
"message": message,
"dt": dt,
},
)
# Send message to WebSocket
self.send(text_data=text_data)
<div id="content" class="row h-75 overflow-auto" style="max-height: 75vh; overflow-y: scroll">
{% comment %}Install Forloop to hydrate messages{% endcomment %}
</div>
<div hx-ws="connect:/ws/chat/test_room/">
<form action="#" hx-ws="send:submit">
<input id="chat-message-input" name="chat_message" >
<button/>
</form>
</div>
<div hx-swap-oob="beforeend:#content" class="media w-75 mb-3">
<div class="media-body ml-3">
<div class="bg-light rounded py-2 px-3 mb-2">
<p class="text-info">{{author}}</p>
<p class="text-small mb-0 text-muted">{{message}}</p>
</div>
<p class="small text-muted">{{ dt|time:"h:i A" }} | {{ dt|date:"M j" }}</p>
</div>
</div>
Chat Room
Server Partial
https://about.contexte.com/fr/notre-actualite/how-we-developed-a-custom-collaborative-editor-for-our-journalists
https://docs.google.com/presentation/d/1jW7vTiHFzA71m2EoCywjNXch-RPQJuAkTiLpleYFQjI/edit
From Production / https://www.youtube.com/watch?v=3GObi93tjZI&t=488s
<input type="text" name="q"
hx-get="/trigger_delay"
hx-trigger="keyup changed delay:500ms"
hx-target="#search-results"
placeholder="Search..."
>
<div id="search-results"></div>
https://au.linkedin.com/pub/iqbal-bhatti/14/63/493