Binding for "change" in backbone model not working - backbone.js

Here's the Example
I was following this excellent tutorial by Thomas Davis : What is a model?
Somehow the 'change' binding is not firing. What am I doing wrong here?

Backbone is checking if the set value is the same as the previous value (look at https://github.com/documentcloud/backbone/blob/master/backbone.js#L210 and on).
In your example, the array is still the same but the value inside changed. This is tricky to solve. Creating a new copy of the array seems to be overhead. I would suggest to call the change event directly in your adopt function as a solution:
adopt: function(newChildsName){
var children_array = this.get('children');
children_array.push(newChildsName);
this.set({children:children_array});
this.trigger("change:children");
}
I would suggest to create an issue on backbone github repository to maybe add a "force" option to force the update (thus triggering the event) of attributes on a model.

Here is a bit awkward solution:
adopt: function(newChildsName){
var children_array = this.get('children').splice(0);
children_array.push(newChildsName);
this.set({children:children_array});
}

Instead of using children as an plain array we can use it as an collection and listen to the add,remove events of the collection.

Related

Binding to property of model attribute in Backbone.js

Disclaimer: I'm new to Backbone.js (coming from AngularJS), so I may have an inaccurate mental model of how this is supposed to work.
I have an object, characterNodes, which I'm making an attribute on my model. characterNodes looks something like this:
var characterNodes = {
character_1: {
stories: [// list of Stories]
},
character_2: {
stories: [// list of Stories]
}
...
}
My Backbone Model looks something like this:
var StoryGraph = joint.dia.Graph.extend({
initialize: function() {
// Call parent constructor
StoryGraph.__super__.initialize.apply(this, []);
this.set('characterNodes', characterNodes);
this.on('change:characterNodes', function() {
alert('test');
});
}
});
Each Story has a property "isUnlocked" which is changed elsewhere in the application. I want to fire an event (ie. that is, the alert 'test' should pop up) whenever this property is changed. With the code as it is above, the event never seems to fire.
I can't get a clear understanding from the Backbone docs whether this is supposed to work - does on('change:characterNodes') fire whenever any property (or sub-property, or sub-sub-property, etc) of characterNodes changes? Or only when the pointer to the object changes, that is, when it's replaced with another object? Or am I doing something else wrong? Thanks!
Backbone doesn't do any magic, basically, the change event is fired only if you set the "characterNodes" to a new object. If you're changing a nested property of that object, Backbone doesn't know it happened. You have three options: a) Change the whole object (e.g. by creating a copy), b) fire the change event manually (m.trigger("change:characterNodes")) whenever you change a nested property, c) Do not use nested objects for this. Have "character1_Stories" as a top level property.
Options c) is preferable. Try to keep properties in your models flat. Option a) is also fine but it has the disadvantage of having to copy the object. Option b) is not recommended. This is because Backbone keeps track of the previous value of the model properties (m.previous("characterNodes")). If you change a nested property, the previous value will have the same reference to the same object as the new value, therefore, it won't reflect its previous state.
Try to call a function instead define the function, and pass the third argument to keep the context call.
Something like this:
this.on('change:characterNodes', this.AlertSomething, this);
Hope it helps.

this.save in backbone

