To be quite honest I'm stuck trying to override Backbone's sync() method for a Model, I have the signature for the function in place, and it gets triggered correctly, but I don't know what to put in the function body in order for it to make a default call to DELETE but with extra arguments.
ie.
class Master.Models.Member extends Backbone.Model
urlRoot: '/api/members/'
sync: (method, model, options) ->
params = _.clone options
Backbone.sync method, model, params
I call it like this:
......
remove: ->
#model.destroy
collective_id: the_id
My intention there, is to pass the collective_id param you see there to the server. But even though it's inside the options hash for sync() and I clone it, It won't make it to the server!
How can I send that extra param to the server?
(As it is, the only thing that reaches the server is the Model's id)
Thanks in advance!
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
Params need to be sent in options.data, so try:
coffeescript
remove: () ->
#model.destroy
data: JSON.stringify
collective_id: the_id,
contentType: 'application/json'
javascript
remove: function() {
return this.model.destroy({
data: JSON.stringify({
collective_id: the_id
}),
contentType: 'application/json'
});
}
Related
How do I set a property to my backbone model and keep it "globally" in every instance of the model.
var myModel = new MyModel();
myModel.set('test', 1234);
And now, in another view, I instantiate the model again:
var myModel = new MyModel();
myModel.get('test')
Should return 1234, not null.
Any ideas on how to do it?
I need this because I fetch a token from my rest api and I want to pass it to every request.
So, after I do myModel.fetch(), I would like to add the value of the token, bind it to the model, so I'll have it in all my future requests.
Edit, here is the actual code:
var proxy = new Proxy(),
target = '?csurl=http://laravelws.dev/account/token';
proxy.urlRoot = proxy.urlRoot + target;
proxy.fetch(
{
dataType: 'json',
success : function (resp) {
that.token = resp.get('_token');
proxy.set('_token', that.token);
},
error : function (model, xhr, options) {
console.log('router.js - could not fetch the CSRF token');
}
}
);
I want to atach the token to the proxy model, and make the token available for all the future instances of the model.
This code goes into the router's initialize function - so this get request will be made every time someone lands on my app, or refreshes the page.
Use defaults:
var MyModel = Backbone.Model.extend({
defaults: {
test: 1234
}
});
Edit:
To achieve what you're looking for, you could set the value on the prototype:
Proxy.prototype.token = "XXX";
var proxy = new Proxy();
console.log(proxy.token);
Just be aware that the token value will be shared (if you change the value, it changes for all proxy instances).
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'm trying to understand how a portion of backbone.js works. I have to fetch a collection of models once the app begins. I need to wait until fetch is complete to render each view.
I'm not 100% sure the best approach to take in this instance.
var AppRouter = Backbone.Router.extend({
routes: {
"": "home",
"customer/:id": "customer"
},
home: function () {
console.log("Home");
},
customer: function (id) {
if (this.custromers == null)
this.init();
var customer = this.customers.at(2); //This is undefined until fetch is complete. Log always says undefined.
console.log(customer);
},
init: function () {
console.log("init");
this.customers = new CustomerCollection();
this.customers.fetch({
success: function () {
console.log("success");
// I need to be able to render view on success.
}
});
console.log(this.customers);
}
});
The method I use is the jQuery complete callback like this:
var self = this;
this.model.fetch().done(function(){
self.render();
});
This was recommended in a Backbone bug report. Although the bug report recommends using complete, that callback method has since been deprecated in favor of done.
You can also do this with jquery 1.5+
$.when(something1.fetch(), something2.fetch()...all your fetches).then(function() {
initialize your views here
});
You can send your own options.success to the collections fetch method which runs only when the fetch is complete
EDIT (super late!)
From the backbone source (starting line 624 in 0.9.1)
fetch: function(options) {
options = options ? _.clone(options) : {};
if (options.parse === undefined) options.parse = true;
var collection = this;
var success = options.success;
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp);
};
Note the second to last line. If you've passed in a function in the options object as the success key it will call it after the collection has been parsed into models and added to the collection.
So, if you do:
this.collection.fetch({success: this.do_something});
(assuming the initialize method is binding this.do_something to this...), it will call that method AFTER the whole shebang, allowing you trigger actions to occur immediately following fetch/parse/attach
Another useful way might be to bootstrap in the data directly on page load. This if from the
FAQ:
Loading Bootstrapped Models
When your app first loads, it's common to have a set of initial models that you know you're going to need, in order to render the page. Instead of firing an extra AJAX request to fetch them, a nicer pattern is to have their data already bootstrapped into the page. You can then use reset to populate your collections with the initial data. At DocumentCloud, in the ERB template for the workspace, we do something along these lines:
<script>
var Accounts = new Backbone.Collection;
Accounts.reset(<%= #accounts.to_json %>);
var Projects = new Backbone.Collection;
Projects.reset(<%= #projects.to_json(:collaborators => true) %>);
</script>
Another option is to add the following inside of your collections initialize method:
this.listenTo(this.collection, 'change add remove update', this.render);
This will fire off the render method whenever the fetch is complete and/or the collection is updated programmatically.
You Can Use on and Off Methods
if you want to add trigger method like suppose if you want on success you want to call render method so please follow below example.
_this.companyList.on("reset", _this.render, _this);
_this.companyList.fetchCompanyList({firstIndex: 1, maxResult: 10}, _this.options);
in Model js please use like
fetchCompanyList: function(data, options) {
UIUtils.showWait();
var collection = this;
var condition = "firstIndex=" + data.firstIndex + "&maxResult=" + data.maxResult;
if (notBlank(options)) {
if (notBlank(options.status)) {
condition += "&status=" + options.status;
}
}
$.ajax({
url: "webservices/company/list?" + condition,
type: 'GET',
dataType: 'json',
success: function(objModel, response) {
UIUtils.hideWait();
collection.reset(objModel);
if (notBlank(options) && notBlank(options.triggerEvent)) {
_this.trigger(options.triggerEvent, _this);
}
}
});
}