I am building a backbone app for the first time - and it is going great.
However, I don't think I am creating views for my collection of models in the correct way and when I bind events they fire for every view when I only want them to fire for one.
Here is my backbone code (a snippet):
(function(){
Series = Backbone.Model.extend({
defaults:{
active:false
}
});
SeriesGridItemView = Backbone.View.extend({
el:'#model-grid',
defaults: {
active : false
},
initialize: function(){
this.render();
this.listenTo(this.model, 'change', this.setState);
},
render: function(){
this.template = _.template( $('#template-model-grid-item-view').html() );
this.view = $(this.template(this.model.toJSON())).appendTo(this.$el);
},
setState: function(){
this.active = this.model.get('active');
this.view.toggleClass('active',this.active);
},
events: {
'click':'toggle'
},
toggle: function(e){
e.stopPropagation();
e.preventDefault();
console.log('clicked');
return false;
}
});
SeriesCollection = Backbone.Collection.extend({
model: Series,
setPrice : function(p){
this.forEach(function(m){
var active = 0;
_.each(m.get('vehicles'),function(v){
if(v.price <=p){
v.active = true;
active++;
}
else{
v.active = false;
}
});
m.set('active',active>0);
});
}
});
series = new SeriesCollection(window.BMW.data.series);
series.forEach(function(m,i){
var c = i+1;
if(c > 3){
c%=3;
}
m.set('column','1');
new SeriesGridItemView({model:m});
});
})();
And here is the JSON that constructs the models:
window.BMW.data.series = [
{
seriesId:1,
name:'1 Series',
slug:'1-series',
order:0,
vehicles:[
{
seriesId:1,
price:200
},
{
seriesId:2,
price:300
}
]
},
{
seriesId:2,
name:'3 Series',
slug:'3-series',
order:1,
vehicles:[
{
seriesId:1,
price:400
},
{
seriesId:2,
price:500
}
]
},
{
seriesId:3,
name:'4 Series',
slug:'4-series',
order:3,
vehicles:[
{
seriesId:1,
price:100
},
{
seriesId:2,
price:300
}
]
},
{
seriesId:4,
name:'6 Series',
slug:'6-series',
order:4,
vehicles:[
{
seriesId:1,
price:100
},
{
seriesId:2,
price:300
}
]
},
{
seriesId:6,
name:'X3',
slug:'x3',
order:5,
vehicles:[
{
seriesId:1,
price:500
},
{
seriesId:2,
price:800
}
]
}
];
And here is my template for the views
<script type="text/template" id="template-model-grid-item-view">
<div id="series-<%=seriesId%>" class="grid-item-view column-<%=column%>">
<div class="label"><%= name %></div>
<div class="thumbnail">
<img src="/Content/themes/BMW/img/series/small/<%= slug %>.png"/>
</div>
</div>
</script>
The views assemble correctly, but when I click one view, the event fires on all of the views. Can someone please point me in the right direction?
Since you omitted the selector in your events
object of your views the following applies
Per the Backbone Documentation:
Omitting the selector causes the event to be bound to the view's root element (this.el).
The problem is each of the SeriesGridItemView
's bind click
events to #model-grid
and each view is a child of #model-grid
. In your example, you register 5 click events and when you click on any of your views, all 5 events are triggered.
Without changing any of your other code, one solution is to setup you events
object to return a function so you can specify an id
selector for each of your views.
events: function() {
var selector = '#series-' + this.model.get('seriesId'); // this id corresponds to your template id
var ev = {};
ev['click ' + selector] = 'toggle';
return ev;
},
Another option and the one I prefer, is to not specify #model-grid
as your root element for all your views. It would end up looking like: demo
SeriesGridItemView = Backbone.View.extend({
// remove the following line
el:'#model-grid',
// .. all else is the same ../
});
series = new SeriesCollection(window.BMW.data.series);
series.forEach(function(m,i){
var c = i+1;
if(c > 3){
c%=3;
}
m.set('column','1');
$('#model-grid').append(new SeriesGridItemView({model:m}).el);
});
A side suggetion
In your render
function, there's no need to create variable, you can access your element by using $
:
render: function(){
this.template = _.template( $('#template-model-grid-item-view').html() );
this.$el.append(this.template(this.model.toJSON())));
},
setState: function(){
this.active = this.model.get('active');
this.$el.toggleClass('active',this.active);
},