In Backbone.js, why do silent changes trigger change events eventually? - backbone.js

When I pass {"silent":true} while setting an attribute in a Backbone model, why doesn't that just suppress the change:attribute event? What is the advantage of firing that event the next time an attribute is changed?
Update
Backbone 0.9.10 changed the behavior of passing { "silent": true }. From the changelog:
Passing {silent:true} on change will no longer delay individual
"change:attr" events, instead they are silenced entirely.
Browse the changelog here

This has confused me for some time as well.
The reason is that {silent:true} does not mean "Do everything as normal, but just don't trigger the event".
From various comments and answers by #jashkenas, what it seems to mean is "simply change the attribute value (and add it to the 'changedAttributes' hash), but defer all other "change-related" activities until later".
'silent' doesn't prevent the change event for that/those properties, it simply queues up the 'announcement' until the next change event is triggered.
So, its probably better named something like defer.
Relevant information:
https://github.com/documentcloud/backbone/pull/850
the point of a "silent" change is that it isn't considered a change from the models point of view. Later, when the change actually occurs, you get the full difference all at once.
https://github.com/documentcloud/backbone/issues/870
Because the point of silent changes is that you're allowed to twiddle with the internal state of your model, temporarily, without actually making a change. Later, when the attribute actually changes, the validation runs and events are emitted. It wouldn't make sense to emit an error event when making a silent change.
Update 4/7/2013
Note: I have not tested this to confirm behavior, this is just based on my reading of the release notes...
As of Backbone 0.9.10, the behavior described above has changed. In that version (and newer), silent:true suppresses the change:attr events entirely - not just delays them.
http://backbonejs.org/#changelog

In backbone 0.9.2 the set function runs validation before any changes are updated.
// Run validation.
if (!this._validate(attrs, options)) return false;
In case of {silent: true} option is passed, the validation code will not be executed.
if (options.silent || !this.validate) return true;
That means, model.set({value: 100}, {silent: true}); is able to set "invalid" value into model, so attributes are updated, but change event's are not firing.
It is useful, then you want to update some field and prevent whole mode validation, so if model is not yet completed, the change still propagated to attributes. But you usually you also want the view to show the change, so you have to manually call model.change() or model.trigger('change:value', model, value).

Related

how can i get information about who change the variable i set in the watch (angular)

example
$scope.$watch('data', function(){});
The function starts when data will change but if I want to know what function cause to the change.
You can't directly interrogate the variable 'data' to check what method changed it and I'm not aware of $watch having an 'origin' style property.
So you'd need to track this yourself, so I'd recommend having an intermediary method which changes the actual data, and all others call this.
Said method could then store the source and then the watch would be triggered.
But if doing that do you still need the watch?
Alternatively you could just set a flag denoting the origin.
You could also use evaluation in your watch statement as explained in an interesting article by Cameron Boehmer, that way you could only trigger your watch if certain other conditions (like flags) are met.

Is there a way to tell apart from user based changes to model and ones that came from the database?

I would like to trigger auto save on my model every time something changes, but only if the change came from the UI (i.e. the view). I mean, if the change came from the database, then there is no point in saving it again, it just came from the database...
However fetch and save can trigger change events (fetch because it brings potentially different state of the model, and save as it can bring the id for a newly created model)
So in the change event, I'd like to know what caused the change.
e.g. was it a call from the view?:
this.model.set("foo", "bar"); //triggers a change event as foo's value changed
Or a result of a sync operation?:
model.fetch(); //triggers a change event as the model changed in the DB
model.save(); //triggers a change event as the id was empty
Is there a way to tell them apart?
Once solution I thought of is wrapping view calls to the model's set with a setAttribute method, and triggering a custom changeDueToUIAction event, but I'm sure this has been solved before and I'm sure in a better way...
Or am I missing something altogether and the problem is that I ask the wrong question?
I'd say there are several custom solutions involving more or less boilerplate. And there is an easy one, though still adding a bit of boilerplate.
(Without any major change to Backbone) you can't know the source of the change (thinking about it, how would you have access to it in the Model#set method?). What you have access to in a listener is 2 objects. First one is the model which was changed. Second is the options you passed when calling the set method. So I guess you could do something like that:
// in the view
this.model.set('foo', 'bar', {'isFromUI': true});
// in your model
this.listenTo(this, 'change', function(model, flags) {
if(flags && flags.isFromUI) this.save();
});

Difference between backbone on/off (teardown) and listenTo/stopListening Implementation

