Backbone PUT request to cross domain is not appending id to URL - javascript

I have the following backbone model.
var aModel = Backbone.Model.extend({
url: 'http://api.site1.com/list
});
modelObj = new aModel();
Domain of my application is product.site1.com, so all the request made to server by my application are cross domain requests.
All my backbone requests to the API needs common header, so I have written following backbone sync method
var sync = Backbone.sync;
Backbone.sync = function(method, model, options) {
options.beforeSend = function (xhr) {
xhr.setRequestHeader('key1', 'ABCD');
xhr.setRequestHeader('key2', 'EFGH');
};
sync(method, model, options);
};
When I execute fetch method like below
modelObj.fetch();
My application makes a OPTIONS request to the api.site1.com/list
On success of that request it makes a GET request to api.site1.com/list
This is the same case for POST as well.
When I perform
modelObj.save({name: "abc"}) before fetch, my app sends OPTIONS request to api.site1.com/list, on success it performs POST request.
Whereas when I do it for update i.e PUT,
I expect backbone appends id to the url, like api.site1.com/list/1 and performs OPTIONS request
But when I check my dev tools, the OPTIONS request is made to api.site1.com/list. On success, it makes PUT request to api.site1.com/list instead of api.site1.com/list/1
Why is this happening and how to handle this case?

By setting aModel.url, you force the url used in the requests. Set aModel.urlRoot instead :
urlRoot model.urlRoot or model.urlRoot()
Specify a urlRoot if you're
using a model outside of a collection, to enable the default url
function to generate URLs based on the model id.
Try
var aModel = Backbone.Model.extend({
urlRoot: 'http://api.site1.com/list'
});

Related

How to add custom HTTP header to all AJAX calls in BackboneJS models?

I'm working on a backbonejs app which utilises a RESTful API for all the model data. I need to be able to pass a customer HTTP header for authentication, which includes a localStorage item, to all ajax calls made through the model's fetch(), save(), etc.
I know how to pass the header for each one individuall, but I can't figure out a way to do it for all calls without copying/pasting the same code over and over. I've read some things about overriding the backbonejs sync, but I can't quite understand how I could do that.
I've decided to create a 'BaseModel' which all my other models would extend from, thinking this might be an easy way to do this (it's made it easy for my api url root).
Here is the basics of my models:
BaseModel:
var Backbone = require("backbone");
// Declare our options we'll use to extend the base view
var modelOptions = {
apiUrl: 'api.php/v1.1',
};
// Export our extended view
module.exports = Backbone.Model.extend(modelOptions);
Sample Model:
var BaseModel = require("./baseModel");
// Declare our options we'll use to extend the base view
var modelOptions = {
initialize: function() {
this.url = this.apiUrl+'/Cars';
},
};
// Export our extended view
module.exports = BaseModel.extend(modelOptions);
$.ajaxSetup({
headers: { 'custom-header': ' value' }
});
Also, you can add a header (or set of headers) to every request then use the beforeSend hook with $.ajaxSetup():
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader('custom-header', 'value');
}
});
I believe you can set the global AJAX header through jQuery like:
$.ajaxSetup({
headers: yourCustomHeader
});
or
Backbone.$.ajaxSetup({
headers: yourCustomHeader
});

How to set headers globally for specific url after get from login service using angularjs?

