如何在Flask模板中将数据从Flask传递给JavaScript?

158
我的应用程序调用一个返回字典的API。我想将这个字典中的信息传递给视图中的JavaScript。我在JS中使用Google Maps API,因此我想将其传递一个包含长/纬度信息的元组列表。我知道render_template会传递这些变量到视图中以便在HTML中使用,但我该如何将它们传递到模板中的JavaScript中呢?
from flask import Flask
from flask import render_template

app = Flask(__name__)

import foo_api

api = foo_api.API('API KEY')

@app.route('/')
def get_data():
    events = api.call(get_event, arg0, arg1)
    geocode = event['latitude'], event['longitude']
    return render_template('get_data.html', geocode=geocode)
10个回答

187

你可以在模板的任何地方使用{{ variable }},不仅仅是HTML部分。因此,以下代码应该可以正常工作:

<html>
<head>
  <script>
    var someJavaScriptVar = '{{ geocode[1] }}';
  </script>
</head>
<body>
  <p>Hello World</p>
  <button onclick="alert('Geocode: {{ geocode[0] }} ' + someJavaScriptVar)" />
</body>
</html>

将其看作两个阶段的过程:首先,Jinja(Flask使用的模板引擎)生成文本输出。这会发送给用户,用户执行他看到的JavaScript。如果您希望将Flask变量作为数组在JavaScript中可用,则必须在输出中生成数组定义:

<html>
  <head>
    <script>
      var myGeocode = ['{{ geocode[0] }}', '{{ geocode[1] }}'];
    </script>
  </head>
  <body>
    <p>Hello World</p>
    <button onclick="alert('Geocode: ' + myGeocode[0] + ' ' + myGeocode[1])" />
  </body>
</html>

Jinja还提供了来自Python的更高级结构,因此您可以将其简化为:

<html>
<head>
  <script>
    var myGeocode = [{{ ', '.join(geocode) }}];
  </script>
</head>
<body>
  <p>Hello World</p>
  <button onclick="alert('Geocode: ' + myGeocode[0] + ' ' + myGeocode[1])" />
</body>
</html>

你也可以使用 for 循环、if 语句等等,查看Jinja2文档以获得更多信息。

此外,请参考Ford的答案,他指出了tojson过滤器,它是Jinja2标准过滤器集合的补充。

编辑于2018年11月:tojson现在已包含在Jinja2的标准过滤器集合中。


2
非常感谢,mensi!那是我的最初想法,但 Flask 的文档并没有明确表明您也可以在 JS 中使用 {{var}} 形式。感谢您澄清这一点。 - mea
2
@mea:你也可以使用模板引擎来生成任意基于文本的文件,我也用它来动态生成TeX文件(-> PDF)和电子邮件,它非常灵活;) - mensi
1
快速跟进问题:如果我在JS中使用for循环,可以在Python变量中使用索引变量吗,例如{{geocode [i]}}? - mea
2
这很有道理,但你发布的解决方案似乎需要我手动编写JS数组的内容。我希望能够以更加程序化的方式实现,这样我就可以传递一个长度可变的Python列表,然后JS for循环可以迭代该长度并将它们附加到JS数组中。如果我表达不清楚,请原谅,因为我对JS和Web开发还比较陌生。 - mea
3
如果你想要将数据储存在另一个文件中,通常最简单的方法是使用 json 模块,将你的数据以 json 对象的形式进行转储。 - mensi
显示剩余8条评论

137

将几乎任何Python对象转换为JavaScript对象的理想方法是使用JSON。JSON作为系统间传输的格式非常好,但有时我们会忘记它代表JavaScript对象表示法。这意味着将JSON注入模板与注入描述该对象的JavaScript代码相同。

Flask提供了一个Jinja过滤器:tojson将结构转储为JSON字符串并标记为安全,以便Jinja不会自动转义它。

<html>
  <head>
    <script>
      var myGeocode = {{ geocode|tojson }};
    </script>
  </head>
  <body>
    <p>Hello World</p>
    <button onclick="alert('Geocode: ' + myGeocode[0] + ' ' + myGeocode[1])" />
  </body>
</html>

这适用于任何可进行JSON序列化的Python数据结构:

python_data = {
    'some_list': [4, 5, 6],
    'nested_dict': {'foo': 7, 'bar': 'a string'}
}
var data = {{ python_data|tojson }};
alert('Data: ' + data.some_list[1] + ' ' + data.nested_dict.foo + 
      ' ' + data.nested_dict.bar);

4
下次在JavaScript控制台中遇到“Uncaught SyntaxError:Unexpected token &”错误时,请尝试这个方法。 - scharfmn
9
我认为这个回答比被采纳的回答更为严谨和合乎逻辑。 - Konrad
1
我在运行代码时遇到了错误:TypeError: Object of type Undefined is not JSON serializable - jbuddy_13

