Leonidas
Leonidas

Reputation: 2160

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.

Upvotes: 15

Views: 7554

Answers (7)

Siddharth
Siddharth

Reputation: 73

A simple one will be, hope this will help someone.

  1. Create a object of Backbone Model:

    var importModel = new ImportModel();
    
  2. 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) { }
    });
    

Upvotes: 0

Yuriy Gatilin
Yuriy Gatilin

Reputation: 132

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.

Upvotes: 2

Rahil Wazir
Rahil Wazir

Reputation: 10132

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

Upvotes: 1

Georgios Syngouroglou
Georgios Syngouroglou

Reputation: 19974

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

Upvotes: 2

Koen.
Koen.

Reputation: 26999

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

...

Upvotes: 19

Roy M J
Roy M J

Reputation: 6938

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.

Upvotes: 7

Prisoner
Prisoner

Reputation: 27628

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.

Upvotes: 7

Related Questions