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.
Related
I'm trying to design a load more type of system where every time you press load more you add data to the existing collection. This is a rough sketch of how the UI looks.
Everything works pretty great except as you would except everytime I re-run the
items.fetch
What it does: It overrides the entire collection with the new data
What I want it to do: I want the new records returned to be added to the records collection not override old 'records'
How can I make this work so that the newly fetched records are appended to existing records and not overridden?
Add { remove: false } to your fetch call:
items.fetch({ remove: false, data: { nextId: this.info.get('nextId') } });
What's happening here is Collection#fetch will synchronize its models with the data the server returns. That includes adding new models, updating models already present, and removing models that are not included in the response.
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})
The available set options are add, remove, and merge. Setting one (or all) to false will disable that functionality in the fetch.
It sounds like you just want { remove: false }, leaving the add and merge functionality.
I'm not familiar with backbone-relational,
But with a normal collection, you can do the following in parse method:
Backbone.Collection.extend({
parse: function(response){
// you can update the info here like this.info = response.info or similar
return _.union(this.toJSON(), response.records);
}
});
Basically we combine the response with existing data and return it so that we maintain the previous data when the collection updates
I have some unexpected behavior in a Backbone view, "add" is being triggered for every model in a collection that was fetched, but I would expect just sync would be. So I know something funny is going on but I'm having trouble detecting what it is as the code touches a lot of files.
Does anyone have any advice how I can debug where/what is triggering the add? Or even what may cause add to be triggered for every model when I'm just fetching the collection?
For example, when I run:
#listenTo #collection, 'add', #adding
#listenTo #collection, 'sync', #synching
adding: -> console.log 'adding'
synching: -> console.log 'synching'
I get 'render' logged 20 times (number of models in the collection), but 'synching' just once, as expected.
A regular Backbone.Collection.fetch(...) uses Collection.set(...) to populate the models. Here's the line from the source showing the code. Collection.set(...) fires add events for every model returned.
If you're actually fetching the "initial" set of models, you probably want to "reset" the collection when the data is returned:
var collection = new MyCollection();
collection.fetch({ reset: true });
This will cause the collection to use reset when the data is returned, which does not fire add events. Here's the source for reset(...); note it uses this.add(...) but passes in the silent flag. So you will get a single reset event, followed by a single sync event.
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.
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 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.