157

My app makes a call to an API that returns a dictionary. I want to pass information from this dict to JavaScript in the view. I am using the Google Maps API in the JS, specifically, so I'd like to pass it a list of tuples with the long/lat information. I know that render_template will pass these variables to the view so they can be used in HTML, but how could I pass them to JavaScript in the template?

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)
davidism
  • 121,510
  • 29
  • 395
  • 339
mea
  • 1,573
  • 2
  • 10
  • 5

9 Answers9

187

You can use {{ variable }} anywhere in your template, not just in the HTML part. So this should work:

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

Think of it as a two-stage process: First, Jinja (the template engine Flask uses) generates your text output. This gets sent to the user who executes the JavaScript he sees. If you want your Flask variable to be available in JavaScript as an array, you have to generate an array definition in your output:

<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 also offers more advanced constructs from Python, so you can shorten it to:

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

You can also use for loops, if statements and many more, see the Jinja2 documentation for more.

Also, have a look at Ford's answer who points out the tojson filter which is an addition to Jinja2's standard set of filters.

Edit Nov 2018: tojson is now included in Jinja2's standard set of filters.

mensi
  • 9,580
  • 2
  • 34
  • 43
  • 2
    Much obliged, mensi! That was my initial thought but the documentation for Flask doesn't make it readily clear that you can use the {{var}} form in JS as well. Thanks for clearing that up. – mea Jun 24 '12 at 15:13
  • 2
    @mea: you can also use the template engine to generate arbitrary text-based files, I have also used it to dynamically generate TeX files (-> PDF) and email, it's quite versatile ;) – mensi Jun 24 '12 at 15:17
  • 1
    quick follow up question: if I do a for loop in JS, can I use the index variable within the python variable e.g. {{geocode[i]}} ? – mea Jun 25 '12 at 15:51
  • 2
    That makes sense, but the solution you posted seems to require me to hand-code in the contents of the JS array. I was hoping I could do it more programmatically such that I could pass a Python list of variable length and a JS for loop could iterate over the length and then append them to the JS array. Sorry if I am not making myself clear enough, but I am rather green to JS and web dev. – mea Jun 25 '12 at 16:40
  • Wow, awesome! I guess there's no way for this insertion to work in a javascript file included in the template? – Felix Sep 24 '13 at 02:38
  • Only if you serve the file through flask as well. I usually try to keep external files static though and supply user data via a big json-style javascript object as an input to the functions in the external file... – mensi Sep 24 '13 at 08:03
  • Is this secure? Say I wanted to pass a `currentUserCanViewXYZ` Boolean from Flask in this way, and then use JavaScript to show/hide `XYZ`. Would the user then just be able to edit the DOM to change whether they can view `XYZ`, or does it not work like this. By "JavaScript" I really mean react if that makes a difference. – Jake Stockwin Jul 27 '17 at 22:50
  • @JakeStockwin if you want to hide parts, you need to use {{ if ... }} etc in the template to ensure the content is never generated in the first place. Anything you send to the client can be seen by the client. – mensi Jul 31 '17 at 15:57
  • Can you pass them into a separate javascript file? It feels kind of iffy to code the script within the html file. – Rocket Pingu Mar 16 '18 at 05:21
  • 3
    @RocketPingu if you want to pass data in a separate file, it's usually easier to just use the `json`module to dump your data in the form of a json object – mensi Mar 17 '18 at 16:30
  • however, if geocode is a list of tuples that contains data, how do you make a loop that loops through every single tuple of the list –  Jul 12 '18 at 03:34
  • `| safe` may be useful as well depending on how your variable is formatted – jimh Dec 07 '20 at 18:34
  • is it possible to display the stored variable without using innerHTML? Something like storing the value to a variable and then use that variable to display the value in the page – Alexandru DuDu Sep 19 '22 at 15:38
137

The ideal way to go about getting pretty much any Python object into a JavaScript object is to use JSON. JSON is great as a format for transfer between systems, but sometimes we forget that it stands for JavaScript Object Notation. This means that injecting JSON into the template is the same as injecting JavaScript code that describes the object.

Flask provides a Jinja filter for this: tojson dumps the structure to a JSON string and marks it safe so that Jinja does not autoescape it.

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

This works for any Python structure that is JSON serializable:

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);
ford
  • 10,687
  • 3
  • 47
  • 54
40

Using a data attribute on an HTML element avoids having to use inline scripting, which in turn means you can use stricter CSP rules for increased security.

Specify a data attribute like so:

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

Then access it in a static JavaScript file like so:

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

// jQuery
var geocode = JSON.parse($("#mydiv").data("geocode"));
Mark Amery
  • 143,130
  • 81
  • 406
  • 459
mcote
  • 634
  • 6
  • 6
17

Alternatively you could add an endpoint to return your variable:

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

Then do an XHR to retrieve it:

fetch('/api/geocode')
  .then((res)=>{ console.log(res) })
Vinnie James
  • 5,763
  • 6
  • 43
  • 52
  • 3
    Yes, you *could*, and in some architectures (esp. SPAs) this is the proper way to do things, but bear in mind that there are several disadvantages to doing this versus baking the data into the page when you serve it: 1. it's slower, 2. it requires slightly more code even to do sloppily, and 3. it introduces at least two extra front-end states that you will likely need to handle cleanly in your UI (namely the state where the XHR request is still in flight, and the one where it's failed completely), which requires a bunch of extra JavaScript code and introduces an extra source of potential bugs. – Mark Amery Dec 27 '18 at 20:16
6

Working answers are already given but I want to add a check that acts as a fail-safe in case the flask variable is not available. When you use:

var myVariable = {{ flaskvar | tojson }};

if there is an error that causes the variable to be non existent, resulting errors may produce unexpected results. To avoid this:

{% if flaskvar is defined and flaskvar %}
var myVariable = {{ flaskvar | tojson }};
{% endif %}
Patrick Mutuku
  • 1,095
  • 15
  • 13
4

Just another alternative solution for those who want to pass variables to a script which is sourced using flask, I only managed to get this working by defining the variables outside and then calling the script as follows:

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

If I input jinja variables in test123.js it doesn't work and you will get an error.

tsando
  • 4,557
  • 2
  • 33
  • 35
  • 3
    -1; this answer doesn't make sense. I think (based on the phrasing *"a script which is sourced using flask"* and your apparent expectation that you'd be able to use template variables in `/static/test123.js`) that you're misunderstanding how ` – Mark Amery Dec 27 '18 at 20:21
2
<script>
    const geocodeArr = JSON.parse('{{ geocode | tojson }}');
    console.log(geocodeArr);
</script>

This uses jinja2 to turn the geocode tuple into a json string, and then the javascript JSON.parse turns that into a javascript array.

nackjicholson
  • 4,557
  • 4
  • 37
  • 35
1

Well, I have a tricky method for this job. The idea is as follow-

Make some invisible HTML tags like <label>, <p>, <input> etc. in HTML body and make a pattern in tag id, for example, use list index in tag id and list value at tag class name.

Here I have two lists maintenance_next[] and maintenance_block_time[] of the same length. I want to pass these two list's data to javascript using the flask. So I take some invisible label tag and set its tag name is a pattern of list index and set its class name as value at 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%}

After this, I retrieve the data in javascript using some simple javascript operation.

<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
  • 1,604
  • 1
  • 17
  • 15
-2

Some js files come from the web or library, they are not written by yourself. The code they get variable like this:

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

This method makes js files unchanged(keep independence), and pass variable correctly!

jayzhen
  • 111
  • 1
  • 1
  • 7