Encoding JSON to URL params for Backbone.Model.save (patch = true) - javascript

I'm having some trouble correctly forming a Backbone.Model.save call. The web service I'm calling consumes URL parameters, but what I have in Javascript is an object of changed fields. For example, I have the object {foo: 'bar', yar: 'har'}, and I want Backbone.Model.save to send a patch request to a URL like http://server/path/to/service?foo=bar&yar=har
Sounds simple, right? It's giving me a bunch of trouble anyway. Here's what I've got so far (which doesn't work; I have success/error callbacks, too, but I don't think those are important for the question):
object =
foo: 'bar',
yar: 'har'
model.save object,
patch: true
I've tried some other options, too:
model.save object,
patch: true
emulateJSON: true
This set contentType to "application/x-www-form-urlencoded", which is good, but the data sent in the ajax request by Backbone.sync was: {model: "{"foo": "bar", "yar": "har"}". The service got that and has no idea what to do with a "model" property.
model.save object,
patch: true
contentType: "application/x-www-form-urlencoded"
This just codes object as a string and stuffs that into options.data. Again, the service doesn't know what to do with it.
Any other ideas on how I can get this to conform to my service's spec? I can make the ajax call myself and update the model (and the collection it belongs to) myself, but I'd really rather not do that. An ajax request that works for me is:
$.ajax
url: "http://server/path/to/service"
type: "PATCH"
data: object
Update: The reason my two earlier options didn't work is clear in Backbone.js itself:
// 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} : {};
}
Looking at this, I thought maybe if I stuffed the object into object into options.data and sent in empty attributes, perhaps it'd work:
model.save {},
patch: true
data: object
Apparently this tried to PATCH an option "[object Object]". I guess it did a stringify of the object... somewhere... but this may be close to the right answer?

It looks like what I was looking for is the processData option to jQuery.ajax. Backbone.sync does the following by default:
// Don't process data on a non-GET request.
if (params.type !== 'GET' && !options.emulateJSON) {
params.processData = false;
}
Thus, it wasn't processing the object into URL parameters for me. (jQuery API)
So, a working bit of code would be:
model.save {},
patch: true
data: object
processData: true
In truth, I may not be using Backbone.Model correctly overall... but, at least it's working. :P

Related

HTTP POST Request with JSON payload in Google Apps Script