I am on way to learning backbonejs.
I am working with the popular todo list tutorial.
I have certain questions about which i am a bit confused:
In one the models i found this function:
toggle: function() { this.save({completed: !this.get(’completed’)});}
The thing that i don't understand is this.save function. How does it work? What does it actually saves and where. And what does the code inside this function means: completed: !this.get and so on.
In one of the views i found this line of code:
this.input = this.$(’#new-todo’);
Now what does this.input means? And i also don't understand the sytnax this.$('#new-todo');
Let me know if more code is needed for comprehension. Also if anyone could point me to great learning resources for backbone, it will be awesome. Currently i am learning from 'Backbone Fundamentals' by addyosmani.
Backbone Model and Collection both have url properties.
When set properly backbone will make a HTTP POST request with the model as a payload to the url when saved for the first time (id property has not peen set). I you call save and the models id has been already set, backbone will by default make PUT request to the url. Models fetch function generates a GET request and delete a DELETE request.
This is how backbone is made to work with RESTfull JSON interfaces.
When saving a model you can define the actual model to save like it's done in the example.
Read the Backbone.js documentation. It's ok!
http://backbonejs.org/#View-dollar
this.$('#new-todo') // this.$el.find('#new-todo')
toggle: function() { this.save({completed: !this.get(’completed’)});}
Its basically saving inverse value to "completed" attribute of model. so if model's current attribute is true, it would save it to false !
regarding this.input = this.$(’#new-todo’);
Its basically saving/caching DOM with id "new-todo" from current VIEW's 'el' to view instance's 'input' property. so that we do not have to call jQuery methods for getting the same element when we need in future.
hope this helps.
:)
I too am a backbone newbie and i had been in search of good tutorials that gave good insights into the basics and i found after around 3-4 days of searching. Go through backbonetutorials.com and there is a video compiled which gives exactly what we need to know about Routers, Collections, Views and Models.
The sample working can be found at : http://backbonetutorials.com/videos/beginner/
Although this tutorial is a very basic one, you need to have basic jquery, javascript knowledge. Keep http://www.jquery.com opened in another tab as well when you go through the sample codes. Documentation is extremely useful.
Once you have good knowledge of jquery then if you go through the tutorials, you will understand and pick it up a lot better. And once you get hold of the MV* pattern of backbone you'll love it.
p.s : Do not copy paste codes or functions if you need to learn, type them.!!..
Cheers
Roy
toggle: function() { this.save({completed: !this.get(’completed’)});}
Backbone Model have a url property, when you set a property backbone makes a HTTP request to that url to save that value to the data source.
Here it is setting the value of "completed" attribute with inverse of earlier "completed" value, which will be saved to the data source

Backbone.js event system (model and collection)

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.

Cleanest way to destroy every Model in a Collection in Backbone?

On the first attempt I wrote
this.collection.each(function(element){
element.destroy();
});
This does not work, because it's similar to ConcurrentModificationException in Java where every other elements are removed.
I tried binding "remove" event at the model to destroy itself as suggested Destroying a Backbone Model in a Collection in one step?, but this will fire 2 delete requests if I call destroy on a model that belongs to a collection.
I looked at underscore doc and can't see a each() variant that loops backwards, which would solve the removing every element problem.
What would you suggest as the cleanest way to destroy a collection of models?
Thanks
You could also use a good, ol'-fashioned pop destroy-in-place:
var model;
while (model = this.collection.first()) {
model.destroy();
}
I recently ran into this problem as well. It looks like you resolved it, but I think a more detailed explanation might also be useful for others that are wondering exactly why this is occurring.
So what's really happening?
Suppose we have a collection (library) of models (books).
For example:
console.log(library.models); // [object, object, object, object]
Now, lets go through and delete all the books using your initial approach:
library.each(function(model) {
model.destroy();
});
each is an underscore method that's mixed into the Backbone collection. It uses the collections reference to its models (library.models) as a default argument for these various underscore collection methods. Okay, sure. That sounds reasonable.
Now, when model calls destroy, it triggers a "destroy" event on the collection as well, which will then remove its reference to the model. Inside remove, you'll notice this:
this.models.splice(index, 1);
If you're not familiar with splice, see the doc. If you are, you can might see why this is problematic.
Just to demonstrate:
var list = [1,2];
list.splice(0,1); // list is now [2]
This will then cause the each loop to skip elements because the its reference to the model objects via models is being modified dynamically!
Now, if you're using JavaScript < 1.6 then you may run into this error:
Uncaught TypeError: Cannot call method 'destroy' of undefined
This is because in the underscore implementation of each, it falls back on its own implementation if the native forEach is missing. It complains if you delete an element mid-iteration because it still tries to access non-existent elements.
If the native forEach did exist, then it would be used instead and you would not get an error at all!
Why? According to the doc:
If existing elements of the array are changed, or deleted, their value as passed to callback will be the value at the time forEach visits them; elements that are deleted are not visited.
So what's the solution?
Don't use collection.each if you're deleting models from the collection. Use a method that will allow you to work on a new array containing the references to the models. One way is to use the underscore clone method.
_.each(_.clone(collection.models), function(model) {
model.destroy();
});
I'm a bit late here, but I think this is a pretty succinct solution, too:
_.invoke(this.collection.toArray(), 'destroy');
Piggybacking on Sean Anderson answer.
There is a direct access to backbone collection array, so you could do it like this.
_.invoke(this.collection.models, 'destroy');
Or just call reset() on the collection with no parameters, destroy metod on the models in that collection will bi triggered.
this.collection.reset();
http://backbonejs.org/#Collection-models
This works, kind of surprised that I can't use underscore for this.
for (var i = this.collection.length - 1; i >= 0; i--)
this.collection.at(i).destroy();
I prefer this method, especially if you need to call destroy on each model, clear the collection, and not call the DELETE to the server. Removing the id or whatever idAttribute is set to is what allows that.
var myCollection = new Backbone.Collection();
var models = myCollection.remove(myCollection.models);
_.each(models, function(model) {
model.set('id', null); // hack to ensure no DELETE is sent to server
model.destroy();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>
You don't need underscore and for loop for this.
this.collection.slice().forEach(element => element.destroy());

Backbone.js model validate method fails to fire

I'm trying to understand how Backbone.js model validation works, but I'm seeing some odd inconsistencies. In one place in my app, the validate method is getting called as expected. In another place, however, Backbone.js seems to be passing in a { silent: true } object to the validator, even though I don't want it to.
Here's a jsFiddle that illustrates the issue. The validate method should be called When the Copy buttons are clicked or the values change, but when I step through the code it's clear that the _validate function is being passed the { silent: true } option.
What am I missing?
Update: Figured out what was going on here. I created this jsFiddle originally to replicate an issue I was having that was actually the opposite of this question--I was trying to add an empty model to a collection and validation was firing and preventing me from doing so. When I made the Fiddle, though, it worked as I wanted my app to work. Validation wasn't firing when an empty model was added. I couldn't figure out the difference.
Turns out I was using Backbone.js 0.9.0 in my application and version 0.9.1 in my jsFiddle. Jeremy made changes to validation in 0.9.1 to make it work the way I wanted it to work in my app (see this issue on GitHub). Mystery solved.
Backbone specifically does not call _validate when you're making a new model.
Jeremy suggests that you do:
var mymodel = new MyModel();
mymodel.set({params});
Here's our exchange on github: can't override silent:true
From the Backbone docs, it seems you have to either call set or save on the model in order for validate to trigger.
I updated the jsfiddle so that set is called, and the now the validation function gets triggered:
http://jsfiddle.net/J3uuH/12/

Resources