How to Override Backbone.Sync

How to override Backbone.sync?

Take a look at this annotated source example where they overwrite Backbone.sync with a localstorage alternative

backbone-localStorage

Basically Backbone.sync should be a function that takes 4 arguments:

Backbone.sync = function(method, model, options) { };

You need to fire either options.success or options.error depending on whether the method succeeded. The methods are in the format:

  • "create" : expected that you create the model on the server
  • "read" : expected that you read this model from the server and return it
  • "update" : expected that you update the model on the server with the argument
  • "delete" : expected that you delete the model from the server.

You need to implement those 4 methods and define whatever you want for your "server"

Of course these are only the things that Backbone.sync must implement. You may implement more methods and you may pass more paramaters back to success but it's best not to do this.

It's best to make sure it does the same as Backbone.sync does currently so that your programming to an interface rather then an implementation. If you want to switch out your modified Backbone.sync for say the localStorage one you won't have to extend it yourself to match your extended Backbone.sync"

[Edit]

Also do note that you can use multiple implementations of sync. Every reference to Backbone.sync is actaully (this.sync || Backbone.sync) so you just have to do something like:

var MyModel = Backbone.Model.extend({ 
...

"sync": myOwnSpecificSync,

...
});

Backbone.sync is just the default global one that all models use unless the models have a sync method specifically set.

Override Backbone.sync() at Model level to send extra params?

When you call .destroy(), .fetch() or .save() they all call Model.sync which only calls Backbone.sync. It's a proxy function. This is intended to provide a nice hook for modifying the AJAX behavior of a single model or any models that extend from that model.

  • Solution 1: Override the Global Backbone.sync to JSON.stringify and modify the contentType when you've specified data to be sent with the delete request.
    • Pros: You can call model.destroy() and optionally pass in an options parameter
  • Solution 2 - Override the Model.sync method.
    • Pros: The override only affects individual models. Isolated changes.
    • Cons: All models that wish to delete with data need to extend from the correct 'base model'
  • Solution 3 - Don't override anything and explicitly call model.sync with all of the stringify and contentType stuff.
    • Pros: Very isolated changes, won't affect any other models. Useful if you're integrating with a large codebase.

[Solution 1] - Global Override of Backbone.sync (this will affect all models)

javacript version

var oldBackboneSync = Backbone.sync;
Backbone.sync = function( method, model, options ) {
// delete request WITH data
if ( method === 'delete' && options.data ) {
options.data = JSON.stringify(options.data);
options.contentType = 'application/json';
} // else, business as usual.
return oldBackboneSync.apply(this, [method, model, options]);
}

Usage:

var model, SomeModel = Backbone.Model.extend({ /* urlRoot, initialize, etc... */});
model = new SomeModel();
model.destroy({
data: {
/* data payload to send with delete request */
}
});


[Solution 2] - Override Backbone.destroy on a base model and extend other models from that.

override

// Create your own 'enhanced' model 
Backbone.EnhancedModel = Backbone.Model.extend({
destroy: function( options ) {
if ( options.data ) {
// properly formats data for back-end to parse
options.data = JSON.stringify(options.data);
}
// transform all delete requests to application/json
options.contentType = 'application/json';
Backbone.Model.prototype.destroy.call(this, options);
}
});

usage

var model, SomeModel = Backbone.EnhancedModel.extend({ /* urlRoot, initialize, etc... */})
model = new SomeModel();
SomeModel.destroy({
data: {
/* additional data payload */
}
});


[Solution 3] - Call .destroy() with correct parameters.

If sending data with your destroy requests is an isolated thing, then this is an adequate solution.

When calling model.destroy() pass in the data and contentType options like so:

javascript version/usage

var additionalData = { collective_id: 14 };
model.destroy({
data: JSON.stringify(additionalData),
contentType: 'application/json'
});


The "Problem" (with Backbone, not solutions):

Backbone.js makes the assumption (view source) that delete requests do not have a data payload.

// delete methods are excluded from having their data processed and contentType altered.
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
params.contentType = 'application/json';
params.data = JSON.stringify(options.attrs || model.toJSON(options));
}

In their assumed RESTful API call, the only data required is the ID which should be appended to a urlRoot property.

var BookModel = Backbone.Model.extend({
urlRoot: 'api/book'
});
var book1 = new BookModel({ id: 1 });
book1.destroy()

The delete request would be sent like

DELETE => api/book/1
contentType: Content-Type:application/x-www-form-urlencoded; charset=UTF-8

How do you override the sync method for a Backbone collection (as opposed to a model)?

