In this video we will assume this Flask app:
import uuid
from flask import Flask, render_template, request
app = Flask(__name__)
todos = []
@app.route("/")
def index():
return render_template("04-index.html", todos=render_all_todos())
@app.route("/todos")
def render_all_todos():
return "".join([render_todo_item(todo["id"], todo["content"]) for todo in todos])
def render_todo_item(todo_id, todo_content):
return f"""<div id="{ todo_id }">
<span>{ todo_content }</span>
<button hx-post="/delete/{ todo_id }"
hx-target="#{ todo_id }"
hx-swap="outerHTML">
Delete
</button>
</div>
"""
@app.route("/add", methods=["POST"])
def add():
global todos
content = request.form["content"]
todo = {"id": "uniq" + str(uuid.uuid4())[:10], "content": content}
todos.append(todo)
return render_todo_item(todo["id"], todo["content"]), 201
@app.route("/delete/<id>", methods=["POST"])
def delete(id):
global todos
todos = [todo for todo in todos if todo["id"] != id]
return "", 200
if __name__ == "__main__":
app.run(debug=True)
This app assumes that there is a folder called templates
with an 03-index.html
file that looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Simple HTMX Todo App</title>
<script src="https://unpkg.com/htmx.org"></script>
</head>
<body>
<h1>Simple HTMX Todo App</h1>
<form hx-post="/add" hx-target="#todo-list" hx-swap="beforeend">
<input type="text" name="content"
placeholder="Add a new todo" id="text-input" required>
<button type="submit">Add</button>
</form>
<div id="todo-list"
hx-trigger="load"
hx-get="/todos"
hx-swap="innerHTML">
</div>
</body>
</html>
Notice how the backend contains a lot of logic now, but that the code for the frontend is relatively modest? This is not unusual when you are writing code with HTMX.
The main thing to observe here is how the HTML code that we return can itself also contain HTMX attributes. This makes it easy to implement the deletion behavior for each individual element. When that happens we need to sync with the backend, but the UI for this is relatively straightforward to implement because the logic of this all takes place on the backend.