JavaScript, fetch, и JSON
Возможно, вы захотите сделать вашу HTML-страницу динамичной, изменяя данные без перезагрузки всей страницы. Вместо отправки HTML <form> и перенаправления для повторного рендеринга шаблона, вы можете добавить JavaScript, который вызывает fetch() и заменяет содержимое на странице.
fetch() — это современное встроенное решение JavaScript для отправки запросов со страницы. Возможно, вы слышали об других методах и библиотеках «AJAX», таких как XMLHttpRequest() или jQuery. В современных браузерах они больше не нужны, хотя вы можете использовать их или другую библиотеку в зависимости от требований вашего приложения. Эти документы будут сосредоточены только на встроенных функциях JavaScript.
Рендеринг Шаблонов
Важно понимать разницу между шаблонами и JavaScript. Шаблоны рендерятся на сервере до отправки ответа в браузер пользователя. JavaScript выполняется в браузере пользователя после того, как шаблон рендерится и отправляется. Поэтому невозможно использовать JavaScript для влияния на то, как рендерится шаблон Jinja, но можно рендерить данные в JavaScript, который будет выполняться.
Чтобы предоставить данные JavaScript при рендеринге шаблона, используйте фильтр tojson() в блоке <script>. Это преобразует данные в допустимый 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>
Менее распространённый подход — добавить данные в атрибут data- тега HTML. В этом случае вы должны использовать одинарные кавычки вокруг значения, а не двойные, в противном случае вы получите недопустимый или небезопасный HTML.
<div data-chart='{{ chart_data|tojson }}'></div>
Генерация URL-адресов
Другой способ получить данные с сервера в JavaScript — отправить запрос за ними. Сначала вам необходимо знать URL-адрес запроса.
Самый простой способ сгенерировать URL-адреса — продолжать использовать url_for() при рендеринге шаблона. Например:
const user_url = {{ url_for("user", id=current_user.id)|tojson }}
fetch(user_url).then(...)
Однако, возможно, вам понадобится сгенерировать URL-адрес на основе информации, которую вы знаете только в JavaScript. Как обсуждалось выше, JavaScript выполняется в браузере пользователя, а не как часть рендеринга шаблона, поэтому вы не можете использовать url_for в этот момент.
В этом случае вам необходимо знать «корневой URL», под которым обслуживается ваше приложение. В простых настройках это /, но это также может быть что-то другое, например https://example.com/myapp/.
Простым способом сообщить вашему коду JavaScript об этом корневом адресе является установка его в качестве глобальной переменной при рендеринге шаблона. Затем вы можете использовать его при генерации URL-адресов из JavaScript.
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. Мы не будем рассматривать все доступные параметры и будем использовать только 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-форма, и к нему можно обратиться с помощью request.form в представлении Flask.
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, либо новой частью страницы для добавления или замены, либо совершенно новой страницей. В общем случае, если вы возвращаете всю страницу, лучше обработать это с помощью перенаправления, как показано в предыдущем разделе. В следующем примере показано, как заменить <div> HTML, возвращённым запросом.
<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 из представлений
Чтобы вернуть объект JSON из представления API, вы можете напрямую вернуть словарь из представления. Он будет сериализован в 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()
© 2007–2022 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/2.3.x/patterns/javascript/