I have a Backbone collection with a load of models.
Whenever a specific attribute is set on a model and it is saved, a load of calculations fire off and the UI rerenders.
But, I want to be able to set attributes on several models at once and only do the saving and rerendering once they are all set. Of course I don't want to make several http requests for one operation and definitely dont want to have to rerender the interface ten times.
I was hoping to find a save method on Backbone.Collection that would work out which models hasChanged(), whack them together as json and send off to the back end. The rerendering could then be triggered by an event on the collection. No such luck.
This seems like a pretty common requirement, so am wondering why Backbone doesn't implement. Does this go against a RESTful architecture, to save several things to a single endpoint? If so, so what? There's no way it's practical to make 1000 requests to persist 1000 small items.
So, is the only solution to augment Backbone.Collection with my own save method that iterates over all its models and builds up the json for all the ones that have changed and sends that off to the back end? or does anyone have a neater solution (or am I just missing something!)?
I have ended up augmenting Backbone.Collection with a couple of methods to handle this.
The saveChangeMethod creates a dummy model to be passed to Backbone.sync. All backbone's sync method needs from a model is its url property and toJSON method, so we can easily knock this up.
Internally, a model's toJSON method only returns a copy of it's attributes (to be sent to the server), so we can happily just use a toJSON method that just returns the array of models. Backbone.sync stringifies this, which gives us just the attribute data.
On success, saveChanged fires off events on the collection to be handled once. Have chucked in a bit of code that gets it firing specific events once for each of the attributes that have changed in any of the batch's models.
We then just need the getChanged() method on a collection. This returns an object with 2 properties, an array of the changed models and an object flagging which attributes have changed:
Although this is slight abuse of the intended use of backbones 'changed model' paradigm, the whole point of batching is that we don't want anything to happen (i.e. any events to fire off) when a model is changed.
We therefore have to pass {silent: true} to the model's set() method, so it makes sense to use backbone's hasChanged() to flag models waiting to be saved. Of course this would be problematic if you were changing models silently for other purposes - collection.saveChanged() would save these too, so it is worth considering setting an alternative flag.
In any case, if we are doing this way, when saving, we need to make sure backbone now thinks the models haven't changed (without triggering their change events), so we need to manually manipulate the model as if it hadn't been changed. The saveChanged() method iterates over our changed models and calls this changeSilently() method on the model, which is basically just Backbone's model.change() method without the triggers:
Usage:
RE. RESTfulness.. It's not quite right to do a PUT to the collection's endpoint to change 'some' of its records. Technically a PUT should replace the entire collection, though until my application ever actually needs to replace an entire collection, I am happy to take the pragmatic approach.
You can define a new resource to accomplish this kind of behavior, you can call it
MyModelBatch
.You need to implement a new resource in you server side that is able to digest an
Array
of models and execute the proper action:CREATE
,UPDATE
andDESTROY
.Also you need to implement a
Model
in your Backbone client side with one attribute which is the Array of Models and a specialurl
that doesn't make use theid
.About the re-render thing I suggest you to try to have one View by each Model so there will be as much renders as Models have changed but they will be detail re-renders without duplication.
This is what i came up with.