Как передать данные из Flask в JavaScript в шаблоне?

124

Мое приложение обращается к API, который возвращает словарь. Я хочу передать информацию из этого dict в JavaScript в представлении. Я использую API Карт Google в JS, в частности, поэтому я хотел бы передать ему список кортежей с информацией long / lat. Я знаю, что 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)
теа
источник

Ответы:

141

Вы можете использовать в {{ 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 .

Также посмотрите ответ Форда, который указывает на tojsonфильтр, который является дополнением к стандартному набору фильтров Jinja2. .

Изменить ноябрь 2018: tojsonтеперь включен в стандартный набор фильтров Jinja2.

MENSI
источник
2
Очень признателен, менси! Это было моей первоначальной мыслью, но в документации по Flask не ясно, что вы также можете использовать форму {{var}} в JS. Спасибо, что прояснили это.
mea
2
@mea: вы также можете использовать механизм шаблонов для создания произвольных текстовых файлов, я также использовал его для динамического создания файлов TeX (-> PDF) и электронной почты, он довольно универсален;)
mensi
быстрый ответ на вопрос: если я выполняю цикл for в JS, могу ли я использовать индексную переменную в переменной python, например {{geocode [i]}}?
MEA
1
Это имеет смысл, но решение, которое вы опубликовали, похоже, требует, чтобы я вручную кодировал содержимое массива JS. Я надеялся, что смогу сделать это более программно, так что я смогу передать список Python переменной длины, а цикл JS for может выполнять итерацию по длине, а затем добавлять их в массив JS. Извините, если я недостаточно ясно выражаюсь, но я довольно зеленый для JS и веб-разработчиков.
MEA
1
@RocketPingu, если вы хотите передать данные в отдельном файле, обычно проще использовать jsonмодуль для
выгрузки
114

Идеальный способ превратить практически любой объект Python в объект JavaScript - использовать JSON. JSON - отличный формат для передачи данных между системами, но иногда мы забываем, что это означает JavaScript Object Notation. Это означает, что внедрение 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>

Это работает для любой структуры Python, сериализуемой в формате JSON:

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
Попробуйте это в следующий раз, когда войдете Uncaught SyntaxError: Unexpected token &в консоль javascript.
scharfmn 05
8
Я считаю этот ответ более убедительным и логичным, чем принятый.
Конрад
Я получил ошибку при запуске кода:TypeError: Object of type Undefined is not JSON serializable
jbuddy_13
27

Использование атрибута данных в элементе 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"));
mcote
источник
11

В качестве альтернативы вы можете добавить конечную точку для возврата вашей переменной:

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

Затем выполните XHR, чтобы получить его:

fetch('/api/geocode')
  .then((res)=>{ console.log(res) })
Винни Джеймс
источник
Да, вы могли бы , и в некоторых архитектурах (особенно в SPA) это правильный способ делать что-то, но имейте в виду, что у этого есть несколько недостатков по сравнению с записью данных на странице, когда вы ее обслуживаете: 1. это медленнее, 2. требуется немного больше кода, даже если он работает небрежно, и 3. он вводит как минимум два дополнительных состояния внешнего интерфейса, которые вам, вероятно, нужно будет аккуратно обработать в своем пользовательском интерфейсе (а именно, состояние, в котором запрос XHR все еще находится в процессе , и тот, где он полностью потерпел неудачу), что требует кучи дополнительного кода JavaScript и вводит дополнительный источник потенциальных ошибок.
Марк Эмери
3

Еще одно альтернативное решение для тех, кто хочет передавать переменные в скрипт, полученный с помощью flask, мне удалось заставить это работать, только определив переменные снаружи, а затем вызвав скрипт следующим образом:

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

Если я введу переменные jinja, test123.jsэто не сработает, и вы получите сообщение об ошибке.

tsando
источник
Именно то, что я искал.
shrishinde
1
-1; этот ответ не имеет смысла. Я думаю (основываясь на фразе «сценарий, созданный с использованием flask» и вашем очевидном ожидании, что вы сможете использовать переменные шаблона /static/test123.js), что вы неправильно понимаете, как работает <script>s with src. Это не функция Flask. Браузер , когда он разбирает такой сценарий, делает отдельный запрос HTTP , чтобы получить сценарий. Содержимое скрипта не запекается в шаблоне Flask; действительно, Flask, скорее всего, завершил отправку шаблонного HTML в браузер к тому моменту, когда браузер даже запросит скрипт.
Марк Эмери
3

Рабочие ответы уже даны, но я хочу добавить проверку, которая действует как отказоустойчивая, если переменная flask недоступна. Когда вы используете:

var myVariable = {{ flaskvar | tojson }};

если есть ошибка, из-за которой переменная не существует, возникающие ошибки могут привести к неожиданным результатам. Чтобы этого избежать:

{% if flaskvar is defined and flaskvar %}
var myVariable = {{ flaskvar | tojson }};
{% endif %}
Патрик Мутуку
источник
2
<script>
    const geocodeArr = JSON.parse('{{ geocode | tojson }}');
    console.log(geocodeArr);
</script>

Он использует jinja2 для превращения кортежа геокода в строку json, а затем javascript JSON.parseпревращает это в массив javascript.

nackjicholson
источник
1
Почему бы вам просто не сериализовать json в python
Mojimi
0

Что ж, у меня есть хитрый метод для этой работы. Идея заключается в следующем:

Сделайте несколько невидимых HTML-тегов, например <label>, <p>, <input> и т. Д., В теле HTML и создайте шаблон в идентификаторе тега, например, используйте индекс списка в идентификаторе тега и значение списка в имени класса тега.

Здесь у меня есть два списка maintenance_next [] и maintenance_block_time [] одинаковой длины. Я хочу передать эти два списка данных в javascript с помощью колбы. Итак, я беру какой-то невидимый тег label и устанавливаю его имя тега как образец индекса списка и устанавливаю его имя класса как значение в index.

{% 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, используя простую операцию 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>

Танмой Датта
источник
-1

Некоторые файлы js поступают из Интернета или библиотеки, они не написаны вами. Код они получают такие переменные:

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

Этот метод делает файлы js неизменными (сохраняя независимость) и правильно передает переменную!

jayzhen
источник