Como posso passar dados do Flask para JavaScript em um modelo?

123

Meu aplicativo faz uma chamada para uma API que retorna um dicionário. Quero passar informações deste ditado para JavaScript na exibição. Estou usando a API do Google Maps no JS, especificamente, então gostaria de passar uma lista de tuplas com as informações longas / latinas. Eu sei que render_templateessas variáveis ​​serão passadas para a exibição para que possam ser usadas em HTML, mas como eu poderia passá-las para JavaScript no modelo?

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)
mea
fonte

Respostas:

140

Você pode usar {{ variable }}em qualquer lugar do seu modelo, não apenas na parte HTML. Portanto, isso deve funcionar:

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

Pense nisso como um processo de dois estágios: primeiro, o Jinja (o mecanismo de gabarito usado pelo Flask) gera sua saída de texto. Isso é enviado ao usuário que executa o JavaScript que ele vê. Se você deseja que sua variável Flask esteja disponível em JavaScript como uma matriz, é necessário gerar uma definição de matriz em sua saída:

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

O Jinja também oferece construções mais avançadas do Python, para que você possa reduzi-lo para:

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

Você também pode usar forloops, ifinstruções e muito mais. Consulte a documentação do Jinja2 para obter mais informações.

Além disso, dê uma olhada na resposta da Ford, que aponta o tojsonfiltro, que é uma adição ao conjunto padrão de filtros do Jinja2 .

Edit Nov 2018: tojsonagora está incluído no conjunto padrão de filtros do Jinja2.

mensi
fonte
2
Muito obrigado, mensi! Esse foi o meu pensamento inicial, mas a documentação do Flask não deixa claro que você também pode usar o formulário {{var}} em JS. Obrigado por esclarecer isso.
mea 24/06
2
@mea: você também pode usar o modelo de motor para gerar arquivos baseados em texto arbitrárias, eu também tê-lo usado para gerar dinamicamente arquivos tex (-> PDF) e e-mail, é bastante versátil;)
mensi
pergunta rápida sobre acompanhamento: se eu faço um loop for em JS, posso usar a variável index dentro da variável python, por exemplo, {{geocode [i]}}?
mea 25/06
1
Isso faz sentido, mas a solução que você postou parece exigir que eu codifique manualmente o conteúdo da matriz JS. Eu esperava poder fazê-lo de forma mais programática, para que eu pudesse passar uma lista Python de comprimento variável e um loop JS for poderia iterar ao longo do comprimento e anexá-los à matriz JS. Desculpe se eu não estou me esclarecendo o suficiente, mas sou bastante verde para JS e web dev.
mea 25/06
1
@RocketPingu se você quiser passar dados em um arquivo separado, normalmente é mais fácil simplesmente usar o jsonmódulo para despejar seus dados na forma de um objeto JSON
mensi
113

A maneira ideal de obter praticamente qualquer objeto Python em um objeto JavaScript é usar JSON. O JSON é ótimo como um formato para transferência entre sistemas, mas às vezes esquecemos que significa JavaScript Object Notation. Isso significa que injetar JSON no modelo é o mesmo que injetar código JavaScript que descreve o objeto.

O Flask fornece um filtro Jinja para isso: tojsondespeja a estrutura em uma string JSON e a marca como segura, para que o Jinja não a escape automaticamente.

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

Isso funciona para qualquer estrutura Python que seja JSON serializável:

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);
vau
fonte
4
Tente isso na próxima vez que você entrar Uncaught SyntaxError: Unexpected token &no console javascript.
scharfmn
8
Acho essa resposta mais sólida e lógica do que a resposta aceita.
224 Konrad Konrad
TypeError: Object of type Undefined is not JSON serializable
Ocorreu
27

O uso de um atributo de dados em um elemento HTML evita o uso de scripts embutidos, o que, por sua vez, significa que você pode usar regras mais rígidas de CSP para aumentar a segurança.

