Using backbone.js, when a collection's remove method is called a "remove" event is fired.
How can I extend this "remove" event to pass extra data, such as certain attributes of the particular model being removed?
How can I bind to the "remove" event triggered by a specific model, specified by id or cid?
I suppose any solution would also be applicable for the "change" event? Thanks for help.
If you're removing a model from a collection, you shouldn't need that model anymore. I guess I'm missing the point of extending remove to do more than just remove something.
When you call remove on a collection, you pass the model or an array of models in the collection to the remove function. I would recommend doing any last minute work you need with those models just before you call the remove function on your collection. At that point you should have all the models and their attributes that you plan on removing.
To bind to the change event of a specific model you just need to get the model you want from the collection and bind to that:
var myModel = myCollection.get(id); //using the id of the model
or
var myModel = myCollection.getByCid(cid); //using the cid of the model
Now bind to that model:
myModel.bind("change", function() {
//do something
});
or, bind change to all the models in a collection:
myCollection.bind("change", function(model) {
//do something, model is the model that triggered the change event
});
Related
In relation with My another question on Backbone collection View add not being called with a model
I've my models
var Client = Backbone.Model.extend({
idAttribute: 'ip'
});
var Colony = Backbone.Collection.extend({
url: '/presence/knock',
model: Client
});
I've also tried
var Client = Backbone.Model.extend({
ipAttribute: function(){
return this.options.ip
}
});
I've also tried using the id attribute instead of using ip. But nothing works. It seems Backbone is comparing the whole object. not the idAttribute So If two objects are EXACTLY same its not triggering add However If there is any change which is not in idAttribute is still thinks the model is new and triggers add
I've a Fiddle on http://jsfiddle.net/3QE3u/ that uses mock ajax (based on the solution of the above mentioned question that I asked few days ago).
If there is any change which is not in idAttribute is still thinks
the model is new and triggers add
In your code in the fiddle you have also binded "change" event of the collection to "addAll" function
this.collection.bind('change', this.addAll);
If you comment this line, your code will work as you expected.
Here it how it works now:
When first fetch function is called "add" event is fired for the item with ip "127.0.0.2" and "change" is fired for the item with ip "127.0.0.1". When fetch function is called for the second time "change" event is fired for both items. For each consecutive call to fetch, "change" event is fired for two items. (side note: refresh event is never trigger at all)
You can write another method to handle the "change" event and in that method you can update existing html element to reflect the update.
Here I have a Backbone.js Model - Contact and a Collection - Contacts with Contact as the Model. I've two views where in one I use collection to list all the contacts and in the second the model directly to show a single contact. But when I'm using the model directly I'm able to get the 'change' event fired while using with the collection the 'change' event, (even 'all' events of the model) is not fired. Am I missing some extra bind here to make it work with collection?
var Contact = Backbone.Model.extend({
defaults: {
id: 0,
urlDisplayPicBig: '',
urlDisplayPicSmall: ''
},
initialize: function () {
this.bind('change', this.doSomething);
},
generateUrls: function () { //earlier doSomething()
...
}
...
});
var Contacts = Backbone.Collection.extend({
model: Contact,
...
});
Update
While using both collection & single model instances I have to run generateUrls() to update urlDisplayPicBig & urlDisplayPicSmall based on the 'id' of the model.
When you do fetch on a collection:
the collection will reset
and that will
replace a collection with a new list of models (or attribute hashes), triggering a single "reset" event at the end. [...] Using reset with no arguments is useful as a way to empty the collection.
So a fetch on a collection will remove all the models that are currently in the collection and replace them with brand new model instances. No "change" events will occur, there will only be a single "reset" event.
When you do a fetch on a model:
A "change" event will be triggered if the server's state differs from the current attributes.
So calling fetch is pretty much the same loading the data from the server manually and calling set to change the model.
Summary: Collection#fetch doesn't trigger any "change" events, just a "reset" event; Model#fetch will trigger a "change" event if something changes.
If you just want to add a couple new attributes when creating new model instances then you can add new attributes to the incoming JSON using Model#parse.
SubCollection extends Backbone.Collection
Model extends Backbone.Model
subcollection: new SubCollection()
model1 = new Model
model2 = new Model
When the collection in model1 changes I need to update the collection in model2. They cant be a reference to the same collection, when one changes I need to react to the change and apply it to the collection in the other model.
How would I do this? Is this hard to do?
Thanks!
well,
we can't really be sure that there is only the model1 and model2, we could have a model3 and model4, so we cannot actually go binding manually to the models, otherwise you would get a big mess like this:
// not an option... >> huge mess :)
model1.bind('add', myFunction());
model2.bind('add', myFunction());
model3.bind('add', myFunction());
so, what we can do instead
would be to implement an event aggregator in our application. and work with custom events instead.
// application object
var app = {
evt: _.extend({}, Backbone.Events);
};
// subcollection
var SubCollection = Backbone.Collection.extend({
initialize: function(){
_.bindAll(this, "bubbleEvent", "catchBubbledEvent");
this.bind('reset', this.myBubble);
this.bind('add', this.myBubble);
this.bind('reset', this.myBubble);
//... every event you want to catch
app.evt.bind('myCustomEvent', this.catchBubbledEvent);
},
bubbleEvent: function(x, y){
// triggering a general event, passing the parameters
app.evt.trigger('myCustomEvent', x, y, this);
},
catchBubbledEvent: function(x, y, originalCollection) {
// catch any event raised on the event aggregator and cancel out the loop (don't catch events raised by this very own collection :)
if(originalCollection.id === this.id)
return;
// do your stuff here ...
}
});
//model
var myModel = Backbone.Model.extend({
// notice me setting a unique ID in the collection, i pass in the client id of this instance of the model
subCollection: new SubCollection({id: this.cid});
});
so basically we catch every event of the collection we want to, then we pass it trough to a general event on the single event Aggregator we have for our whole app, anything can bind to that, and do stuff when the proper event is raised, so can our collection bind to it, and do stuff. since this is possible that your collection catches the event that it sent out himself, we need a small test to cancel out these situations... and only continue when another collection raised this event.
Are there any events my models can bind to, to know their collection has been reset?
When I call:
collection.reset()
I want those removed models to be destroyed and in turn any views to know they are gone. What should I bind to here?
From the fine manual:
reset collection.reset(models, [options])
[...] triggering a single "reset" event at the end.
So bind to the collection's reset event and hope that no one uses the {silent: true} option to do things behind your back.
#mu's answer is correct, but you might also need to know that a model that is added to a collection has the .collection property, which points to the parent collection. So if you are instantiating your models manually, you can just do this:
var myModel = new MyModel();
collection.add(myModel);
collection.bind('reset', model.cleanUp(), model);
But if you're instantiating your models via the collection, e.g. with collection.fetch(), you need to bind to the collection in the initialize() method of the model:
var MyModel = Backbone.Model.extend({
initialize: function() {
if (this.collection) {
this.collection.bind('reset', this.cleanUp(), this);
}
}
// etc
});
I'm pretty new to Backbone so excuse me if this question is a little obvious.
I am having problems with a collection inside of a model. When the collection changes it doesn't register as a change in the model (and doesn't save).
I have set up my model like so:
var Article = Backbone.Model.extend({
defaults: {
"emsID" : $('html').attr('id')
},
initialize: function() {
this.tags = new App.Collections.Tags();
},
url: '/editorial_dev.php/api/1/article/persist.json'
});
This works fine if I update the tags collection and manually save the model:
this.model.tags.add({p : "a"});
this.model.save();
But if the model is not saved the view doesn't notice the change. Can anyone see what I am doing wrong?
initialize: function() {
this.tags = new App.Collections.Tags();
var model = this;
this.tags.bind("change", function() {
model.save();
});
},
Bind to the change event on the inner collection and just manually call .save on your outer model.
This is actually an addendum to #Raynos answer, but it's long enough that I need answer-formatting instead of comment-formatting.
Clearly OP wants to bind to change and add here, but other people may want to bind to destroy as well. There may be other events (I'm not 100% familiar with all of them yet), so binding to all would cover all your bases.
remove also works instead of destroy. Note that both remove and destroy fire when a model is deleted--first destroy, then remove. This propagates up to the collection and reverses order: remove first, then destroy. E.g.
model event : destroy
model event : remove
collection event : destroy
collection event : remove
There's a gotcha with custom event handlers described in this blog post.
Summary: Model-level events should propagate up to their collection, but can be prevented if something handles the event first and calls event.stopPropagation. If the event handler returns false, this is a jQuery shortcut for event.stopPropagation();
event.preventDefault();
Calling this.bind in a model or collection refers to Underscore.js's bind, NOT jQuery/Zepto's. What's the difference? The biggest one I noticed is that you cannot specify multiple events in a single string with space-separation. E.g.
this.bind('event1 event2 ...', ...)
This code looks for the event called event1 event2 .... In jQuery, this would bind the callback to event1, event2, ... If you want to bind a function to multiple events, bind it to all or call bind once for each event. There is an issue filed on github for this, so this one will hopefully change. For now (11/17/2011), be wary of this.