Backbone add event - backbone.js

I have a collection where is has an event that gets fired when a model is added. I have read in the docs where it should have an options parameter but not able to get to it.
I basically want to find the index the model is at in the collection.
Inside my collection I have this.
initialize: function( ) {
this.bind( 'add', this.onModelAddedd, this );
},
onModelAddedd: function( model, options ){
console.log("options = ", options);
}

The documentation is a little unclear on this so your confusion is understandable. From the fine manual:
Catalog of Events
Here's a list of all of the built-in events that Backbone.js can fire. You're also free to trigger your own events on Models and Views as you see fit.
"add" (model, collection, options) — when a model is added to a collection.
...
So the second argument to the add handler is the collection itself. The ubiquitous options that you're looking for is always the last argument so you want this:
onModelAddedd: function(model, collection, options) {
console.log("options = ", options);
}
Demo (open your console please): http://jsfiddle.net/ambiguous/Das2t/
The final options argument is implied to be the last argument throughout the documentation but it isn't explicitly spelled out anywhere.

Related

Using Remove event to detect the model removed from the collection in Backbone.js

My question is , is there a way to detect the model that got removed from the collection when we bind/listen to the collection's add Event.
for ex:
this.listenTo(monthsOnBoardCollection, "remove", function(){
//is it possible here to find what got removed from the collection ?
}
You have the Catalog of Events which shows the arguments being passes to the event.
"remove" (model, collection, options) — when a model is removed from a collection.
So it's basically:
this.listenTo(monthsOnBoardCollection, "remove", function(model, collection, options){
//now you have the model, the collection and the options which were passed to the remove method
}

Binding Backbone change event to attribute of model in a collection

I have a view that takes a collection like below:
MyView = Backbone.View.extend({
initialize: function() {
this.collection.on("change://an attribute of a model in aCollectionToRender", someAction);
}
});
var MyViewInstance = new MyView({
collection: aCollectionToRender
});
Basically, I don't want MyView to listen for changes on the entire collection, only a single attribute of the model that the collection contains.
I realize there are a number of alternatives to this:
Create views for each model of aCollectionToRender and attach .on("change") events in those views. I don't want to do this because it's not the right thing to do for my situation
Just have a this.collection.on("change") event and have the event handler filter based on my requirements. This is less elegant, and if Backbone already allows me to write event filters as I asked above, this is duplicate code
I was just wondering if there's any way to write an event listener that already does the filtering. This question may also be a duplicate; I looked but I didn't find anything exactly like this, however, there are a lot of Backbone questions
Maybe I am misunderstanding your question, but you can do exactly what you show. Check out the Backbone documentation.
MyView = Backbone.View.extend({
initialize: function() {
this.collection.on("change:attributeName", this.someAction /*function to call*/, this);
},
someAction: function(model, value, options){
}
});

Debugging Backbone.js: rendering after collection fetch()

I'm trying to do basic render() after fetch() on collection (Backbone 0.9.2):
var ProjectListView = Backbone.View.extend({
el: $('#container'),
initialize: function () {
this.collection = new ProjectsCollection();
this.collection.bind("change", _.bind(this.render, this));
this.collection.fetch({ success: function () { console.log("collection fetched"); } });
...
},
render: function () {
console.log("rendered");
...
Creating new View instance prints out:
collection fetched
So the render() never gets called after fetch(). What am I doing wrong here? There are no exceptions present.
Any tips how to debug these sort of things in backbone?
Ps.
It seems that this feature is poorly documented given the number of questions on SO.
From the fine manual:
fetch collection.fetch([options])
Fetch the default set of models for this collection from the server, resetting the collection when they arrive. [...] When the model data returns from the server, the collection will reset.
And what does reset do? reset does this:
reset collection.reset(models, [options])
[...] Use reset to replace a collection with a new list of models (or attribute hashes), triggering a single "reset" event at the end.
So fetch calls reset to update the collection's models and reset triggers a "reset" event, not a "change" event. None of the models have changed and a collection's "change" events come from its models.
You should have render bound to "reset":
initialize: function () {
this.collection = new ProjectsCollection();
this.collection.bind("reset", _.bind(this.render, this));
this.collection.fetch(...);
}
If you want to listen for "change" events on the contained models then you can bind a "change" handler to the collection since:
You can bind "change" events to be notified when any model in the collection has been modified,
[...]
Any event that is triggered on a model in a collection will also be triggered on the collection directly, for convenience.
The collection will also generate "add" and "remove" events as the collection itself changes.
Newer versions of Backbone no longer reset collections during fetch:
When the model data returns from the server, it uses set to (intelligently) merge the fetched models, unless you pass {reset: true}, in which case the collection will be (efficiently) reset.
And set:
[...] performs a "smart" update of the collection with the passed list of models. If a model in the list isn't yet in the collection it will be added; if the model is already in the collection its attributes will be merged; and if the collection contains any models that aren't present in the list, they'll be removed. All of the appropriate "add", "remove", and "change" events are fired as this happens
So with newer versions of Backbone you'll want to list for the "add", "remove", and "change" events (which a collection based view should be listening for anyway); you could also use {reset: true} on the initial fetch and listen to "reset" as well. I'd recommend the following approach for collection based views:
Listen to "add" and handle that event with a callback that simply adds one item to the view, don't throw everything away and re-render.
Listen to "remvoe" and handle that event with a callback that only removes the newly removed model.
Listen to "change" and handle that with a callback that replaces (or updates) the appropriate item.
Listen to "reset" and bind that to render. Then pass {reset: true} to the collection's initial fetch call.
That will trap the important events and the collection-view will do the minimal amount of work to handle each one. Of course, this strategy isn't applicable to all situations but I think it is a good starting point.
This changed in 1.0
http://backbonejs.org/#changelog
"If you'd like to continue using "reset", pass {reset: true}."
Ok, so until some one can explain why binding didn't work, I used following workaround:
initialize: function () {
var self = this;
this.collection = new ProjectsCollection();
this.collection.fetch({ success: function () { self.render(); } });

How can I get a list of models from the collection that have changes since constuction

If I initialize a collection with an array of objects for the model attributes when a dialogview is initialized. Then the dialog view that lets the user edit the list updates those model values with calls to model set. When the dialog's ok button is clicked, Does backbone provide a way to get the list of only those models that changed since the collection was created/initialized?
There are various model methods that look tempting:
Model#hasChanged
Model#changedAttributes
Model#previous
Model#previousAttributes
But don't be fooled, those only apply while a "change" event is being triggered:
Note that this method, and the following change-related ones, are only useful during the course of a "change" event.
so they're useless after the event has been triggered and handled.
I think you have to track which models have changed yourself. You can do this on the collection itself without too much effort since
Any event that is triggered on a model in a collection will also be triggered on the collection directly, for convenience.
and the collection can bind to its own events. For example, you could have something like this in your collection:
Backbone.Collection.extend({
initialize: function() {
this.delta = { };
this.on('change',​​​​​ this._a_model_has_changed);
},
changed_models: function() {
return _.chain(this.delta).values();
},
_a_model_has_changed: function(m) {
this.delta[m.id] = m;
}
});
Then you could get the models that have changed by calling collection.changed_models(). You'd want to listen for other events as well so that you could update this.delta when models are deleted or synced with the server; the above is just for illustration. If you didn't want an Underscore object returned you could use this instead:
changed_models: function() {
return _(this.delta).values();
}
but being able to collection.changed_models().each(function() { ... }) is convenient.
Demo: http://jsfiddle.net/ambiguous/8PQh9/
You could also let the models track their own dirtiness through a similar set on the models. Then you could do something like this:
collection.filter(function(m) { return m.is_dirty() });
where, of course, is_dirty would return true if the model had been changed.

Is it possible to dynamically define a comparator function for a collection?

I have a basic collection :
myCollection = Backbone.Collection.extend({
model: myModel
url: '/theurl',
initialize: function(){
this.fetch();
})
})
When initialized, the collection receives items ordered by date.
I would like to be able to dynamically reorder the collection, using another model attribute (name, rating, etc.).
In the view associated with the collection, I tried to bind a click event to a callback like this :
reorderByName: function(){
myCollection.comparator = function(item){
return item.get('name');
});
this.render();
})
Unfortunately, that does not seem to work. Any suggestions as to how I could do it ?
Thanks.
It looks like you've only done half of what you need to do. You've given the collection its comparator, but you've not told it to resort itself. So you need to add something like this statement right before you render:
myCollection.sort();
Note that this will trigger a reset event which would be received by any object that you've bound this event to. If you wish to suppress triggering that event, you could make the call this way:
myCollection.sort({silent: true});
Hope this helps.
I found this while looking for a solution to my own issues with comparator. I thought I would add, in case anyone else finds this question, that if the models in your collections have undefined values for the property by which your comparator is sorting, the sort won't happen. Be sure to use defaults on your model if they're not required and you're gonna sort by 'em. ;)

Resources