PDFkit javascript issues - ruby-on-rails

I have been looking into using PDFKit to generate pdf reports for a Rail
3 app I'm working on. Basically if I use PDFKit as middleware any
page in the app is nicely rendered to pdf including the javascript generated graphs.
However, I want to use a different layout for pdf reports that removes any of
the sidebar or navigation details so instead of using the middleware option I have been playing around with adding the following to the relevant controller action
format.pdf {
html = render_to_string(:action => "show.html.erb", :layout => "report.html.erb")
kit = PDFKit.new(html)
kit.stylesheets << "#{Rails.root}/public/stylesheets/application.css"
send_data kit.to_pdf, :filename => "file.pdf", :type => :pdf}
(I also tried the neater option of extracting this functionality to a renderer option as Katz describes here but the logic and the problem are the same)
This definitely solves my layout problem but it appears that none of the app's javascripts are being run and the graphs are no longer appearing. I had a look at the PDFKit source but I couldn't spot any major differences in the way the pdfs are produced.
I'm still finding my feet with a lot of things with rails so I'm sure it is probably something pretty obvious that is staring me in the face. Any pointers that anyone might have would be greatly appreciated

This basically is the solution I went with which is roughly based on Katz's really great blog post
ActionController.add_renderer :pdf do |template, options|
html = render_to_string template, options
html = translate_paths(html, env)
kit = PDFKit.new(html)
css_files = Array.wrap(options.delete(:css)).each do |css_sheet|
kit.stylesheets << "#{Rails.root}/public/stylesheets/#{css_sheet}.css"
end
send_data kit.to_pdf, :filename => options[:filename], :type => Mime::PDF
end
The translate_paths method is basically the same as the one used in PDKKit rack middleware code which can be seen here and below
def translate_paths(body, env)
# Host with protocol
root = "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}/"
body.gsub(/(href|src)=(['"])\/([^\"']*|[^"']*)['"]/, '\1=\2' + root + '\3\2')
end

add :print_media_type => true as an option with the middleware for example: config.middleware.use "PDFKit::Middleware", :print_media_type => true
add :media => "all" while including your stylesheets
in you stylesheet file add the following
#media print {
#id_of_divs_to_hide{
display: none;
}
}

Related

Download pdf instead of showing it in the browser

