33

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?

Nikhil Agrawal
  • 26,128
  • 21
  • 90
  • 126
a paid nerd
  • 30,702
  • 30
  • 134
  • 179

4 Answers4

24

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.)

Julien
  • 9,216
  • 4
  • 37
  • 38
12

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.

  1. The client sends {"resources": [{resource1},{resource2}]}
  2. The server replaces the entire collection with the new information from the client, and returns the information after it's been persisted: {"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.

carpeliam
  • 6,691
  • 2
  • 38
  • 42
  • 1
    +1, I'm still not sure why collection doesn't have a save option. One note, when I tried using your above code I got a `Uncaught TypeError: Property 'url' of object [object Object] is not a function` on the `@url()` call. Referencing it as a property instead of a function fixed that for me: `@url` – Mike Vormwald Jan 04 '13 at 04:44
  • @MikeV definitely- `url` can be either a property or a method, so you'd want to use whichever one's appropriate for you. – carpeliam Jan 04 '13 at 22:26
  • @Crungmungus, eh. I used CoffeeScript because it's terse and gets the point across without getting bogged down. Maybe at some point I'll update this with JS, but do you really think that the CoffeeScript makes it harder to understand? – carpeliam Oct 16 '13 at 05:59
  • I had the same error as @MikeV. I think this is a result of saving a JSON object rather than a model. I solved this by creating a simple-wrapper Model with nothing more than a url property defined in it, just for the purpose of saving this unified collection. My call looks then like this: `Backbone.sync('update', new MyWrapperModel({ mythings: this.models })` – mikebridge Oct 01 '14 at 21:46
6

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

Community
  • 1
  • 1
philfreo
  • 41,941
  • 26
  • 128
  • 141
0

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