Say that I have a Collection and I\'ve made changes to many of its Models. What\'s the best way to save all of the changes using a single HTTP request?
Usually REST backends handle single instance creation/update. You would need to change that to accept an array of objects.
That said, on the client side, you would need to go directly to the Backbone.sync function
Backbone.sync = function(method, model, options)
In this case your model should be an array of model. The method should be "create" or "save" and the options take the same type of options as a jQuery ajax call (error, success, etc.)
I'm going to do the Wrong Thing here and quote Wikipedia regarding proper RESTful practices: a PUT to example.com/resources
should replace the entire collection with another collection. Based on this, when we had to support editing multiple items simultaneously, we wrote up this contract.
{"resources": [{resource1},{resource2}]}
{"resources": [{"id":1,...},{"id":2,...}]}
We wrote the server half of the contract in Rails, but here's the client half (in CoffeeScript, sorry!):
class ChildElementCollection extends Backbone.Collection
initialize: ->
@bind 'add', (model) -> model.set('parent_id', @parent.id)
url: -> "#{@parent.url()}/resources" # let's say that @parent.url() == '/parent/1'
save: ->
response = Backbone.sync('update', @, url: @url(), contentType: 'application/json', data: JSON.stringify(children: @toJSON()))
response.done (models) => @reset models.resources
I thought this was a lot easier to implement then overriding Backbone.sync. One comment on the code, our collections were always child objects, which should explain why the code sets a "parent_id" whenever an object is added to the collection, and how the root of the URL is the parent's URL. If you have root-level collections that you want to modify, then just remove the @parent
business.
This code adds a new method to the collection prototype just to call the save method of those models that had changed. It worked for me:
Backbone.Collection.prototype.saveAll = function(options) {
return $.when.apply($, _.map(this.models, function(m) {
return m.hasChanged() ? m.save(null, options).then(_.identity) : m;
}));
};
Gist link: https://gist.github.com/julianitor/701c677279bac1529b88
You should extend Backbone.Collection
, giving it a save()
method that would check each of its models hasChanged()
.
Then it should call Backbone.sync
, which you'll probably have to extend a little into a custom sync function. If you do use a custom Backbone.sync
function, then be sure to set it on your collection.
var CollectionSync = function(method, model, [options]) {
// do similar things to Backbone.sync
}
var MyCollection = Backbone.Collection.extend({
sync: CollectionSync,
model: MyModel,
getChanged: function() {
// return a list of models that have changed by checking hasChanged()
},
save: function(attributes, options) {
// do similar things as Model.save
}
});
A different approach (using a model to represent the collection) is here: "How" to save an entire collection in Backbone.js - Backbone.sync or jQuery.ajax?
I also like https://stackoverflow.com/a/7986982/137067