JSFiddle: http://jsfiddle.net/umgauper/jnf8B/743/
I'm trying to create a basic shopping cart app. When an item is clicked, I want its title and price added to the shopping cart table.
I am creating a separate instance of the view for each model. However, when an item is clicked, the table data that is added is always the model data for the item1 model, instead of the model data I would expect to be associated with the clicked DOM element. What do I have to change in order to get the model data of the clicked element?
var app = app || {};
var Item = Backbone.Model.extend({
defaults: {
order: '',
title: '',
price: 0.01,
inCart: false
}
});
var item1 = new Item({order: 1, title: "shoes", price: 30.00});
var item2 = new Item({order: 2, title: "socks", price: 5.00});
var item3 = new Item({order: 3, title: "shirt", price: 20.00});
var ItemsCollection = Backbone.Collection.extend({model: Item});
var items = new ItemsCollection([item1, item2, item3]);
app.ItemView = Backbone.View.extend({
template: _.template($("#item-template").html()),
initialize: function() {
this.render()
},
render: function() {
$("#display").append(this.template(this.model.attributes));
},
el: "#container",
events: {
'click .title': 'moveToCart'
},
moveToCart: function(e) {
e.stopImmediatePropagation();
var title = this.model.get("title");
var price = this.model.get("price");
$("table").append("<tr><td>" + title + "</td><td>" + price + "</td></tr>");
}
});
var itemView1 = new app.ItemView({model: item1});
var itemView2 = new app.ItemView({model: item2});
var itemView3 = new app.ItemView({model: item3});
<div id="container">
<div id="display">
</div>
<table id="cart">
<thead>
<th>Title</th>
<th>Price</th>
</thead>
</table>
</div>
<script type="text/template" id="item-template">
<div>
<p class="title"><%= title %></p>
<p class="price">$<%= price %></p>
</div>
</script>
Your problem is that all your app.ItemView
s share the same el
:
app.ItemView = Backbone.View.extend({
//...
el: "#container",
and the events are bound to the el
through delegation. The result is that your events are all confused because everyone is trying to listen for the same event on the same element.
The easiest solution is to move the <div>
out of your #item-template
template:
<script type="text/template" id="item-template">
<p class="title"><%= title %></p>
<p class="price">$<%= price %></p>
</script>
and make it the el
for the ItemView
. A Backbone view creates an empty <div>
as its el
if you don't specify the el
any other way so you can just drop the el
declaration from ItemView
. You'd also need to adjust render
to account for the new structure:
render: function() {
this.$el.append(this.template(this.model.toJSON()));
$('#display').append(this.el);
return this;
}
The return this
at the end of render
is standard practice so that you can say things like:
$x.append(view.render().el);
Moving the $('#display').append(...)
call up one level would also be common and would help keep the view self contained. Also notice that I switched to this.model.toJSON()
instead of this.model.attributes
; pretending that attributes
doesn't exist is generally a good idea so that you don't accidentally go behind Backbone's back and confuse things.
Updated fiddle: http://jsfiddle.net/ambiguous/cad0kxee/