JavaScript、fetch
和 JSON¶
您可能希望使您的 HTML 页面动态化,通过更改数据而无需重新加载整个页面。您可以添加 JavaScript,调用 fetch()
并替换页面上的内容,而不是提交 HTML <form>
并执行重定向以重新渲染模板。
fetch()
是现代的、内置的 JavaScript 解决方案,用于从页面发出请求。您可能听说过其他 “AJAX” 方法和库,例如 XMLHttpRequest()
或 jQuery。在现代浏览器中,这些不再是必需的,尽管您可以根据应用程序的需求选择使用它们或其他库。这些文档将只关注内置的 JavaScript 功能。
渲染模板¶
理解模板和 JavaScript 之间的区别非常重要。模板在服务器上渲染,然后在响应发送到用户的浏览器之前。JavaScript 在用户的浏览器中运行,在模板渲染并发送之后。因此,不可能使用 JavaScript 来影响 Jinja 模板的渲染方式,但可以将数据渲染到将要运行的 JavaScript 中。
要在渲染模板时向 JavaScript 提供数据,请在 <script>
块中使用 tojson()
过滤器。这将把数据转换为有效的 JavaScript 对象,并确保任何不安全的 HTML 字符都被安全地渲染。如果您不使用 tojson
过滤器,您将在浏览器控制台中收到 SyntaxError
。
data = generate_report()
return render_template("report.html", chart_data=data)
<script>
const chart_data = {{ chart_data|tojson }}
chartLib.makeChart(chart_data)
</script>
一种不太常见的模式是将数据添加到 HTML 标签的 data-
属性中。在这种情况下,您必须在值周围使用单引号,而不是双引号,否则您将生成无效或不安全的 HTML。
<div data-chart='{{ chart_data|tojson }}'></div>
生成 URL¶
从服务器获取数据的另一种方法是请求数据。首先,您需要知道要请求的 URL。
生成 URL 最简单的方法是继续在渲染模板时使用 url_for()
。例如
const user_url = {{ url_for("user", id=current_user.id)|tojson }}
fetch(user_url).then(...)
但是,您可能需要根据您仅在 JavaScript 中知道的信息来生成 URL。如上所述,JavaScript 在用户的浏览器中运行,而不是作为模板渲染的一部分,因此您无法在该点使用 url_for
。
在这种情况下,您需要知道您的应用程序所服务的 “根 URL”。在简单的设置中,这是 /
,但也可能是其他内容,例如 https://example.com/myapp/
。
告诉您的 JavaScript 代码关于此根 URL 的一个简单方法是在渲染模板时将其设置为全局变量。然后您可以在从 JavaScript 生成 URL 时使用它。
const SCRIPT_ROOT = {{ request.script_root|tojson }}
let user_id = ... // do something to get a user id from the page
let user_url = `${SCRIPT_ROOT}/user/${user_id}`
fetch(user_url).then(...)
使用 fetch
发出请求¶
fetch()
接受两个参数,一个 URL 和一个包含其他选项的对象,并返回一个 Promise
。我们不会涵盖所有可用的选项,并且只会对 promise 使用 then()
,而不是其他回调或 await
语法。阅读链接的 MDN 文档以获取有关这些功能的更多信息。
默认情况下,使用 GET 方法。如果响应包含 JSON,则可以将其与 then()
回调链一起使用。
const room_url = {{ url_for("room_detail", id=room.id)|tojson }}
fetch(room_url)
.then(response => response.json())
.then(data => {
// data is a parsed JSON object
})
要发送数据,请使用数据方法(例如 POST),并传递 body
选项。数据最常见的类型是表单数据或 JSON 数据。
要发送表单数据,请传递填充的 FormData
对象。这使用与 HTML 表单相同的格式,并且将在 Flask 视图中使用 request.form
访问。
let data = new FormData()
data.append("name", "Flask Room")
data.append("description", "Talk about Flask here.")
fetch(room_url, {
"method": "POST",
"body": data,
}).then(...)
通常,首选将请求数据作为表单数据发送,就像提交 HTML 表单时一样。JSON 可以表示更复杂的数据,但除非您需要它,否则最好坚持使用更简单的格式。发送 JSON 数据时,还必须发送 Content-Type: application/json
标头,否则 Flask 将返回 400 错误。
let data = {
"name": "Flask Room",
"description": "Talk about Flask here.",
}
fetch(room_url, {
"method": "POST",
"headers": {"Content-Type": "application/json"},
"body": JSON.stringify(data),
}).then(...)
跟踪重定向¶
响应可能是重定向,例如,如果您使用 JavaScript 而不是传统的 HTML 表单登录,并且您的视图返回重定向而不是 JSON。JavaScript 请求确实会跟踪重定向,但它们不会更改页面。如果您想更改页面,您可以检查响应并手动应用重定向。
fetch("/login", {"body": ...}).then(
response => {
if (response.redirected) {
window.location = response.url
} else {
showLoginError()
}
}
)
替换内容¶
响应可能是新的 HTML,可以是页面中要添加或替换的新部分,也可以是全新的页面。通常,如果您要返回整个页面,最好使用上一节中显示的重定向来处理。以下示例显示如何用请求返回的 HTML 替换 <div>
。
<div id="geology-fact">
{{ include "geology_fact.html" }}
</div>
<script>
const geology_url = {{ url_for("geology_fact")|tojson }}
const geology_div = getElementById("geology-fact")
fetch(geology_url)
.then(response => response.text)
.then(text => geology_div.innerHTML = text)
</script>
从视图返回 JSON¶
要从 API 视图返回 JSON 对象,您可以直接从视图返回 dict。它将自动序列化为 JSON。
@app.route("/user/<int:id>")
def user_detail(id):
user = User.query.get_or_404(id)
return {
"username": User.username,
"email": User.email,
"picture": url_for("static", filename=f"users/{id}/profile.png"),
}
如果您想返回另一种 JSON 类型,请使用 jsonify()
函数,该函数创建一个响应对象,并将给定数据序列化为 JSON。
from flask import jsonify
@app.route("/users")
def user_list():
users = User.query.order_by(User.name).all()
return jsonify([u.to_json() for u in users])
通常,在 JSON 响应中返回文件数据不是一个好主意。JSON 不能直接表示二进制数据,因此必须对其进行 base64 编码,这可能很慢,需要更多带宽来发送,并且不容易缓存。相反,请使用一个视图来提供文件,并生成一个指向所需文件的 URL 以包含在 JSON 中。然后,客户端可以在获取 JSON 后发出单独的请求以获取链接的资源。
在视图中接收 JSON¶
使用 json
属性的 request
对象,将请求的正文解码为 JSON。如果正文不是有效的 JSON,或者 Content-Type
标头未设置为 application/json
,则会引发 400 Bad Request 错误。
from flask import request
@app.post("/user/<int:id>")
def user_update(id):
user = User.query.get_or_404(id)
user.update_from_json(request.json)
db.session.commit()
return user.to_json()