Reputation: 3
I have two types of templates in my Backbone.js project. The single-item template and list-of-items template.
Also, there are two types of views in the project: The single-item template and list-of-items respectively.
I use RequireJS to to make application modular. So, I guess, it's a good practice to keep templates separate too. But there's a problem: I cant' make my templates to "communicate". When I render my CollectionView, it makes in the page only one element - empty <ul id="spaceman-list">
and there's no collection of items in it. In my listview I use addItem functuion to wrap each collection item by itemView, but it's not working. The list just doesn't render in the dom.
I don't wanna use such messy things like _.each()
, I wanna to keep my code clean.
Here's the both views code: list view
// app/js/views/spaceman-list.js
'use strict';
(function(w) {
define([
'jquery',
'underscore',
'backbone',
'../models/spaceman',
'../views/spaceman-item',
'../collections/spaceman-collection',
'text!../templates/spaceman-list.html'
], function($, _, Backbone, SpacemanModel, SpacemanItemView, SpacemanCollection, listTemplate) {
var SpacemanListView = Backbone.View.extend({
el: '#container',
render: function() {
this.collection.on('reset', this.addItem, this);
this.collection.each(this.addItem, this);
var data = {
spacemen: this.collection.models
};
var compiled = _.template(listTemplate, data);
this.$el.html(compiled);
return this;
},
addItem: function(item) {
var newSpaceman = new SpacemanItemView({model: item});
this.$el.find('ul').append(newSpaceman.render());
}
});
return SpacemanListView;
});
})(window);
And item view:
// app/js/views/spaceman-item.js
'use strict';
(function(w) {
define([
'jquery',
'underscore',
'backbone',
'../models/spaceman',
'text!../templates/spaceman-item.html'
], function($, _, Backbone, SpacemanItem, itemTemplate) {
var SpacemanItemView = Backbone.View.extend({
el: '#spaceman-list',
render: function() {
var data = {
spaceman: this.model
};
this.$el.html(_.template(itemTemplate, data));
},
});
return SpacemanItemView;
});
})(window);
And also, as I told above, I keep my underscore templates separate: This is list template:
// app/js/templates/spaceman-list.html
<ul id="spaceman-list"></ul>
And this is item template:
// app/js/templates/spaceman-item.html
<li><% spaceman.get('name') %></li>
And also, I adding here my app entry point:
// app/js/application.js
'use strict';
(function(w) {
define([
'jquery',
'underscore',
'backbone',
'collections/spaceman-collection',
'views/spaceman-list'
// Request router.js
], function($, _, Backbone, ItemsList, ListView) {
var App = Backbone.View.extend({
initialize: function(){
// Pass in our Router module and call it's initialize function
console.log('Yeeha!');
var endurance = new ItemsList([
{name: 'Cooper'},
{name: 'Brend'},
{name: 'Romilly'},
{name: 'Doyle'},
{name: 'Mann'}
]);
console.log(endurance);
var view = new ListView({collection:endurance});
view.render();
}
});
return App;
});
})(window);
I'm just going crazy about this. How can I make separate templates to "see" each other?
Upvotes: 0
Views: 140
Reputation: 434965
You say this in SpacemanListView#addItem
:
this.$el.find('ul').append(newSpaceman.render())
but SpacemanItemView#render
doesn't return anything:
render: function() {
var data = {
spaceman: this.model
};
this.$el.html(_.template(itemTemplate, data));
},
SpacemanItemView
also says:
el: '#spaceman-list'
so all the SpacemanItemView
instances will be trying to use the same el
when their render
functions say this.$el.html(...)
. Presumably your templates/spaceman-list.html
contains <ul id="spaceman-list">
or something similar so #spaceman-list
won't resolve to anything until the collection view has been placed on the page; but you're calling addItem
before the collection view is on the page so the item views are all trying to render into the same nonexistent element.
The solution is twofold:
render
functions traditionally return this;
so that the caller can $container.append(view.render().el)
to put things on the page so do this.el
s (i.e. el: '#something'
in a view definition) leads to all kinds ordering and event problems, things work better when each view creates and owns its own el
.addItem
should look more like this:
addItem: function(item) {
var newSpaceman = new SpacemanItemView({model: item});
this.$('ul').append(newSpaceman.render().el);
}
Note the this.$
shortcut and the .el
in the append
call.
Then we rework the item view:
var SpacemanItemView = Backbone.View.extend({
tagName: 'li',
render: function() {
var tmpl = _.template(itemTemplate);
this.$el.html(tmpl({ spaceman: this.model }));
return this;
},
});
No more el
but now we have tagName: 'li'
, this will tell Backbone to create an empty <li>
as the view's el
. The el
documentation covers how the various view properties are used to figure out what el
will be.
Also note that the _.template(tmpl, data)
form of _.template
stopped working in Underscore 1.7.0 so I fixed that too.
You'd also adjust the templates/spaceman-item.html
template so that it is just the content of the <li>
:
<% spaceman.get('name') %>
I'd recommend similar changes to the collection view so that it creates and owns its el
too.
Upvotes: 1