Jak mogę przekazać dane z Flask do JavaScript w szablonie?

124

Moja aplikacja wywołuje interfejs API, który zwraca słownik. Chcę przekazać informacje z tego dyktu do JavaScript w widoku. Używam interfejsu API Google Maps w JS, więc chciałbym przekazać mu listę krotek z długimi / szerokimi informacjami. Wiem, że render_templateprzekaże te zmienne do widoku, aby można ich było używać w HTML, ale jak mogę przekazać je do JavaScript w szablonie?

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
źródło

Odpowiedzi:

142

Możesz użyć {{ variable }}dowolnego miejsca w swoim szablonie, nie tylko w części HTML. Więc to powinno działać:

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

Potraktuj to jako proces dwuetapowy: Po pierwsze, Jinja (silnik szablonów używany przez Flask) generuje tekst. Jest to wysyłane do użytkownika, który wykonuje widziany JavaScript. Jeśli chcesz, aby zmienna Flask była dostępna w JavaScript jako tablica, musisz wygenerować definicję tablicy w swoim wyniku:

<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 oferuje również bardziej zaawansowane konstrukcje z Pythona, więc możesz je skrócić do:

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

Możesz także używać forpętli, ifinstrukcji i wielu innych, więcej informacji znajdziesz w dokumentacji Jinja2 .

Spójrz również na odpowiedź Forda, który wskazuje tojsonfiltr, który jest dodatkiem do standardowego zestawu filtrów Jinja2 .

Edytuj listopad 2018: tojsonjest teraz zawarta w standardowym zestawie filtrów Jinja2.

mensi
źródło
2
Bardzo zobowiązany, mensi! To była moja pierwsza myśl, ale dokumentacja Flaska nie wyjaśnia jasno, że możesz używać formularza {{var}} także w JS. Dzięki za wyjaśnienie tego.
mea
2
@mea: możesz również użyć silnika szablonów do generowania dowolnych plików tekstowych, użyłem go również do dynamicznego generowania plików TeX (-> PDF) i e-maili, jest dość wszechstronny;)
mensi
szybkie pytanie uzupełniające: jeśli wykonam pętlę for w JS, czy mogę użyć zmiennej indeksu w zmiennej Pythona, np. {{geocode [i]}}?
mea,
1
Ma to sens, ale rozwiązanie, które opublikowałeś, wymaga ode mnie ręcznego kodowania zawartości tablicy JS. Miałem nadzieję, że mógłbym to zrobić bardziej programowo, tak że mógłbym przekazać listę Pythona o zmiennej długości, a pętla JS for mogłaby iterować po długości, a następnie dołączyć je do tablicy JS. Przepraszam, jeśli nie wyrażam się wystarczająco jasno, ale jestem raczej zielony dla JS i twórców stron internetowych.
mea
1
@RocketPingu jeśli chcesz przekazać dane w osobnym pliku, zwykle łatwiej jest po prostu użyć jsonmodułu do zrzucenia danych w postaci obiektu json
mensi
114

Idealnym sposobem na przeniesienie praktycznie dowolnego obiektu Pythona do obiektu JavaScript jest użycie formatu JSON. JSON świetnie sprawdza się jako format do przenoszenia między systemami, ale czasami zapominamy, że oznacza JavaScript Object Notation. Oznacza to, że wstrzyknięcie JSON do szablonu jest tym samym, co wstrzyknięcie kodu JavaScript opisującego obiekt.

Flask zapewnia do tego filtr Jinja: tojsonzrzuca strukturę do łańcucha JSON i oznacza ją jako bezpieczną, aby Jinja nie przeszukiwała jej automatycznie.

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

Działa to dla każdej struktury Pythona, którą można serializować w formacie 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);
bród
źródło
4
Spróbuj tego następnym razem, gdy przejdziesz Uncaught SyntaxError: Unexpected token &do konsoli javascript.
scharfmn
8
Uważam, że ta odpowiedź jest bardziej solidna i logiczna niż zaakceptowana odpowiedź.
Konrad
TypeError: Object of type Undefined is not JSON serializable
Wystąpił
27

Użycie atrybutu danych w elemencie HTML pozwala uniknąć konieczności używania wbudowanych skryptów, co z kolei oznacza, że ​​można użyć bardziej rygorystycznych reguł CSP w celu zwiększenia bezpieczeństwa.

Określ atrybut danych w następujący sposób:

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

