htmx afterRequest事件与afterSwap事件失效的场景及解决方法
使用 htmx 进行开发产品,然后部署到阿里云、Cloudflare、AWS 等云服务器是我目前主要的做法。后端开发语言会根据情况采用 Python 或者 Golang,至于前端就采用 htmx 库。
可能是因为没有认真地研读过 htmx 源码,加上有时候懒散没有认真思考,所以在使用的过程中也会遇到一些看似莫名其妙但又似乎合情合理的问题。遇到的其中一个问题就是 htmx:afterRequest事件 及 htmx:afterSwap事件 失效的问题。
现在把前因后果及解决方法记录下来,以免日后重蹈覆辙。
- htmx 事件失效场景回顾
- 事件失效原因分析
- 解决方法
htmx 事件失效场景回顾
用户登录或者一些数据编辑表单有时候使用了弹出窗口(纯CSS实现的弹窗,有兴趣的朋友可以阅读一下《HTMX + 原生 CSS 实现 HTML 模态弹出窗口的解决方案》一文),在表单提交成功后通常需要弹出一些“操作成功。”之类的提示并且关闭弹窗,根据 htmx 的文档,自然就是使用 htmx:afterRequest 或 htmx:afterSwap 这两个事件来实现了。
诡异的事情来了,数据提交及后台数据保存都是正确的,响应也是正常的,但就是不触发 htmx:afterRequest 及 htmx:afterSwap 事件。
事件失效原因分析
在相同的页面,添加测试的代码,发现 afterRequest 及 afterSwap 事件是可以正常触发的,这就排除了全局代码的影响,原因应该就出在弹窗的代码上面了。
弹窗是通过 hx-get 获取的,也可以排除掉 htmx 没有初始化代码片段的原因了,因为弹窗的 hx-post 是可以正常触发的。
先来看看经过了简化排除了干扰代码的弹窗代码:
<div class="popup-container">
<div class="popup-box">
<input type="hidden" name="id" id="id" value="Something ID">
<button hx-post="/endpoint"
hx-target=".popup-container"
hx-swap="outerHTML"
hx-include="#id"
hx-on:htmx:after-request="alter('操作成功。');"
>确定</button>
</div>
</div>
以上代码就是通过页面的一个按钮触发 hx-get 从后台获取到的 html 代码片段,在页面上会以弹窗的形式出现,popup-container 类就是弹窗的类。因为在操作成功后要把弹窗去除,所以 hx-target 是直接指向的这个弹窗的类,使用 hx-swap="outerHTML" 把它整个替换,操作成功后后台返回一个空字符串,这样就相当于把弹窗的代码去除了,达到了弹窗消失的效果。
原本以为 htmx:after-request 会在浏览器的事件栈中,即使弹窗代码被交换了也会执行。然而,事实证明,只要hx-on:htmx:after-request 这些代码一消失,事件就会跟着消失,并不会像预想一样事件在堆栈中继续出栈。
afterSwap 事件也是如此,因为上面弹窗的代码在交换时就消失了,所以也不会被触发。个人感觉这个不太合理,但也没办法,既然用人家的东西就得按人家的思路来。
找到了原因,要解决这个问题就好办了。
解决方法
我们要实现的功能是在用户提交成功且后台处理成功后出现操作成功提示,并且移除弹窗。既然无法通过 hx-swap 一次过处理,那我们就分开两步走:保留弹窗的 html 代码的前提先弹出操作成功的提示,再通过 JavaScript 代码移除弹窗。
根据以上思路,就可以把弹窗的代码改成这样:
<div class="popup-container">
<div class="popup-box">
<input type="hidden" name="id" id="id" value="Something ID">
<button hx-post="/endpoint"
hx-swap="none"
hx-include="#id"
hx-on:htmx:after-request="alter('操作成功。');closePopup(event);"
>确定</button>
</div>
</div>
alter 是用来显示操作成功提示的,closePopup 是用来移除弹窗的 JS 函数。这样改造之后,就完美触发 htmx:after-request 事件,达到了我们的目的。