Backbone.js and FormData - javascript

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) { }
});

Related

sending FormData with angular $http

I'm writing an angular-1.5.0-rc0 application
In this application I create FormData, fill it with values and I need to send it with $http request. for now I use jquery ajax with the following code:
this.addDrink = function () {
var data = new FormData();
var drinkBrand = caller.drink.drinkBrand;
var drinkType = caller.drink.drinkType;
var drinkFlavor = caller.drink.drinkFlavor;
var liquidColor = caller.drink.liquidColor;
var drinkCompany = caller.drink.drinkCompany;
var liquidIsTransparent = caller.drink.liquidIsTransparent;
var drinkImage = caller.drink.drinkImage;
var caloriesFor100g = caller.drink.caloriesFor100g;
var alcoholSum = caller.drink.alcoholSum;
var alcoholSumType = caller.drink.alcoholSumType;
data.append('alcohol_sum_type', alcoholSumType);
data.append('drink_brand', drinkBrand);
data.append('drink_type', drinkType);
data.append('drink_flavor', drinkFlavor);
data.append('liquid_color', liquidColor);
data.append('liquid_is_transparent', liquidIsTransparent);
data.append('drink_image', drinkImage);
data.append('drink_company', drinkCompany);
data.append('calories_for_100g', caloriesFor100g);
data.append('alcohol_sum', alcoholSum);
$.ajax({
url: 'https://api.myalcoholist.com:8888/drink',
data: data,
processData: false,
contentType: false,
type: 'POST',
success: function (data) {
if (!data.success) {
alert(data.data);
} else {
alert('done');
}
}
});
};
as you can see I set processData and ContentType to false in order to be able to send FormData in jquery ajax call. how can it be done with $http?
$scope.addDrink = function(files) {
// append all what u want.
$http.post('https://api.myalcoholist.com:8888/drink', data, {
headers: {'Content-Type': undefined },
transformRequest: angular.identity
}).success( ...all right!... ).error( ..damn!... );
};

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 custom model request

Is there any way to create your own ajax method to make a POST request instead of using save ?
Save not only fires the AJAX request but triggers events and validation, so I wouldn't write my "own" save method.
However, you can always write a model function like:
var SomeModel = Backbone.Model.extend({
urlRoot: "/some/url",
altSave: function () {
$.post(this.urlRoot, {
/*assemble your post data*/
}, function (response) {
});
}
});
var s = new SomeModel();
s.altSave();
calling s.altSave(); will fire a POST request.
That's right and it works but I think this is a better solution:
'use strict';
define([
'underscore',
'backbone',
'config/appConfig'
],function(_,Backbone,Config){
var StatsModel = Backbone.Model.extend({
urlRoot: Config.urlStats,
url: function() {
var url = this.urlRoot + "/resource";
return url;
},
defaults: {
metricID: '',
groupByID: ''
},
requestStats: function(opts) {
var url = this.url(),
options = {
url: url,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(this.attributes)
};
_.extend(options, opts);
return (this.sync || Backbone.sync).call(this, null, this, options);
}
});
return StatsModel;
});
Thank you very much

How can i get data from FormData in javascript?

I need to read data from FormData? I try to read something like someFormatData["valueName"] but it not working.
options["fileId"] or options["file"] does not work. Also I try options.fileId same result:
function upload(file, fileId, callback) {
var formData = new FormData();
formData.append("file", file);
formData.append("fileID", fileId);
$.ajax({
url: '/url',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
callback(response);
}
});
}
asyncTest("test upload chunk", function() {
var blob = new Blob(["Hello world!"], { type: "text/plain" }),
options = null,
fileID ="someFileID",
response;
jQuery.ajax = function(param) {
options = param; // THIS is FormData object
// how to read fileId and file from here
};
upload(blob, fileID, function (data) {
response = data;
});
options.success({
someProp: 'responseFromServer'
});
setTimeout(function() {
QUnit.equal(options, "dataTosend", "parameters is OK");
QUnit.equal(response["someProp"], "responseFromServer", "Response ok");
start();
},1000);
});
If you take your FormData object you can use a few different methods on it… What you are looking for is
formData.get()
or
formData.getAll()
https://developer.mozilla.org/en-US/docs/Web/API/FormData
Note that the get() method is not fully supported on all browsers.
You can read using this
formData.get('fileId') // to read Id
formData.get('file') // to read the file
Another way to list all entries of a FormData :
for(const entry of formData){
console.log(entry); // Array: ['entryName', 'entryValue']
}

Backbone.js fetch with parameters

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);
}

Categories

Resources