Search code examples
javascripttemplatesunderscore.jsunderscore.js-templating

Underscore.js - template does not compile on first time


I am trying to render a template with underscore.js from the function triggered when an ajax call (which provides me the JSON I need) succeeds.

I'm experiencing some kind of strange behaviour:

  • When the ajax call succeeds for the first time, I get this error:

Uncaught ReferenceError: response is not defined

  • When it succeeds for the second time, without refreshing the page, everything goes as intended.

My JSON has this structure:

{
    data: [ 
        item1: {count: "1", url: "http://example1.com", id:"arstd", formatted:"1"},
        item2: {count: "53", url: "http://example2.net", id:"hneio", formatted:"0"},
        ...
    ]
}

My underscore.js template:

<script type="text/template" id="count_template">
    <% _.each (response.data, function (item) { %>
    <a href="<%- item.url %>">
        <li>
            <p id="<%- item.id %>" class="name">
                <%- item.formatted %>
            </p>
            <p id="<%- item.id %>_count" class="count">
                <%- item.count %>
            </p>
        </li>
    </a>
    <% }); %>
</script>

My ajax callback function:

var on_result_count_fetched = (function () {
    var totals = $(".regions .count");
    var ajax_loader = $("#ajax_loader");
    var c_tl = $('#count_template');
    var count_div = $('#res_count');
    //getting the template for the count response
    if (c_tl) {
        var c_template = _.template(c_tl.html());
    }
    _.templateSettings.variable = "response";
    //real callback
    return function (response) {
        if (response.redirect) {
            window.location.replace(data.redirect);
        } else {
            //hide loading animation
            ajax_loader.hide();
            if (!response && _.isEmpty(response)) {
                var tmp = $("<button>In case of fail: do this other action!</button>")
                tmp.click (function() {
                    fetch_results ("searx");
                });
            } else {
                console.log(response);
                var tmp = c_template(response);
            }
            count_div.empty();
            count_div.append(tmp);
        }
    }
}());

Solution

  • When you say _.template(some_string), Underscore will use the values from _.templateSettings to parse some_string and convert it to a JavaScript function. Once _.template has returned a compiled template function to you, the contents of _.templateSettings no longer matter.

    You're doing this sort of thing:

    var t = _.template(some_string);
    _.templateSettings.variable = "response";
    

    so your _.templateSettings.variable assignment comes too late to affect your _.template call. You need to adjust _.templateSettings before calling _.template so this:

    if (c_tl) {
        var c_template = _.template(c_tl.html());
    }
    _.templateSettings.variable = "response";
    

    should look more like:

    if (c_tl) {
        _.templateSettings.variable = "response";
        var c_template = _.template(c_tl.html());
    }
    

    Or you could skip _.templateSettings altogether and say:

    var tmp = c_template({ response: response });
    

    when calling the template function. Messing around with _.templateSettings can have side effects if your other templates aren't expecting to have to use response to access their data. Configuring _.templateSettings globally in exactly one place or leaving it alone completely tend to work better.