I was building a small app for adding and deleting li from ul using Backbonejs.One of the SO members cymen helped me code it, using that i tailored the code a little.currently if i add one element and delete , it works , but the second time i add an element (to ul) and go to delete it , i get
Uncaught TypeError: Cannot call method ‘remove’ of undefined
Pasting my code here ,
HTML :
<input type="text" id="name">
<button id="add">Add</button>
<ul id="mylist"></ul>
JS:
$(function(){
var myCollection = Backbone.Collection.extend();
var myView = Backbone.View.extend({
el:$('body'),
tagName:'li',
initialize : function(e){
this.collection.bind("add",this.render,this);
this.collection.bind("remove",this.render,this);
},
events:{
'click #add' : 'addfoo'
},
addfoo : function(){
var myname= $('#name').val();
$('#name').val('');
this.collection.add({name:myname});
},
render : function(){
$('#mylist').empty();
this.collection.each(function(model){
console.log("myView");
var remove = new myRemoveView({model:model});
remove.render();
});
}
});
var myRemoveView = Backbone.View.extend({
el:$('body'),
events:{
'click .button':'removeFoo'
},
removeFoo : function(){
console.log("here");
this.model.collection.remove(this.model);
},
render : function(){
console.log("second view");
$('#mylist').append('<li>'+this.model.get('name') + "<button class='button'>"+"delete"+"</button></li>");
return;
}
});
var view = new myView({collection: new myCollection()});
});
Two things i did not understand :
i) in the removeFoo function , we write
this.model.collection.remove(this.model)
shouldnt this have been this.collection.model.remove , something of that sort ?
ii) i add a li to ul , then i delete it , when i add another li (appending to ul works perfect) but this time when i go to delete it throws me the above error : Uncaught TypeError :cannot call method ‘remove’ of undefined
can you please help me figure out these 2 doubts in my code , btw SO member cymen’s code works like a charm only my tailored code (above) is giving me errors.
SO member cymen’s code : JS Fiddle for his code
Thank you
First of all, your
myRemoveViewis using<body>as itsel:That means that every time you hit the delete button, you’re going to trigger
removeViewon every singlemyRemoveViewyou’ve made. That’s certainly not what you want to happen and part of the reason that cymen is usingtagName: 'li'. There are two general rules for views and theirels:eland oneelper view. You don’t want views sharing the sameelbecause of the event handling problem; doubly so you don’t want several instances of the same view sharing anel.elshould be the part of the DOM that the view cares about, no more and no less. This makes cleaning up quite easy, just delete the DOM element associated with your view and most things go away with it (DOM events in particular); for non-DOM events, we have aremovemethod on views.Another thing, your view shouldn’t (generally) be messing with the DOM outside it’s own
el. This looks very odd:The caller should be responsible for figuring out where your
elgoes, your view should only concern itself with things happen inside it, some other view should be responsible for#mylist. Also,rendermethods conventionally returnthisso that you can do this:to put them into the DOM.
Specifying both
tagNameandelin a view:is pointless, the
tagNamewill be ignored andelwill be used.You’re also using Backbone 0.5.3 in your fiddle. You should be using the latest versions whenever possible.
If we correct the above, then everything starts working (again):
and:
Demo: http://jsfiddle.net/ambiguous/2z4SA/1/
So what sort of craziness was going on with your original version? The key is
el: $('body')in yourmyRemoveView. First we’ll add a little logging method tomyRemoveViewto make it easier to watch what happens:Note that
cidis an internal unique ID that Backbone creates, it is just a convenient way to keep track of things. Then we’ll callthis._loginremoveFooandrender:Here’s a simple process that should show you were everything goes wrong:
You can follow along here: http://jsfiddle.net/ambiguous/yLYNL/
First we’ll add a and this pops up in the console:
Then we add b and see this:
Your
myViewrender redraws the whole collection so we seec1for a andc3for the new b. So far so good, everything makes sense.Now, when we’ll try to delete a; first we see that we
removeFooon a:That will trigger a
myView#renderwhich redraws the whole collection. The whole collection is just b at this point so we seec3rendered again and everything still makes sense:But now we see everything go sideways:
You’ll see
c3show up because you have twomyRemoveViewinstances (one for a and one for b) bound to the sameelso both of them will see theclick .deleteevent, it just so happens thatc1sees it first.But what is that
c1doing there? That’s the one that doesn’t have a collection, that’s the the one that is triggering your original error. You never detach yourmyRemoveViews from events on<body>so you have a zombie: even though the<li>is gone, the view is still bound to<body>through the view’sdelegatecall. So you have a zombie view that references a zombie model, zombies zombies everywhere and you left your zombie fighting kit in the car. But why doesn’tc1have a collection? Well, you did :on
c1to remove it from the collection; removing a model from a collection removes the model’scollectionbecause, well, the model is no longer in a collection.