Reputation: 1011
I have a tree of categories and I would like to do rending using by Backbone.js instead of using jQuery or do rendering on the server-side. I have the following breakout that I described as template:
<li>
<select class="categories">
<option value="">Select</option>
</select>
<input class="edit" type="button" value="Edit">
<input class="add" type="button" value="Add">
</li>
The tag select
I render separately as inner view. When I change select I need get nested categories from the server and append them to the tag li
using the template above wrapped in the tag ul
. In outer view I create inner view and listen events on click at editing and adding. I have a trouble with the last events when the level of nesting is more than one because they are fires equal times to the level of nesting. What am I doing wrong?
The below code snippet shows how I work with outer and inner views:
var CategoriesInnerView = Backbone.View.extend({
tagName: 'select',
initialize: function(){
_.bindAll(this,'addOne','addAll');
this.collection.bind('reset',this.addAll);
},
addOne: function(category){
this.$el.append(new CategoryView({model:category}).render().el);
},
addAll: function(){
this.collection.each(this.addOne);
},
events: {
'change':'changeSelected'
},
changeSelected:function(){
var children = new Categories();
children.url = 'categories/' + this.$el.val();
var childrenView = new CategoriesOuterView({collection:children});
this.$el.parent().find('ul').remove();
this.$el.parent().append(childrenView.render().el);
children.fetch();
}
});
var CategoriesOuterView = Backbone.View.extend({
tagName: 'ul',
template: _.template($('#categories-template').html()),
initialize:function(){
this.inner = new CategoriesInnerView({collection:this.collection});
},
render: function(){
this.$el.html(this.template);
this.inner.setElement(this.$('select')).render();
return this;
},
events: {
'click .edit':'edit',
'click .add': 'add'
},
edit: function(){
this.renderForm(this.collection.get(this.inner.$el.val()));
},
add: function(){
this.renderForm(new Category());
},
renderForm:function(category){
// some code to render the form
}
});
Upvotes: 3
Views: 2468
Reputation: 33364
When you set up nested views, you have to take into account the fact that events will bubble up the DOM tree and that Backbone handles DOM events at the view.el level. This means that in your scenario, nested nodes will also trigger events in the parent nodes if you let the event go up the hierarchy.
See http://jsfiddle.net/PX2PL/ for a demo
A simple solution would be to stop the event propagation in your callbacks
var CategoriesOuterView = Backbone.View.extend({
events: {
'click .edit':'edit',
'click .add': 'add'
},
edit: function(e) {
e.stopPropagation();
this.renderForm(this.collection.get(this.inner.$el.val()));
},
add: function(e) {
e.stopPropagation();
this.renderForm(new Category());
}
}
And an updated demo http://jsfiddle.net/PX2PL/1/
Upvotes: 3
Reputation: 35725
We really need to seem some of your code to answer this properly, but it sounds like one of two things is happening:
1) you are instantiating your view on the same element multiple times
2) your event selectors are too broad
But without actually seeing (the relevant parts of) your view, it's hard to say more than that.
Tangentially-Related Side Note
BTW, when you have something like this, there are two basic approaches you can take:
1) you can have your parent view create sub views, and put the event handling on the sub-views
2) you can have your parent view create sub views, or not (it could create all the HTML itself), and put the event handling on it.
The advantage of #1 is simplicity: your event handlers can just refer to this
to refer to the relevant view. However, there's a problem with #1 if you have to scale it up too much: millions of views, each with their own event handlers, will hurt performance.
So, if performance is/will be important, #2 is better because you're only doing a single set of event hookups. However, you're event handlers will have to be smarter, because they'll have to figure out what element their working on based on the provided event (ie. e.target
, vs. this
in the #1 approach).
Upvotes: 2