I am having a hard time understanding sub-views with Backbone and am trying to learn how to use them.
I want to create a simple unordered list using a main view for the container, and a sub-view for each list item.
Here is a Fiddle to what I have currently: http://jsfiddle.net/LrZrJ/
Here is the HTML I want to see in the end:
<div class="js-container">
<h3>Stat = ok</h3>
<ul>
<li>Hello world</li>
</ul>
</div>
Here is my JavaScript:
var TheModel = Backbone.Model.extend({
default: {
photos: '',
stat: ''
}
});
var TheCollection = Backbone.Collection.extend({
model: TheModel,
url: 'http://api.flickr.com/services/rest/?page=1&api_key=a2978e5ce30c337e3b639172d3e1a0d1&tags=candy&method=flickr.photos.search&per_page=3&format=json&jsoncallback=?'
});
// The main view
var List = Backbone.View.extend({
el: '.js-container',
initialize: function () {
this.collection = new TheCollection();
this.item = new ListItem();
return this;
},
render: function () {
var self = this;
this.collection.fetch({
dataType: 'jsonp',
success: function (data) {
var listTemplate = _.template( $('#listContainer').html(), {
collection: self.collection.toJSON()
});
self.$el.html(listTemplate);
self.$el.html( self.item.render() );
}
});
return this;
}
});
var ListItem = Backbone.View.extend({
render: function () {
var itemTemplate = _.template( $('#item').html(), {} );
return itemTemplate;
}
});
var myList= new List();
myList.render();
Here's my HTML:
<script type="text/template" id="listContainer">
<h3>Stat = <%- collection[0].stat %></h3>
<ul>
</ul>
</script>
<script type="text/template" id="item">
<li>Hello world</li>
</script>
<div class="js-container">
</div>
You're almost there. You have two problems right here:
self.$el.html(listTemplate);
self.$el.html( self.item.render() );
First of all, jQuery's html
function replaces everything:
When
.html()
is used to set an element's content, any content that was in that element is completely replaced by the new content.
That means that your second .html
call:
self.$el.html( self.item.render() );
overwrites everything from the first one. You probably want to use append
instead, that will just add the <li>
to whatever you append
to. You'll also want to append
to the <ul>
, not the self.$el
; Backbone includes a handy this.$
function in your view's for searching within the view's el
:
$ (jQuery)
view.$(selector)
[...] It's equivalent to running: view.$el.find(selector)
So to find the <ul>
and put the <li>
inside it, you can say this:
self.$('ul').append(self.item.render())
While I'm here, it is common for a view's render
to return this
and then the caller would something.append(v.render().el)
to get the view onto the page. You could use setElement
to replace the sub-view's el
with your <li>
from the template:
var ListItem = Backbone.View.extend({
render: function () {
var itemTemplate = _.template( $('#item').html(), {} );
this.setElement(itemTemplate);
return this;
}
});
and then self.$('ul').append(self.item.render().el)
in the parent.
Demo: http://jsfiddle.net/ambiguous/JHV9w/1/
An alternative would be to add tagName: 'li'
to ListItem
so that Backbone would create an <li>
as that view's el
and then remove the <li>...</li>
from the template. That would give you a ListItem
like this:
var ListItem = Backbone.View.extend({
tagName: 'li',
render: function () {
var itemTemplate = _.template( $('#item').html(), {} );
this.$el.html(itemTemplate);
return this;
}
});
and an #item
template like this:
<script type="text/template" id="item">
Hello world
</script>
This structure would (IMO) be more standard than what you're currently working with (but Backbone isn't very opinionated so do what works for you).