I need to override Backbone.sync to allow PUT the problem is i don't know how and where to put it.
This is my Model:
define([
'underscore',
'backbone'
], function(_, Backbone) {
var Input = Backbone.Model.extend({
url: 'http://localhost/InterprisePOS/SOP/mobilecreateinvoice/',
initialize: function(){
},
toJSON : function() {
return _.clone({ input:this.attributes });
},
});
return Input;
});
This is my Save function in my view:
save: function(){
invoice = new Invoice();
input = new Input();
invoice.set({POSWorkstationID: "POS7"});
invoice.set({POSClerkID: "admin"});
invoice.set({CustomerName: "Alice in Wonderland Tours"});
invoice.set({IsFreightOverwrite: true});
invoice.set({BillToCode: "CUST-000009"});
InvoiceCollection.add( invoice );
//var invoices = JSON.stringify({Invoices: InvoiceCollection.toJSON()});
_.each(this.collection.models, function(cart){
InvoiceDetailCollection.add( cart );
});
input.set({ "Invoices": InvoiceCollection.toJSON() });
input.set({ "InvoiceDetails": InvoiceDetailCollection});
alert( JSON.stringify(input.toJSON()) );
input.save();
}
The default Backbone sync handler maps CRUD to REST like the following:
create → POST /collection
read → GET /collection[/id]
update → PUT /collection/id
delete → DELETE /collection/id
Sometimes older servers emulate HTTP by mimicking the HTTP method with _method and X-HTTP-Method-Override header. If that is the case, you should set Backbone.emulateHTTP to true
If you want custom mappings, then you would need to override Backbone.sync. An example of overriding could be like the following:
Backbone.sync = function(method, model, options, error) {
// Backwards compatibility with Backbone <= 0.3.3
if (typeof options == 'function') {
options = {
success: options,
error: error
};
}
var resp = function(resp) {
if (resp.status) {
options.success(method != 'read' ? model : resp.data);
}
else {
options.error('Record not found ' + resp.data);
}
};
var store = model.customStorage || model.collection.customStorage;
switch (method) {
case 'read': model.id ? store.read({id: model.id}, resp) : store.readAll(resp); break;
case 'create': store.create(model.attributes, resp); break;
case 'update': store.update(model.attributes, resp); break;
case 'delete': store.delete(model.id, resp); break;
}
};
Where customStorage is your implementation, it could be anything you want that respects the methods I created. Some time ago, I created a backbone sync override for HTML5 WebSQL Storage, it is open sourced located on GitHub https://github.com/mohamedmansour/backbone.webStorage
I hope this helps you get started! Good Luck!
Related
I am trying to set up a variant fetch method on my backbone model that will fetch the current model for a given user. This is available from the API on /api/mealplans/owner/{username}/current.
I have written the following model. I commented out the URL Root, as the prototype fetch call was simply using the urlRoot and I wanted to see if that was overriding the url parameter I passed in portions somehow.
var mealPlan = Backbone.Model.extend({
name: 'Meal Plan',
//urlRoot: '/api/mealplans',
defaults: {},
fetchCurrent: function (username, attributes, options) {
attributes = attributes || {};
options = options || {};
if (options.url === undefined) {
options.url = "/api/mealplans/owner/" + username + "/current";
}
return Backbone.Model.prototype.fetch.call(this, attributes, options);
},
validate: function (attributes) {
// To be done
return null;
}
});
I've seen this done, in some variations in other places, such as at backbone.js use different urls for model save and fetch - In that case the code is slightly different (I started with that and broke it down to make it easier for me to read.)
The options object has the url parameter in it fine when I pass it to fetch, but then it seems to ignore it!
I was assuming the same parameters to fetch as to save - This is not the case.
The method signature for fetch ONLY takes 'options' and not 'attributes', hence the url parameter wasn't found.
The model code should look a bit more like this..
var mealPlan = Ministry.Model.extend({
name: 'Meal Plan',
urlRoot: '/api/mealplans',
defaults: {
},
fetchCurrent: function (username, options) {
options = options || {};
if (options.url === undefined) {
options.url = this.urlRoot + "/owner/" + username + "/current";
}
return Backbone.Model.prototype.fetch.call(this, options);
},
validate: function (attributes) {
// To be done
return null;
}
});
I think it is better to override url() method, like below:
var mealPlan = Ministry.Model.extend({
name: 'Meal Plan',
urlRoot: '/api/mealplans',
//--> this gets called each time fetch() builds its url
url: function () {
//call the parent url()
var url=Backbone.Model.prototype.url.call(this);
//here you can transform the url the way you need
url += "?code=xxxx";
return url;
}
...
besides, in your example above I think there is a mistake and you should replace fetchCurrent by fetch
I'm trying to figure out the "correct" way of accomplishing custom update
functions in Backbone.js Models. An example of what I'm trying to do is:
var Cat = Backbone.Model.extend({
defaults: {
name : 'Mr. Bigglesworth',
location : 'Living Room',
action : 'sleeping'
},
sleep: function () {
// POST /cats/{{ cat_id }}/action
// { action: "sleep" }
},
meow: function () {
// POST /cats/{{ cat_id }}/action
// { action: "meow" }
}
})
From what I can tell, the Backbone.Collection.save() method only performs the
following:
POST /cats/{{ cat_id }}
{ name: 'Mr. Bigglesworth', location: 'Living Room', action: '{{ value }} '}
But the API I'm working with won't let me change action that way, only by:
POST /cats/{{ cat_id }}/action
{ action: "{{ value }}" }
Hopefully that makes sense?
Any help would be appreciated.
You can pass the URL as a parameter when you call save. Maybe you can do something like this:
var Cat = Backbone.Model.extend({
urlRoot: '/cats/',
defaults: {
name : 'Mr. Bigglesworth',
location : 'Living Room',
action : 'sleeping'
},
sleep: function () {
var custom_url = this.urlRoot + this.id + "/action";
this.save({}, { url: custom_url});
// POST /cats/{{ cat_id }}/action
// { action: "sleep" }
},
});
See here: Posting form data using .save() to pass url parameters.
You can also implement the sync method to use another URL if you always want to use a custom URL on update. See for example here: backbone.js use different urls for model save and fetch.
There are different approaches you can take to solve this, but IMO the cleanest is to override Backbone.sync to act the way you want it to act if it's universal to the server backend you're connecting to.
For instance, if you want every one of your models/collections to interact with a particular backend implementation, this approach makes a lot of sense.
This way you can leave the rest of the Collection (or Model) code as the Backbone default but it will work the way you want it to work.
For example:
// Store the default Backbone.sync so it can be referenced later
Backbone.vanillaSync = Backbone.sync;
// Most of this is just copy-pasted from the original Backbone.sync
Backbone.sync = function(method, model, options) {
var type = methodMap[method];
// Default options, unless specified.
_.defaults(options || (options = {}), {
emulateHTTP: Backbone.emulateHTTP,
emulateJSON: Backbone.emulateJSON
});
// Default JSON-request options.
var params = {type: type, dataType: 'json'};
// Ensure that we have a URL.
if (!options.url) {
params.url = _.result(model, 'url') || urlError();
}
// START ADD YOUR LOGIC HERE TO ADD THE /action
// Add the action to the url
params.url = params.url + '/' + options.action;
// Remove the action from the options array so it isn't passed on
delete options.action;
// END ADD YOUR LOGIC HERE TO ADD THE /action
// Ensure that we have the appropriate request data.
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
params.contentType = 'application/json';
params.data = JSON.stringify(options.attrs || model.toJSON(options));
}
// For older servers, emulate JSON by encoding the request into an HTML-form.
if (options.emulateJSON) {
params.contentType = 'application/x-www-form-urlencoded';
params.data = params.data ? {model: params.data} : {};
}
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
// And an `X-HTTP-Method-Override` header.
if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
params.type = 'POST';
if (options.emulateJSON) params.data._method = type;
var beforeSend = options.beforeSend;
options.beforeSend = function(xhr) {
xhr.setRequestHeader('X-HTTP-Method-Override', type);
if (beforeSend) return beforeSend.apply(this, arguments);
};
}
// Don't process data on a non-GET request.
if (params.type !== 'GET' && !options.emulateJSON) {
params.processData = false;
}
// If we're sending a `PATCH` request, and we're in an old Internet Explorer
// that still has ActiveX enabled by default, override jQuery to use that
// for XHR instead. Remove this line when jQuery supports `PATCH` on IE8.
if (params.type === 'PATCH' && window.ActiveXObject &&
!(window.external && window.external.msActiveXFilteringEnabled)) {
params.xhr = function() {
return new ActiveXObject("Microsoft.XMLHTTP");
};
}
// Make the request, allowing the user to override any Ajax options.
var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
model.trigger('request', model, xhr, options);
return xhr;
};
In the above example I assumed you had sent the action via the options array, if you actually wanted the static word /action you could just replace that block with:
// Add the action to the url
params.url = params.url + '/action';
This should give you the cleanest implementation while still keeping the rest of your code clean.
After going through the backbone documentation, it is evident that to send a delete request for a model we need to set the urlRoot and id. Another technique which came to my mind was to implement a sync for my model.
since my server uses POST for delete, i use emulateHTTP = true.
How can i acheive the above task so that the request url for my model will be of the form
http://myWerService.com/myresource/deleteMyModel?modelName="abc"
So, how can i set my id as modelName
What is the difference between the below two url patterns
http://myWerService.com/myresource/deleteMyModel?modelName="abc"
http://myWerService.com/myresource/deleteMyModel/abc
The reason being, i saw every example use the second url pattern and i have no idea about the distinction between the two.
I have the url set for this model for create request, how can i use a different url (i want to use the above specified url) for sending a delete request
I am not sure if this is exactly what you are asking, but maybe this example will help:
var MyModel = Backbone.Model.extend({
ID: 0,
Name: '',
... other attributes for the model ...
},
initialize: function (id) {
this.ID = id;
},
idAttribute: 'Name', // set the id attribute as the name attribute
// you can have different urls for different operations
methodToURL: {
'read': 'GetModel',
'update': 'UpdateModel',
'delete': 'DeleteModel'
},
// overwrite the sync function
sync: function (method, model, options) {
options = options || {};
options.url = model.methodToURL[method.toLowerCase()];
switch (method) {
case 'read':
case 'update':
case 'delete':
options.url += '/' + this.id;
break;
}
Backbone.sync(method, model, options);
}
});
I would like to change the URL generated when my entity calls destroy. Instead of writing an HTTP DELETE to /{Action}/{EntityID}, I would like to send /{Action}/{EntityID}/{SecondEntityID}.
item.destroy({
data: $.param({
playlistId: playlistId
}),
processData: true,
success: callback,
error: function (error) {
console.error(error);
}
});
I thought that something like this might work, but it doesn't seem to append on any additional parameters. Do I have to implement my own sync method in its entirety if I want to extend just destroys' URL?
You can override is through passing in a .url property in options when you call destroy. Since I assume you'd want to do this for every single call, you can do this:
var MyModel = Backbone.Model.extend({
destroy: function(options) {
// Override URL
options || (options = {});
// You can put whatever you need here,
options.url = 'http://www.awesome.com/destroy/' + this.get('id') + '/' + this.get('secondaryId');
// Call Model.destroy().
// We are reusing the existing functionality from Backbone.Model.destroy().
Backbone.Model.prototype.destroy.apply(this, arguments);
}
});
var m= new MyModel({ id: 123, secondaryId: 456 });
// Note: You need to set 'id' in order for destroy() call to be successful.
m.destroy({
sucess: function() { console.log('good'); },
error: function() { console.log('bad'); }
});
If you open up Firebug or Chrome Dev Tools, you should see an XHR/AJAX call was made to www.awesome.com.
Since you mentioned that you want to do this across ALL entities that you have, what you can do in that case is to create a BaseModel in your application, and have all your entities extend from it.
Anyway, hope this helps!
JSFiddle for this: http://jsfiddle.net/EwQaD/
I have some trouble appending a token to the backbone url query string and hope you guys could help me out here. Three things to know,
There is a rest api that expects a token with each request
An nginx backend that does auth, serves the backbone app + proxy req to the api under /api
i'm a new to javascript + backbone :/
The backbone app actually reads the token from a cookie and I need to append this to the request url everytime backbone makes a call. I see this can be done by overriding backbone sync. but it troubles me in a few different things. like, this is what I do
console.log('overriding backbone sync');
var key ="token";
Backbone.old_sync = Backbone.sync
Backbone.sync = function(method, model, options) {
if (method === 'read') {
if (!(model.url.indexOf('?key=') != -1)) {
model.url = model.url + '?key=' + key;
}
} else {
old_url = model.url();
if (!(old_url.indexOf('?key=') != -1)) {
model.url = function() {
return old_url + '?key=' + key;
}
}
}
Backbone.old_sync(method, model, options);
};
model.url was returning a function when its not a "read" method and didn't know how to handle it well and the other trouble is when a consecutive request is made, the token is added twice. I tried to remove it with that indexOf stuff with no luck.
Is there a better way to do this ?
I don't think you need to override sync at all:
var globalKey = 'key123';
var urlWithKey = function(url, key) {
return function() {
return url + "?key=" + key;
};
};
var MyModel = Backbone.Model.extend({
url: urlWithKey('/my/url/', globalKey)
});
If you now create an object and save it, a POST request to my/url/?key=key123 is sent.
I guess you could also override the url method if this is the behavior you need for all of your Backbone models.
A general note: in Backbone most parameters, such as url can be a function or a value. I don't know why in your example it was a function once and a value in another case, but you always must be able to handle both ways if you override some of the internal functions. If you look at Backbone's sourcecode you will see that they use getValue to access these parameters:
var getValue = function(object, prop) {
if (!(object && object[prop])) return null;
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
};
Update: Overloading the url method for all models could work like this:
var globalKey = 'key123';
(function() {
var baseUrl = Backbone.Model.prototype.url;
Backbone.Model.prototype.url = function() {
return this.baseUrl + "?key=" + globalKey;
};
})()
var MyModel = Backbone.Model.extend({
baseUrl: '/my/url/'
});
You could also leave the regular Backbone.Model as it is, and create your own base class. See http://documentcloud.github.com/backbone/#Model-extend for details.
Just set your URL like so:
url : function() {
return "/my/url" + this.key;
}
In your overridden .sync, you only need to set the key property.