May 3, 2020
Estimated Post Reading Time ~

Creating customizable & beautiful PDFs using jsPDF API, AEM and Angular

In this article, you will be able to learn one way in which you can do the same using two amazing technologies namely: AEM and Angular.

So what are we waiting for! Let’s get started.

What are the options available?
There are two major APIs that assist in creating PDF from Angular:

  1. jsPDF by MrRio.
  2. jsPDF-AutoTable by simonbengtsson
I will walk you through the second one since I had to create PDFs from html tables with header and footer and jsPDF-autoTable provides quite a convenient way to achieve the same. I hope you will explore the other one on your own.

How do we get started?
As mentioned in the official github link, you can get the library into your project using any of the following:
What next?
Now for this to work in AEM, you can either:
  1. Keep the dependency in your Angular package.json and ensure that it gets built as part of your Angular project which then gets copied into an AEM clientlibrary folder for the application. (Recommended since you no longer have to keep on updating the raw files and it will be taken care of automatically.)
  2. Or you can copy the raw files for jspdf.min.js and jspdf.plugin.autotable.js from the same github link and place those into an AEM clientlibrary folder. Use this folder as a dependency wherever PDF creation is needed as shown below:
Client library for keeping jspdf library files

Add unique categories for including elsewhere

Including the pdf library in another client-library folder:
Just add the pdf categories in your required folder as dependencies

There you go! The necessary library files will now be available to your scripts wherever you include the training.components.statement clientlibrary call.

The important part 
Note: For the code, you can simply explore examples.js or the demo link provided in the github link for the same. I will be explaining to you the basic differences between the various approaches.

To initialize a new pdf object you need to do the following:
var doc = new jsPDF();

Now let us explore basic use cases for the PDF generation:

Plain text in PDF:
doc.text(‘Text to be printed in PDF’, start-x-axis-position-from-left, start-y-axis-position-from-top);

e.g.
var doc = new jsPDF();
doc.text(“This is basic text”, 14, 15);

PDF from an html table:
Give the html table id or a class so as to identify the same.
<table id=”my-table”>…….</table>

Now in jsPDF:
Just initialize an autoTable object with html attribute set to the value of the identifier.

