98

Let's say I have a Python variable:

list_of_items = ['1','2','3','4','5']

and I pass it to Jinja by rendering HTML, and I also have a function in JavaScript called somefunction(variable). I am trying to pass each item of list_of_items. I tried something like this:

{% for item in list_of_items %}
<span onclick="somefunction({{item}})">{{item}}</span><br>
{% endfor %}

Is it possible to pass a list from Python to JavaScript or should I pass each item from list one by one in a loop? How can I do this?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
user1843766
  • 1,013
  • 1
  • 8
  • 5

7 Answers7

138

To pass some context data to javascript code, you have to serialize it in a way it will be "understood" by javascript (namely JSON). You also need to mark it as safe using the safe Jinja filter, to prevent your data from being htmlescaped.

You can achieve this by doing something like that:

The view

import json

@app.route('/')
def my_view():
    data = [1, 'foo']
    return render_template('index.html', data=json.dumps(data))

The template

<script type="text/javascript">
    function test_func(data) {
        console.log(data);
    }
    test_func({{ data|safe }})
</script>

Edit - exact answer

So, to achieve exactly what you want (loop over a list of items, and pass them to a javascript function), you'd need to serialize every item in your list separately. Your code would then look like this:

The view

import json

@app.route('/')
def my_view():
    data = [1, "foo"]
    return render_template('index.html', data=map(json.dumps, data))

The template

{% for item in data %}
    <span onclick=someFunction({{ item|safe }});>{{ item }}</span>
{% endfor %}

Edit 2

In my example, I use Flask, I don't know what framework you're using, but you got the idea, you just have to make it fit the framework you use.

Edit 3 (Security warning)

NEVER EVER DO THIS WITH USER-SUPPLIED DATA, ONLY DO THIS WITH TRUSTED DATA!

Otherwise, you would expose your application to XSS vulnerabilities!

