Help me please... I try this:
model.bind 'all', this.console, this
And when I change something inside of model I have double-firing of my handler.
But when I changed event for binding:
model.bind 'change', this.console, this
Everything is ok. Why? What is nature of this?
A single change to the model's a attribute will trigger two events:
change
change:a
and binding to all means that your event handler will get called for both of them:
Callbacks bound to the special "all" event will be triggered when any event occurs, and are passed the name of the event as the first argument.
If you only bind to the change event, the your handler will only get called for the overall "part of the model changed" event and you won't see the change:a that is specific to the change on just that attribute.
For example, given this:
class M extends Backbone.Model
m = new M
m.on 'all', (ev) -> console.log ev
m.set a: 'b'
You'll get change and change:a in the console.
Demo: http://jsfiddle.net/ambiguous/yRDjd/
But if you do this:
class M extends Backbone.Model
m = new M
m.on 'change', -> console.log 'the model changed'
m.on 'change:a', -> console.log 'a changed'
m.set a: 'b'
you'll see separate messages for each change event.
Demo: http://jsfiddle.net/ambiguous/wnmXe/
Related
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.
My events are not loading automatically. When I added delegateEvents() at the end of the render() method, it worked for a while. I do not want to use delegateEvents, but now, even with delegateEvents the events are not loading.
I reckon the DOM is not known at the time, so the events aren't bound, but how do I check (debug) that?
View:
class EditGroup extends BaseView
initialize: ->
#render()
render: ->
html = _.template tpl, #model.toJSON()
#$el.html html
for own key, options of FormConfig[#model.type]
options.key = key
options.value = #model.get key
input = new Input options
input.on 'valuechanged', (key, value) => #model.set key, value
#$('section.'+key).html input.$el
#delegateEvents() # doesn't work
#
DOM:
h2 Edit Group
section.title
section.type
section.members
button.save.btn.btn-primary(onclick="return false") Save changes
In the sections type and member there are typeaheads and selects rendered (Backbone views). One works without delegateEvents and the other works with. The events in the parent view (shown above) don't work at all. Removing the for-loop doesn't make any difference.
Ok, I solved it, but I don't understand the workings.
I'm using a 'view manager', which registers all views and shows (read: attaches the html to the DOM) the parent view. The view manager's show function is triggered when a new route is fired, but the route was fired double, once as "route:edit" and once as "route". I catch them with router.on "all", (eventName) -> etc.. I reckon the events are bound to the html, but the html is overridden by the second router event without the bindings and attached to the DOM. Question remains why the route is fired twice.
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.
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(); } });
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.