Rails 4: How to upload files with AJAX

17,172

Solution 1

Have a look into the remotipart gem: https://github.com/JangoSteve/remotipart -- may get you all of the way there with very little work!

Solution 2

Using @rails/ujs.

view (.html.erb):

<%= file_field_tag :file, { id: "ajax_file_upload"} %>

controller(_controller.rb):

def update
  @record = YourModel.find(params[:id])

  respond_to do |format|
    if @record.update_attributes(params[:your_model])
      format.json { render json: { success: true } }
    else
      error_messages = @record.errors.messages.values.flatten
      format.json { render json: { success: false, errors: error_messages } }
    end
  end
end

javascript(.js)

const uploadFile = element => {
  const formData = new FormData();
  formData.append("your_model[attribute_name]", element.target.files[0]);

  Rails.ajax({
    url: "your_model/:id",
    type: "PUT",
    beforeSend(xhr, options) {
      options.data = formData;
      return true;
    },
    success: response => {
      if (response.success) {
        alert("File uploaded successfully");
      }
      else {
        alert(response.errors.join("<br>"));
      }
    },
    error: () => {
      alert("ajax send error");
    }
  });
};

const documentOnReady = () => {
  const fileField = document.getElementById("ajax_file_upload");
  if (fileField) {
    fileField.addEventListener("change", uploadFile);
  }
}

document.addEventListener("turbolinks:load", documentOnReady);

Note: No need to setRequestHeader in ajax while using FormData.

FormData uses the same format a form would use if the encoding type were set to "multipart/form-data"

Solution 3

IMHO Rails is not perfect when dealing with upload files using AJAX, especially if you want a progress bar. My suggestion is to use Javascript for the form submission over an AJAX request like you suggested in (2). If you are comfortable with Javascript you will not have many problems.

I recently used the same approach by using this very simple JS library https://github.com/hayageek/jquery-upload-file and I wrote more details here http://www.alfredo.motta.name/upload-video-files-with-rails-paperclip-and-jquery-upload-file/

For an application with a form to upload a movie with title and description the JS code looks like follow:

$(document).ready(function() {
  var uploadObj = $("#movie_video").uploadFile({
    url: "/movies",
    multiple: false,
    fileName: "movie[video]",
    autoSubmit: false,
    formData: {
      "movie[title]": $('#movie_title').text(),
      "movie[description]": $('#movie_description').text()
    },
    onSuccess:function(files,data,xhr)
    {
      window.location.href = data.to;
    }
  });

  $("#fileUpload").click(function(e) {
    e.preventDefault();
    $.rails.disableFormElements($($.rails.formSubmitSelector));
    uploadObj.startUpload();
  });
});

Far from perfect, but gives you flexibility on your frontend.

Share:
17,172

Related videos on Youtube

emersonthis
Author by

emersonthis

I am a designer, developer, and problem solver. I make websites and stuff. I work with brazen startups, modest individuals, earnest small business, and everyone in between. I care as much about how things look as how they work. I enjoy writing and teaching what I know. The best part about my job is constantly learning new things.

Updated on June 06, 2022

Comments

  • emersonthis
    emersonthis almost 2 years

    I want to upload files with AJAX. In the past I accomplished this by using the magical jQuery form plugin and it worked great. Currently I'm building a Rails app and trying to do things "The Rails Way" so I'm using the Form Helper and the paperclip gem to add file attachments.

    The rails docs warn that the Form Helper does not work for AJAX file uploads:

    Unlike other forms making an asynchronous file upload form is not as simple as providing form_for with remote: true. With an Ajax form the serialization is done by JavaScript running inside the browser and since JavaScript cannot read files from your hard drive the file cannot be uploaded. The most common workaround is to use an invisible iframe that serves as the target for the form submission.

    It seems clear there's no off-the-shelf solution. So I'm wondering what's the smartest thing to do. Seems like I have several options:

    1. Use the form helper and the iframe trick.
    2. Use the form helper + load jQuery form plugin to submit the file (not sure if this will play nice with Rails's authenticity token, etc)
    3. Use the form helper + paperclip + [some other gem] to extend it's functionality to allow AJAX form submission.

    All three seem possible. I know the least about #3, specifically the [some other gem] part. I found two similar questions (this and this) which mention a branch of Pic-Upload called Uploadify but those are both 2 years old and deal with Rails 2 and 3 (and Uploadify hasn't been updated in years). So given how much has changed, I think this is really a whole new question:

    What's the best way to upload files with AJAX in Rails 4?

    • Mike Szyndel
      Mike Szyndel over 10 years
      I think the quote clearly explains why you always did it "using the magical jQuery form plugin" ;)
    • emersonthis
      emersonthis over 10 years
      Any advice on the smoothest way to use it along side Rails's form helper? I'm thinking I should NOT use the :remote=>true and just let the form plugin do the work.
    • Jay
      Jay almost 10 years
      That plugin is awesome-- just tried it out and working like a charm. Thanks for the informative question :)
  • Jay
    Jay almost 10 years
    Lots of open issues, I was particularly scared off by those regarding rails 4 compatibility
  • Santanu
    Santanu about 7 years
    I have used remotipart with rails 4, image got uploaded, but the response update.js.erb is not getting executed. PLz help
  • amir ntm
    amir ntm over 5 years
    FYI this works in Rails 5 with Turbolinks 5 as well.