40

在HTML元素上使用一个数据属性可以避免使用内联脚本,从而使您可以使用更严格的CSP规则来增强安全性。

指定数据属性可像这样:

<div id="mydiv" data-geocode='{{ geocode|tojson }}'>...</div>

然后在静态的JavaScript文件中像这样访问它:

// Raw JavaScript
var geocode = JSON.parse(document.getElementById("mydiv").dataset.geocode);

// jQuery
var geocode = JSON.parse($("#mydiv").data("geocode"));

17

或者,您可以添加一个端点来返回您的变量:

@app.route("/api/geocode")
def geo_code():
    return jsonify(geocode)

然后执行XHR以检索它:

fetch('/api/geocode')
  .then((res)=>{ console.log(res) })

3
是的,你可以这样做,在某些架构(特别是单页面应用程序)中这是正确的方法,但请记住,与在服务页面时将数据嵌入页面相比,这种方法有几个缺点:1. 它更慢,2. 即使粗略地完成,也需要稍微编写更多的代码,3. 它引入了至少两种额外的前端状态,您可能需要在 UI 中干净地处理它们(即 XHR 请求仍在进行中和完全失败的状态),这需要大量的额外 JavaScript 代码,并引入了一个额外的潜在错误来源。 - Mark Amery

7
已经给出了有用的答案,但我想添加一个检查功能,以防 Flask 变量不可用。当使用以下代码时:
var myVariable = {{ flaskvar | tojson }};

如果存在导致变量不存在的错误,可能会产生意外的结果。为了避免这种情况:

{% if flaskvar is defined and flaskvar %}
var myVariable = {{ flaskvar | tojson }};
{% endif %}

4

对于想要将变量传递给使用flask引用的脚本的人,我提供了另一种替代方案。我只能通过在外部定义变量,然后按如下方式调用脚本才成功运行:

    <script>
    var myfileuri = "/static/my_csv.csv"
    var mytableid = 'mytable';
    </script>
    <script type="text/javascript" src="/static/test123.js"></script>

如果我在test123.js中输入jinja变量,它不会起作用,并且会出现错误。

3
-1;这个答案没有意义。我认为(基于“使用flask源的脚本”这种措辞以及您显然期望在/static/test123.js中使用模板变量),您误解了带有src<script>的工作原理。它们不是Flask的特性。浏览器在解析这样的脚本时,会发出单独的HTTP请求来获取脚本。脚本内容不会被Flask嵌入到模板中;实际上,Flask可能在浏览器请求脚本之前就已经完成了向浏览器发送模板化HTML的过程。 - Mark Amery

2
<script>
    const geocodeArr = JSON.parse('{{ geocode | tojson }}');
    console.log(geocodeArr);
</script>

这里使用jinja2将地理编码元组转换为json字符串,然后javascript中的JSON.parse将其转换为javascript数组。


为什么要将有效的JS代码转换为字符串,然后再解析它,存在引号不匹配的危险? - gre_gor

1

嗯,我有一个棘手的方法来完成这个工作。思路如下-

在HTML body中创建一些不可见的HTML标签,比如<label>, <p>, <input>等,并在标签id中设置一个模式,例如使用列表索引在标签id中,使用列表值在标签类名中。

这里我有两个长度相同的列表maintenance_next[]和maintenance_block_time[]。我想使用flask将这两个列表的数据传递给javascript。因此,我取一些不可见的label标签,并将其标签名称设置为列表索引的模式,并将其类名设置为索引处的值。

{% for i in range(maintenance_next|length): %}
<label id="maintenance_next_{{i}}" name="{{maintenance_next[i]}}" style="display: none;"></label>
<label id="maintenance_block_time_{{i}}" name="{{maintenance_block_time[i]}}" style="display: none;"></label>
{% endfor%}

接下来,我使用一些简单的javascript操作来检索数据。

<script>
var total_len = {{ total_len }};

for (var i = 0; i < total_len; i++) {
    var tm1 = document.getElementById("maintenance_next_" + i).getAttribute("name");
    var tm2 = document.getElementById("maintenance_block_time_" + i).getAttribute("name");
    
    //Do what you need to do with tm1 and tm2.
    
    console.log(tm1);
    console.log(tm2);
}
</script>


0

太棒了!这个帖子帮了我很多!这是我的伪代码:

mypage.html

<script>
    var user = {{username}}
</script>

mypage.js

console.log('username = ' + user)

-2

一些js文件来自于网络或库,它们不是由你自己编写的。 它们获取变量的代码如下:

var queryString = document.location.search.substring(1);
var params = PDFViewerApplication.parseQueryString(queryString);
var file = 'file' in params ? params.file : DEFAULT_URL;

这个方法可以使js文件保持独立性,同时正确地传递变量!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接