I'm having trouble getting Google App Script's UrlFetchApp class to correctly format a JSON payload in a HTTP request.
Here's how I'm formatting the payload:
var data = {
'RequestData': {
'ItemNumber': '1',
'StockItemId': '2',
'ImageUrl': 'www.example.com'
}
};
var payload = JSON.stringify(data);
Surely enough, Logger.log(payload) returns:
{"RequestData":{"ItemNumber":"1","StockItemId":"2","ImageUrl":"www.example.com"}}
However, when I integrate this with the actual request:
var url = 'https://example.com/api/method';
var options = {
'method': 'post',
'contentType':'application/json',
'payload': payload,
'muteHttpExceptions': true
};
and examine Logger.log(UrlFetchApp.getRequest(url, options).toSource()), I get this:
({method:"post",
payload:"{\"RequestData\":{\"ItemNumber\":\"1\",\"StockItemId\":\"2\",\"ImageUrl\":\"www.example.com\"}}",
followRedirects:true,
validateHttpsCertificates:true,
useIntranet:false, contentType:"application/json",
url:"https://example.com/api/method"})
i.e. all the quotes are escaped, as if JSON.stringify is being called twice on the payload.
I thought GAS might be calling an equivalent of JSON.stringify on the payload behind the scenes because contentType is specified as "application/json" but if I pass the data raw without stringifying it, I get:
({method:"post",
payload:"RequestData=%7BItemNumber%3D1,+StockItemId%3D2,+ImageUrl%3Dwww.example.com%7D",
followRedirects:true,
validateHttpsCertificates:true,
useIntranet:false, contentType:"application/json",
url:"https://example.com/api/method"})
which is weird and (I'm pretty sure) an invalid JSON string. Either way, the request fails (400 Bad Request) and I'm pretty sure it's because I can't get the JSON across properly.
All the docs and examples I can find seem to suggest that JSON.stringify(payload) is the way to go but I'm clearly having issues...
Have also had the thought that this might have something to do with the implementation of the .toSource() method and that my problem might lie elsewhere but I have no way of knowing / otherwise being able to check the request body I'm sending.
Any insight would be much appreciated!

Angular $http attach data as object

I'm trying to get list of tracks from soundcloud API with angularjs.
The parameters i'm trying to send are:
1) client_id (string)
2) duration (object with two properties).
Here's the code:
var CLIENT_ID = 'a81f01ef5d0036415431e8be76c8db0e';
var TRACKS_URL = 'https://api.soundcloud.com/tracks.json';
var app = angular.module('soundcloud', []);
app.controller('tracksController', function ($scope, $http) {
$http({
url: 'https://api.soundcloud.com/tracks.json',
method: 'GET',
data: {
client_id: CLIENT_ID,
duration: { // in milliseconds
from: 300000,
to: 400000
}
}
})
.success(function (data) {
$scope.trackList = data;
})
.error(function () { alert('error'); });
});
These parameters aren't recognized at all when I check the request in the broweser's debugger.
I tried to use 'params' instead of 'data', but this way it turns the 'duration' object to json --> then I get status 500 in response.
When I only send the client_id in params, it works fine because there's no object, only string.
jQuery's ajax method works fine: https://jsfiddle.net/oacwz1up/3/
What should I do ? How can I send the parameters normally ?
Help please! Thanks!
This happens because Angular serializes the request parameters differently than JQuery.
Angular 1.4 will address this problem introducing a paramSerializer property on the $http config object (and on $httpProvider.defaults). This can be used for arbitrarily serializing the requests parameters (for all or a particular request).
Note, that the feature is available only since v1.4.0-rc.0.
Besides the default serializer (which converts objects to JSON strings), there is an additional built-in serializer that emulates JQuery's param(): $httpParamSerializerJQLike.
You can use it like this:
$http.get(<API_URL>, {
params: {...},
paramSerializer: '$httpParamSerializerJQLike'
});
See, also, this short demo.
If you are using an older version of Angular, you could do one of the following:
Construct the whole URL (including the query string) yourself (possibly using an $http request interceptor to automatically apply this to all requests).
Keep the params in a flat format that will result in Angular's serializing them as expected:
var REQ_PARAMS = {
client_id: 'a81f01ef5d0036415431e8be76c8db0e',
'duration[from]': 200000,
'duration[to]': 205000
};
If you look at $http documentation, on request it applies a default transformation which is $http.defaults.transformRequest and as described will do the following (as you saw) :
If the data property of the request configuration object contains an object, serialize it into JSON format.
What you need to do is override this function by specifying your own transformRequest object.
function appendTransform(defaults, transform) {
// We can't guarantee that the default transformation is an array
defaults = angular.isArray(defaults) ? defaults : [defaults];
// Append the new transformation to the defaults
return defaults.concat(transform);
}
$http({
url: '...',
method: 'GET',
transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
return doTransform(value);
})
});
You need to find a way to get the same syntax as jQuery provide which is :
https://api.soundcloud.com/tracks.json?client_id=a81f01ef5d0036415431e8be76c8db0e&duration[from]=200000&duration[to]=205000
Use a condition is is an object and generate manually your String. Should not be difficult.
that's a strange API - I don't know why they don't do something like "duration_from" rather than requiring duration[from] - as suggested you could certainly transform the request, but if this is just a one off you could also try simply hard-coding it using url escaped values for [ and ]:
var dataToSend = {
client_id: 'a81f01ef5d0036415431e8be76c8db0e',
'duration%5Bfrom%5D': 200000,
'duration%5Bto%5D': 205000
};
$http.get('http://api.soundcloud.com/tracks.json', {params:dataToSend});
The params property is way to transform query in restful way. Just transform data in dataToSend object, and it will work.
This is a URL that should be created:
https://api.soundcloud.com/tracks.json?client_id=a81f01ef5d0036415431e8be76c8db0e&duration%5Bfrom%5D=200000&duration%5Bto%5D=205000