mdeous
  • 17,513
  • 7
  • 56
  • 60
  • 10
    What might be better, is not passing as a JSON string and doing `{{item|tojson|safe}}` instead – Nick Garvey May 15 '14 at 01:26
  • 3
    -1; this is broken and insecure in the general case, and there is only a narrow set of circumstances in which using it is okay. If any of the items contains a space, the output will be broken, and if one of them is `" > "` then you'll be XSSed. Yes, you have a security warning, but even so, this is a bad answer; there are clean ways of doing this that *aren't* broken (like using Flask's `tojson` filter), and those should be used instead. – Mark Amery Dec 27 '18 at 20:57
  • Awesome, It required to use "{{ data | safe}}" to load data from jinja variables. – Ryan Chou Feb 27 '19 at 02:13
  • 1
    {{ data | safe }} worked for me on the list as it appeared, without the need to convert to json first. – rbennell Apr 11 '19 at 10:30
39

I had a similar problem using Flask, but I did not have to resort to JSON. I just passed a list letters = ['a','b','c'] with render_template('show_entries.html', letters=letters), and set

var letters = {{ letters|safe }}

in my javascript code. Jinja2 replaced {{ letters }} with ['a','b','c'], which javascript interpreted as an array of strings.

Godsmith
  • 2,492
  • 30
  • 26
  • 6
    -1; this won't work for all values, and is dangerous if any of the content is user-provided. For example, if one of the strings in your list is `` then it'll break your page. – Mark Amery Dec 27 '18 at 21:00
25

You can do this with Jinja's tojson filter, which

Dumps a structure to JSON so that it’s safe to use in <script> tags [and] in any place in HTML with the notable exception of double quoted attributes.

For example, in your Python, write:

some_template.render(list_of_items=list_of_items)

... or, in the context of a Flask endpoint:

return render_template('your_template.html', list_of_items=list_of_items)

Then in your template, write this:

{% for item in list_of_items %}
<span onclick='somefunction({{item | tojson}})'>{{item}}</span><br>
{% endfor %}

(Note that the onclick attribute is single-quoted. This is necessary since |tojson escapes ' characters but not " characters in its output, meaning that it can be safely used in single-quoted HTML attributes but not double-quoted ones.)

Or, to use list_of_items in an inline script instead of an HTML attribute, write this:

<script>
const jsArrayOfItems = {{list_of_items | tojson}};
// ... do something with jsArrayOfItems in JavaScript ...
</script>

DON'T use json.dumps to JSON-encode variables in your Python code and pass the resulting JSON text to your template. This will produce incorrect output for some string values, and will expose you to XSS if you're trying to encode user-provided values. This is because Python's built-in json.dumps doesn't escape characters like < and > (which need escaping to safely template values into inline <script>s, as noted at https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements) or single quotes (which need escaping to safely template values into single-quoted HTML attributes).

If you're using Flask, note that Flask injects a custom tojson filter instead of using Jinja's version. However, everything written above still applies. The two versions behave almost identically; Flask's just allows for some app-specific configuration that isn't available in Jinja's version.

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
7

To add up on the selected answer, I have been testing a new option that is working too using jinja2 and flask:

@app.route('/')
def my_view():
    data = [1, 2, 3, 4, 5]
    return render_template('index.html', data=data)

The template:

<script>
    console.log( {{ data | tojson }} )
</script>

the output of the rendered template:

<script>
    console.log( [1, 2, 3, 4] )
</script>

The safe could be added but as well like {{ data | tojson | safe }} to avoid html escape but it is working without too.

Sylhare
  • 5,907
  • 8
  • 64
  • 80
2

I can suggest you a javascript oriented approach which makes it easy to work with javascript files in your project.

Create a javascript section in your jinja template file and place all variables you want to use in your javascript files in a window object:

Start.html

...
{% block scripts %}
<script type="text/javascript">
window.appConfig = {
    debug: {% if env == 'development' %}true{% else %}false{% endif %},
    facebook_app_id: {{ facebook_app_id }},
    accountkit_api_version: '{{ accountkit_api_version }}',
    csrf_token: '{{ csrf_token }}'
}
</script>
<script type="text/javascript" src="{{ url_for('static', filename='app.js') }}"></script>
{% endblock %}

Jinja will replace values and our appConfig object will be reachable from our other script files:

App.js

var AccountKit_OnInteractive = function(){
    AccountKit.init({
        appId: appConfig.facebook_app_id,
        debug: appConfig.debug,
        state: appConfig.csrf_token,
        version: appConfig.accountkit_api_version
    })
}

I have seperated javascript code from html documents with this way which is easier to manage and seo friendly.

muratgozel
  • 2,333
  • 27
  • 31
  • -1 because, like most other answers here, this will break if the string variables you're templating in contain certain characters or substrings (like a single quote, or ``). The claim in your final paragraph about SEO being affected by whether a site uses inline JavaScript or external scripts also seems dubious to me. – Mark Amery Dec 28 '18 at 00:05
  • @MarkAmery Thanks for the clarification. There are 2 methods I use today to get server variables in the browser. **First one is a little bit different from the answer**: `` Data is json encoded and must be escaped of course. And **the second one is making an API request** If your data depend on the visitor its best to get it by making an api request. – muratgozel Dec 28 '18 at 08:45
0

you can do it

<tbody>
    {% for proxy in proxys %}
            <tr>
                <td id={{proxy.ip}}>{{proxy.ip}}</td>
                <td id={{proxy.port}}>{{proxy.port}}</td>
                <td>{{proxy.protocol}}</td>
                <td>{{proxy.speed}}</td>
                <td>{{proxy.type}}</td>
                <td>{{proxy.city}}</td>
                <td>{{proxy.verify_time}}</td>
                <td>
                    <button type="button" class="btn btn-default" aria-label="Left Align">
                        <span class="glyphicon glyphicon-paste" aria-hidden="true" onclick="copyProxy('{{proxy.ip}}', '{{proxy.port}}')"></span>
                    </button>
                </td>
            </tr>
        {% endfor %}
    </tbody>
</table>
Wollens
  • 81
  • 5
-1

Make some invisible HTML tags like <label>, <p>, <input> etc. and name its id, and the class name is a pattern so that you can retrieve it later.

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

Now you can retrieve the data in javascript using some javascript operation like below -

<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