Serve pdf using :remote => true - ruby-on-rails

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

Related

Can not fill automatically form with mechanize on ruby

I am trying to test mechanize gem. I would like to subscribe to a website using the form. I selected all the elements correctly ( I guess) but the submit button, does not submit the form. When the form is submitted a mail is instantly received. I am a new to the gem, and I am testing registering to a website with a single mandatory field. I am using the webservice for spam mail to check if it works, so every mail field could substitute with anything before #moncourrier.fr.nf and later check if a mail is received on yopmail website, but I got nothing. Do you have any idea why is not working?
require 'rubygems'
require 'mechanize'
require'pry'
agent = Mechanize.new
agent.get('http://blackburnecreek.com/newsletter-registration') do |page|
form=agent.page.forms[1]
button=form.button_with(:value => "Subscribe")
agent.page.forms[1]["EMAIL"]= "testmechanizegem#moncourrier.fr.nf"
agent.submit(form, button)
end
Your form is not getting submitted, because the actual form uses jquery which submits the form with some extra params. They are unbinding the submit event of form and are performing a ajax request to submit the form instead with an extra param id.
You can confirm so by looking at the request being initiated in network tab of your browser it contains following params:
Also the page runs following javascript which adds the extra id param to the request:
function mce_init_form(){
jQuery(document).ready( function(jQuery) {
var options = { errorClass: 'mce_inline_error', errorElement: 'div', onkeyup: function(){}, onfocusout:function(){}, onblur:function(){} };
var mce_validator = jQuery("#mc-embedded-subscribe-form").validate(options);
jQuery("#mc-embedded-subscribe-form").unbind('submit');//remove the validator so we can get into beforeSubmit on the ajaxform, which then calls the validator
options = { url: 'http://blackburnecreek.us8.list-manage.com/subscribe/post-json?u=68692d660a16c9b7e4be6f51e&id=f19ba31349&c=?', type: 'GET', dataType: 'json', contentType: "application/json; charset=utf-8",
See the url: part in above function. So when you're submitting it via mechanize you're performing an invalid request(no param id).
I guess mechanize doesn't support execution of javascript. I would recommend you to use capybara which uses web drivers like poltergiest that facilitates support for js execution.

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 4: How to upload files with AJAX

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.

Rails 3 - Link to :remote, updating the address URL

I'm currently toying with updating page content via the following:
<%= link_to(content_tag(:span, 'Settings'), edit_admin_store_path, :remote => true)%>
With the javascript as such:
$('nav li a').bind("ajax:success", function(event, data){
console.log(event + data);
$('div#loading').hide();
$('div#container div#content').html(data).hide().fadeIn('100');
});
And was wondering if there is a 'rails way' to also update the address url as well?
Thanks a plenty for any help/advice!
Basically, you are asking for the HTML5 history.pushState method. Great documentation can be found here, at the mozilla developers network.
To be simple, you would push into the history by doing something like this:
var stateObj = { foo: "bar" };
history.pushState( stateObj, "new page title", "forbear.html" )
This will cause the URL to display http://www.yoursite.html/whatever/foobar.html depending on what the URL currently looks like.
Cheers!

Detecting if this is an iframe load or direct

I am looking to only show a form if it is pulled on a page within an iframe. How do I do that? Is there a server side solution?
If you are using JQuery... (installation instructions here: http://jquery.com/ )
$(document).ready(function(){
if( window == window.top) { $('form#myform').hide(); }
});
Which just hides the form with id "myform" if the window is not the topmost window.
I can't think of purely serverside way, but you could use a bit of hybrid javascript/rails.
assuming that you have a dedicated iframe layout template e.g. 'layouts/iframe.erb'
you could put some javascript in the head to check if it is being loaded as an iframe, and if it is not, redirect to an action and maybe display a flash msg "can only load this page inside application"
The javascript/rails for the head
<script type="text/javascript">
function parentExists()
{
return (parent.location == window.location)? true : false;
};
function check_modal(){
if (parentExists()) {
window.location = '<%= url_for( :controller => "home", :action => 'iframe_action', :iframe_fail => 'true')%>'}
}
check_modal()
</script>
notice the param :iframe_fail which you could check for in a controller and do whatever you please if that param is present e.g. display flash msg or redirect
example controller
def iframe_action
if params[:iframe_fail]
flash[:notice] = 'can only load inside app'
else
#do something else
end
end
Not real pretty but might help you get the job done.
My iframe tag was like
%iframe{:height => "98%", :width => "98%",:"id" => "profileIframe"}
I wanted to hide header of my webpage within this iframe hence I used code as:
var $frame = $(window.parent.frames["profileIframe"]).contents();
$frame.find('.header-ui').hide();
If you observe then contents() returns a element as "#document", which is html of iframe, hence calling a javascript without this will try to access your actual webpage rendered in background of iframe.
You can only check it on the client side via JavaScript.
However: DO NOT DO THAT. There are plenty of legitimate uses of putting a site in a (i)frame. Breaking out of such iframe or changing your site in any way in such circumstances them will only make your users pissed unhappy.

Resources