underscore: _.template: allow undefined keys

When the template function needs an element that is not defined it returns an Error. It would be great if the template function would act equivalent as if the value of the element is undefined

_.template('<%= foo %>', {foo: undefined});

returns “”, but

_.template('<%= foo %>', {});

returns a ReferenceError

The reason I’m requesting this is, that I usually use underscore to template my views. But sometimes the attributes of the model are not yet available. In this case the application returns an error, it would be better if the template function would be more lax.

About this issue

  • Original URL
  • State: closed
  • Created 13 years ago
  • Comments: 29 (10 by maintainers)

Most upvoted comments

This bug really sucks. I’m not going to use underscore templates again. -.-

All of these are unnecessary. If you really want to not have to define the variables you’re going to use, just use a data object with properties.

Instead of:

templateFunction({a: a, b: b, c: c});

Do:

templateFunction({data: {a: a, b: b, c: c}});

And then in your code, you can refer to data.a, data.b, or data.z without getting a reference error.

It’s an old subject, but you always can access all variables with arguments[0]

<%= arguments[0].foo %> works if foo is undefined

image

_.template('<%= obj.undef %>')({}) // empty string

Minifiers should not break that as it’s a static string, but can we reasonably rely on that? It’s arguaby more elegant than arguments[0].

Edit: documentation says

[options.variable**=‘obj’**] (string): The data object variable name.

So I’d say it’s even more “public API” than arguments[0] and you should definitely prefer this 😃

@velikayikci

    _.template(value, {  });

Please note that this only compiles the template. It doesn’t render it. Unless you’re using a very old version of Underscore.

To anyone still running into issues like these, two words of advice.

Firstly, explicitly setting the variable when compiling the template will let you access arbitrary unset properties on the root data object unpunished and also give you better performance.

var withoutVariable = _.template('<%= foo %>');
withoutVariable({ foo: 1 }); // renders '1'
withoutVariable({ bar: 1 }); // error

var withVariable = _.template('<%= root.foo %>', { variable: 'root' });
withVariable({ foo: 1 }); // renders '1'
withVariable({ bar: 1 }); // fine, renders 'undefined'

I like naming this variable root. Another common choice is data. I also liked a suggestion earlier in this issue thread to use o.

Secondly, Underscore nowadays has has and get. Using these in your templates will let you access arbitrarily nested properties in the data, no nested if statements required.

var templateText = `
<%= _.get(root, ['foo', 'bar', 'baz'], 'oops') %>
Two-syllable properties <% 
if (_.has(root, ['foobar', 'barbaz'])) { 
    %>present<%
} else {
    %>absent<%
} %>
`;

var compiledTemplate = _.template(templateText, { variable: 'root' });

compiledTemplate({
    foo: {
        bar: {
            baz: 'hello',
        },
    },
    foobar: {
        barbaz: null,
    },
});
// renders 'hello\nTwo-syllable properties present'

compiledTemplate({});
// renders 'oops\nTwo-syllable properties absent'

Why doesn’t _.undefined(foo) even work within templates? This is absurd