Article summary
I recently worked on a PDF export feature for our Ember.js web app. There’s some interesting plumbing involving Active Job and PDFKit, but today’s story is about the interaction of wkhtmltopdf with Ember apps.
wkhtmltopdf is a headless browser that, given a URL, spits out a PDF. The problem we encountered is that wkhtmltopdf didn’t recognize when our Ember app was done loading, and thus at a good point to save the PDF.
As a result, we’d get a PDF of the app’s loading screen, like this:
wkhtmltopdf Options
Checking out the command line options, the --javascript-delay
parameter caught my attention. With a sufficiently large delay, it worked–the page finished loading before the PDF was saved–but it didn’t feel right. I hate writing delays. If things go well, the activity you’re waiting on finishes quickly, and you waste time waiting on it. If things go poorly, you might not have waited long enough.
Then I saw the --window-status
option. Perfect! When you’re ready for wkhtmltopdf to do its thing, just set window.status
to a string of your choice from JavaScript:
window.status = 'readyToPrint';
So here’s the meat of the problem: Where’s the right place to do this in an Ember app?
Before you read on to my solution, if you’re interested in figuring this out yourself, you can try it out with this jsbin:
wkhtmltopdf window-status readyToPrint http://output.jsbin.com/yihica#/docs/1
This example demonstrates the main hurdle we encountered: Ember’s loading routes. For full credit, your solution should also permit wkhtmltopdf to grab good PDFs of all four pages:
Solution
We tried a few approaches before arriving at this. Our current implementation observes the currentPath property on the ApplicationController
to discover when the app is done loading and then performs the side effect afterRender:
App.ApplicationController = Ember.Controller.extend
settleDown: (->
if @get('currentPath').indexOf('loading') == -1
Ember.run.scheduleOnce 'afterRender', @, =>
window.status = 'readyToPrint'
).observes('currentPath')
I suspect there may be other (better?) approaches, but this works reliably for all the pages we need to print. How would you do it?