How to Print a PDF?

I am using Carbone to generate a PDF. I usually do not need to save it (though I have that working) but just print it.

I can display the PDF just fine, but I see no way to print it from the PDF Viewer. I was playing with the iFrame but didn’t get it working there and also saw no way to print so did not pursue.

I tried to throw some html to a new tab but window.open() doesn’t work in the sandbox.

I know I could run the whole PDF gen outside of Retool but would rather not add another moving part.

Ok, got it figure out. The key was a custom component with Allow popups to escape sandbox enabled. Took a little bit of figuring as I am not a React guy, but luckily I have done a bit with vue.js. The other thing that got me a head start was this post from @church.

Here is how I did this with Carbone.io for other to follow.

Set up an account with carbone.io and make a template. I will let their docs guide you in this.

Drop a custom component on the app.

Turn on Allow popups to escape sandbox.

Edit the model to pass in the data you are passing to the carbone.io including the Auth token you got and the templateId for whichever template you want. Mine looks something like this:

{  
    "data": {{qryLineItemsSelect.data}},
    "carboneAuthToken": {{carboneAuthToken.value}},
    "carboneTemplateId": {{carboneInvoiceTemplate.value}}
}

I am just making a simple button that displays an invoice. The button is a material-ui button that doesn’t quite look the the standard retool version but is passable. Replace the entire MyCustomComponent with this:

  const MyCustomComponent = ({ triggerQuery, model, modelUpdate }) => (
        <Button
          color="primary"
          variant="contained"
          size="small"
          fullWidth="true"
          onClick={() => {
        	printInvoice(model.data, model.carboneAuthToken, model.carboneTemplateId)
      	  }}
        >
          Open Invoice
        </Button>
  );

Now you just need a function to do the actual work of making and displaying the PDF. You can put this in the same script block:

async function printInvoice(data, authToken, templateId) {
  const resp = await fetch('https://render.carbone.io/render/' + templateId, {
    method:"POST",
    mode: 'cors',
    body : JSON.stringify({
      "convertTo" : 'pdf',
      "data" : data
    }),
    headers : {
      "content-type": 'application/json',
      "Authorization": 'Bearer ' + authToken,
      "carbone-version": '3',
    }
  }).then(res => res.json());
  if (resp && resp.success === true && resp.data && resp.data.renderId) {
  // Get the result with a simple HTTP GET 
	var newWindow=window.open(`https://render.carbone.io/render/${resp.data.renderId}`, '_blank');
    newWindow.focus(); 
  } else if (resp && resp.error) {
    return (resp.error);
  }
}    

The PDF opens in another tab and sends your eyeballs there!

I cannot seem to get the print dialog working programmatically, window.print() just flashes a blank dialog in Chrome, Edge and FF so that’s kinda weird. If anyone gets that part working, let us know eh?

Also, if you know how to make that button look more seamless please pass that along.

1 Like

Thank you for that awesome write up!

As for printing, I’m actually not too sure how many options we have built into Retool. All JS is run in a sandbox, so a lot of window functions might not be able to escape out. You could use the utils.downloadFile() or utils.downloadPage() methods built into Retool (docs here: https://docs.retool.com/docs/scripting-retool#utilsdownloadpagefilename--selectorstoexclude-componentstoexclude-scale-fullscreen-). One caveat of downloadPage is that any tables with paginated data would only display the first page so you would need to make the table large enough to fit all of the data.

Would this work for your use case? I assume no, but this is my best guess at the moment. Will write back in if I can find a way to print directly from your app!

utils.downloadFile() is working well with the PDF, just forces extra steps on the user to get it printed.

Thanks. I have not messed with any JS sandboxing so am unfamiliar with it limitations. The iFrame is letting be break free of the sandbox, so I assume you could basically do the same thing I did to get the browser access to the PDF. Of course as I said I was unable to get the print dialog to stick around long enough to actually print, but hopefully I was just ding something wrong.

Not everyone is savvy enough to try and make a custom component to do that job, so I would highly recommend investing some treasure in adding general printing/reporting capabilities to Retool. Maybe integrate Carbone or some other handlebars based framework? I'll do a Feature Request post.

Absolutely. Our PDF handling and printing could use a couple feature requests (which do get read and addressed as we plan our quarters, so you’re not yelling into the void 😅)