Backbone.js fetch with parameters - javascript

Following the documentation, I did:
var collection = new Backbone.Collection.extend({
model: ItemModel,
url: '/Items'
})
collection.fetch({ data: { page: 1} });
the url turned out to be: http://localhost:1273/Items?[object%20Object]
I was expecting something like http://localhost:1273/Items?page=1
So how do I pass params in the fetch method?

changing:
collection.fetch({ data: { page: 1} });
to:
collection.fetch({ data: $.param({ page: 1}) });
So with out over doing it, this is called with your {data: {page:1}} object as options
Backbone.sync = function(method, model, options) {
var type = methodMap[method];
// Default JSON-request options.
var params = _.extend({
type: type,
dataType: 'json',
processData: false
}, options);
// Ensure that we have a URL.
if (!params.url) {
params.url = getUrl(model) || urlError();
}
// Ensure that we have the appropriate request data.
if (!params.data && model && (method == 'create' || method == 'update')) {
params.contentType = 'application/json';
params.data = JSON.stringify(model.toJSON());
}
// For older servers, emulate JSON by encoding the request into an HTML-form.
if (Backbone.emulateJSON) {
params.contentType = 'application/x-www-form-urlencoded';
params.processData = true;
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 (Backbone.emulateHTTP) {
if (type === 'PUT' || type === 'DELETE') {
if (Backbone.emulateJSON) params.data._method = type;
params.type = 'POST';
params.beforeSend = function(xhr) {
xhr.setRequestHeader('X-HTTP-Method-Override', type);
};
}
}
// Make the request.
return $.ajax(params);
};
So it sends the 'data' to jQuery.ajax which will do its best to append whatever params.data is to the URL.

You can also set processData to true:
collection.fetch({
data: { page: 1 },
processData: true
});
Jquery will auto process data object into param string,
but in Backbone.sync function,
Backbone turn the processData off because Backbone will use other method to process data
in POST,UPDATE...
in Backbone source:
if (params.type !== 'GET' && !Backbone.emulateJSON) {
params.processData = false;
}

Another example if you are using Titanium Alloy:
collection.fetch({
data: {
where : JSON.stringify({
page: 1
})
}
});

try {
// THIS for POST+JSON
options.contentType = 'application/json';
options.type = 'POST';
options.data = JSON.stringify(options.data);
// OR THIS for GET+URL-encoded
//options.data = $.param(_.clone(options.data));
console.log('.fetch options = ', options);
collection.fetch(options);
} catch (excp) {
alert(excp);
}

Related

AngularJS's $htttp not passing form data into the POST request like jQuery's $ajax

I have a problem converting jQuery's $.ajax requests to Angularjs's $http requests (Angularjs v1.8.x).
I have a form with one text input and one image/file input an I am sending requests via form's submit button with ng-click="uploadImage($e)".
I think the problem lies in Angularjs not sending filled form data together with the request.
My $.ajax requests are working fine, but for my $http requests, there are no Form Data inside browser's Developer Console - see the screenshots below. Responses to my $http requests are 200, but returned data is not correct because form data is never sent with the request.
For both $.ajax and $http I prepare form data with this function:
/**
* #var form is an HTML element
*/
function prepData(form) {
var fdata = new FormData();
for(var i = 0; i < form.length; i++) {
var NN = form[i].nodeName.toUpperCase();
var name = form[i].name;
if(typeof name != "undefined" && name != "") {
if(NN == "INPUT") {
var NT = form[i].type.toUpperCase();
if(NT == "FILE") {
for(var j = 0; j < form[i].files.length; j++) {
fdata.append(name, form[i].files[j], form[i].files[j].name);
}
} else if(NT == "CHECKBOX" || NT == "RADIO") {
if(form[i].checked == true) {
fdata.append(name, form[i].value);
}
} else {
fdata.append(name, form[i].value);
}
} else {
fdata.append(name, form[i].value);
}
}
}
return fdata;
}
$.ajax request options:
var ajaxOptions = {
type : "POST",
async : true,
url : getFullUrl(),
data : prepData(form),
dataType : self.dataType,
beforeSend: function (request) {
request.setRequestHeader("translate", User.settings.lang);
request.setRequestHeader("userid", User.logged);
}
};
$http request options:
var httpOptions = {
type : "POST",
async : true,
url : getFullUrl(),
data : prepData(form),
dataType : self.dataType,
headers : {
"translate": User.settings.lang,
"userid": User.logged,
"X-Requested-With": "XMLHttpRequest",
"Content-Type": undefined,
"transformRequest": []
},
eventHandlers: {
"readystatechange": function(event) {
this.xhr = event.currentTarget;
}
}
};
When I compare $.ajax and $http in browser's developer tools I can see that their response-headers are identical, but request-headers differs in these properties (see the screenshots below):
Accept
Content-Type
SCREENSHOT 1 - $.ajax() (jQuery):
SCREENSHOT 2 - $http() (AngularJS v1.8.x):
The equivalent to $.ajax's type: ... is $http's method: ....
In general, an $http request should look like this:
var httpOptions = {
method : "POST",
// ...
};
$http(httpOptions).then(function(res) {
// ...
}, function(res) {
// ...
});

callback in jquery ajax not working when using jquery ajax cache code below

Below is my code and issue is with cache code is not working properly if any ajax call has callback in success.
var localCache = {
/**
* timeout for cache in millis
* #type {number}
*/
timeout: 30000,
/**
* #type {{_: number, data: {}}}
**/
data: {},
remove: function (url) {
delete localCache.data[url];
},
exist: function (url) {
return !!localCache.data[url] && ((new Date().getTime() - localCache.data[url]._) < localCache.timeout);
},
get: function (url) {
console.log('Getting in cache for url' + url);
return localCache.data[url].data;
},
set: function (url, cachedData, callback) {
localCache.remove(url);
localCache.data[url] = {
_: new Date().getTime(),
data: cachedData
};
if ($.isFunction(callback)) callback(cachedData);
}
};
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
if (options.cache) {
var complete = originalOptions.complete || $.noop,
url = originalOptions.url;
//remove jQuery cache as we have our own localCache
options.cache = false;
options.beforeSend = function () {
if (localCache.exist(url)) {
complete(localCache.get(url));
return false;
}
return true;
};
options.complete = function (data, textStatus) {
localCache.set(url, data, complete);
};
}
});
function handleAjaxRequests(url, parameters, headers, method, successHandler, options, errorHandler) {
if (typeof (method) === 'undefined') {
method = "GET";
}
if (typeof (headers) === 'undefined') {
headers = "";
}
if (typeof (parameters) === 'undefined') {
parameters = "";
}
successHandler = typeof (successHandler) === 'undefined' ? function (data) {} : successHandler;
errorHandler = typeof (errorHandler) === 'undefined' ? function (data) {} : errorHandler;
return $.ajax({
method: method.toUpperCase(),
url: url,
// async: false,
data: parameters,
headers: headers,
success: function (data) {
console.log('hi');
successHandler(data, options);
console.log('bye');
},
error: function (data) {
$('.loader').hide();
errorHandler(data);
},
});
}
As per the above code after successfully run ajax successHandler(data, options);function should be the trigger but it not due to above cache handler code. I have no idea why this is not working. If I write simple something rather than callback function it is working. Same issue with datatable Ajax callbacks.
I have to use above cache handler at global level in my project doesn't matter ajax request is from datatable or from any other source.
Above cache code is from here https://stackoverflow.com/a/17104536/2733203
As discussed in the chatroom I've made some changes in your code :
var localCache = {
/**
* timeout for cache in millis
* #type {number}
*/
timeout: 30000,
/**
* #type {{_: number, data: {}}}
**/
data: {},
remove: function(url) {
delete localCache.data[url];
},
exist: function(url) {
return !!localCache.data[url] && ((new Date().getTime() - localCache.data[url]._) < localCache.timeout);
},
get: function(url) {
console.log('Getting in cache for url ' + url);
return localCache.data[url].data;
},
set: function(url, cachedData, callback) {
localCache.remove(url);
localCache.data[url] = {
_: new Date().getTime(),
data: cachedData
};
console.debug('caching data for '+url, cachedData);
if ($.isFunction(callback)) callback(cachedData);
}
};
$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
if (options.cache) {
var complete = originalOptions.complete || $.noop,
url = originalOptions.url;
//remove jQuery cache as we have our own localCache
options.cache = false;
options.beforeSend = function() {
if (localCache.exist(url)) {
console.log('using cache, NO QUERY');
complete(localCache.get(url));
return false;
}
console.log('sending query');
return true;
};
options.complete = function(data, textStatus) {
localCache.set(url, data, complete);
};
}
});
function handleAjaxRequests(url, parameters, headers, method, successHandler, options, errorHandler) {
method = method || "GET";
headers = headers || {};
parameters = parameters || {};
return $.ajax({
method: method.toUpperCase(),
url: url,
cache: true,
// async: false,
data: parameters,
headers: headers,
success: successHandler,
error: errorHandler,
});
}
handleAjaxRequests('/echo/json/', {p1: 'hey'}, null, 'POST', function(data){console.log('first success without cache', data);});
setTimeout(function(){
handleAjaxRequests('/echo/json/', {p1: 'hey'}, null, 'POST', function(data){console.log('success! with cache hopefully', data);});
}, 2000);
Fiddle here
added some logs in the localCache methods to see what's happening. Cache is never used so I've added the missing cache:true option
Added some logs inside beforeSend method to monitor the toggle between cache and query. Everything works fine.
Cleaned up the arguments null checks and removed empty function(){} (use $.noop() instead btw.
Now the core of your issue. The callbacks errorHandler and successHandler are arguments. $.ajax is asynchronous! it means at some point of the execution, right after this call is made, you won't be sure if the variable has the same value. Easiest solution is to just reference the function directly and let jQuery do the scope management. Hardest solution would be to give these functions to the context option in ajax settings which I don't recommend.
Now, the solution you use allows you to directly call $.ajax without a wrapper method. Why don't you use it directly? simpler and less prone to errors
EDIT: I'm really not fond of context so there is another alternative.
function handleAjaxRequests(url, parameters, headers, method, successHandler, options, errorHandler) {
method = method || "GET";
headers = headers || {};
parameters = parameters || {};
return $.ajax({
method: method.toUpperCase(),
url: url,
cache: true,
// async: false,
data: parameters,
headers: headers,
success: (function(handler, opt) {
return function( /*Anything*/ data, /*String*/ textStatus, /*jqXHR*/ jqXHR) {
console.log('hi');
handler(data, opt);
console.log('bye');
};
})(successHandler, options),
error: (function(handler, opt) {
return function( /*jqXHR*/ jqXHR, /*String*/ textStatus, /*String*/ errorThrown) {
console.log('ouch');
handler(errorThrown);
};
})(errorHandler, options),
});
}
You scope the function with this well known javascript trick aka currying.
New fiddle here.
EDIT 2: if you want successHandler to run even when getting from cache you should use complete instead of success
function handleAjaxRequests(url, parameters, headers, method, successHandler, options, errorHandler) {
method = method || "GET";
headers = headers || {};
parameters = parameters || {};
return $.ajax({
method: method.toUpperCase(),
url: url,
cache: true,
// async: false,
data: parameters,
headers: headers,
complete: (function(handler, opt) {
return function( /*Anything*/ data, /*String*/ textStatus, /*jqXHR*/ jqXHR) {
console.log('hi');
handler(data, opt);
console.log('bye');
};
})(successHandler, options),
error: (function(handler, opt) {
return function( /*jqXHR*/ jqXHR, /*String*/ textStatus, /*String*/ errorThrown) {
console.log('ouch');
handler(errorThrown);
};
})(errorHandler, options),
});
}
Fiddle here.

How to Implement function return from http request

I have an ajax call as follow
$.ajax({
datatype:'json',
url: 'http://localhost:9090/openidm/policy/managed/user/'+storeUserId,
type:'get',
xhrFields: {
withCredentials: true
} ,
corssDomain:true,
headers: {
"X-Requested-With":"XMLHttpRequest"
},
success: function (result){
var validations = result.properties[1].policies[1];
console.log(validations.policyFunction);
},
error:function (error){
console.log (error);
}
});
});
The above ajax call return the policyFunction as follow :
function (fullObject, value, params, property) {
var isRequired = _.find(this.failedPolicyRequirements, function (fpr) {
return fpr.policyRequirement === "REQUIRED";
}), isNonEmptyString = (typeof (value) === "string" && value.length), hasMinLength = isNonEmptyString ? (value.length >= params.minLength) : false;
if ((isRequired || isNonEmptyString) && !hasMinLength) {
return [{"policyRequirement":"MIN_LENGTH", "params":{"minLength":params.minLength}}];
}
return [];
}
i wanna to implement that function in my javascript file. Like passing parameters to that function. So how can i implement.
You can invoke it in the browser like so:
policyFunction = eval("(" + validations.policyFunction + ")");
failures = policyFunction.call({ failedPolicyRequirements: [] },
fullObject,
value,
params,
propertyName);
Where fullObject is the entire object, value is the value of the particular property, params are any parameters needed within the validation function, and propertyName is the name of the property.

