Backbone.js: Model events are not fired while used within a Collection - javascript

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.

Related

Backbone collection.reset()

So there's a piece of functionality with which I've been struggling for a while now. I'm using .where() method in order to retrieve an array of objects from the Collection and then I reset this Collection with this array.
# Fetch the collection
collection = App.request(collection:entites)
console.log collection
>collection {length: 25, models: Array[25] ... }
When the event fires it passes options for .where() method and starts reset process:
# Get new models
new_models = collection.where(options)
# Reset collection with the new models
collection.on 'reset', (model, options) ->
console.log options.previousModels
return
collection.reset(new_models)
console.log collection
>collection {length: 5, models: Array[5] ... }
In the View responsible of rendering this collection I listen to the 'reset' event and render the View accordingly.
initialize: ->
#listenTo(#collection, 'reset', #render)
It works just as expected: event fires, collections undergoes reset and the View re-renders reseted collection. But when the event fires second time the collection doesn't sync with the server and new_models = collection.where(options) receives a collection that was already reseted in a previous event run and returns an empty array.
What are my options here? Each event run I need an initial collection of all models to work with. Should I just request new instance of the collection on the each run or can I make it in a more cleaner manner, i.e. save original state somewhere and pass it for the event run instead of fetching new collection from the server? Please advise.
Yes. Another way to achieve it is, when you filter the collection, using .where(), you can trigger Backbone.Events custom event which you view can listen to. This way, the original collection does not reset, there only is a change in array which will trigger the custom event.
A sample code for using custom events in backbone is:
var object = {};
_.extend(object, Backbone.Events);
object.on("collectionFiltered", function(arrayOfFilteredModels) {
// do stuff to render your view with the new set of array.
// You can use underscore templating to traverse the array for rendering view.
});
object.trigger("collectionFiltered", collection.where(options);

Backbone collection.fetch({update: true}) doesn't compare id add's same model again

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.

Backbone: How to update a model that isNew without fetching entire collection?

I have a form that create a model for a collection. That fires an add event to the collection. I have that binded to a method:
this.collection.bind('add', this.addOne, this)
fires...
addOne: function(tenant) {
var self = this
var collection = this.collection
var view = new TenantView({model: tenant,collection:collection});
self.$el.append(view.render().el);
}
The create syncs it to the database but, the new appended view still isNew to backbone since it hasn't fetched the collection and grabbed the id for the new model.
My question, how can I grab the synced model from the server (that has the id and isn't isNew) without fetching the entire collection then append it?
Use the sync event instead of add...
this.collection.bind('sync', this.addOne, this);
The add event gets fired immediately when calling create; but sync gets fired once the server has responded to the create method, so it should include the correct model id.
You can also wait for the server's response, before adding the model to the collection -- to do that use wait: true in create's options hash: collection.create({ ... }, { wait: true }).
Creating a model will cause an immediate "add" event to be triggered on the collection, as well as a "sync" event, once the model has been successfully created on the server. Pass {wait: true} if you'd like to wait for the server before adding the new model to the collection.

React to events from a collection within a model?

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.

backbone.js passing extra data along with events

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
});

Categories