I am currently using backbone to implement my app. As part of memory management, I will trigger a teardown of all the views when I am switching views
teardown: ->
for viewName, view of #subViews
view.teardown()
for object, events of #objectEvents
#_handleObjectEvents("off", object, events)
#off()
#remove()
#undelegateEvents()
#
Is this approach sufficient to ensure that most of the memory issues are resolved? The challenge I see here is that I need to track all the subviews of each view and call teardown for all main views and subviews as part of the cleanup.
I did some searching and found that backbone also has these two events: 'listenTo' and 'stopListening' where we control the binding of events to models at the view level.
view.listenTo(model, 'change', view.render);
view.stopListening(model);
My questions is, is there an overlap between my teardown implementation and using 'stopListening'? Can I just solely use 'stopListening' for memory management?
The short answer is yes, there is an overlap.
The more complicated answer is listenTo/stopListening methods introduced in Backbone 0.9.9 already use on/off methods but with some useful addition – they store current event listeners in internal object called _listeners.
The benefit of using this object is that you always know full list of all your listeners – you can iterate over it and remove specific elements from it (remember that a listener is just a function and a function is just an object).
So, you can call it this way:
this.stopListening(emitting_object, ["reset", "add"]) // Removes listeners for "reset" and "add" on emitting_object
this.stopListening(emitting_object) // Removes all listeners on emitting_object
this.stopListening() // Iterates over _listeners object and removes all listeners (probably the most usable case)
So, using this method, you can convert your teardown method to something like this:
this.teardown = function(){
this.stopListening();
...
}
I'd recommend using listenTo method. The niceness of it is that when you use the remove method on your view, it will automatically unbind (call stopListening) on what it's listening to. According to Derrick Bailey, it also unbinds the events under the events property.
What I will do, since I am in the process of upgrading my app to 0.9.9 from 0.9.2 (which actually still works so far), is just switch around all of my ons/offs to listenTo and stopListening. I also, mostly, have close methods on there. I will, however, still call undelegateEvents, just in case. Doesn't hurt to know that you're still getting rid of the event listening.

backbone.js: does calling collection.reset() removes the models as well?

I read in the backbone documentation that calling collection.reset() clears the collection. I want to know if it removes the models as well or do they continue to live in memory?
If they're not removed is there an easier way to remove all the models in a collection without iterating through the models and calling model.remove()?
You could listen for the reset event from the model and do your cleanup and this.destroy() in response. That's what the event hooks are for. See http://backbonejs.org/#Events-catalog
Note: You absolutely should not change or override any method or property prefixed by an underscore, such as _removeReference. The underscores mean that it is intended as an internal method or property, and that the internal implementations may change (their API's are considered unstable). Upgrading Backbone could break any code that relies on underscore-prefixed methods, even if the release is advertised as a backwards-compatible change.
I know your question says "without iterating", but it really is the most reliable way of handling this. Consider a case where a model has been moved from one collection to another, but it's still listening on the first collection's reset event (because a programmer six months later didn't notice the connection).
Now when the first collection gets reset, the moved model gets destroyed. Oops!
Iterating over the collection probably is the best way to handle this if you don't have an endpoint on your API that will delete all objects in a collection in batch on the API server (which is often how this is handled).
Luckily, that iteration is pretty easy:
destroyAll: function () {
var promises = [];
while(this.models.length > 0) {
promises.push( this.models[0].destroy() );
}
// handle errors communicating with the server
$.when(promises).fail(function (response) {
this.trigger('syncError',
response);
}.bind(this));
}
What you are looking for is, probably, for the models to be garbage-collected. That is, that nobody has a reference to these models anymore, after they are removed from the collection.
Backbone does its part of removing the references that it set on the models, when they are removed from the collection. However, you have to do your own cleanup if your code has references to those models. Most of the time, this happens if those models are registered as event listeners, like in this example: http://jsfiddle.net/dira/4uxp4/2/
Taking a look at the implementation of reset, you could change _removeReference to call a cleanup function on the model as well. And in the model, remove the model from all the listeners/all the other objects that keep a reference to it.

iOS: When adding object as a property listener, do I have to "unregister" the listener before releasing my object?

As I registered the my object as a property listener. Is it necessary for me to perform any sort of "unlistening" before I release the object.
You should not continue to observe objects you are not retaining (this applies to KVO, delegation, notification, any any other listener pattern). Here is the danger:
You observe an object ("observed") and retain it.
Some other object also retains "observed"
You release "observed"
You deallocate
"observed" changes a property and notifies you
The program crashes
While there are several patterns that you may believe make the above impossible, they are all much more fragile than just unregistering yourself when you release the observed object, particularly in your dealloc.

Resources