Backbone's Collection sync looks exactly the same as the Model's sync method:

// Proxy `Backbone.sync` by default.
sync: function() {
return Backbone.sync.apply(this, arguments);
},

thats because both do the same, they simply "proxy" the Backbone.sync method. The reason they are there is to allow implementations to change the sync logic on a per type basis and not having to touch the main sync method which will influence all models and collections in your project.

I would advise doing something like the following for your collection because you probably dont want to mimic Backbone's sync logic yourself, it does quite a few things for you and messing with it can cause problems that could be hard to solve later on.

var MyCollectionType = Backbone.Collection.extend({

sync: function(method, model, options){
//put your pre-sync logic here and call return; if you want to abort

Backbone.Collection.prototype.sync.apply(this, arguments); //continue using backbone's collection sync
}
});

Overriding Backbone Sync to use diiferent calls for fetch/save/destroy

You can provide your collections or models with a custom sync function which will be called instead of Backbone.sync when you fetch/update/destroy an element. You can then tailor the options to emit a request matching your server setup. For example,

var M = Backbone.Model.extend({

sync: function(method, model, options) {
options || (options = {});

// passing options.url will override
// the default construction of the url in Backbone.sync

switch (method) {
case "read":
options.url = "/myservice/getUser.aspx?id="+model.get("id");
break;
case "delete":
options.url = "/myservice/deleteUser.aspx?id="+model.get("id");
break;
case "update":
options.url = "/myService/setUser.aspx";
break;
}

if (options.url)
return Backbone.sync(method, model, options);
}

});

var c = new M({id: 1});
c.fetch();
c.save();
c.destroy();

And a Fiddle simulating these calls http://jsfiddle.net/nikoshr/4ArmM/

If using PUT and DELETE as HTTP verbs bothers you, you can force a POST by adding Backbone.emulateHTTP = true;
See http://jsfiddle.net/nikoshr/4ArmM/1/ for a revised version.

override backbone.sync only for put

Backbone._sync = Backbone.sync;
Backbone.sync = function(method, model, options) {
var params = _.clone(options);
delete model.attributes.id;
params.success = function(model) {
if(options.success) options.success(model);
};
params.error = function(model) {
if(options.error) options.error(model);
};
Backbone._sync(method, model, params);
}

Where to code inorder to override backbone.sync

The strategy behind Backbone framework is to make it simple for editing and flexible for every need. So if you look up the source code you'll find out that every method, which calls Backbone.sync in fact calls first "this.sync".

From the Backbone manual you can read :

The sync function may be overriden globally as Backbone.sync, or at a
finer-grained level, by adding a sync function to a Backbone
collection or to an individual model.

So you have two options

Option One - Replacing global Backbone.sync function

If you override the global Backbone.sync you should place your code in your global application file ( actually anywhere you want, but it must be evaluated ( executed ) at your initial javascript loading, to work as expected

// Anywhere you want

Backbone.sync = function(method, collection, options) {
console.log(method, collection options)
}

This will override Backbone.sync and actually will display on your console what is called every time you call collection.fetch, save, delete, etc.

Here you have no default Methodmap, infact you have nothing else except the arguments :

  • method - which is a string - 'read', 'create', 'delete', 'update'
  • collection - which is your collection instance which calls the method
  • options - which has some success, error functions, which you may or may not preserve.

Debug this in your browser, while reading the Backbone source code, it's very easy to understand.

Option Two - Adding to your model/collection sync method

This is used if you wish to use the default Backbone.sync method for every other model/collection, except the one you specifically define :

mySocketModel = Backbone.Model.extend({ 
sync : function(method, collection, options) {
console.log('socket collection '+this.name+' sync called');
}
});

Partners = new mySocketModel({ name : 'partners' });
Users = new mySocketModel({ name : 'users' });
Log = new Backbone.Collection;

So if you call Partners.fetch() or Users.fetch(), they won't call Backbone.sync anymore, but yor Log.fetch() method will.

Backbone - overriding sync using Prototype not assigning values

Instead of sync() try sync.call(this, arguments)

Overriding Backbone sync with a progress event listener

Here's what finally worked for us:

sync: function(method, model, options) { 
options.beforeSend = function(xhr, settings) {
settings.xhr = function() {
var xhr = $.ajaxSettings.xhr();
xhr.upload.addEventListener("progress", function (event) {
Math.ceil(event.loaded/event.total*100);
}, false);
return xhr;
}
}
return Backbone.sync(method, model, options);
}


Related Topics



Leave a reply



Submit