18
votes

Still learning backbone so bear with me;

I'm trying to add a new model with blank fields to a view, but the template I've created has a whole bunch of

<input value="<%= some_value %>" type="whatever" />

Works perfectly fine when fetching data, it populates it and all goes well. The trouble arises when I want to create a new (blank) rendered view, it gives me

Uncaught ReferenceError: some_value is not defined

I can set defaults (I do already for a few that have default values in the db) but that means typing out over 40 of them with blanks; is there a better way of handling this?

I'm fiddling around with the underscore template itself, trying something like <%= if(some_value != undefined){ some_value } %> but that also seems a bit cumbersome.

8

8 Answers

10
votes

No,

There is no actual fix for this due to the way underscore templates are implemented.

See this discussion about it:

I'm afraid that this is simply the way that with(){} works in JS. If the variable isn't declared, it's a ReferenceError. There's nothing we can do about it, while preserving the rest of template behavior.

The only way you can accomplish what you're looking for is to either wrap the object with another object like the other answer suggested, or setting up defaults.

24
votes

Pass the template data inside a wrapper object. Missing property access won't throw an error:

So, instead of:

var template = _.template('<%= foo %><%= bar %>');
var model = {foo:'foo'};
var result = template(model); //-> Error

Try:

var template = _.template('<%= model.foo %><%= model.bar %>');
var model = {foo:'foo'};
var result = template({model:model}); //-> "foo"
12
votes

Actually, you can use arguments inside of your template:

<% if(!_.isUndefined(arguments[0].foo)) { %>
       ...
<% } %>
5
votes

If you check source code for generated template function, you will see something like this:

with (obj||{}) {
  ...
  // model property is used as variable name
  ...
}

What happens here: at first JS tries to find your property in "obj", which is model (more about with statement). This property is not found in "obj" scope, so JS traverses up and up until global scope and finally throws exception.

So, you can specify your scope directly to fix that:

<input value="<%= obj.some_value %>" type="whatever" />

At least it worked for me.

3
votes

Actually, you can access vars like initial object properties.

If you'll activate debugger into template, you can find variable "obj", that contains all your data.

So instead of <%= title %> you should write <%= obj.title %>

0
votes

lodash, an underscore replacement, provides a template function with a built-in solution. It has an option to wrap the data in another object to avoid the "with" statement that causes the error.

Sample usage from the API documentation:

// using the `variable` option to ensure a with-statement isn’t used in the compiled template
var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
compiled.source;
// → function(data) {
//   var __t, __p = '';
//   __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
//   return __p;
// }
0
votes

A very simple solution: you can ensure that your data collection is normalized, i.e. that all properties are present in each object (with a null value if they are unused). A function like this can help:

function normalizeCollection (collection, properties) {
  properties = properties || [];
  return _.map(collection, function (obj) {
    return _.assign({}, _.zipObject(properties, _.fill(Array(properties.length), null)), obj);
  });
}

(Note: _.zipObject and _.fill are available in recent versions of lodash but not underscore)

Use it like this:

var coll = [
  { id: 1, name: "Eisenstein"},
  { id: 2 }
];

var c = normalizeCollection(coll, ["id", "name", "age"]);
// Output =>
// [
//  { age: null, id: 1, name: "Eisenstein" },
//  { age: null, id: 2, name: null }
// ]

Of course, you don't have to transform your data permanently – just invoke the function on the fly as you call your template rendering function:

var compiled = _.template(""); // Your template string here
// var output = compiled(data); // Instead of this
var output = compiled(normalizeCollection(data)); // Do this 
0
votes

You can abstract @Dmitri's answer further by adding a function to your model and using it in your template.

For example:

Model :

new Model = Backbone.Model.extend({
    defaults: {
        has_prop: function(prop) {
            return _.isUndefined(this[property]) ? false : true;
        }
    }
});

Template:

<% if(has_prop('property')) { %>
    // Property is available
<% } %>

As the comment in his answer suggests this is more extendable.