Następnie uzyskaj do niego dostęp w statycznym pliku JavaScript, takim jak:

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

// jQuery
var geocode = JSON.parse($("#mydiv").data("geocode"));
mcote
źródło
11

Alternatywnie możesz dodać punkt końcowy, aby zwrócić zmienną:

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

Następnie wykonaj XHR, aby go odzyskać:

fetch('/api/geocode')
  .then((res)=>{ console.log(res) })
Vinnie James
źródło
Tak, może , aw niektórych architektur (ESP OSO). To jest właściwy sposób do robienia rzeczy, ale należy pamiętać, że istnieje kilka wad robi to w porównaniu do pieczenia dane na stronę, kiedy to służyć: 1. to wolniej, 2. wymaga nieco więcej kodu, nawet by wykonać niechlujnie, i 3. wprowadza co najmniej dwa dodatkowe stany front-endu, które prawdopodobnie będziesz musiał obsługiwać czysto w swoim interfejsie użytkownika (mianowicie stan, w którym żądanie XHR jest nadal w ruchu i ten, w którym całkowicie się nie udało), co wymaga sporej ilości dodatkowego kodu JavaScript i wprowadza dodatkowe źródło potencjalnych błędów.
Mark Amery,
3

Kolejne alternatywne rozwiązanie dla tych, którzy chcą przekazać zmienne do skryptu, który jest pobierany przy użyciu flask. Udało mi się to osiągnąć tylko poprzez zdefiniowanie zmiennych na zewnątrz, a następnie wywołanie skryptu w następujący sposób:

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

Jeśli wprowadzę zmienne jinja, test123.jsto nie działa i pojawi się błąd.

tsando
źródło
Dokładnie to, czego szukałem.
shrishinde
1
-1; ta odpowiedź nie ma sensu. Myślę, że (na podstawie frazowania „skryptu, które jest pozyskiwane za pomocą kolby” i swoją pozorną oczekiwanie, że byłbyś w stanie używać zmiennych w szablonie /static/test123.js), że jesteś nieporozumienie jak <script>sz srcy pracy. To nie jest funkcja Flask. Przeglądarka , gdy analizuje taki scenariusz sprawia osobnego żądania HTTP, aby uzyskać skrypt. Zawartość skryptu nie jest umieszczana w szablonie przez Flask; w rzeczywistości Flask prawdopodobnie zakończył wysyłanie szablonu HTML do przeglądarki, zanim przeglądarka zażąda skryptu.
Mark Amery,
3

Odpowiedzi robocze są już podane, ale chcę dodać sprawdzenie, które działa jako zabezpieczenie przed awarią w przypadku, gdy zmienna kolby jest niedostępna. Kiedy używasz:

var myVariable = {{ flaskvar | tojson }};

Jeśli wystąpi błąd, który powoduje, że zmienna nie istnieje, wynikowe błędy mogą dać nieoczekiwane rezultaty. Uniknąć tego:

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

Używa jinja2 do przekształcenia krotki geokodowania w ciąg json, a następnie javascript JSON.parsezamienia to w tablicę javascript.

nackjicholson
źródło
1
Dlaczego po prostu nie zserializujesz
pliku
0

Cóż, mam trudną metodę do tej pracy. Pomysł jest następujący-

Utwórz niewidoczne znaczniki HTML, takie jak <label>, <p>, <input> itp. W treści HTML i utwórz wzorzec w identyfikatorze tagu, na przykład użyj indeksu listy w identyfikatorze tagu i wartości listy w nazwie klasy tagu.

Tutaj mam dwie listy Maintenance_next [] i Maintenance_block_time [] o tej samej długości. Chcę przekazać dane z tych dwóch list do javascript za pomocą kolby. Więc biorę jakiś niewidoczny znacznik etykiety i ustawiam jego nazwę jako wzorzec indeksu listy i ustawiam jego nazwę klasy jako wartość w indeksie.

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

Następnie pobieram dane w javascript za pomocą prostej operacji 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>

Tanmoy Datta
źródło
-1

Niektóre pliki js pochodzą z sieci WWW lub biblioteki i nie są zapisywane przez Ciebie. Kod, który otrzymują zmienna, taka jak ta:

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

Ta metoda sprawia, że ​​pliki js pozostają niezmienione (zachowaj niezależność) i poprawnie przekazują zmienną!

jayzhen
źródło