Backbone Model : Ajax request in parse override

I have a scenario where a fetch() call of a model will return data from which a property will need be passed to another API and return type from that API will be the actually required data.
var Issue = Backbone.Model.extend({
urlRoot: 'https://api.github.com/repos/ibrahim-islam/ibrahim-islam.github.io/issues',
parse: function(response, options){
var markdown = new Markdown({ text : response.body });
markdown.fetch({
contentType: 'application/json',
type: 'POST',
data: JSON.stringify( markdown.toJSON() ),
success: function(data){
response.body = data;
}
});
return response;
}
});
var Markdown = Backbone.Model.extend({
defaults:{
'text': '',
'mode' : 'markdown'
},
url: 'https://api.github.com/markdown'
});
So, when an Issue will be fetched:
var issue = new Issue({id: 1});
issue.fetch().then(function(){
//do stuff
});
It will have a property of body containing markdown syntax text which in turn I need to pass to another API and get the that response which will be passed down to view.
As can be seen from above, I tried overriding parse but its return type has to be an object and fetch will be async so what can I do here to make this work?
NOTE: I know aggregating the data in server and then receiving it will be best idea but that is not possible atm.
You could override the sync method in your Issue model to chain your requests.
var Issue = Backbone.Model.extend({
urlRoot: 'https://api.github.com/repos/ibrahim-islam/ibrahim-islam.github.io/issues',
sync: function(method, model, options) {
if (method !== 'read')
return Backbone.sync.apply(this, arguments);
// first request
var xhr = Backbone.ajax({
type: 'GET',
dataType: 'json',
url: _.result(model, 'url')
});
// second request
return xhr.then(function (resp1) {
var markdown = new Markdown({text : resp1.body || 'body'});
var data = markdown.toJSON();
// the callback to fill your model, will call parse
var success = options.success;
return Backbone.ajax({
url: _.result(markdown, 'url'),
dataType: 'html',
contentType: 'application/json',
type: 'POST',
data: data
}).then(function(resp2) {
// sets the data you need from the response
var resp = _.extend({}, resp1, {
body: resp2
});
// fills the model and triggers the sync event
success(resp);
// transformed value returned by the promise
return resp;
});
});
}
});
The options hash passed to Model.sync contains the callbacks to model.parse, you can use it to set the attributes on your model when you're satisfied with your data.
And a demo http://jsfiddle.net/puwueqe3/5/
I think you would have to override the model's fetch to get this to work
Consider what the default fetch looks like:
fetch: function(options) {
options = _.extend({parse: true}, options);
var model = this;
var success = options.success;
options.success = function(resp) {
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
if (!model.set(serverAttrs, options)) return false;
if (success) success.call(options.context, model, resp, options);
model.trigger('sync', model, resp, options);
};
wrapError(this, options);
return this.sync('read', this, options);
},
(github)
That implementation would not support an async version of model.parse, but since you create a model class using .extend you can override this with your own implementation so lets look at what it does. It takes an options objects, creates a success callback and then delegates to Backbone.Sync.
It's that callback that calls parse and that's what needs to be made to support async.
The quickest, dirtiest way to do this is probably to just copy and modify the existing default fetch.
var MyModel = Backbone.Model.extend({
fetch: function(options) {
options = _.extend({parse: true}, options);
var model = this;
var success = options.success;
options.success = function(resp) {
function parser(resp, options, cb) {
...do your async request stuff and call cb with the result when done...
}
parser(resp, options, function(result) {
if (!model.set(result, options)) return false;
if (success) success.call(options.context, model, resp, options);
model.trigger('sync', model, resp, options);
});
};
wrapError(this, options);
return this.sync('read', this, options);
}
});
This is just an example of how you might try to solve this. I've not tested it and it might not work but I don't see any immediately obvious reasons why it shouldn't.

