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()
© 2010 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/stable/patterns/javascript/