Power Up Your Oracle JET — Generating PDFs

Create PDF documents the easy way with pdfmake & Oracle JET

Generating PDF files is one of the things that Oracle JET does not offer. If you want to do this, you have to make use of a third party library. In this story I will show you how to generate and download a PDF document from a given user object. You can apply the shown concepts to any other use case. There are a few pitfalls along the way, which you will hopefully be able to avoid thanks to this guide.

An alternative to this approach would be using the window.print function, which brings up the browser-specific print view. Most browsers allow the user to create a PDF file from this view. In most cases though, you want more control over what is printed and how it looks. Plus, the user experience is greatly improved because (1) a direct download reduces the amount of steps the user has to execute and (2) if we are honest, a lot of users do not even know that they can export a PDF via the print dialog.

I assume that you already have the basics of JET figured out. You will need a small webapp up and running to integrate the PDF generation into. If you do not have one that fits, you may also use the basic starter template that the Yeoman generator creates (generate it via yo oraclejet:app --template basic pdfmake-example).

The resulting document will look like this for my own user data:

Screenshot of the generated PDF document

Setup

We will be using the awesome pdfmake library for generating and printing the PDF file. Download it via npm install --save pdfmake. This new dependency has to be set up to be included to the grunt build command. To do that, add the following line to the copyCustomLibsToStaging.fileList array in the scripts/grunt/config/oraclejet-build.js file:

{
cwd: 'node_modules/pdfmake/build',
src: ['*.js'], // matches pdfmake.js and vfs_fonts.js
dest: 'web/js/libs/pdfmake'
}

This adds all JavaScript files from node_modules/pdfmake/build/ to your lib folder. The files are pdfmake.js / pdfmake.min.js (the actual PDF generation logic) and vfs_fonts.js (the fonts used by the pdfmake library). Do not forget to run grunt build afterwards.

You will need both the logic and the fonts for generating the PDF files, so they have to be loaded via RequireJS. This is where I initially struggled, because you have to first load the fonts and then the pdfmake library. Fortunately, RequireJS has something called shims for exactly these problems (more about shims). You are actually already using a jQuery shim in your JET app. Change the shim object in your requirejs.config call to be as follows (the jQuery shim should already be there):

shim: {
'jquery': {
exports: ['jQuery', '$']
},
'vfsfonts': {
deps: ['pdfmake']
}
}

These dependencies of course also need to be added to the paths which are supplied to the requirejs.config call:

'pdfmake': 'libs/pdfmake/pdfmake',
'vfsfonts': 'libs/pdfmake/vfs_fonts'

The pdfCreator module

I recommend to put your PDF logic into a separate module. Create a pdfCreator.js file and add the standard RequireJS module definition logic:

define([], function () {
function createPDF (user) {
/* ... */
}
  return {
fromUser: createPDF
};
});

This exports a fromUser function, which you will call via pdfCreator.fromUser(user) later. But first we need to require the needed dependencies. You can do this by passing the array ['pdfmake', 'vfsfonts'] as the first parameter into the define call. The shim that we configured in the setup step makes sure that everything loads in the correct order. As we want to call functions on the pdfmake library, we need to pass pdfmake into the function that implements our module. The complete module definition should look like this:

define(['pdfmake', 'vfsfonts'], function (pdfmake) {
function createPDF (travel) {
/* ... */
}
  return {
fromUser: createPDF
};
});

With that out of the way, let us add the PDF generation logic. In pdfmake, documents are described as plain JavaScript objects. The document object must at least contain a content property. This is where the actual PDF content is declared. We want to display user data in a table-like format, so we first write the user data into a two-dimensional array:

var table = [
[
{text: 'User', style: 'tableHeader'},
{text: '', style: 'tableHeader'}
],
[
'Name',
user.name
],
[
'Twitter Handle',
user.twitter
],
[
'Favorite Band',
user.band
]
];

This defines a 4*2 table, where the first row is intended to be a header. Note that we intentionally set the second header to the empty string because we only want a header for the first column. Text can be either defined directly as a string, or via an object having the text property. Because table is a plain JavaScript array, we can dynamically add more content if we want:

if (user.color) {
table.push(['Favorite Color', user.color]);
}

We also want a nice heading for our document. We create an object which stores a text and has the header style:

var title = {
text: 'User Info for ' + user.name,
style: 'header'
};

But pdfmake does not yet know what that header style means, so we have to define these styles now. pdfmake also allows you to style your document using CSS-like key-value pairs. These may be declared inline, but most of the times you want to store them in an object that is passed into the styles property of the document object. This way, you can reuse them for different parts of the document. We introduce a style for the document header and the table header as follows:

var styles = {
header: {
fontSize: 18,
bold: true,
margin: [0, 0, 0, 10]
},
tableHeader: {
bold: true,
fontSize: 13,
color: 'black'
}
};

One last thing that you may want to do is displaying some kind of header logo. A lot of officially-looking documents have a logo of the issuing company printed at the top right or top left. This is a little tricky, because you cannot just, for example, set display: inline as in CSS. Instead, we do this:

var image = {
image: 'data:image/png;base64,iVBORw0KGgoAAAANS...',
width: 73,
height: 28,
alignment: 'right',
margin: [0, 25, 25, 0]
};

Note that we set the alignment to right because we went the image at the top right corner of the document. It also gets assigned a margin of 25 pixels, each to the top and to the right. As we are running the application in the browser, we cannot access a file system. Therefore the image is base64 encoded. You can use an online service for this, I used base64-image.de.

Our various parts of the document can now be combined into the document definition as follows:

var document = {
background: image,
content: [
title,
{
table: {
widths: [200, '*'],
headerRows: 1,
body: table
},
layout: 'headerLineOnly',
margin: [0, 10, 0, 0]
}
],
styles: styles
};

Note the content property, which is defined as an array in pdfmake. Here we set the title and describe the table. The table definition expects a two-dimensional array as its body (which we defined earlier). We also set the widths of the tables columns, inform the table that the first row should be the headerRow and set a specific pre-defined layout so it looks nice. We also set the logo as a background image. This is important because otherwise it would mess up the document. Feel free to add the image to the content and see how that affects the layout.

Again, here is a screenshot of the final PDF document:

Screenshot of the generated PDF document

That wraps up the document definition. Now we simply have to create the PDF via pdfmake.createPdf(document) which allows us to initiate the download of the PDF via pdf.download(). You can also create functions for printing the PDF or opening it in the browser via print() and open(), respectively.

You can now require this module anywhere in your Oracle JET app and use its fromUser function to download a PDF of the users data. You can do a lot more with pdfmake — head over to the playground if you want to take a deeper look. The complete pdfCreator example is available as a gist on github.

Conclusion / TL;DR

  • use pdfmake for generating PDF files
  • setup a shim such that RequireJS knows how to load pdfmake and its fonts
  • define document data and tables using plain JavaScript objects and arrays
  • create the PDF via pdfmake.createPdf(document) and then download via pdf.download()
Show your support

Clapping shows how much you appreciated Janis Krasemann’s story.