本教程是学习使用 django 和 htmx 实现无限滚动的第七部分。我们将遵循 htmx 文档,逐步实现待办事项列表的无限滚动功能。完整系列教程可在 dev.to/rodbv 查看。
更新部分模板以支持多项目加载
实现无限滚动需要一次返回多个待办事项(项目的下一页),并将其加载到现有部分模板中。这需要修改部分模板的结构。当前模板渲染单个待办事项,如下图所示:
我们需要调整结构,使部分模板包含循环:
修改 core/templates/index.html 模板:
{% partialdef todo-items-partial inline %} {% for todo in todos %} <!-- ... li 元素内的代码不变 ... --> {% endfor %} {% endpartialdef %}
接下来,我们将添加 hx-get 和 hx-trigger=”revealed” 属性来实现无限滚动,但首先,我们需要修改视图函数,使其返回多个项目而不是单个项目:
...之前的代码... def _create_todo(request): title = request.POST.get("title") if not title: raise ValueError("title is required") todo = Todo.objects.create(title=title, user=request.user) return render( request, "tasks.html#todo-items-partial", # ... ) ...之后的代码...
测试显示内容仍然正确,页面外观也一致,这表明我们为实现无限滚动打好了基础。
实现无限滚动
在模板中,我们需要向 /tasks 发送一个 hx-get 请求,并设置 hx-trigger=”revealed”,这意味着只有当元素即将进入屏幕可见区域时才触发 get 请求。我们需要将其添加到列表的最后一个元素之后,并指定要加载的“页面”数据。在本例中,我们一次显示 20 个项目。
修改模板:
{% partialdef todo-items-partial inline %} {% for todo in todos %} <!-- ... li 元素内的代码不变 ... --> {% endfor %} {% if next_page_number %} <!-- 加载器 --> {% endif %} {% endpartialdef %}
列表底部的“加载中”图标由 if next_page_number 条件控制,它有两个作用:指示何时加载更多数据,以及何时显示加载器(当加载器进入可见区域时)。它将触发对 /tasks 的 hx-get 调用,并传递要检索的页码。next_page_number 属性由上下文提供。
hx-swap:outerhtml 属性指示我们将用从服务器获取的新数据替换该元素的 outerhtml 内容,这很好,因为我们不仅显示了新数据,还移除了加载图标。现在我们可以修改视图文件。
/tasks 视图函数当前总是返回完整的模板:
@require_http_methods(["get", "post"]) @login_required def tasks(request): if request.method == "post": return _create_todo(request) # get /tasks context = { "todos": request.user.todos.all().order_by("-created_at"), "fullname": request.user.get_full_name() or request.user.username, } return render(request, "tasks.html", context)
代码已修改为按最新待办事项优先排序;对于较长的列表,在底部添加新项目并与无限滚动混合是没有意义的——新项目最终会出现在列表中间。
我们需要区分常规 get 请求和 htmx 请求,为此,我们将只返回待办事项列表和部分模板。可以使用 django-htmx 库简化操作,但目前我们只需检查 htmx 头部并使用 Django 分页器处理分页。
# core/views.py ...之前的代码... PAGE_SIZE = 20 ...之前的代码... @require_http_methods(["get", "post"]) @login_required def tasks(request): if request.method == "post": return _create_todo(request) page_number = int(request.GET.get("page", 1)) all_todos = request.user.todos.all().order_by("-created_at") paginator = Paginator(all_todos, PAGE_SIZE) curr_page = paginator.get_page(page_number) context = { "todos": curr_page.object_list, "fullname": request.user.get_full_name() or request.user.username, "next_page_number": page_number + 1 if curr_page.has_next() else None, } template_name = "tasks.html" if "hx-request" in request.headers: template_name += "#todo-items-partial" return render(request, template_name, context)
首先,我们检查 page 参数,如果不存在则将其设置为 1。
然后,我们检查请求中的 hx-request 头部,这将告知我们请求是否来自 htmx,并让我们相应地返回部分模板或完整模板。
这段代码需要测试,但在测试之前,让我们先尝试一下。观察网络工具,看看页面滚动时如何触发请求,直到到达最后一页。您还可以看到动画“加载中”图标短暂显示;我已将网络速度限制为 4g,以使其可见时间更长。
添加测试
最后,我们可以添加测试来确保分页按预期工作:
# core/tests/test_view_tasks.py ...之前的测试... @pytest.mark.django_db def test_tasks_pagination(client, make_todo, make_user): user = make_user() client.force_login(user) # 创建两页数据 for i in range(PAGE_SIZE + 3): make_todo(title=f"Todo #{i}", user=user) response = client.get(reverse("tasks")) context = response.context assert context["next_page_number"] == 2 assert len(context["todos"]) == PAGE_SIZE # 添加头部以确保将其作为 HTMX 请求处理 response = client.get(reverse("tasks") + "?page=2", HTTP_HX_Request="true") content = response.content.decode().strip() assert content.startswith("<") # ...
现在就完成了!这是迄今为止我使用 htmx 最有趣的体验。本文的完整代码在这里。
下一篇文章,我可能会考虑使用 Alpine.js 添加一些客户端状态管理,或者添加“截止日期”功能。敬请期待!
以上就是使用 HTMX 和 Django 创建待办事项应用程序,部分无限滚动的详细内容,更多请关注php中文网其它相关文章!