这是获取帖子信息的代码。我在 stackoverflow 上看到了一些东西,但没有一个真正解决了使用 htmx 无限滚动的核心示例。
{% for post in get_feed_posts(session['username']) %}
{% if post[7] != "off" %}
{% from 'macros.html' import post_block %}
{{ post_block(post) }}
{%endif%}
{%endfor%}
</div>
更新:这不会运行测试路线
DATA = [{"name":"Neil"},{"name":"Corie"}]
def get_data(page=0, pagesize=1):
print(DATA[pagesize * page: pagesize*page + pagesize])
return DATA[pagesize * page: pagesize*page + pagesize]
@app.route('/test', methods=['POST', 'GET'])
def test():
data = get_data(page=request.args.get("page", 0, type=int))
return render_template("test.html", data=data)
-- html
<tr hx-get="/test/?page=0"
hx-trigger="revealed"
hx-swap="afterend">
<td>Agent Smith</td>
<td>[email protected]</td>
<td>55F49448C0</td>
</tr>
如果您查看 https://htmx.org/examples/infinite-scroll/ ,您会发现当用户向下滚动页面时,它会使用
page=n
查询参数向服务器发送 GET 请求,所以你的服务器应该返回一个新的 html 数据“页面”
在你的 Flask 服务器上:
@app.get("/some-route")
def some_route():
data = get_data(page=request.args.get("page", 0, type=int))
return render_template("my-template.html", data=data)
您的
get_data()
函数返回不同的数据,具体取决于页码
这通常是使用 SQL Alchemy 完成的。这是一个例子:https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-ix-pagination
但您也可以只使用如下列表:
DATA = [{"name":"Neil"},{"name":"Corie"}, ....]
def get_data(page=0, pagesize=20):
return DATA[pagesize * page: pagesize*page + pagesize]
我有同样的经历,试图这样做 - 我在哪里称呼什么?在使用 Flask 和 Bootstrap 时,我还必须弄清楚在控制器/宏/模板方案中放置什么。
所以这是一个端到端,涵盖所有三个
宏观
这里我们有关键的无限滚动位 - 这是一个将以无限滚动样式处理分页的宏
{% macro htmx_infinite_table_rows(paginate, endpoint) -%}
{% for item in paginate.items -%}
{% if loop.last and paginate.has_next -%}
<tr
hx-get="{{ url_for(endpoint, page=paginate.next_num) }}"
hx-trigger="revealed"
hx-swap="afterend"
>
{% else -%}
<tr>
{% endif -%}
{{ caller(item) }}
</tr>
{% endfor -%}
{% endmacro -%}
备注:
模板 - 基础
根据最佳实践,我们的页面都扩展了一个base.html文件,其中包括标准的导航栏、页眉和页脚。一般来说,我在这里使用引导程序,它有一个很好的交互式表,但是(据我所知)该表需要完整的数据集加载。这是一个很好的交互式表格,但是如果有 10,000 条记录怎么办?这就是我们进行无限滚动的原因。
无论如何,这就是为什么你会在我的 base.html 中看到所有 Bootstrap 内容
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{% block title %}Some Title{% endblock %}</title>
<style>
.htmx-indicator {
opacity: 0;
transition: opacity 500ms ease-in;
}
.htmx-indicator.htmx-request {
opacity: 1
}
.htmx-request.htmx-indicator {
opacity: 1
}
table,
th,
td {
border: 1px solid lightgray;
border-collapse: separate;
padding: 10px 5px;
}
</style>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" type="text/css"
href="http://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.16.0/extensions/filter-control/bootstrap-table-filter-control.css">
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.13/css/bootstrap-multiselect.css">
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap-select.min.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.10/css/all.css"
integrity="sha384-+d0P83n9kaQMCwj8F4RJB66tzIwOKmrdb46+porD/OvrJ+37WqIM7UoBtwHO6Nlg" crossorigin="anonymous">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
</head>
<body>
{% include 'navbar.html' %}
<div class="container">
<div class="row row-lg-4">
<div class="col">
{% block head %}
{% include 'head.html' %}
{% endblock %}
</div>
</div>
{% include 'messages.html' %}
{% block body %}
<div class="row">
<div class="col-lg-9">
{% block leftbody %}
{% endblock %}
</div>
<div class="col-lg-3 rounded">
{% block rightbody %}
{% endblock %}
</div>
<div>
{% block notetext %}
{% endblock %}
</div>
<div class="table">
{% block sampledata %}
{% endblock %}
</div>
</div>
{% endblock %}
{% include 'footer.html' %}
</div>
</body>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js"
integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.12.1/js/bootstrap-select.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.13/js/bootstrap-multiselect.js"></script>
<script src="https://unpkg.com/[email protected]/dist/bootstrap-table.min.js"></script>
<script
src="https://unpkg.com/[email protected]/dist/extensions/filter-control/bootstrap-table-filter-control.min.js"></script>
<script src="https://unpkg.com/[email protected]"
integrity="sha384-EzBXYPt0/T6gxNp0nuPtLkmRpmDBbjg6WmCUZRLXBBwYYmwAUxzlSGej0ARHX0Bo" crossorigin="anonymous"
defer></script>
</body>
</html>
备注:
模板 - 页面
{% from "macros.html" import htmx_infinite_table_rows %}
{% if 'hx_request' in request.headers -%}
{% call(record) htmx_infinite_table_rows(records, 'scroll.home') -%}
<!-- once we're infinitely scrolling, we're just calling this block -->
<td>{{ record.Package }}</td>
<td>{{ record.Module }}</td>
<td style="background-color:#F0F0F0"><a href="{{ url_for('scroll.afFunction', afFunctionID=record.id) }}"
data-toggle="popover" title="[Hover help text]" data-content="[Explain what's behind the link]">{{
record.Function }}</a> </td>
<td>{{ record.Description }}</td>
{% endcall -%}
{% else -%}
<!-- On first load, we are *not* infinitely scrolling, so we setup the page -->
{% extends "base.html" %}
{% block body %}
<table>
<thead>
<tr>
<th>Package</th>
<th>Module</th>
<th>Function</th>
<th>Function Description</th>
</tr>
</thead>
<tbody>
{% call(record) htmx_infinite_table_rows(records, 'scroll.home') -%}
<td>{{ record.Package }}</td>
<td>{{ record.Module }}</td>
<td style="background-color:#F0F0F0"><a href="{{ url_for('scroll.afFunction', afFunctionID=record.id) }}"
data-toggle="popover" title="[Hover help text]" data-content="[Explain what's behind the link]">{{
record.Function }}</a> </td>
<td>{{ record.Description }}</td>
{% endcall -%}
</tbody>
</table>
<!-- On first load, we are *not* infinitely scrolling, so we setup the page -->
<img class="htmx-indicator" width="60" src="/static/loading.gif"></center>
{% endblock %}
<!-- Just a reminder - the rest of the base template will be here (footer, scripts, etc.) -->
{% endif -%}
备注:
如果这是第一次加载,我们会正常运行页面,但需要注意的是,我们使用调用来表示 - 回想一下宏,如果它没有进行无限滚动加载,那么它只是返回:
{%其他-%}
{% 万一 -%}如果它正在进行无限滚动加载,那么它会调用第一个块
在某种程度上,我们需要做的是在一个完整的页面中创建一个开放式表格,因此无论页码是多少,我们都需要最后一行来触发新页面数据的加载,但不是一个全新的网页
最好将整行放在单独的页面中,每次都调用它,并带有 {% include somepage.html %}
控制器
这很简单(有一个警告) - 这是一个使用 Mongo 的版本
@scroll_blueprint.route('/<int:page>')
@scroll_blueprint.route('/')
def home(page=1):
theseAFFunctions = Mongo_AF_Functions.objects.order_by("PackageModuleFunction").paginate(page, 10)
return render_template("scrollfunctionsAF.html",
records=theseAFFunctions)
备注:
现在,这里有一个关键点 - 我们将宏称为“htmx_infinite_table_rows” - 这只是我们编造的一个名称。除了表格行之外,我们还能做些什么吗?是的,我们只需要找出“给定当前数据的表中最后一行,这应该触发更多数据的加载”的 html 等效项。
这是带有 Bootstrap 卡的版本:
宏观
{% macro htmx_infinite_cards(paginate, endpoint) -%}
{% for item in paginate.items -%}
{% if loop.last and paginate.has_next -%}
<div class="row"
hx-get="{{ url_for(endpoint, page=paginate.next_num) }}"
hx-trigger="revealed"
hx-swap="afterend"
>
{% else -%}
<div class="row">
{% endif -%}
{{ caller(item) }}
</div>
{% endfor -%}
{% endmacro -%}
模板
{% from "macros.html" import htmx_infinite_cards %}
{% if 'hx_request' in request.headers -%}
{% call(record) htmx_infinite_cards(records, 'scroll.home') -%}
<!-- once we're infinitely scrolling, we're just calling this block -->
<!-- remember the <row> element is in the call so we don’t need it here -->
<div class="card">
<div class="card-body">
<h4 class="card-title">
{{ record.Function }}
</h4>
<div class="card-text">
<dl class="row">
<dt class="col-sm-4">Function Name:</dt>
<dd class="col-lg-5">{{ record.Function }}</dd>
<dt class="col-sm-4">Package:</dt>
<dd class="col-lg-5">{{ record.Packages }}</dd>
<dt class="col-sm-4">Module</dt>
<dd class="col-lg-5">{{ record.Module }}</dd>
<dt class="col-sm-4">Description</dt>
<dd class="col-sm-5">{{ record.Description}}</dd>
</dl>
</div>
<div class="card-footer">
<a href="{{ url_for('scroll.afFunction', afFunctionID=record.id) }}"
data-toggle="popover" title="[Hover help text]" data-content="[Explain what's behind the link]">{{
record.Function }}</a>
</div>
</div>
</div>
{% endcall -%}
{% else -%}
<!-- On first load, we are *not* infinitely scrolling, so we setup the page -->
{% extends "base.html" %}
{% block body %}
<div class="col-lg-9">
{% call(record) htmx_infinite_cards(records, 'scroll.home') -%}
<!-- the <row> element is in the call -->
<div class="card">
<div class="card-body">
<h4 class="card-title">
{{ record.Function }}
</h4>
<div class="card-text">
<dl class="row">
<dt class="col-sm-4">Function Name:</dt>
<dd class="col-lg-5">{{ record.Function }}</dd>
<dt class="col-sm-4">Package:</dt>
<dd class="col-lg-5">{{ record.Packages }}</dd>
<dt class="col-sm-4">Module</dt>
<dd class="col-lg-5">{{ record.Module }}</dd>
<dt class="col-sm-4">Description</dt>
<dd class="col-sm-5">{{ record.Description}}</dd>
</dl>
</div>
<div class="card-footer">
<a href="{{ url_for('scroll.afFunction', afFunctionID=record.id) }}"
data-toggle="popover" title="[Hover help text]" data-content="[Explain what's behind the link]">{{
record.Function }}</a>
</div>
</div>
</div>
{% endcall -%}
</div>
<!-- On first load, we are *not* infinitely scrolling, so we setup the page -->
<img class="htmx-indicator" width="60" src="/static/loading.gif"></center>
{% endblock %}
<!-- Just a reminder - the rest of the base template will be here (footer, scripts, etc.) -->
{% endif -%}
显然这一切都需要一段时间来编码,但是一旦你掌握了它,每次都会有几乎相同的模式。
参考资料: