Search code examples
javascriptjsonflasksyntax-errorjsonparser

JS works embedded in Flask template, but not in separate file


Good afternoon!

I am working on a Flask application. In one of the templates (HTML file) called reserve.html, I have a block of embedded JS that works perfectly fine. When I try to move it to a separate file (static/reserve.js)to make the HTML more readable, I get this "uncaught error" in Chrome's console:

Uncaught SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)
    at JSON.parse (<anonymous>)
    at reserve.js:1:21

(reserve.js is placed correctly in the "static" folder as required by Flask)

This is the line to which the error is referring (line 1 of reserve.js where I use JSON to convert a Python dict to a JS object):

var day_list = JSON.parse('{{ day_list|tojson }}');

The dict comes from a Python file daylist.py and the data is sent through the Flask app main.py to the reserve.html template. From there, it is parsed into JS object using JSON string, as shown in the previous line.

I have tried several methods of fixing quotes (to avoid multiple " or ' clashing with each other) and the error did not go away.

Here is reserve.html with JS embedded, and it works:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>Make a reservation</h1>

    <h2>Choose a day:</h2>

    <script>
        var day_list = JSON.parse('{{ day_list|tojson }}');

        function today(button_id, div_id) {
            var button = document.getElementById(button_id);
            var div = document.getElementById(div_id);

            if (div.style.display == "block") {
                div.style.display = "none";
                button.style.backgroundColor = "white";
            } else {
                div.style.display = "block";
                button.style.backgroundColor = "blue";
            }

            for (const day in day_list) {
                if (day_list[day].num != button_id) {
                    document.getElementById(day_list[day].num).style.backgroundColor = "white";
                    document.getElementById(day_list[day].name).style.display = "none";
                }
            }
        }

        for (const day in day_list) {
            console.log(day_list[day]);
        }
    </script>

    {% for day in day_list %}
        <form id="{{ day.order }}" method="POST">
            <input
                type="button"
                id="{{ day.num }}"
                onclick="today(button_id='{{ day.num }}', div_id='{{ day.name }}')"
                value="{{ day.num }}"
                style="background-color: white;"
            >

            <div id="{{ day.name }}" style="display: none;">
                <label>Hello</label>
            </div>
        </form>
    {% endfor %}
</body>
</html>

If it helps, also, here is the relevant part of main.py:

from flask import Flask, render_template
from daylist import list_days

app = Flask(__name__)

@app.route('/create-reservation/', methods = ["GET", "POST"])
def create_reservation():
    # if request.method == "POST":
    #     name = request.form.get("name")
    #     date = request.form.get("date")
    #     hours = request.form.get("hours").split()
    #     courts = request.form.get("courts").split()
    #     reserve(name, date, hours, courts)
    day_list = list_days()
    return render_template('reserve.html', day_list = day_list)

if __name__ == "__main__":
    app.run(debug=True)

Now, here is reserve.html with the JS script linked instead of embedded:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="{{ url_for('static', filename='reserve.js') }}"></script>
</head>
<body>
    <h1>Make a reservation</h1>

    <h2>Choose a day:</h2>

    {% for day in day_list %}
        <form id="{{ day.order }}" method="POST">
            <input
                type="button"
                id="{{ day.num }}"
                onclick="today(button_id='{{ day.num }}', div_id='{{ day.name }}')"
                value="{{ day.num }}"
                style="background-color: white;"
            >

            <div id="{{ day.name }}" style="display: none;">
                <label>Hello</label>
            </div>
        </form>
    {% endfor %}
</body>
</html>

And this is static/reserve.js:

var day_list = JSON.parse('{{ day_list|tojson }}');

function today(button_id, div_id) {
    var button = document.getElementById(button_id);
    var div = document.getElementById(div_id);

    if (div.style.display == "block") {
        div.style.display = "none";
        button.style.backgroundColor = "white";
    } else {
        div.style.display = "block";
        button.style.backgroundColor = "blue";
    }

    for (const day in day_list) {
        if (day_list[day].num != button_id) {
            document.getElementById(day_list[day].num).style.backgroundColor = "white";
            document.getElementById(day_list[day].name).style.display = "none";
        }
    }
}

for (const day in day_list) {
    console.log(day_list[day]);
}

Please leave a comment if you'd like any more details that might be helpful.


Solution

  • Jinja, the template engine built into Flask, is limited to the templates and interprets all expressions contained in them on the server side. The static files, such as your JavaScript file, are obtained from the client and, if necessary, interpreted in its JavaScript engine.
    Jinja ignores these files.

    For this reason, the expression {{ data_list|tojson }} outsourced to the JavaScript is no longer taken into account by Jinja and remains as a string. The JavaScript engine does not understand these parts of the code and throws an error because it is not valid JavaScript code or, in your case, JSON format.

    So your code works as long as the specific line remains within the template.

    As a tip, it is not necessary to convert the data into a string by using quotation marks and then parse it again using JSON.parse(...). The following assignment is completely sufficient.

    const day_list = {{ day_list|tojson }};