Especifique um atributo de dados da seguinte maneira:

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

Em seguida, acesse-o em um arquivo JavaScript estático, assim:

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

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

Como alternativa, você pode adicionar um ponto de extremidade para retornar sua variável:

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

Em seguida, faça um XHR para recuperá-lo:

fetch('/api/geocode')
  .then((res)=>{ console.log(res) })
Vinnie James
fonte
Sim, você poderia , e em algumas arquiteturas (especialmente os SPAs), essa é a maneira correta de fazer as coisas, mas lembre-se de que existem várias desvantagens em fazer isso em comparação com a inserção dos dados na página quando você os serve: 1. é mais lento, 2. requer um pouco mais de código, mesmo que seja desleixado, e 3. introduz pelo menos dois estados extras de front-end com os quais você provavelmente precisará lidar corretamente na sua interface do usuário (ou seja, o estado em que a solicitação XHR ainda está em andamento , e aquele em que falhou completamente), que requer um monte de código JavaScript extra e introduz uma fonte extra de possíveis bugs.
precisa
3

Apenas outra solução alternativa para quem deseja passar variáveis ​​para um script que é originado usando o balão, eu só consegui fazer isso definindo as variáveis ​​fora e depois chamando o script da seguinte maneira:

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

Se eu inserir variáveis ​​jinja, test123.jsele não funcionará e você receberá um erro.

tsando
fonte
Exatamente o que eu estava procurando.
shrishinde
1
-1; esta resposta não faz sentido. Eu acho que (com base no fraseado "um script que é originária usando frasco" e sua expectativa evidente que você seria capaz de usar variáveis de modelo em /static/test123.js) que você está mal-entendido como <script>s com srcs trabalho. Eles não são um recurso do Flask. O navegador , quando analisa esse script, faz uma solicitação HTTP separada para obter o script. O conteúdo do script não é inserido no modelo pelo Flask; de fato, o Flask provavelmente terminou de enviar o HTML de modelo para o navegador no momento em que o navegador solicita o script.
Mark Amery
3

As respostas de trabalho já foram fornecidas, mas quero adicionar uma verificação que atue como à prova de falhas, caso a variável do balão não esteja disponível. Quando você usa:

var myVariable = {{ flaskvar | tojson }};

se houver um erro que faça com que a variável seja inexistente, os erros resultantes poderão produzir resultados inesperados. Para evitar isso:

{% if flaskvar is defined and flaskvar %}
var myVariable = {{ flaskvar | tojson }};
{% endif %}
Patrick Mutuku
fonte
2
<script>
    const geocodeArr = JSON.parse('{{ geocode | tojson }}');
    console.log(geocodeArr);
</script>

Isso usa jinja2 para transformar a tupla de geocódigo em uma string json e, em seguida, o javascript JSON.parsetransforma isso em uma matriz javascript.

nackjicholson
fonte
1
Por que você não apenas serializar o JSON em python
Mojimi
0

Bem, eu tenho um método complicado para este trabalho. A ideia é a seguinte:

Faça algumas tags HTML invisíveis como <label>, <p>, <input>etc. no corpo HTML e faça um padrão no ID da tag, por exemplo, use o índice de lista na ID da tag e o valor da lista no nome da classe da tag.

Aqui eu tenho duas listas maintenance_next [] e maintenance_block_time [] do mesmo comprimento. Quero passar esses dados da lista para javascript usando o balão. Então, pego uma etiqueta de etiqueta invisível e defino seu nome de tag como um padrão de índice de lista e defino seu nome de classe como valor no índice.

{% 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%}

Depois disso, recupero os dados em javascript usando alguma operação javascript simples.

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

Tanmoy Datta
fonte
-1

Alguns arquivos js vêm da web ou da biblioteca e não são escritos por você. O código que eles obtêm variável assim:

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

Este método mantém os arquivos js inalterados (mantém a independência) e passa a variável corretamente!

jayzhen
fonte