Handle AJAX response with Remotipart - ruby-on-rails

I am using Remotipart to upload images via AJAX in a Rails project. The image upload is handled by the rails controller and works fine.
But the form is a multi-step form, and image upload is just the first of the steps. After uploading the image it's rendered in the view and what I need is that after that, go to the next step if the image was uploaded, but can't figure out how to do it. Can't even make work this awaiting an AJAX response:
$(document).ready (e) ->
$("form").bind "ajax:complete", (e, data, status, error) ->
if data.status is 200 or data.status is 201
alert 'yum'
My controller looks like:
def upload
if request.post?
dir = "/img/" + current_user.slug + "/pictures/queue/"
ndir = dir + view_context.random_s(10) + '.jpg'
File.rename(params[:file].tempfile, "public" + ndir)
render :text => "$('#picture #photo').html('<img src=\"#{ndir}\">');"
end
end
But how I said this part works great.
I think it could be possible doing it by just adding an event handler to the form or just using render :text in the controller, but that wouldn't be a nice thing.
I have seen some answer on SO about this, but no one works.

Related

Capybara not rendering .js.erb

Hi: I've been stuck on this test using Capybara for some days now and can't work out a solution.
Have some DOM modification to be performed using AJAX, long story short:
1) My ajax event triggers the request successfully:
$.ajax({ url: 'nice_url', data: data, complete: function() {debugger}
});
2) The controller receives the request just fine:
def update_dom
<other stuff>
respond_to do |format|
format.js
end
end
3) IMPORTANT: the file update_dom.js.erb is correctly rendered in DEV environment but NOT in the test using Capybara + RSpec.
update_dom.js.erb:
debugger; //first line, breakopint no stopp in Test
<% unless %W(boolean datetime).include?(#operators[:type])%>
$.each($('.values'), function(i, valueField){
<other stuff, etc...>
This is actually the whole issue and will appreciate any thoughts on it
I had an error on my *.js.erb file which was not being reflected on the developers' console stack trace. I was able to debug it using following guide (used plain firefox instead of firebug but still worked): solution

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>

Redirect and then render

Okay, so real quick, I am using a file upload plugin http://plugins.krajee.com/file-input to upload my images. The plugin expects some sort of response from the server, and i want to send back an empty json object.
But when the images are uploaded, I also need to redirect immediately to another place so people can sort of make changes to the order.
Rails says I can't use render and redirect, but says i can redirect and return.
How do i redirect and return the empty json object??
def create
if !params[:images].nil?
package = Package.first
#photos = Array.new
#order = current_user.orders.new
#order.save
#order.order_items.each{|d| d.delete} #Stupid hack to prevent creation of fake order items. Don't know what is causing this yet
params["images"].each do |i|
photo = current_user.photos.create
photo.write(i.original_filename, i.read)
photo.save
#order.order_items.create(photo_id: photo.id, size_id: package.size_id, material_id: package.material_id)
end
redirect_to edit_order_path(#order) and return
else
flash[:danger] = "Please select at least one photo to upload"
redirect_to upload_photos_path
end
end
If the upload plugin you're using is expecting a JSON response and you would like to redirect after a successful upload, then you'll need to do it client side.
If you're not using Rails 4 or Turbolinks, you can simply redirect via window.location.replace. From your Rails code it looks like you're batch uploading in which case you'll want to assign a callback to the filebatchuploadsuccess event as per the docs
Example:
$('#fileinputid').on('filebatchuploadsuccess', function(event, data, previewId, index) {
// files have been successfully uploaded, redirect
window.location.replace( '/your_path_here' );
});
If you are using Turbolinks, the above code will be exactly the same except that instead of window.location.replace, you can use Turbolinks.visit
Example:
$('#fileinputid').on('filebatchuploadsuccess', function(event, data, previewId, index) {
// files have been successfully uploaded, redirect
Turbolinks.visit( '/your_path_here' );
});

Rails controller renders JSON in browser

I have a simple controller that I have responding to both html and json. I'm using the json response for a Backbone app. Everything works as expected, except that when I click a link that uses the show method, and then click the back button, the index method just prints a big string of JSON into the browser. If I refresh, it displays HTML as expected. Here's the controller.
class RecipesController < ApplicationController
def index
#user = User.find(params[:user_id])
#recipes = Recipe.all
respond_to do |format|
format.html
format.json { render json: Recipe.where(user_id: params[:user_id]).featured }
end
end
...
end
I tried adding a check for response.xhr?, and only rendering JSON if it was an AJAX request, but that didn't work.
Edit
This is a Rails 3 app not utilizing turbolinks.
Edit 2
Here is the relevant Backbone code.
# app/assets/javascripts/collections/recipe_list_.js.cofee
#App.Collections.RecipeList = Backbone.Collection.extend
url: ->
"/users/#{#userId}/recipes"
model: window.App.Models.Recipe
initialize: (opts) ->
#userId = opts.userId
# app/assets/javascripts/app.js.coffee
$ ->
urlAry = window.location.href.split('/')
userId = urlAry[urlAry.length - 2]
App = window.App
App.recipeList = new App.Collections.RecipeList(userId: userId)
App.recipeListView = new App.Views.RecipeListView
If you're referring to a chrome and turbolinks issue, then an easy fix is to disable caching on ajax requests:
$.ajaxSetup({cache: false})
you could try using /recipes.html
and /recipes.json
and /recipes/1.html and /recipes/1.json
instead of relying on backbone and history to always send the correct headers
I bet it's due to turbolink, or ajax based page rendering (backbone, remote=true, ...)
I always disable turbolink and keep control over which links are remote=true, and for all ajax response I insert this javascript line at the end
history.pushState(null, '', '/the/requested/url' );
If you don't want to manually implement this line for each of your link responses, you can wrap it in an ajax:complete event (more info), and I assume turbolink has an event you can use as well.
Second part of the trick is to bind popstate so when your users click on the "back" button the page will be refreshed from the server (through the url that was pushState-ed earlier) and the ajax/js/json/whatever response won't be displayed anymore.
setTimeout( function () {
$(window).bind('popstate', function () {
window.location = location.href;
});
}, 500);
As you see I wrap the popstate event binding in a setTimeout, because if you don't do that you may have trouble with some browser that would infinitely refresh the page.
Are you using Chrome? if so this is a known issue. When you hit the back button chromes serves the page from cache since what was returned was json that is what it dumps on the screen. This post has some suggested workarounds
https://code.google.com/p/chromium/issues/detail?id=108766

Serve pdf using :remote => true

I have a form_tag that generates a pdf and serves it to the user. The final send_data is like :
send_data pdf.render, type: 'application/pdf', :filename => filename, :compress => true
This works fine with a standar form, but it does not work when i try to use ajax to set :remote => true.
The ultimate thing i want to do is use :disable_with to disable the button while the pdf is generating.
Any ideas on how i could fix this ?
I know is is an old thread but I thought I'd let you know how I got this to work in case anyone else finds there way here as I did.
The problem with using ajax for the request is that the response (the pdf file) is next to impossible to handle and then serve to the user as a file so don't bother. Instead I used rails built in caching to get the pdf served after generation.
Once you have the link working "conventionally" add the :remote => true, :disable_with => 'loading...' to your link to ajaxify it.
Set config.action_controller.perform_caching = true in your config > enviroments > development.rb so you can see this working before pushing to production =p.
Use Action Caching on your download action and set an appropriate expiration if required i.e. caches_action :download, :expires_in => 5.minutes at the top of your controller.
This means that when you click on your link it will send the ajax request, generate the pdf and return the page. Now the page is cached so if you click the link again the request will take milliseconds not seconds. Next all we need to do is a little javascript to download the pdf once its ready.
However you do your javascript (unobtrusively or not) add the following:
$("#download_link").on('ajax:success', function(){
document.location = $(this).attr("href")
});
This will get the browser to follow the link as if the :remote => true wasn't there once the ajax request is successful (i.e. the pdf is generated). Because the response has been cached it'll download immediately.
use a remote link to begin the rendering process
Have your server begin a background job to create the PDF, return to the user a job id
Use javascript to poll the server on a timer, sending the job id and asking if it's complete
Once it is complete, redirect the user to a page where they can download the PDF.
OK, you have to use the various AJAX callbacks.
Assume you have a link on your page to generate the PDF.
<%= link_to 'Generate PDF', '/generate_pdf', :id=>'genpdf', :remote=>true %>
Now you have to bind events to the 'genpdf' element created by the above. i.e.
$('#genpdf').bind('ajax:beforesend', disablepdf)
$('#genpdf').bind('ajax:complete', enablepdf)
Then define the two javascript functions declared above to manipulate the HTML element as needed.
var disablepdf = function() { $("#genpdf").removeAttr("disabled") };
var enablepdf = function() { $("#genpdf").attr("disabled", "disabled") };

Resources