transform request data with resource service in angular

I'm trying to send just the array of my data to the server. I found this post discussing how to send an array, but I can't get it to work.
AngularJS: ngResource and array of object as params to URL
The problem I am having is my resource gets sent back to me as JSON like this
{
Results: []
}
So when I ask for my resources,
var collaboratorResource = api.CollaboratorList.get({Id: Id });
but then, if I try something like
collaboratorResource.$save($scope.collaborators);
When I look at firebug, it shows that my data is being sent as
{
Results: []
}
when in reality, I don't want to send the data as an object with the array as a Results property. I want to send it just as an array []. I need to do that since the api is legacy and expects that.
I've been trying to see if transformRequest works, like if I did
collaboratorResource.$save({data: $scope.collaborators, transformRequest: function (data, headers) { return data.results; }});
collaboratorResource.$save({}, $scope.collaborators);
collaboratorResource.$save($scope.collaborators);
But that doesn't seem to work either. Is this even possible? As an aside, if I use $http like this, it works:
$http({ method: "POST", data: $scope.collaborators, url: collaboratorUrl });
I'm just not sure how to use the $resource service properly since I'd prefer to wrap everything in $resource if possible and not have a hybrid of both if possible. Thanks.

should I return the entire record again as a response from server to a POST when I do $update on an angular resource?

In my factory
return $resource('rest/records/:id', {}, {
query: {
method: 'GET',
isArray: true,
// RestSQL has an extra struct around the array
transformResponse: function(data) {
return angular.fromJson(data).records;
}
},
create: { method: 'POST' },
update: { method: 'PUT', params: {id: '#id'},
});
Which is used by a service which is in turn used by a controller.
I get the correct record in the controller.
Then when I edit and want to save the record
I do record.$update();
my rest POST is successful as I get a valid response from server.
But the response from server is just some kind of success message e.g. "{ "number": 2 }".
Now I have an angular template which uses this records properties e.g. {{record.image}}
But as soon as I do record.$update();
records properties disapper and record now simply looks like
record = {"number" : 2}
So should I return the entire record as a response to the POST action or should I update in some other fashion so as to the record doesnt get its properties overwritten by the response from server?
A temporary way of getting around this of course is
$scope.temp = jQuery.extend(true, {}, $scope.record);
$scope.temp.$update();
I am returning the updated object on POST's, PUT's etc. (see also this: Should a RESTful 'PUT' operation return something) all the time. So,
no problem with always returning the updated object in the response
don't use tricks and more code to sove a problem that can be solved by applying a very simple solution (in this case, returning the updated object). I love those fixes: Simple solution, problem solved and code removed instead of adding things here and padding things there etc., making code unreadable und unnecessarily complex.

Add access_token in backbone.js

In my REST server, it's requiring the access_token to be present in every request. i.e. in POSTing data, access_token needs to be submitted together with the attributes.
How do I configure backbone.js to add access_token to every GET, PUT, POST and DELETE request?
Thanks.
Okay, I think I found a way how to do it in jQuery.
$.ajaxSetup (
{
data: { access_token: 'my_access_token' }
}
);
Backbone uses jQuery/Zepto for AJAX requests, so you can use the functionality available in those libraries.
To add custom headers to all XHR calls made by jQuery, you can use the jQuery.ajaxSend event, which is triggered before every ajax request, and modify the jqXHR it receives as an argument.
Edit based on OP's comments:
Probably the simplest way to modify the sent data is to override the Backbone.sync function. You could wrap the native implementation, and add the required property there:
var nativeSync = Backbone.sync;
Backbone.sync = function (method, model, options) {
//for POST/PUT requests, add access token to the request
if(model && (method === 'create' || method === 'update')) {
var data = _.extend(model.toJSON(), {
access_token: 'token'
});
options.data = JSON.stringify(data);
}
//call the native Backbone.sync implementation
nativeSync(method, model, options);
};

Categories

Resources