e.g.
var doc = new jsPDF();
doc.autoTable({html:”#my-table”}); //as simple as that!

Text relative to an already printed autoTable:
Suppose you have created an autotable already and you want a text below the table. You can do that using:

e.g.
var doc = new jsPDF();
doc.autoTable({html:”#my-table”});
let finalY = doc.previousAutoTable.finalY; //this gives you the value of the end-y-axis-position of the previous autotable.

doc.text(“Text to be shown relative to the table”, 12, finalY + 10);

Print an Image in the PDF:
This is a bit complex and not as straightforward as adding a text. I was trying to add an SVG image and having a hard time getting it to work. Thanks to a teammate of mine I got to know that SVGs are not supported and you have to first convert them to either JPEG or PNG using an online tool like https://image.online-convert.com.

Next, you need to convert the JPEG/PNG image into a data URI object using an online tool like the one provided by dopiaza or sveinbjorn.

Once you have the datauri, save it in a var and use addImage function of jsPDF as shown below:

var doc = new jsPDF();var img = “data:image/jpg;base64,jtSEjBYHkTNc5E………………………”;/* addImage explained below:
param 1 -> image in code format
param 2 -> type of the image. SVG not supported. needs to be either PNG or JPEG.
param 3 -> X axis margin from left
param 4 -> Y axis margin from top
param 5 -> width of the image
param 6 -> height of the image
*/doc.addImage(img, ‘JPEG’, 150, 10, 40, 20); //specify the image format in the function. Can be one among JPEG/PNG.


Define Header and Footer in PDF:
The header and footer are set using the didDrawPage function of jsPDF which is used to detect if a complete page has been drawn or not. We have already seen above how to use html attributes to render the PDF body. In this section, we will see how to use the head and body attributes of autoTable.

Note that the head and body define the head and body of the table and not the header or footer of the page.

This means any custom styling that you write will be applied to the head or body of the table and not the page header or footer.

e.g.

// toPdf function to print the pdfBody which is an array of jsonobjects holding the table data into pdf.
ctrl.toPdf = function(pdfBody) {
    var doc = new jsPDF();
    var totalPagesExp = "{total_pages_count_string}"; //placeholder for total number of pages 
    doc.autoTable({
        styles: {
            cellPadding: 0.5,
            fontSize: 12
        },
        //startY: 30, /* if start position is fixed from top */
        tableLineColor: [0, 0, 0], //choose RGB
        tableLineWidth: 0.5, //table border width
        head: headRows(), //define head rows
        body: bodyRows(pdfBody.length, pdfBody),
        /*first param is the number of rows in total and second param is the actual content. This is the actual body of the pdf between the header and footer*/
        bodyStyles: {
            margin: 40,
            fontSize: 10,
            lineWidth: 0.2,
            lineColor: [0, 0, 0]
        },
        /*whatever you write in didDrawPage comes in every page. Header or footer is determined from startY position in the functions.*/
        didDrawPage: function(data) {
     
            // Header
            doc.setFontSize(12);
            var fileTitle = "Test document";
            var img = "data:image/jpg;base64,jtSEjBYHkTNc5E………………………"; //use addImage as explained earlier.
            doc.text(fileTitle, 14, 35);
            doc.addImage(img, 'JPEG', 157, 10, 40, 20);
     
            // Footer
            var pageSize = doc.internal.pageSize;
            //jsPDF 1.4+ uses getHeight, <1.4 uses .height
            var pageHeight = pageSize.height ? pageSize.height : pageSize.getHeight();
            // jsPDF 1.4+ uses getWidth, <1.4 uses .width
            var pageWidth = pageSize.width ? pageSize.width : pageSize.getWidth();
            doc.autoTable({
                html: '#footer_text_pdf',
                startY: pageHeight - 50,
                styles: {
                    halign: 'center',
                    cellPadding: 0.2
                }
            });
            var str = "Page " + doc.internal.getNumberOfPages()
            // Total page number plugin only available in jspdf v1.0+
            if (typeof doc.putTotalPages === 'function') {
                str = str + " of " + totalPagesExp;
            }
            doc.setFontSize(10);
            doc.text(str, data.settings.margin.left, pageHeight - 10);
        },
        margin: {
            bottom: 60, //this decides how big your footer area will be
            top: 40 //this decides how big your header area will be.
        }
    });
    // Total page number plugin only available in jspdf v1.0+
    if (typeof doc.putTotalPages === 'function') {
        doc.putTotalPages(totalPagesExp);
    }
    fileName = 'Statement.pdf'
    doc.save(fileName); //this downloads a copy of the pdf in your local instance.
};

Let’s look at the headRows and bodyRows function:
/*define key for identifying column and value to display in PDF table head. (Table head and not page header) */
function headRows() {
    return [{
        id: 'ID',
        name: 'Name',
        email: 'Email',
        city: 'City',
        expenses: 'Sum'
    }];
}
/*define key for identifying column and value to display in PDF table footer. (Table footer and not page footer)*/
function footRows() {
    return [{
        id: 'ID',
        name: 'Name',
        email: 'Email',
        city: 'City',
        expenses: 'Sum'
    }];
}
/* bodyRows returns the pdfData in the form of jsonArray which is then parsed by the autoTable function */
function bodyRows(rowCount, pdfBody) {
    rowCount = rowCount || 10;
    let body = [];
    for (var j = 0; j < rowCount; j++) {
        body.push({
            id: j+1,
            name: pdfBody[j].name,
            email: pdfBody[j].email,
            city: pdfBody[j].city,
            expenses: pdfBody[j].expenses,
        });
    }
    return body;
}

You can apply styling and change font as shown below:
examples.custom = function() {
var doc = new jsPDF();
doc.setFontSize(12);
doc.setFontStyle('arial');
doc.autoTable({
    head: head,
    body: body,
    startY: 15,
    /* apply styling to table body */
    bodyStyles: {
        valign: 'top'
    },
    /* apply global styling */
    styles: {
        cellWidth: 'wrap',
        rowPageBreak: 'auto',
        halign: 'justify'
    },
    /* apply styling specific to table columns */
    columnStyles: {
        text: {
            cellWidth: 'auto'
        }
    }
});
return doc;
};

There are three out of the box themes for the PDF:
examples.themes = function() {
    var doc = new jsPDF();
    doc.setFontSize(12);
    doc.setFontStyle('bold');
    doc.text('Theme "striped"', 14, 16); //default
    doc.autoTable({
        head: headRows(),
        body: bodyRows(5),
        startY: 20
    });
    doc.text('Theme "grid"', 14, doc.autoTable.previous.finalY + 10);
    doc.autoTable({
        head: headRows(),
        body: bodyRows(5),
        startY: doc.autoTable.previous.finalY + 14,
        theme: 'grid'
    });
    doc.text('Theme "plain"', 14, doc.autoTable.previous.finalY + 10);
    doc.autoTable({
        head: headRows(),
        body: bodyRows(5),
        startY: doc.autoTable.previous.finalY + 14,
        theme: 'plain'
    });
return doc;
};

There is a lot more than you can do with the jsPDF-autoTable plugin and this is only a kick-starter that I have provided.

Go ahead and make your own beautiful PDFs! 


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.