Gerando relatórios na Web

No universo de aplicações desktop existe uma demanda muito grande de relatórios, também existente na WEB.

Pesquisando sobre as libs/frameworks uma delas me chamou a atenção pela simplicidade e de facilidade no aprendizado, o pdfmake, que é uma lib javascript onde possibilita imprimir PDFs diretamente no navegador ou delegue-o no backend do ecosistema NodeJS, podendo ser utilizada a mesma definição de documento em ambos os casos.

Neste post irei abordar a utilização do pdfmake do lado do client(Front-end), e o framework Angular 4 no front-end, mas você pode usar o que desejar.

Verificação de instalações!

Antes de começar é necessário certificar que o Nodejs 6.9.5 (ou Superior), o @angular/cli e o NPM 3 (ou Superior) estão instalados.

$ node -v 
$ npm -v
$ ng -v

Criei um diretório com nome pdf-client.

$ mkdir pdf-client 
$ cd pdf-client

Utilizei o angular/cli para poder criar o projeto

$ ng new myPdf
$ cd myPdf

Pronto! Nosso projeto está criado, para testar basta roda o comando:

$ ng serve

Configurando o pdfmake!

Usei cdnjs, CDN ou Rede de Fornecimento de Conteúdo, onde são hospedados todas as bibliotecas populares.

Alterei o index.html dentro do diretório /myPDF/src/

<!doctype html>  
 <html>  
  <head>  
    <meta charset="utf-8">  
    <title>MyPdf</title>  
    <base href="/">  
    <meta name="viewport" content="width=device-width, initial-scale=1">  
    <link rel="icon" type="image/x-icon" href="favicon.ico">  
    <!-- Adicionei as duas linhas abaixo --> 
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.28/pdfmake.js"></script>  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.28/vfs_fonts.js"></script>  
  </head>  
  <body>  
    <app-root>Loading...</app-root>  
  </body>  
 </html>

Utilizando o pdfmake!

Alterei o app.component.html que se encontra no diretório /myPdf/src/app/_

<div>  
    <h1></h1>  
    <button class="btn-default" (click)="openPdf()">Print default</button>  
    <button class="btn-primary" (click)="openPdfColors()">Print colorful</button>  
    <button class="btn-red" (click)="openPdfImage()">Print image</button>  
 <div>

Alterei o app.component.css que se encontra no diretório /myPdf/src/app/

.btn-default {  
   background-color: #008CBA;  
   border: none;  
   color: white;  
   padding: 15px 32px;  
   text-align: center;  
   text-decoration: none;  
   display: inline-block;  
   font-size: 16px;  
 }  
 .btn-primary {  
   background-color: #4CAF50;  
   border: none;  
   color: white;  
   padding: 15px 32px;  
   text-align: center;  
   text-decoration: none;  
   display: inline-block;  
   font-size: 16px;  
 }  
 .btn-red {  
   background-color: #f44336;  
   border: none;  
   color: white;  
   padding: 15px 32px;  
   text-align: center;  
   text-decoration: none;  
   display: inline-block;  
   font-size: 16px;  
 }

Criei um service onde ficarão as funções e dados

$ ng g s app.service

Adicionei o código no app.service.ts dentro do diretório /myPdf/src/app/

import { Injectable } from '@angular/core';  
 declare let pdfMake: any;  
 @Injectable()  
 export class AppService {  
  public products = [   
       {'code': 1, 'description': 'Nestlé Cookie 100g'},  
       {'code': 2, 'description': 'Danico biscuit 100g'},  
       {'code': 3, 'description': 'Rice Uncle Joao 5kg'},  
       {'code': 3, 'description': 'Coffee milk 1kg'}  
  ];  
  constructor() { }  
  getProduct() {  
       return this.products;  
  }  
  reportDefault(docDefinition) {  
       pdfMake.createPdf(docDefinition).open()  
  }  
 }

Importei o app.service.ts para o app.module.ts dentro do diretório /myPdf/src/app/

 import { BrowserModule } from '@angular/platform-browser';  
 import { NgModule } from '@angular/core';  
 import { FormsModule } from '@angular/forms';  
 import { HttpModule } from '@angular/http';  
 import { AppComponent } from './app.component';  
 import { AppService } from './app.service';  
 @NgModule({  
  declarations: [  
   AppComponent  
  ],  
  imports: [  
   BrowserModule,  
   FormsModule,  
   HttpModule  
  ],  
  providers: [AppService],  
  bootstrap: [AppComponent]  
 })  
 export class AppModule { }

