Backbone sync not updating collection - backbone.js

I thought this would update my model with the response from the server (the call works fine and returns the expected values) but my collection and its models are not being updated. What's wrong with this? Its using create because it posts the the collection which is then returned with changes. As I mentioned the changes come back but the console.log shows that the collection has not changed.
test: function(){
Backbone.sync('create', this.importCollection, {
success : _.bind(function(e) {
console.log(this.importCollection);
},this)
});
},

If you look at Backbone.Sync method in the annotated source, you can see that nowhere does it ever manipulate the model/collection directly, it only makes the ajax call using the model/collection instance.
If you want to call the Backbone.Sync method manually, you're gonna have to populate your collection manually after the ajax call is done. If you want to see how Backbone does it, you can take a look at the Backbone.Collection fetch method.

Related

Backbone try to update model in server after save

I use Rails 4 + backbone in my application.
Everything is good. New model is created in backbone and saved by calling:
newItem.save(null, {success: this.sendSuccess, error: this.sendError});
However, implementing a new feature I need to change one of the model attributes. What I see that a PUT action is fired just before sendSuccess is called, which I want to avoid.
Moreover, the url is very strange. Save action calls this url:
Remote Address:127.0.0.1:3000
Request URL:http://www.lvh.me:3000/api/user/1/tickets
Request Method:POST
and then, after server return the json with the modified attribute, backbone calls this url:
Remote Address:127.0.0.1:3000
Request URL:http://www.lvh.me:3000/api/user/1/tickets
Request Method:PUT
without the ticket id!
Is there any way to prevent backbone fire an update when server return the model with different attributes?
The problem was that I had a listener in my model exactly on the column that server changed:
initialize: function() {
this.on("change:status", this.statusChanged, this);
},
Now I had to figure out why the update url does not contain the model id.
I figured out that when I first created the model, from some reasons I couldn't assign it to the collection, so in order to save it I assign the url manually:
var newTicket = new MyApp.Ticket( ticketData );
newTicket.url = this.collection.url();
Now, the bug is that url is a function, and I simply overrided it!
I changed the code to:
newTicket.urlRoot = this.collection.url();
and now it works.
Backbone will always perform PUT if your model has an id attribute setted. Which makes sense when using RESTfull.
Be sure that you're really SAVING(new model withoud an ID) a data to server instead of UPDATING(model with an ID) to server.

Backbone.Model.save and don't update client

