Rails 4: How to upload files with AJAX - ruby-on-rails

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:
Use the form helper and the iframe trick.
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)
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?

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

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"

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.

Related

File download feature in grails application

I am looking to create a file on the fly and offer a download link to the user in a GRAILS application.
I followed the approach from here. I have no errors however it doesn't seem to work. Here's my controller code.
`render (file: pptFile, fileName:'someppt.pptx', contentType: 'application/octet-stream')
Client side code makes an AJAX call to retrieve the file from server. It does not cause the server to force downloading of the file on the client (browser). Here's the client side code.
$.ajax({
type : 'POST',
url : '<<URL>>',
success: function(result) {
var uri = 'data:application/octet-stream;charset=UTF-8,' +
encodeURIComponent(result);
window.open(uri, 'somePPT.pptx');
},
failure: function(){
alert ('failure')
}
});
Perhaps something akin to this (paraphrased, but used for downloading a json file):
def someControllerMethod() {
def dlContent = someService.marshalJson()
def contentType = "application/octet-stream"
def filename = "someFilename.json"
response.setHeader("Content-Disposition", "attachment;filename=${filename}")
render(contentType: contentType, text: dlContent as JSON)
}
okay. So I finally got this to work. As proposed by #railsdog and many others (This problem has been discussed on other threads in stackoverflow but the specific case I had was slightly different from those) I ended up writing to response directly from server and took out the AJAX call. The only reason I was doing an AJAX call was because I did not want to submit the current page that had the "generate file" functionality (There are many data elements on the page and I did not want to re-do the entire page just for downloading the file). So I ended up using an anchor tag with target as "_blank". Here's the code snippet
<a href="myControllerMethodToGenerateFileAndWriteToHTTPResponseDirectlyAsSuggestedByOthersInThisPost"
target="_blank"/>
This actually opened a new page and did the submission to initiate the download. Problem solved. It's working fine in CHROME. :) Thanks guys!
I like the solution using the render method from #railsdog !
A slightly other approach which I used so far was:
def controllerMethod() {
...
File file = sepaXmlService.createTransfersFile(...)
response.setContentType("application/xml")
response.setHeader("Content-disposition", "attachment;filename=${file.getName()}")
OutputStream out = response.getOutputStream()
out.write(file.bytes)
out.close()
file.delete()
return
...
}
In the view I use the following statement in the form:
<g:actionSubmit action="controllerMethod" class="btn" value="Get XML!" /></td>
I think it should also be possible to use a
<g:link controller="foobar" action="controllerMethod" class="btn">GetXML</g:link>

Posting form between 2 rails applications including files

I am trying to post a form from one rails app to another hosted on different domains and including files.
I attempted:
$(document).on("click", ".application-form .submission input", function(e){
e.preventDefault();
var form = $(".application-form form");
var data = new FormData(form);
var url = "http://example.com/action";
$.ajax({
url: url,
type: 'POST',
success: function(){
alert('success');
},
error: function(){
alert('error');
},
data: data,
cache: false,
contentType: false,
processData: false
});
});
But the remote server doesn't receive any data, params merely contains the controller name and action.
I also have tried using remotipart and setting up my form with form_for(#application, url: "http://example.com/action", html: {multipart: true}, remote: true) and if I include a file, the server receives [object Object] instead of correct param names and values. If I do not include a file, then the params are sent and received correctly, but I need to include files with the upload.
What is going on to cause [object Object] to replace all my form data when I include a file? Using remotipart 1.2.1. And I've seen this and it does not apply.
There are a lot of issues with attempting what you are:
Cross Domain Requests (CORS)
Form
Uploading
CORS
Your first issue is you have to ensure you're able to pass the data between the two domains. CORS is a standard protocol, whereby you can only send requests to pre-approved domains
Basically, you need some way to whitelist the possible connecting domains, and with Rails, you'll be best to use the RACK-CORS gem
I would begin by ensuring this is set up on your "receiving" domain
Form
Secondly, you need to compile the correct form data when you submit
I would use the .serialize() function:
$(document).on("click", ".application-form .submission input", function(e){
e.preventDefault();
var form = $(".application-form form");
var data = form.serialize();
var url = "http://example.com/action";
$.ajax({
url: url,
type: 'POST',
success: function(){
alert('success');
},
error: function(){
alert('error');
},
data: data,
cache: false,
contentType: false,
processData: false
});
});
This will probably explain the lack of params being sent to your other domain
Upload
Uploading files creates a totally new issue, as it takes a lot to get the file-upload process to work through ajax
There are plugins such as JQuery-FileUpload-Rails allow you to upload files through ajax. From my understanding, this works by providing custom middleware to handle the file upload over ajax. I'm not totally sure how it works, but I know it does, as we've used t multiple times
In general, you cannot directly upload files from a form via AJAX. You have to 'fake' it using either a hidden iframe, Flash, or something similar (might be different in the newest browsers). The jQuery Form plugin does a good job of simplifying this, and there are lots of other plugins as well to help with this.

Ajax call on ruby on rails

I have a ajax call using ruby on rails. I'm getting a success but I don't know how to use the data result of the ajax call.
$.ajax({
url: "/search/get_listing?listing_id" + id,
dataType: 'JSON',
success: function(data) {
var listing = JSON.parse(data);
$("#modalPrice").html(data.city);
}
});
Controller:
#listings_data = Listings.find_by(id: params[:id])
render :json => #listings_data.to_json
Using data.city won't work. I'm expecting to get the values retrieve from the model by simply putting . on the variable
var listing = JSON.parse(data);
Still no luck. Help guys. Thanks!
JSON.parse is Ruby code, API of JSON gem. How can you guys use that in Javascript :)
jQuery can process JSON object data directly. Just use:
success: function(data) {
$("#modalPrice").html(data.city);
}
For example, you can render in the controller:
render :json => { :city => #listings_data }
On the JS:
success: function(data) {
var listing = data.city;
}
I'm having similar problems everytime I use AJAX in rails since the response seems to differ depending on how you return the value or how you are handling the success in JS. Try this:
success: function(data, status, xhr) {
var listing = JSON.parse(xhr.responseText);
$("#modalPrice").html(data.city);
}
I usually use Firebug (Firefox Plugin) to set a breakpoint in my success handlers to check the arguments where exactly the response is in. Sometimes it's in the first value, sometimes in some other and then it may be xhr.response or even xhr.responseText. It's confusing me every time.
To use Firebug for this, press F12 on your page, select the 'Script' pane and find the code you want to check. Click next to the row number of your code where you want your breakpoint. In this case, you could've chosen the var listing line. When the code is executed (after your click), the browser will stop there and you can check the passed arguments on the right side.

Unable to post files using ajax post

We use ASP.Net Mvc 4.0.
My objecctive is to save a form with both normal input fields as well as file input fields.
I should be able to add extra data while posting.
I should be able to do perform few actions on 'Ajax Post's Success.
We used ajax post to post the form data as we could accomplish above 2, but failed in serializing and posting of files to server.
Whenever we post using ajax post, always Request.Files.Count == 0, when i check in my controller's Post Action.
ajax post i have used is:
function PostData(formId, eventSource, eventName, eventArgs, controlId) {
var $dialogForm = $("#" + formId + "Form");
fdata = $dialogForm.serialize();
fdata = fdata + '&eventSource=' + eventSource + "&eventName=" + eventName + '&eventArgs=' + eventArgs;
$.ajax({
url: $dialogForm.attr("action"),
type: $dialogForm.attr("method"),
cache: false,
data: fdata,
success: function (result) {
ProcessEvent(result);
}
});
}
Please provide me a solution for this!
well you cannot upload files when you go with the concept of ajax. But there are tweaks which are used to upload file and form data using ajax. Whenever a file type is encountered in a form the form data along with file can be copied to an iframe and the iframe can be submitted which give you a feel that file has been uploaded along with other form data using ajax.
There are various plugin available in jquery which ease this task for you.
One of my favourite is ajax form
http://malsup.com/jquery/form/#file-upload
You can use this one..

Strange jquery post problem, wrong post url is used

I have a form I wish to submit via ajax usind the jQuery $.post command.
The form looks like this:
<form action="/wine/merlot/reviews" class="new_review" id="new_review" method="post">
And the jquery call is:
$(document).ready(function() {
$('#new_review').submit(function() {
$.post($(this).attr('action'), $(this).serialize(), null, 'script');
return false;
});
});
I get the following error on the server:
ActionController::MethodNotAllowed (Only get, put, and delete requests are allowed.):
From what I can tell by digging in with firebugs console the problem is the post is posting to this url:
/wine/merlot instead of /wine/merlot/reviews
I can't for the life of me figure out why this is the case.
OK. It turns out I'm an idiot. I had another div on the page with the id "new_review" so I guess it was looking at the wrong element. Renamed and everything working now.
I could not get a form to submit via ajax using the jQuery $.post command with Rails 2.
I modified Ryan Bates' Railcast 136 to submit via a put instead. The kludge I used was to check the (params[:id] == 'update') in the update action to check for this ajax request.
episode-136/store/public/javascripts/application.js
jQuery.fn.blurWithAjax = function() {
this.blur(function() {
// GOOD .ajax javascript update action works even though create on Rails2 does not
jQuery.ajax({
type: "PUT",
url: "/reviews/update",
data: jQuery(this).serialize(),
dataType: "script",
callback: null
});
return false;
})
return this;
};
jQuery(document).ready(function() {
jQuery("#review_content").blurWithAjax();
});
You need to make the parallel changes also;
def create => def update
app/views/reviews/create.js.erb => app/views/reviews/update.js.erb
Not sure, but try /wine/merlot/reviews/ instead of /wine/merlot/reviews?

Resources