Table of contents
构建项目开发环境
技术栈:
- Alpine.js
- HTMX
- tailwindcss
- Django
- django-browser-reload
其中,django-browser-reload有点类似与Angular的HMR,每当html,js或者css在被改动之后保存,该模块会自动刷新页面以帮助开发者更快获得反馈。
在使用Alpine.js,HTMX和tailwindcss的时候,为了方便起见,我会使用CDN链接。如果对配置感兴趣的话也可以浏览这篇文章如何在Django中配置Alpine.js,HTMX和tailwindcss。
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Demo</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,line-clamp"></script>
<script
src="https://unpkg.com/[email protected]"
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
crossorigin="anonymous"
></script>
<script
defer
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"
></script>
</head>
表单提交
这里我们会以TodoList为原型,主要功能是添加Todo项目,保存到数据库,返回204响应并添加响应头HX-Trigger。
对应的代码:
def add_todo(request):
if request.method == 'POST':
new_todo = request.POST["todo"]
Todo.objects.create(title=new_todo)
headers = {
"HX-Trigger": json.dumps({
"show-message": {
"level": "info",
"message": "Todo added successfully!"
},
"todo-updated": None
})
}
return HttpResponse(status=204, headers=headers)
HX-Trigger的功能就是在前端触发事件,以便运行监听该事件的代码,比如:刷新列表,弹消息框。如果只是单纯地触发事件,HX-Trigger:my-event
就够了,但如果需要多加料让前端获取更多信息,那么就得以JSON的格式返回,所以需要用json.dumps()
转换下。这里会触发两个事件show-message
和todo-updated
。Tips:Alpine.js只支持Kebab Case格式的事件即my-event,所以为了让show-message被x-on监听到,我们只能返回小写或者带有“-”的事件。但是HTMX均支持Camel Case和Kebab Case格式事件。
在流程图Step 3 - Return 204 response with HX-Trigger header中,我们也可以返回新的Todo list去替换当前的。而我选择返回204 No Content是因为在HX-Trigger中加入一个事件todo-updated,这样下面的代码就会发送一个请求到“todolist/”链接获取最新的Todo list。
<main>
<div class="lg:px-8 mx-auto max-w-7xl sm:px-6">
<div
id="content"
class="container mx-auto"
hx-get="{% url 'todolist' %}"
hx-trigger="load, todo-updated from:body"
></div>
</div>
</main>
由于事件冒泡,todo-updated事件会一层层向上“冒泡”至body,所以我们需要添加from:body来监听事件,否则只会监听当前节点。
当“todolist/”请求被Django接收到,view函数会取出所有Todo对象,并且在todo.html模板中返回结果。
def todos(request):
todos = Todo.objects.all()
return render(request, template_name="form/todo.html", context={"todos": todos})
<div id="todo-list">
<ul role="list" class="divide-y divide-gray-200">
{% for todo in todos %}
<li class="flex py-4">
<div class="ml-3">
<p class="text-lg font-bold text-gray-900">{{ todo }}</p>
</div>
</li>
{% endfor %}
</ul>
</div>
<form hx-post="{% url 'add' %}">
<div class="flex">
<div class="mr-3 w-full max-w-xs">
<input
type="text"
placeholder="Name"
name="todo"
class="ring-offset-background flex h-10 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm placeholder:text-neutral-500 focus:border-neutral-300 focus:outline-none focus:ring-2 focus:ring-neutral-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
/>
</div>
<div class="flex justify-end">
<button
type="submit"
class="ml-3 inline-flex justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Add
</button>
</div>
</div>
</form>
显示消息框
消息框有非常多的样式,取决于开发者喜好。这里我们使用的是tailwindcss components中的消息框。
<div x-data="{ show: true, message: '' }" aria-live="assertive"
@show-message.document="show=true; message=$event.detail.message; setTimeout(() => show=false, 2000)"
class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6">
<div class="flex w-full flex-col items-center space-y-4 sm:items-end">
<div
x-show="show"
x-transition:enter="transform ease-out duration-300 transition"
x-transition:enter-start="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
x-transition:enter-end="translate-y-0 opacity-100 sm:translate-x-0"
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5">
...
...
...
</div>
</dvi>
</div>
x-data中的show结合x-show将会控制消息框的显示与隐藏。x-transition可以帮助整个过程看上去更丝滑。
其中,这里的精华在于@show-message.document,它结合了HTMX的HX-Trigger事件和Alpine.js的x-on监听器。.document
的功能类似于前面提到的from:body
,为了监听到“冒泡”到最上层的show-message事件。
一旦发现了该事件,执行语句show=true; message=$event.detail.message; setTimeout(() => show=false, 2000)
。
- show=true 显示消息框
- message=$event.detail.message 从“事件对象”里面获取信息,并赋值给message
- setTimeout(() => show=false, 2000) 如果用户没有关闭弹框,则在2秒后自动关闭
Tips:message可以用x-text绑定。
总结
事件冒泡是HTMX与Alpine.js连接在一起的关键点。HX-Trigger触发事件,而x-on则使用.document
或者.window
来监听该事件。