Backbone.js and FormData

Is there any way using Backbone.js and it's model architecture that I can send a formdata object to the server? The problem I'm running into is that everything Backbone sends is encoded as JSON so the formdata object is not properly sent (obviously).
I'm temporarily working around this by making a straight jQuery ajax request and including the formdata object as the data property, but this is less than ideal.
Here is a solution by overriding the sync method, which I use to allow file uploads.
In this case I override the model's sync method, but this can also be the Backbone.sync method.
var FileModel = Backbone.Model.extend({
urlRoot: CMS_ADMIN_URL + '/config/files',
sync: function(method, model, options){
// Post data as FormData object on create to allow file upload
if(method == 'create'){
var formData = new FormData();
// Loop over model attributes and append to formData
_.each(model.attributes, function(value, key){
formData.append(key, value);
});
// Set processData and contentType to false so data is sent as FormData
_.defaults(options || (options = {}), {
data: formData,
processData: false,
contentType: false
});
}
return Backbone.sync.call(this, method, model, options);
}
});
EDIT:
To track upload progress, you can add a xhr option to options:
...
_.defaults(options || (options = {}), {
data: formData,
processData: false,
contentType: false
xhr: function(){
// get the native XmlHttpRequest object
var xhr = $.ajaxSettings.xhr();
// set the onprogress event handler
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
console.log('%d%', (event.loaded / event.total) * 100);
// Trigger progress event on model for view updates
model.trigger('progress', (event.loaded / event.total) * 100);
}
};
// set the onload event handler
xhr.upload.onload = function(){
console.log('complete');
model.trigger('progress', 100);
};
// return the customized object
return xhr;
}
});
...
Just to add an answer to this question, heres how I went about it without having to override the sync:
In my view, I have somethign like:
$('#' + $(e.currentTarget).data('fileTarget')).trigger('click').unbind('change').bind('change', function(){
var form_data = new FormData();
form_data.append('file', $(this)[0].files[0]);
appManager.trigger('user:picture:change', form_data);
});
Which then triggers a function in a controller that does this:
var picture_entity = new appManager.Entities.ProfilePicture();
picture_entity.save(null, {
data: data,
contentType: false,
processData: false,
});
At that point, I'm overriding jQuery's data with my FormData object.
I had a similar requirement and here is what i did :
In View :
var HomeView = Backbone.View.extend({
el: "#template_loader",
initialize: function () {
console.log('Home View Initialized');
},
render: function () {
var inputData = {
cId: cId,
latitude: latitude,
longitude: longitude
};
var data = new FormData();
data.append('data', JSON.stringify(inputData));
that.model.save(data, {
data: data,
processData: false,
cache: false,
contentType: false,
success: function (model, result) {
alert("Success");
},
error: function () {
alert("Error");
}
});
}
});
Hope this helps.
I had the same issue. You can see above the way i solve it.
var $form = $("myFormSelector");
//==> GET MODEL FROM FORM
var model = new MyBackboneModel();
var myData = null;
var ajaxOptions = {};
// Check if it is a multipart request.
if ($form.hasFile()) {
myData = new FormData($form[0]);
ajaxOptions = {
type: "POST",
data: myData,
processData: false,
cache: false,
contentType: false
};
} else {
myData = $form.serializeObject();
}
// Save the model.
model.save(myData, $.extend({}, ajaxOptions, {
success: function(model, data, response) {
//==> INSERT SUCCESS
},
error: function(model, response) {
//==> INSERT ERROR
}
}));
The hasFile is a custom method that extends the JQuery functions.
$.fn.hasFile = function() {
if ($.type(this) === "undefined")
return false;
var hasFile = false;
$.each($(this).find(":file"), function(key, input) {
if ($(input).val().length > 0) {
hasFile = true;
}
});
return hasFile;
};
Just use Backbone.emulateJSON = true;: http://backbonejs.org/#Sync-emulateJSON
will cause the JSON to be serialized under a model parameter, and the request to be made with a application/x-www-form-urlencoded MIME type, as if from an HTML form.
None of the answers worked for me, below is simple and easy solution. By overriding sync method and options.contentType like this:
sync: function(method, model, options) {
options = _.extend({
contentType : 'application/x-www-form-urlencoded;charset=UTF-8'
}, options);
options.data = jQuery.param(model.toJSON());
return Backbone.sync.call(this, method, model, options);
}
A simple one will be, hope this will help someone.
Create a object of Backbone Model:
var importModel = new ImportModel();
Call Save[POST] method of Backbone Model and Pass the FormData Object.
var objFormData = new FormData();
objFormData.append('userfile', files[0]);
importModel.save(objFormData, {
contentType: false,
data: objFormData,
processData: false,
success: function(data, status, xhr) { },
error: function(xhr, statusStr) { }
});

Categories

Resources