JavaScript、fetch、およびJSON JavaScript, ``fetch``, and JSON

ページ全体を再読み込みせずにデータを変更することで、HTMLページに動きを持たせたいと思うかもしれません。HTMLの<form>を提出しテンプレートを再変換するために転送する代わりに、fetch()を呼び出しページ上のコンテンツを置き換えるJavaScriptを加えることができます。 You may want to make your HTML page dynamic, by changing data without reloading the entire page. Instead of submitting an HTML ``<form>`` and performing a redirect to re-render the template, you can add `JavaScript`_ that calls |fetch|_ and replaces content on the page.

fetch()は最近の(the modern)、JavaScriptに組み込みの、ページからリクエストを作成するためのソリューションです。XMLHttpRequest()またはjQueryのような、他の「AJAX」のメソッドとライブラリを聞いたことがあるかもしれません。自分のアプリケーションの要求によっては使うことを選択するかもしれませんが、それらは最近のブラウザではもはや必要ありません。ここのドキュメントはJavaScriptに組み込みの目玉機能(feature)だけに焦点を当てます。 |fetch|_ is the modern, built-in JavaScript solution to making requests from a page. You may have heard of other "AJAX" methods and libraries, such as |XHR|_ or `jQuery`_. These are no longer needed in modern browsers, although you may choose to use them or another library depending on your application's requirements. These docs will only focus on built-in JavaScript features.

テンプレートの変換 Rendering Templates

テンプレートとJavaScriptの違いを理解することは重要です。テンプレートは、ユーザのブラウザにレスポンスが送信される前に、サーバ上で変換されます。JavaScriptは、テンプレートが変換され送信された後、ユーザのブラウザ内で走ります。従って、Jinjaがテンプレートを変換するやり方に影響を与えるためにJavaScriptを使用することはできませんが、(Jinjaとテンプレートを使って)dataを走らせるJavaScriptの中に変換させることは可能です。 It is important to understand the difference between templates and JavaScript. Templates are rendered on the server, before the response is sent to the user's browser. JavaScript runs in the user's browser, after the template is rendered and sent. Therefore, it is impossible to use JavaScript to affect how the Jinja template is rendered, but is is possible to render data into the JavaScript that will run.

テンプレートを変換するときにデータをJavaScriptへ提供するためには、tojson()フィルタを<script>ブロックの中で使用します。これはデータを正当なJavaScriptオブジェクトへ変換し、安全ではないHTML文字が安全なものに変換されることを確実にします。もしtojsonフィルタを使わない場合、ブラウザのコンソールでSyntaxErrorを得るでしょう。 To provide data to JavaScript when rendering the template, use the :func:`~jinja-filters.tojson` filter in a ``<script>`` block. This will convert the data to a valid JavaScript object, and ensure that any unsafe HTML characters are rendered safely. If you do not use the ``tojson`` filter, you will get a ``SyntaxError`` in the browser console.

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が生成されるでしょう。 A less common pattern is to add the data to a ``data-`` attribute on an HTML tag. In this case, you must use single quotes around the value, not double quotes, otherwise you will produce invalid or unsafe HTML.

<div data-chart='{{ chart_data|tojson }}'></div>

URLの生成 Generating URLs

サーバからJavaScriptへデータを取得する別のやり方は、そのためのリクエストを作成することです。最初に、リクエストするURLを知っている必要があります。 The other way to get data from the server to JavaScript is to make a request for it. First, you need to know the URL to request.

URLを生成するシンプルなやり方は、テンプレートを変換するときにurl_for()を変わらず使うことです。例えば: The simplest way to generate URLs is to continue to use :func:`~flask.url_for` when rendering the template. For example:

const user_url = {{ url_for("user", id=current_user.id)|tojson }}
fetch(user_url).then(...)

しかしながら、JavaScriptの中でしか分からない情報に基づいてURLを生成する必要があるかもしれません。既に論じたように、JavaScriptはユーザのブラウザ内で走り、テンプレートの変換の一部としては走らないため、その時点ではurl_forは使えません。 However, you might need to generate a URL based on information you only know in JavaScript. As discussed above, JavaScript runs in the user's browser, not as part of the template rendering, so you can't use ``url_for`` at that point.

この場合、その下でアプリケーションが提供される「root URL」を知っている必要があります。シンプルな設定では、これは/ですが、https://example.com/myapp/のように、他のものである場合もあります。 In this case, you need to know the "root URL" under which your application is served. In simple setups, this is ``/``, but it might also be something else, like ``https://example.com/myapp/``.

JavaScriptのコードにroot を伝えるシンプルなやり方は、テンプレートを変換するとき、rootを(JavaScriptの)グローバルな変数に設定することです。それからJavaScriptからURLを生成するときにそれ(root)を使えます。 A simple way to tell your JavaScript code about this root is to set it as a global variable when rendering the template. Then you can use it when generating URLs from 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を使ったリクエストの作成 Making a Request with ``fetch``