Alterei o app.component.ts dentro do diretório /myPdf/src/app/

 import { Component } from '@angular/core';  
 import { AppService } from './app.service'  
 @Component({  
  selector: 'app-root',  
  templateUrl: './app.component.html',  
  styleUrls: ['./app.component.css']  
 })  
 export class AppComponent {  
  title = 'Pdfmake';  
  constructor(private service: AppService) {}  
  openPdf() {  
   let body: any = [];   
   const header: any = [  
    {text: 'Code', fontSize: 10, bold: true, alignment: 'center'},  
    {text: 'Description', fontSize: 10, bold: true, alignment: 'left'}  
   ]  
   body.push(header);  
   let products = this.service.getProduct().map((data) => {  
    let arr: any = [  
     {text: data.code, fontSize: 9, alignment: 'center'},  
     {text: data.description, fontSize: 9}  
    ]  
    body.push(arr);  
    return arr  
   })  
   const docDefinition = {  
    content: [  
     {text: 'Report of products', style: 'subheader'},  
     {  
      style: 'tableExample',  
      table: {  
       widths: ['auto', 300],  
       body: body  
      }  
     }  
    ],  
    styles: {  
     header: {  
      fontSize: 18,  
      bold: true,  
      margin: [0, 0, 0, 10]  
     },  
     subheader: {  
      fontSize: 16,  
      bold: true,  
      margin: [0, 10, 0, 5]  
     },  
     tableExample: {  
      margin: [0, 5, 0, 25]  
     },  
     tableHeader: {  
      bold: true,  
      fontSize: 13,  
      color: 'black'  
     }  
    },  
    defaultStyle: {  
     // alignment: 'justify'  
    }  
   }    
   this.service.reportDefault(docDefinition);  
  }   
  openPdfColors() {  
   let body: any = [];  
   const header: any = [  
    {text: 'Code', fontSize: 10, bold: true, alignment: 'center', fillColor: '#eeffee'},  
    {text: 'Description', fontSize: 10, bold: true, alignment: 'left', fillColor: '#eeffee'}  
   ]  
   body.push(header);  
   let products = this.service.getProduct().map((data) => {  
    let arr: any = [  
     {text: data.code, fontSize: 9, alignment: 'center', fillColor: '#eeeeff'},  
     {text: data.description, fontSize: 9, fillColor: '#eeeeff'}  
    ]  
    body.push(arr);  
    return arr  
   })  
   const docDefinition = {  
    content: [  
     {text: 'Report of products', style: 'subheader'},  
     {  
      style: 'tableExample',  
      table: {  
       widths: ['auto', 300],  
       body: body  
      }  
     }  
    ],  
    styles: {  
     header: {  
      fontSize: 18,  
      bold: true,  
      margin: [0, 0, 0, 10]  
     },  
     subheader: {  
      fontSize: 16,  
      bold: true,  
      margin: [0, 10, 0, 5]  
     },  
     tableExample: {  
      margin: [0, 5, 0, 25]  
     },  
     tableHeader: {  
      bold: true,  
      fontSize: 13,  
      color: 'black'  
     }  
    },  
    defaultStyle: {  
     // alignment: 'justify'  
    }  
   }    
   this.service.reportDefault(docDefinition);  
  }  
  openPdfImage() {  
   let body: any = [];  
   const header: any = [  
    {text: 'Code', fontSize: 10, bold: true, alignment: 'center', fillColor: '#eeffee'},  
    {text: 'Description', fontSize: 10, bold: true, alignment: 'left', fillColor: '#eeffee'},  
    {text: 'image', fontSize: 10, bold: true, alignment: 'left', fillColor: '#eeffee'}  
   ]  
   body.push(header);  
   let products = this.service.getProduct().map((data) => {  
    let arr: any = [  
     {text: data.code, fontSize: 9, alignment: 'center', fillColor: '#eeeeff'},  
     {text: data.description, fontSize: 9, fillColor: '#eeeeff'},  
     {image: 'data:image/jpeg;base64, 'AQUI EXISTE A IMAGEM, QUE SE ENCONTRA NO GITHUB', width: 25, fillColor: '#eeeeff'}  
    ]  
    body.push(arr);  
    return arr  
   })  
   const docDefinition = {  
    content: [  
     {text: 'Report of products', style: 'subheader'},  
     {  
      style: 'tableExample',  
      table: {  
       widths: ['auto', 300, 'auto'],  
       body: body  
      }  
     }  
    ],  
    styles: {  
     header: {  
      fontSize: 18,  
      bold: true,  
      margin: [0, 0, 0, 10]  
     },  
     subheader: {  
      fontSize: 16,  
      bold: true,  
      margin: [0, 10, 0, 5]  
     },  
     tableExample: {  
      margin: [0, 5, 0, 25]  
     },  
     tableHeader: {  
      bold: true,  
      fontSize: 13,  
      color: 'black'  
     }  
    },  
    defaultStyle: {  
     // alignment: 'justify'  
    }  
   }    
   this.service.reportDefault(docDefinition);  
  }   
 }
$ ng serve

Report

Conclusão

Com o pdfmake é possivel criar vários layouts de relatórios, inclusive existe o [playground][palyground]{:target=”_blank”} com vários exemplos prontos e também é possível modelar o seu código antes de aplicar no seu projeto.

Até o momento está me atendendo muito bem nos meus projetos.

Estou disponibilizando o projeto no GitHub, acesse lá.

Wharley Ornelas

Wharley Ornelas

Meu nome é Wharley Ornelas. Desenvolvedor Full-Stack, com mais de 15 anos de experiência de software. Membro ativo em comunidade de desenvolvimento..