I want to call save on a Backbone model and have it write data to the server, but not update the client. How do I do this?
To clarify: when Backbone saves a model, it sends all the data to the server, and then retrieves the data from the server and updates it on the client. This second step is what I want not to happen.
To clarify further: The model ('parent' model) has an attribute which is itself a model ('child' model); when it's saved to the server, this child model is converted to JSON. When the parent model updates after the save, the attribute that previously contained a reference to the child model is replaced with the parsed JSON object of the child model that was saved. This is what I need not to happen.
When the data is initially pulled from the server, the parent model "reconstitutes" that object into an appropriate child model, but this is an expensive process and there is no reason to re-do it every time save fires on the parent model, since the child model will never change.
It sounds like you do not want to parse your model when you receive the response from the server on a model.save
You can try something such as:
model.save(attributes,{
success: function() { ... },
parse : false // This will be passed to your parse function as an option
});
You would have to set-up your parse function in your corresponding model as follows:
parse: function(resp,options) {
// don't update model with the server response if {parse:false} is passed to save
if (options && !options.parse) return;
/ ... rest of your parse code ... /
Backbone currently defaults options.parse to true. Here is a short-discussion on the topic.
As discussed in that thread, perhaps you want to consider why you do not want want to update the server response to the client. There may be a cleaner way to achieve the results you desire.
Depending on how/what your server setup is, all you really have to do is issue a regular AJAX request. This is exactly what backbone does in the background so you'll just bypass the client side logic.
You could do this with native JavaScript, but I'm fairly sure you have some other library in use that can make things much easier.
For the completeness of this answer, I'll give an example with jQuery:
$.ajax({
type: "POST",
url: "http://same.as.your.model",
data: { "the" : "model" },
dataType: "JSON",
success: function(){
// once the request has returned
}
});
The $.ajax function also has some additional functionality, and you can read about it in the jQuery docs.
On client you mean Views? If you want to save your model but not render your views which happens since save will trigger a change event, you should call save with option silent:true, or set a custom option like dontchange:true when calling save and check it in when handling change. I prefer the custom option, because silent has side effects (at least in my version of backbone 1.0.0)
a little code:
when you save:
model.save({},{dontchange: true});
you install your event listeners in the view:
this.listenTo(model, 'change', function(model, options){
if (options.dontchange)
return;
this.render();
});
I ran into same problem. model.save(attrs,{patch:true, parse:false}) really did not invoke parse method but model was still merged with server response.
It is not elegant, but this worked for me:
model.clone().save(attrs,{patch:true})
I believe it's best to avoid this situation by clean REST api design.

How do I iterate a Backbone Firebase Collection?

I currently started using Firebase as my backend solution for persistance.
I found it easy to create new objects and persist it to Firebase with Backfire with a simple
collection.add(obj)
The issue comes when I try to get a Firebase collection from the server.
For example when i try
console.log(collection);
I get this output:
=> {length: 0, models: Array[0], _byId: Object, _events: Object, constructor: function…}
Which result in an empty models array
console.log(collection.models);
=> []
After some searching, I figured out that Backbone Collections aren't yet loaded at the time I try to log it to the console (see this previous question).
I also tried using
Backbone.Collection.extend({
model: Todo,
firebase: new Backbone.Firebase("https://<your-namespace>.firebaseio.com")
});
To explicitly call fetch from the server and use success callback with no success either.
My question is: How can I get the Firebase Collection and populate the DOM from it?
When you call Backbone.Firebase.Collection.add, it does not get added to the collection synchronously. Rather, it sends the request to Firebase and then waits for the return event. See the code here
Thus, if you immediately try to read the collection, you will see zero elements. However, if you try something like this:
collection.once('add', function() { console.log(collection.length); });
You'll see the element you have added.
Remember that we're dealing with real-time data here, so when you want to populate the DOM, you shouldn't think in terms of a single transaction, but instead rely on events and take everything as you get it (in real time).
So to populate the DOM, do something like this in your view:
Backbone.View.extend({
render: function() {
this.listenTo(this.collection, 'add', this.rowAdded);
},
rowAdded: function(m) {
/* use `m` here to create your new DOM element */
}
});
Additionally, you'll probably want to check out a nice binding library like ModelBinder to help you deal with the constantly changing DOM, so you don't have to re-invent any wheels.
It seems you have to use a Backbone.Firebase.Collection and not a Backbone.Collection which will tell you that your calls to fetch or sync are silently ignored.
Also, Backbone.Firebase's got a read and a readall methods that should get you started. It seems Backbone.Firebase.Collection doesn't inherit this method, but I'm not sure though.
Edit:
As Kato stated in his comment, it seems you don't have to do anything. Just use Backbone.Backfire.Collection and Backbone.Backfire.Model.

Backbone - Using fetched data inside the view

I have fatched the data to Collection variable, but I am not sure what should I do next. How to use this data and to fill the template with it? Here is my code from the render function inside the View.
Collection.url = "../data";
Collection.fetch();
var compiled = _.template(self.data);
self.$el.prepend(compiled(/*MY JSON SHOULD GO HERE*/));
I am a newbie to backbone, so every help is appreaciated.
Here is a Collection definition:
var MainCollection = Backbone.Collection.extend({
model: MainModel,
//localStorage: new Backbone.LocalStorage("kitchen"),
initialize: function (models,options) { }
}), Collection = new MainCollection;
Here is a log of Collection and Collection coverted to JSON:
Assuming Collection is your collection's name (that's pretty confusing I have to say), this is what you're looking for:
self.$el.prepend(compiled(Collection.toJSON()));
Edit:
Don't forget you're fetching the data asynchronously. So when you're evaluating your template, the data hasn't come back yet, and your collection's still empty. Listen to the end of the request ('sync' event I think) or some other events so you know when the collection's populated OR use the success option of the fetch method to specify a callback :)
As for your logs. When you log an object, it will be automatically updated until you check the details. So you logged it while it was empty, but checked it after it was populated (a few ms afterwards).

Does backbone trigger .add when a collection is .fetched?

I am converting my backbone app so that it starts communicating with the server, previously I had just been populating the collection with test data using .add()
I have tied some events to the collections add event. So every time an item is added to the collection I can render the view and update some statistics.
it appears that as soon as i add the .fetch() call to get data from the server the add events stop.
for example
var PayableCommitmentCollection = Backbone.Collection.extend({
model:PayableCommitment,
url:"/cc/account/contributions/",
initialize: function() {
this.bind("add",this.setInitialAmount,this);
}
}
this.SetInitialAmount() is never called after fetch creates the models in the collection.
I also have 2 views that are watching for items to be added to this collection that are now not updating.
My obvious work around is to write my own AJAX call so that I can add the items the same way I have been during development so far, however I'm sure backbone has the smarts to help me out here.
Can anyone suggest a way i can bind to the completion of fetch, or to make it stimulate the add event.
The fetch method accepts a hash of options. One of these options can be the "add" option, which calls add on the collection instead of reset.
collection.fetch({ add: true });
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.
And a reset:
reset collection.reset(models, [options])
Adding and removing models one at a time is all well and good, but sometimes you have so many models to change that you'd rather just update the collection in bulk. Use reset to replace a collection with a new list of models (or attribute hashes), triggering a single "reset" event at the end.
So a fetch will trigger a single "reset" event rather than a bunch of "add" events. You need a collection-wide version of setInitialAmount that you can bind to "reset".
In Backbone 1.0, Collection#fetch has this to say:
fetch collection.fetch([options])
Fetch the default set of models for this collection from the server, setting them on the collection when they arrive.
[...]
The behavior of fetch can be customized by using the available set options. For example, to fetch a collection, getting an "add" event for every new model, and a "change" event for every changed existing model, without removing anything: collection.fetch({remove: false})
So, if you're using 1.0+ then all you need to do is call your fetch with the remove: false option.

Resources