I’ve got this Backbone.Model representing a Google Books API volume:
var Book = Backbone.Model.extend({
defaults: {
volumeInfo : {
title: 'n.a.',
authors: 'n.a.',
publisher: 'n.a.',
publishedDate: 'n.a.',
imageLinks : {
smallThumbnail: '/unavailable.jpg'
}
}
},
parse: function(resp) {
if (resp.volumeInfo.authors) {
resp.volumeInfo.authors = resp.volumeInfo.authors.join(',');
}
return resp;
}
});
Which is fed to this template:
<script type="text/template" id="bookCollectionRow">
<tr>
<td><img class="thumbnail" src="<%= volumeInfo.imageLinks.smallThumbnail %>" /></td>
<td><a target="_blank" href="<%= volumeInfo.canonicalVolumeLink %>"><%= volumeInfo.title %></a></td>
<td><%= volumeInfo.authors %></td>
<td><%= volumeInfo.publisher %></td>
<td><%= volumeInfo.publishedDate %></td>
</tr>
</script>
Upon parsing the template, when a volume JSON does not contain an imageLinks I receive this error:
Uncaught TypeError: Cannot read property 'smallThumbnail' of undefined.
I know I could fix it by checking with an if in the Model or in the template but what’s the purpose of defaults model property then? Does that work only if not overriding parse?
Solution
A few things. First, you shouldn’t have nested objects as backbone model attributes in general – it can be OK if you can always treat the attribute atomically, but this is a perfect example of when you can’t. From a data-model perspective, imageLinks should be its own backbone model class, as should volumeInfo.
Second, if defaults is an object literal ({}) instead of a function, the same object is used as the default attrs for each model instance. I think you want this:
defaults: function(){
return {
volumeInfo : {} // should be new VolumeInfo({}) imo
};
},
But the data model is the bigger issue – .defaults doesn’t do the kind of nested-object-template thing you seem to be going for, and for good reason: it doesn’t work well, this will just be the first of many gotchas you’ll run into if you don’t keep your instance data pretty flat.
Top comments (0)