I am using pdfkit on my rails application (I did this according to Ryan Bates railscast)
Currently it's showing the pdf inside the browser.
How can the pdf be downloaded instead ?
Thanks
EDIT:
Thanks for the response!
But how can I create the data of the pdf in the controller?
The pdf shows up automatically when the url has .pdf. for example when going to this link:
http://localhost:3000/home/index.pdf
It opens a pdf version of the the page automatically on the browser (it's a rails middleware setup).
Try using the send_data method that all rails controllers have.
Something like this:
# Generate a PDF document with information on the client and return it.
# The user will get the PDF as a file download.
def download_pdf
send_data(generate_pdf, :filename => "my_file.pdf", :type => "application/pdf")
end
Assuming it a simple rack end point:
class PdfEndpoint
def call(env)
[200, {"Content-Type" => "application/pdf", "Content-Disposition" => "attachment", "filename" => "filename.pdf"}, pdf_data]
end
end
Also the following Railscast will probably help:
http://railscasts.com/episodes/151-rack-middleware

Saving XML files with Rails

im working on a Rails project that should create XMl files, or to be more specific
use existing XMl templates and put content from the database in it.
So i dont need to create the xml structure, basically just rendering a template with content.
What would be the smartest way to do that?
So far i have a file.xml.erb in my layout folder
and i have a custom route "/renderXML" that does
def renderXML
#reading_question = ReadingQuestion.find(params[:id])
render :file => 'layouts/question.xml'
end
This works, but i also want to save the file, not only show it (actually viewing it is not really needed).
For saving i found this
File.open('fixed.xml','w'){|f| f.write builder.to_xml}
How do i access the rendered file and save it with some method like above?
Perhaps something like:
s = render_to_string :file => 'layouts/question.xml'
File.open('fixed.xml','w'){|f| f.write s}
render :text => s
Another approach :
send_data fixed, :type => 'text/xml; charset=UTF-8;', :disposition =>
"attachment; filename=fixed.xml"

Using Rails googlecharts gem on HTTPS/SSL site

I am using the googlecharts gem in my rails app for some simple charting. It works beautifully, except my app is required to be SSL encrypted at all times. In order to pull the google charts, the charts gem of course makes an http request to google which leads to a browser warning about some insecure content on the page for most users. Has anyone else faced this issue and devised a solution to avoid the warning? I am afraid I will need to figure out a way to make the http call, store the image google returns locally, and then display that in the app but figured someone else has already found a nice way to handle this.
The API Google Charts API endpoint is stored in the class variable ##url inside the Gchart class. So initially I thought of monkeypatching the class variable to set the url to https
# Put this in an initializer
Gchart.send :class_variable_set, :##url, "https://chart.apis.google.com/chart?"
alas Google Charts does not work via https. So we can't use that method. As the Gchart class methods just return a URL we can wrap the calls up in a proxy controller method that does the API call server side and proxies it to the client via the ActionController send_data method using your protocol of choice. That way you don't have to reinvent the Gchart library wheel.
class ChartsController < ApplicationController
require 'net/http'
require 'gchart'
def show
options = params.except :controller, :action
options[:data].map! { |x| x.to_i } if options[:data]
begin
chart = URI.parse(Gchart.send options.delete(:type), options)
send_data Net::HTTP.get(chart), :content_type => 'image/png', :disposition => 'inline'
rescue
raise ActiveRecord::RecordNotFound
end
end
end
The helper you can use in your views:
module ApplicationHelper
def chart_tag(options ={})
image_tag chart_path(options)
end
end
and the route
map.resource :chart, :only => :show
Usage:
<%= chart_tag :type => "line", :size => '200x300', :title => "example title", :bg => 'efefef', :legend => ['first data set label', 'second data set label'], :data => [10, 30, 120, 45, 72] %>
Code is untested but should give you a good start.
Google charts supports now ssl :
use
https://chart.googleapis.com/chart
instead of :
http://chart.apis.google.com/chart
I'm using the GchartRB gem, and a modified version of the first solution worked for me as well. You'll have to use the to_escaped_url method for URI.parse to handle it correctly.
I don't know of an existing plugin that will do this, but you can do it on your own. Simply write a new controller method that will get the chart via HTTP and then return it immediately (no need to save it to a file)
In controller:
require 'net/http'
def googlechart
send_data Net::HTTP.get("http://chart.apis.google.com/chart?#{params[:api]}"),
:content_type => 'image/png',
:disposition => 'inline'
end
In view:
<%= image_tag googlechart_path(:api=>'cht=p&chd=s:Uf9a&chs=200x100&chl=January') %>
Just set up your route and you're all set.

How can you render a template within a layout using Liquid template language?

I'm trying to render a liquid template within a liquid layout (Liquid Template lang, not CSS liquid layout stuff). I can't seem to get the layout part to render. Currently using:
assigns = {'page_name' => 'test'}
#layout = Liquid::Template.parse(File.new(#theme.layout.path).read)
#template = Liquid::Template.parse(File.new(self.template.path).read)
#rend_temp = #template.render(assigns)
#rend_layout = #layout.render({'content_for_layout' => #rend_temp})
render :text => #rend_layout, :content_type => :html
The resulting HTML of the page shows that the 'template' rendered in liquid fine, but it isn't wrapped with the layout (replacing 'content_for_layout' in the layout with the rendered template)
Just to let anyone else know who comes across this problem, the code posted above actually does work, the issue is with the variable named #template. I renamed #template, and #layout to #_tempalte, and #_layout and everything works as expected.
For using liquid in ruby on rails (especially rails 3) - I believe the proper way to render your liquid templates (and also maintain all the work rails is doing for you) is as follows...
The liquid gem itself provides a liquid_view for rails so you can wire up the rails to look for "liquid" templates when you call #render. This liquid_view only works fully with rails 2.3
but can easily be updated to work with rails 3 by making the following update
if content_for_layout = #view.instance_variable_get("#content_for_layout")
assigns['content_for_layout'] = content_for_layout
elsif #view.content_for?(:layout)
assigns["content_for_layout"] = #view.content_for(:layout)
end
assigns.merge!(local_assigns.stringify_keys)
This can be seen here --> https://github.com/danshultz/liquid/commit/e27b5fcd174f4b3916a73b9866e44ac0a012b182
Then to properly render your liquid view just call
render :template => "index", :layout => "my_layout", :locals => { liquid_drop1 => drop, liquid_drop2 => drop }
In our application, since we have a handful of common liquid attributes we have overriden the "render" method in our base controller to automatically include the default locals by referencing #liquid_view_assigns which roll up additionally added liquid drops for the render call
def render(...)
options[:locals] = options.fetch(:locals, {}).merge(liquid_view_assigns)
super
end

Problems with Prawnto options

I'm using Prawnto to generate PDFs in my Rails app. I want three specific options set for my PDFs:
I don't want it to start with a blank page
I want it to download directly (not inline)
I want to specify the filename
Here's my controller method:
def print
#purchase = Purchase.find(params[:id])
prawnto :prawn=>{:skip_page_creation=>true}, :inline=>false, :filename=>#purchase.deal.name + "-" + #purchase.customer.name+".pdf"
end
Without the :skip_page_creation option, the other two options (inline and filename) work fine. But when I add the skip_page_creation option, it goes inline with a default filename. And of course, if I remove skip_page_creation, I get a nice downloaded PDF with a first blank page.
The docs for this library leave something to be desired, but can anyone point me in the right direction?
Cheers!
Aaron.
I've just tried this by changing one of my inline examples which worked ok:
module SharedPdfs
def show
prawnto :prawn => {:skip_page_creation=>true}, :inline => false, :filename => "results_pdf.pdf"
render :template => '/results/show'
end
end
Had a quick look at the prawnto source and it should pickup your prawn options not sure why it isn't but at least you've got it working for now.

Resources