I have this login service:
var url = GetHostUrl.cvmUrl+'/WSUser/users/login';
$http({
method : 'POST',
url : url,
data : $.param({
username:$scope.user_name,
password:$scope.pass,
domain: GetHostUrl.cvmServerUpdated
}),
headers : {'Content-Type': 'application/x-www-form-urlencoded'}
})
.success(function(data){
$localstorage.authtoken = data.authtoken;
}
Have to pass $localstorage.authtoken in headers globally for the specific URL.
There is a feature in AngularJs to intercept all incoming requests and responses, these are hooks that can be triggered before you make a http request or when you receive one. you can add the Auth header to the request and your request should be successful.
more info on Angular Interceptors
Create a Service for making the HTTP calls and use that service to get the $http object.
you can manipulate the request at a single place according to your need
your service code will be something like this
HTTPService={}
HTTPService.method="";
HTTPService.url="";
HTTPService.headers={}
//modify the headers over here
HTTPService.call=function(){
return $http({
method : HTTPService.method,
url : HTTPService.url,
)}
}
return HTTPService;
and use this service's call method to make call across your application.
I can also post the exact same code if this code is unclear.
I would suggest you to create on you own.
So Your Login Service can use HTTPService Service to make http call so that method body is at one place.
You can override the values for the specific URL as required

Backbone.js won't post to the specified URL

I have a Backbone.js application running with this generator https://github.com/yeoman/generator-backbone. I need to make a POST request to a service running on a different server and domain. On my model I have the following:
urlRoot: 'https://custom.com/',
saveData: function(params) {
this.set(params);
if(this.isValid()) {
this.save(null, {
url: 'api/v1/data',
success: function(model, response, options) {
// success
},
error: function(model, response, options) {
// error
}
});
}
}
When I try posting the data, it posts the data to http://localhost:9000/api/v1/data. Any idea why Backbone chooses to POST to the server it is runnning on, instead of the settings I supplied (https://custom.com/api/v1/data)?
On my server, I'm running Restify on top of NodeJS (I'm allowing origin from any client for testing purposes). Here's how I setup CORS:
// Setup CORS
api.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
return next();
});
I doubt there is a problem with the server, since it isn't even being reached by the client because somehow the specified URL automatically maps to http://localhost:9000/api/v1/data instead of https://custom.com/api/v1/data.
UPDATE: If i do it "manually" with jQuery (without using Backbone's API), it works:
saveData: function() {
$.ajax({
type: 'POST',
data: this.toJSON(),
url: 'https://custom.com/api/v1/data'
})
.success(function() {
// success
})
.fail(function() {
// fail
});
}
Is there anyway to do it using Backbone's API?
Passing a url as an option to Backbone.Model.prototype.save will circumvent the url generation logic (i.e. urlRoot) and assume the url option is the full path (including the domain if not POSTing to the current domain).
You should instead set the urlRoot to the path of the entity (or whatever your backend platform may call it), so in your case it should be:
var MyModel = Backbone.Model.extend({
urlRoot: 'https://custom.com/api/v1/data'
});
var myModel = new MyModel();
myModel.save(); // POST https://custom.com/api/v1/data
// Assuming that returns an id of '123';
myModel.save(); // PUT https://custom.com/api/v1/data/123
myModel.destroy(); // DELETE https://custom.com/api/v1/data/123
If your server isn't set up to handle Backbone style RESTful endpoints then you probably just want to override Backbone.Model.prototype.url instead of urlRoot.
If you only wanna test calls to the server, you can download a chrome extension. It will allow you to make calls to a different domain. Here : https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi
Post request to a different domain will not work from browser. Better way to handle this is you can set some redirect rules in server.

How to get current (latest) JSON get request URL?

I have a JQGrid and search filters and loadOnce=false. I search records in grid and i am able to see this json GET type in firebug.
http://localhost:8080/myapp/items/listGrid?ticketId=&_search=true&nd=1393573713370&rows=20&page=1&sidx=id&sord=asc&filters=%7B%22groupOp%22%3A%22AND%22%2C%22rules%22%3A%5B%7B%22field%22%3A%22summary%22%2C%22op%22%3A%22cn%22%2C%22data%22%3A%22Test%22%7D%5D%7D
I want to get this JSON request URL after this request completed I have to add same params to my pdf link.
I try
document.URL
window.location.pathname
jQuery("#itemsGrid").jqGrid('getGridParam', 'url');
output
"localhost:8080/myapp/items/list"
"/myapp/items/list"
"/myapp/items/listGrid?ticketId="
How can I get the same URL?
If it is an ajax call you could decorate the XMLHttpRequest.prototype.open function:
var calls = [];
XMLHttpRequest.prototype._originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
if(method === 'GET') // Only add GET requests
calls.push(url);
this._originalOpen(method, url, async, user, password);
}
Now you can access the last get URL as follows
var lastUrl = calls[calls.length - 1]
Adopted this code from this SO question.
If you need to append the URL with additional parameters you should use postData parameter of jqGrid. I recommend to define parameters as functions (like in the answer). If you need to send user credentials information then you should better place it in custom HTTP headers instead of URL which can be seen by everybody. Usage of loadBeforeSend callback would be the best choice in the case. See the answer and this one for the code example. One more option which you have is the usage of beforeRequest callback to modify url parameter of jqGrid directly before the usage (see the answer)
The usage of https: instead of http: in the final solution would be recommend too.

Backbone.js with express.js - sync using only POST and GET

My app is Backbone.js for client-side, Express.js for back-end.
I have problems with syncing with all parts of my API, using the backbone model and collection(they use urlRoot: "/users").
I'm allowed to use only GET or POST, no PUT or DELETE.
*I'm not allowed to use more models*
Not allowed to use jQuery ajax
My API
add new user:
I need to make a POST to /users with JSON of new data. So I did it just fine with - this.model.save({new data...})
list all users:
My API for that, responses to GET /users, with the right handler - so, this.collection.fetch() - works fine.
Log-In:
My API accepts POST to /users/login for that. How can I add a function "logIn" to my model, that will use custom sync/pass options.url to sync - or any other way - that will POST to /users/login ?
Log-Out:
API accepts POST to /users/logout - how to send this request using my backbone model ?
User By ID:
Same question here for GET /users/:id
Update user:
POST /users/:id - same question again.
--- So actually, the question in short is ---
What is the best way (or the most "right"), to implement methods of a backbone model, that are similar to "model.save()" - but just POST/GET to a bit different path then urlRoot ?
You probably have a couple options here. One would be structuring your models in a way that supports the urls you want. For instance, have a User model and a Session model that deal with updating the user and managing the logged in state separately.
The other thing you should probably do is to use the url method in your models.
Something like this in your User model. (Note: using urlRoot instead of url here is identical, but this is the correct approach for anything more complicated that is needed in the url)
url : function() {
var base = "/users/";
if(this.isNew()) {
return base;
} else {
return base + this.get("id");
}
}
You could extend this same concept to work in your Session model for handling logout vs login based on if the Session is new or not.
Update:
If you must use the same model, the best thing would be to totally bypass the Backbone.sync method and write a custom AJAX call with success/error handlers that know how to clean things up.
login : function() {
$.post("/users/login", {success: function (response) {
// Update user as needed
}, error: function (xhr, response) {
// Handle errors
}
}
}

Categories