fetch()は、URLとURL以外のオプションを持つオブジェクトと、2つの引数を取り、Promiseを返します。ここでは利用可能なオプションを全ては網羅せず、promiseにはthen()だけを使い、その他のコールバックやawait記法は使いません。それらの目玉機能(feature)についての更なる情報は、リンクされているMDNのドキュメントを読んでください。 |fetch|_ takes two arguments, a URL and an object with other options, and returns a |Promise|_. We won't cover all the available options, and will only use ``then()`` on the promise, not other callbacks or ``await`` syntax. Read the linked MDN docs for more information about those features.

標準設定では、GETメソッドが使われます。もしレスポンスがJSONを含んでいたら、それはthen()コールバックの連鎖を使って使用できます。 By default, the GET method is used. If the response contains JSON, it can be used with a ``then()`` callback chain.

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オプションを渡します。最もよくあるデータのタイプはformデータまたはJSONデータです。 To send data, use a data method such as POST, and pass the ``body`` option. The most common types for data are form data or JSON data.

formデータを送るには、データを登録したFormDataオブジェクトを渡します。これはHTMLフォームと同じフォーマットを使用し、Flaskのviewの中からrequest.formを使ってアクセスできるでしょう。 To send form data, pass a populated |FormData|_ object. This uses the same format as an HTML form, and would be accessed with ``request.form`` in a Flask view.

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のformを提出するときに使われるような、formデータを使ってリクエストのデータを送信するのが好ましいです。JSONはより複雑なデータを表現できますが、それを必要とするまではシンプルな方のフォーマットを守った方が良いです。JSONデータを送信するときは、Content_Type: application/jsonヘッダーも同様に送信する必要があり、さもなければFlaskは400エラーを返すでしょう。 In general, prefer sending request data as form data, as would be used when submitting an HTML form. JSON can represent more complex data, but unless you need that it's better to stick with the simpler format. When sending JSON data, the ``Content-Type: application/json`` header must be sent as well, otherwise Flask will return a 400 error.

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(...)

リダイレクトのフォロー Following Redirects

例えば、もし従来のHTMLのformの代わりにJavaScriptを使ってログインし、あなたのviewがJSONの代わりにリダイレクトを返した場合、(fetchで処理する)レスポンスがリダイレクトの場合があります。JavaScriptのリクエストはリダイレクトをフォローしますが、ページは変更しません。もしページを変更したい場合、レスポンスを調べ手作業でリダイレクトを適用できます。 A response might be a redirect, for example if you logged in with JavaScript instead of a traditional HTML form, and your view returned a redirect instead of JSON. JavaScript requests do follow redirects, but they don't change the page. If you want to make the page change you can inspect the response and apply the redirect manually.

fetch("/login", {"body": ...}).then(
    response => {
        if (response.redirected) {
            window.location = response.url
        } else {
            showLoginError()
        }
    }
)

コンテンツの置き換え Replacing Content

追加または置き換えのためのページの新しいセクションであっても、もしくはページ全体であっても、(fetchが処理する)レスポンスは新しいHTMLかもしれません。大概は、もしあなた(のFlaskアプリ)がページ全体を返している場合、前のセクションの中で示したようなリダイレクトを使って処理するのが良いでしょう。以下の例は、リクエストによって返されるHTMLを使ってどのように<div>(だけ)を置き換えるかを示しています。 A response might be new HTML, either a new section of the page to add or replace, or an entirely new page. In general, if you're returning the entire page, it would be better to handle that with a redirect as shown in the previous section. The following example shows how to replace a ``<div>`` with the HTML returned by a request.

<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>

viewからのJSONの返し Return JSON from Views

(Flaskで作成した)APIのviewからJSONのオブジェクトを返すためには、viewからdictを直接返すことができます。それはJSONに自動的にシリアライズされます。 To return a JSON object from your API view, you can directly return a dict from the view. It will be serialized to JSON automatically.

@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のタイプを返したい場合、JSONにシリアライズされた与えられたデータを持つレスポンスのオブジェクトを作成する、jsonify()関数を使います。 If you want to return another JSON type, use the :func:`~flask.json.jsonify` function, which creates a response object with the given data serialized to 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にエンコードする必要があり、それは遅いことがあり、より多くの帯域を使い、簡単にはキャッシュできません。代わりに、(Flaskで)1つのviewを使ってファイルを提供し、望むファイルへのURLを、JSONに含めるために生成します。それから、クライアントはJSONを取得した後、リンクされたリソースを取得するための別のリクエストを作成できます。 It is usually not a good idea to return file data in a JSON response. JSON cannot represent binary data directly, so it must be base64 encoded, which can be slow, takes more bandwidth to send, and is not as easy to cache. Instead, serve files using one view, and generate a URL to the desired file to include in the JSON. Then the client can make a separate request to get the linked resource after getting the JSON.

viewの中でのJSONの受信 Receiving JSON in Views

(Flask側で)リクエストのbodyをJSONとしてデコードするためには、requestオブジェクトのjsonプロパティを使います。もしbodyが正当なJSONでない場合、またはContent-Typeヘッダーがapplication/jsonに設定されていない場合、400 Bad Requestエラーが起こされます。 Use the :attr:`~flask.Request.json` property of the :data:`~flask.request` object to decode the request's body as JSON. If the body is not valid JSON, or the ``Content-Type`` header is not set to ``application/json``, a 400 Bad Request error will be raised.

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()