最近在研究htmx库的时候突发奇想,利用 htmx 和我之前发布的 Python 库html-dsl应该可以做到只使用 Python 代码构建可交互的 Web 应用。在稍作尝试后,我实现了一个简单的 Todo 应用todopy。
技术栈
FastAPI
项目后端使用了FastAPI框架。
html-dsl
html-dsl 是我在数年前开发的一个简单的 Python 库,可以利用 Python 代码构建 HTML 页面,使用比较简单。
htmx
(由 Github Copilot 生成) htmx 是一个 JavaScript 库,它允许您使用 HTML 扩展现有的 Web 应用程序,而无需编写任何 JavaScript。它使用现有的 Web 标准(例如 HTML、CSS 和 JavaScript)来实现 Ajax、WebSockets、Server-Sent Events 和其他现代 Web 功能。htmx 的目标是使 Web 开发更快、更简单、更容易,并提高 Web 应用程序的可访问性。
代码语言:javascript复制<script src="https://unpkg.com/htmx.org@1.9.6"></script>
<!-- have a button POST a click via AJAX -->
<button hx-post="/clicked" hx-swap="outerHTML">Click Me</button>
在上面的示例中,点击按钮后,htmx 将向服务器发送一个 POST 请求,该请求将被路由到/clicked。服务器将返回一个 HTML 片段,该片段将替换按钮的外部 HTML。
tailwindcss
tailwindcss是一个实用的 CSS 库,它提供了一组实用的 CSS 类,可以快速构建页面。
构建页面
整个页面比较简单,核心是一个输入新待办项的表单和一个待办项列表。
整体布局
代码语言:javascript复制@app.get("/")
def root():
return render(
HTML(lang="en")[
HEAD[
META['charset="utf-8"'],
META['name="viewport" content="width=device-width, initial-scale=1"'],
TITLE["Todo App"],
SCRIPT(src="https://unpkg.com/htmx.org@1.9.6"),
SCRIPT(src="https://cdn.tailwindcss.com"),
STYLE[GLOBAL_STYLE],
],
BODY[
DIV(_class="max-w-md max-auto")[
H1(_class="text-2xl font-bold mb-4")["Todo List"],
FORM(_class="mb-4", hx_post="/todos", hx_target="#todo-list")[
DIV(_class="flex items-center")[
INPUT(
type="text",
name="task",
_class="border rounded-l px-3 py-2 w-full",
placeholder="Add new todo",
),
BUTTON(
type="submit",
_class="bg-blue-500 hover:bg-blue-700 text-white font-bold rounded-r px-4 py-2",
)["Add"],
]
],
DIV(id="todo-list", hx_get="/todos", hx_trigger="load"),
],
],
]
)
待办项列表由 id 为 todo-list 的 div 元素渲染,当页面加载完成后,htmx 会向服务器发送一个 GET 请求,服务器返回一个待办项列表的 HTML 片段,然后将其插入到 todo-list 元素中。
表单的提交也由 htmx 处理,当用户点击提交按钮时,htmx 会向服务器发送一个 POST 请求,服务器将新的待办项添加到数据库中,然后返回一个待办项列表的 HTML 片段,htmx 将其插入到 todo-list 元素中。
渲染待办项列表
代码语言:javascript复制def render_todos(todos):
return render(
UL(_class="border rounded-lg overflow-hidden")[
[
LI(_class="flex items-center justify-between px-3 py-2 border-b")[
SPAN(_class="text-lg")[todo],
BUTTON(
hx_delete=f"/todo/{id}",
hx_target="#todo-list",
_class="text-red-500 hover:text-red-700",
)["delete"],
]
for id, todo in todos.items()
]
]
)
页面加载、添加新待办项,以及待办项列表中的删除按钮都会触发重新渲染待办项列表,于是我封装了一个 render_todos 函数,用于渲染待办项列表的 HTML 片段。其中每一个待办项都是一个 li 元素,包含一个 span 元素和一个删除按钮。删除按钮的点击事件由 htmx 处理,当用户点击删除按钮时,htmx 会向服务器发送一个 DELETE 请求,服务器将待办项从数据库中删除,然后返回一个待办项列表的 HTML 片段,htmx 将其插入到 todo-list 元素中。
后端接口
整体比较简单,只有三个接口,分别用于获取待办项列表、添加新待办项和删除待办项。 与常规的 restful 接口不同的是,这里的接口都返回 HTML 片段,而不是 JSON 数据。
代码语言:javascript复制@app.get("/todos")
def todos_list(todos: Annotated[dict, Depends(todos)]):
return render_todos(todos)
@app.post("/todos")
def todos_add(todos: Annotated[dict, Depends(todos)], task: str = Form("task")):
id = uuid.uuid4().hex
todos[id] = task
return render_todos(todos)
@app.delete("/todo/{id}")
def todos_delete(todos: Annotated[dict, Depends(todos)], id: str):
del todos[id]
return render_todos(todos)
总结
这个 todo 应用只是一个玩具项目,不过 htmx 还是很强大的,即使不使用 html-dsl 这种纯 Python 的 HTML 构建库,也可以利用常规的 HTML 模板引擎(例如 Jinjia2)来构建页面,赋予了纯后端开发人员构建可交互 Web 应用的能力。