Backbone.js event system (model and collection) - backbone.js

In Backbone.js (0.9.2) I have the following:
One collection and one method that gets called when something changes
this.collection.on("change", this.methodOne, this);
When I change a value of one model of this collection ...
model.set("value1", "abc");
... the change event from the model BUBBLES UP to the collection and so "methodOne" is called.
When I look into the source code of Backbone.js, "collection", method "add", a private
method "_prepareModel" is called that makes the model - collection reference:
model.collection = this;
But from there on I do not understand how and in which methods this bubble up mechanism
is functioning?
Has anybody got information for me about that?
Thanks alot in advance!
Wolfgang

Take a look at Backbone.Collection _onModelEvent method. In the docs:
Internal method called every time a model in the set fires an event. Sets need to update their indexes when models change ids. All other events simply proxy through. "add" and "remove" events that originate in other collections are ignored.

Related

Passing a Backbone model from a collection to a new view keeps the collection in memory

I have a CompositeView that shows a list of models that I requested from a server, something like (in CoffeeScript):
class List.Stories extends Marionette.CompositeView
template: "stories-list-body"
itemView: List.Story
itemViewContainer: "#stories-list"
class List.Story extends Marionette.ItemView
template: "stories-list-story"
triggers:
"click .js-show-button": "show:button:clicked"
The views are correctly created passing the collection as an argument for the constructor, I can see the elements and when I click on the button it triggers the appropriate event and it's handled. The thing is, when the handler creates a new view showing the model and closes the old one, the collection is still referenced in model.collection taking up some memory.
What would be the correct way to eliminate this reference? Simply using delete model.collection in the handler before replacing the view?
Try doing something like
var model = myCollection.remove(viewModel, { silent: true })
// create new view using `model`
In the example above, viewModel would refer to the view's model (so it would be this.model from within the view).
By removing the model from the collection, it should be garbage collected (assuming it's not referenced anywhere else...).
If it all happens within same controller, i.e. the controller is still open and will be responsible for the event and no Application.vent will be triggered, I think this situation is acceptable if memory leak won't be big. The reason is the controller will be finally closed so no need in a hurry.
If App level vent/request/command will be triggered, you need to take it seriously. Assume you have such code in controller:
#listenTo storiesView, 'itemview:show:button:clicked', (itemView) ->
App.vent.trigger 'show:another:view:with:this:model', itemView.model
Stop here. The model is the old model and won't be garbage collected.
I will use below code instead:
#listenTo storiesView, 'itemview:show:button:clicked', (itemView) ->
model = _.clone itemView.model
App.vent.trigger 'show:another:view:with:this:model', model
The new model is a totally new object and then has nothing related to current view/model/controller.

Backbone model listener stops working as soon as the model is saved

In the initialize function of my backbone View, I created the following listener:
this.listenTo(this.model.get('clusters'), 'add remove', this.saveChanges);
This successfully causes my saveChanges function to be called the first time a model is added/removed from the 'clusters' Collection. There's only one line in that saveChanges function:
this.model.save();
Once that is invoked, adding/removing clusters no longer invokes the "add" or "remove" event. Why would saving the model destroy the listener? Can that be prevented, or is there a way to re-establish the listener?
Or is there something fundamental I'm not understanding about Models and/or Collections...?
Let's break down your code:
You write
this.listenTo(this.model.get('clusters'), 'add remove', this.saveChanges);
Which is equal to
var clusters = this.model.get('clusters');
this.listenTo(clusters, 'add remove', this.saveChanges);
Now I only assume that after you get that event you set() a new clusters object inside your model.
The issue here is that your view still listens to events from that same old clusters object, which is not relevant anymore - your model deals with another object!
Another case could be that Backbone clears your view's event handlers from its model when it's being removed.. Could easily answer for sure if you'd share the whole code.

Accessing other collection inside backbone model

I have 2 post collections and a model as follows.
# router file
#posts = new MyApp.Collections.PostsCollection()
#posts.reset options.posts
#followed_posts = new MyApp.Collections.PostsCollection()
#followed_posts.reset options.followed_posts
# Post model file
class MyApp.Models.Post extends Backbone.Model
paramRoot: 'post'
follow_post: ->
# ajax call
console.log "_________Index:#{this.collection.indexOf(this);}"
console.log this.collection
console.log "_________Followed:"
console.log #followed_posts
class MyApp.Collections.PostsCollection extends Backbone.Collection
model: MyApp.Models.Post
url: '/posts_all'
What I am trying to do is when one of the model changed in one collection, I want to update the other model in other collection too.
These collections may or may not hold same models.
So let's say if a model in #posts changed in my Post model, I want to update that model in #followed_posts too. If #followed_posts doesn't have that model, I need to add a duplicate of the model to #followed_posts collection.
I can access the collection that model belongs, but I cannot access the other collection.
Any ideas appreciated, thanks.
If the two collections are antisocial and can't talk directly to each other, which is usually good design, then you'll need an intermediary -- a global event dispatcher. When a model changes, propagate that event to the dispatcher along with a reference to the model. Listen for the event in the other collection and use the model passed to check for existence and respond as needed.
EDIT:
Backbone's documentation mentions this pattern:
For example, to make a handy event dispatcher that can coordinate
events among different areas of your application: var dispatcher =
_.clone(Backbone.Events)
But in fact, this is such a common pattern that the Backbone object itself is extended with Events. So you can just do:
// In your Post model
#on "change", -> Backbone.trigger "post:change", this, #collection
// And then something like this in the collection class definition:
#listenTo Backbone, "post:change", (model, collection) =>
if post = #get model.cid
post.set model.toJSON()
else
#add model
Also, is followed posts a subset of posts? If so, why not put an attribute on the model designating it as followed? Then you could find all followed posts with a simple filter function.
I would strongly suggest that you should consider having a single collection and add some kind of attribute in the model to differentiate between what kind of posts they are.

backbone model with an array/object property: infinite 'change' event triggered after sync()?

My backbone.js model has an array property. I bound the change event to save().
After sync() (triggered by save(), my app server returns an identical JSON, but backbone thinks the array has been changed (due to a different reference to the array I guess?), and trigger changes again. Then an infinite loop occurs.
save() -> sync() -> triggered `change` -> save()...
What shall I do?
Idea: I can bind the change event to a function that checks if the changed attributes are of type object/array, and do a deep comparison and call save only if the array/object really changed. If true then save()?
Thanks!
Try the Edge version of Backbone (master branch) this behavior changed after 0.9.9 - see https://github.com/documentcloud/backbone/pull/2004
Backbone has a special option on many methods to prevent just this sort of issue: silent:true. If you pass that option to your save method, the resulting sync won't trigger a change event.
So, if you want to set your change event handler to save silently, something like:
changeHandler: function() {
this.save({silent:true});
}
should do the trick.

Backbone-Relational firing add event before server responds

I'm using Backbone-Relational in a project at the moment, and it's not behaving the way it seems like it should.
Let's say Model A has a HasMany relationship with Model B. Model A listens for an "add:model_b" event to render a necessary view for Model B whenever an instance is added to the Model A relationship. In order to render the view template properly though, we need some data from the server. From reading the backbone docs, it seems to me the logical way to do this would be as follows:
modelA.get("model_b").create(newModelData, {wait: true});
As Backbone-Relational implements relations as Backbone Collections, I just call the collection.create method to instantiate my new Model B, and pass {wait: true} to the options in order to delay the "add" event til the server responds.
However, this seems to have no effect: the "add" event is firing instantly, and I'm therefore getting an error when trying to render a view that needs data that is not yet part of the model. Does anyone know why {wait: true} is doing nothing in this instance, and how can I get the desired behavior out of Backbone-Relational?

Resources