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..