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

Bildschirmfoto 2016-08-22 um 17.55.16.png

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.

Bildschirmfoto 2016-08-22 um 18.02.09.png


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.

To report this post you need to login first.

2 Comments

You must be Logged on to comment or reply to a post.

  1. Christian Pesce

    Dear Helmut,

    i found really interesting your blog, but I have a question:
    is this library available on SAP Fiori Client for iPad?

    Thanks,

    Christian

    (0) 

Leave a Reply