Search code examples
javascriptbackbone.jsunderscore.jsmarionetteunderscore.js-templating

Underscore Templates: passing variables from a parent template to a child template


I have an underscore template that looks roughly like this:

<script type="text/template" id="list-template">
    <%= listHeaderTemplate(); %>
    stuff that
    displays a list
    <%= listFooterTemplate(); %>
</script>

<script type="text/template" id="list-footer-template">
    <%= foo %>
</script>

<script>
listTemplate = _.template($("#list-template").html());
listHeaderTemplate = _.template($("#list-header-template").html());
listFooterTemplate = _.template($("#list-footer-template").html());
</script>

What I want to do is to pass the full set of variables that are passed to listTemplate() into listFooterTemplate(). For example,

listTemplate({foo: "foo"});

I can do this by modifying the calls to listHeaderTemplate() / listFooterTemplate() in list-template to package the locals into a dictionary (i.e. listFooterTemplate({foo: foo});) but that seems rather onerous, particularly when a large set of variables are in use, and it requires me to know inside of list-template which variables list-footer-template might use.


Solution

  • When you compile an Underscore template, Underscore turns your template inside out and builds a function that looks, more or less, like this:

    function(obj) {
        // Some set up...
        with(obj || {}) {
            // The template as JavaScript...
        }
        return theTemplateText;
    }
    

    You can't depend on the parameter being called obj, that's bound to break soon or later. You should have safe access to the arguments object though. Having arguments allows you to call other functions with exactly the same arguments as the current function without having to know what the arguments are, you just use apply.

    If you have listHeaderTemplate and listFooterTemplate available to your template, you can simply say:

    <script type="text/template" id="list-template">
        <%= listHeaderTemplate.apply(this, arguments) %>
        stuff that
        displays a list
        <%= listFooterTemplate.apply(this, arguments) %>
    </script>
    

    An easy way is to pass those functions as arguments to your template:

    var listTemplate       = _.template($("#list-template").html());
    var listHeaderTemplate = _.template($("#list-header-template").html());
    var listFooterTemplate = _.template($("#list-footer-template").html());
    
    var html = listTemplate({
        foo: 11,
        listHeaderTemplate: listHeaderTemplate,
        listFooterTemplate: listFooterTemplate
    });
    

    Demo: http://jsfiddle.net/ambiguous/vwjm1kta/