You are on page 1of 6

Client side PDF creation for Fiori apps

4149,068
Even though we are online all the time and SAP Fiori is running on all devices sometimes there is the
demand to export data that is currently visible on the screen to a pdf either to print it or to send it via
email to colleagues or partners.

The general approach to fulfill this demand is to invoke the Adobe Document Service at server side to
create the pdf document and to return it to the frontend.

In this blog I want to show a lightweight client side alternative which is fine when all data is available at
the client.

Example scenario
My example is a Fiori app that I wrote to manage business trips. This app enables me to enter business trips
with date, starttime, endtime, location, … For my tax office I need to print the list of trips and file it to a
physical folder.
The following screenshot shows the startscreen of my application

This screen shows a list report. At the top the user has the possibility to filter the data she wants to see. In the
lower part the data is displayed in a table. The toolbar of the table contains an icon button which enables the
output of the data as pdf file.
If the user clicks this button the pdf is created and opened in a new browser window (tab). As a little goodie
the pdf additionally displays the sum of driven kilometers / miles.
Technical implementation
The creation of the pdf is done with the open source javascript library pdfmake.

Installation of pdfmake
pdfmake is delivered as two js files so it can simply be integrated into every Javascript application like Fiori
apps are. You can either copy the two files somewhere to your webapp folder and reference them or
use bower to install pdfmake. I prefere using bower. Hence the files were installed into the
folder webapp/bower_components/pdfmake/build. Then I refer to them in my manifest.json file like
this:

"resources": {
"js": [
{
"uri": "bower_components/pdfmake/build/pdfmake.min.js"
},
{
"uri": "bower_components/pdfmake/build/vfs_fonts.js"
}
]
}
The “resources” section has to be added under the “sap.ui5” section. That way sapui5 loads the js files at
application startup and the exposed functions can be used everywhere in our app.

Invocation at UI side
The creation of the pdf is invoked by clicking an icon button in the toolbar of the table. Hence this toolbar has
some code like this.

<Toolbar>
<Title id="tableHeader" text="{worklistView>/worklistTableTitle}"/>
<ToolbarSpacer/>
<Button icon="sap-icon://pdf-attachment" press="onPdfExport"
tooltip="{i18n>downloadpdf}"/>
</Toolbar>

In line 04 you see the button which calls the event handler onPdfExport when clicked.

Implementation of the event handler


The event handler is implemented in the views controller.

onPdfExport: function(oEvent) {
var that = this;
// map the bound data of the table to a pdfMake array
var createTableData = function() {
var sum = {count: 0, totalDistance: 0};
var mapArr = that.getView().byId("table").getItems().map(function(obj) {
sum.count += 1;
sum.totalDistance +=
Number.parseInt(obj.getCells()[6].getProperty("number"));
var ret = [{
text: obj.getCells()[0].getProperty("title")
}, {
text: obj.getCells()[1].getProperty("text")
}, {
text: obj.getCells()[2].getProperty("text")
}, {
text: obj.getCells()[3].getProperty("text")
}, {
text: obj.getCells()[4].getProperty("text")
}, {
text: obj.getCells()[5].getProperty("text")
}, {
text: obj.getCells()[6].getProperty("number"),
alignment: 'right'
}];
return ret;
});
// add a header to the pdf table
mapArr.unshift(
[{
text: that.getResourceBundle().getText('pdfDateTitle'),
style: 'tableHeader'
}, {
text: that.getResourceBundle().getText('pdfStartTitle'),
style: 'tableHeader'
}, {
text: that.getResourceBundle().getText('pdfEndTitle'),
style: 'tableHeader'
}, {
text: that.getResourceBundle().getText('pdfDurationTitle'),
style: 'tableHeader'
}, {
text: that.getResourceBundle().getText('pdfLocationTitle'),
style: 'tableHeader'
}, {
text: that.getResourceBundle().getText('pdfCustomerTitle'),
style: 'tableHeader'
}, {
text: that.getResourceBundle().getText('pdfDistanceTitle'),
style: 'tableHeader',
alignment: 'right'
}]
);
// add a summary row at the end
mapArr.push([
{text: that.getResourceBundle().getText('pdfSum'), style: 'sum'},
{text: ""},
{text: ""},
{text: ""},
{text: ""},
{text: ""},
{text: sum.totalDistance.toString(), style: 'sum', alignment: 'right'}
]);
return mapArr;
};
var docDefinition = {
info: {
title: that.getResourceBundle().getText('pdfReportName'),
author: 'TAMMEN IT SOLUTIONS',
subject: that.getResourceBundle().getText('pdfReportSubject')
},
pageOrientation: 'landscape',
footer: function(currentPage, pageCount) {
return {text: currentPage.toString() + ' / ' + pageCount, alignment:
'center'};
},
content: [{
text: that.getResourceBundle().getText('pdfReportTitle'),
style: 'header'
}, {
table: {
headerRows: 1,
widths: [50, 30, 30, 50, '*', '*', 40],
body: createTableData()
},
layout: 'lightHorizontalLines'
}],
styles: {
header: {
fontSize: 18,
bold: true,
margin: [0, 0, 0, 10]
},
tableHeader: {
bold: true,
fontSize: 13,
color: 'black'
},
sum: {
fontSize: 16,
italics: true
}
}
};
pdfMake.createPdf(docDefinition).open();
},

Although this function might look quite complicated it is very simple. Let’s start the explanation at the
last line (108). Here I invoke the pdf creation functionality of the pdfMake library. A docDefinition object
that is created above is passed to the function createPdf. The result of this function, the pdf stream, is
then opened in a new browser window resp. tab.
The creation of the docDefinition begins at line 70. In line 71-75 just some metadata for the pdf is
defined. In line 76 I set the orientation to landscape. The default is portrait which is not wide enough for
my report. Line 77-79 define a footer with the page number that is repeated on each page.
The heart of the report starts at line 80 and ends at line 90. Here the content is created. It starts with a
report title (81-82). Lines 84-89 define the table which has 1 headerRow and seven columns for which
the widths are defined in line 86. Interesting is that you can use wildcards in width definition. Line 87
then creates the body of the table. This is delegated to the local function createTableData to which I
come in a moment.
Lines 89-104 are not very interesting. They just define some styles that are applied by the report.

CreateTableData
The function createTableData creates the rows and columns of the table. It produces an array with n
elements where n = number of table rows. Each row contains an array with seven elements (the table
has seven columns). I apply the JavaScript map function to the array of ListItemBase objects that I get by
invoking getItems() on the table control. The map function is a very powerful function that allow
transforming an array into a new array. Cause pdfMake cannot work with ListItemBase objects I have to
translate these objects into an array that is pdfMake aware of. This transformation is done in lines
10-26. For each cell of the current ListItemBase object I create a pdfMake text object. The transformed
array is contained in the variable mapArr after the map function has finished.
In lines 31-55 I then just add a header row with 7 columns to the mapArr and in lines 57-65 I add the
summarize row that displays the amount of kilometers of all rows. I calculated this amount in line 09.

This transformed array is then returned to as function result so that pdfMake can process it and create
the pdf.

Conclusion
I think that the creation of a pdf file is a very often needed feature and in many cases it is sufficient to
produce it at client side with a lightweight library like pdfMake. So I hope this blog helps some other
developers save time an make users happy.

You might also like