PDF - Steps (that a low code user understands) to create a pdf using a 3rd party

I’ve been using Wappler for a couple of years now and it is an excellent low code development tool. I have for the past year or so been trying to work out how to generate a pdf from a webpage / report that i have created that is built from a significant number (10+) server connects, images and charts.
Whenever the subject of pdf generation is raised in the community it normally gets a response that there are a number of third parties / APIs that will do the job.
An observation is that the experts who respond suggesting 3rd parties seem to be quite expert and at least from my perspective start talking about things that I am not very aware/proficient at. One of the reasons i like wappler and use it is the low code promise but in this case at least my perspective is that trying to get a 3rd party to generate a pdf with data being generated from mysql db queries is beyond my low code capability.

My ask - is it possible for someone to spell out exactly the steps that are required to generate a pdf from a web page that has the following components:

  • multiple server connects
  • ApexChart - charts
  • images (from a sql query)
  • reasonable level of security as the data will be company confidential
  • im using nodejs, heroku, mysql and the latest wappler version

I suspect that if a guide which covers all the steps that a low code user (like myself) can understand and create would remove the background rumble of pdf questions and requests.

As an indication of what Ive tried:

  • server side API action
  • server side EJS to PDF (adding puppeteer)
  • client side API - Action, Data Source and Form
  • 3rd parties - DocRaptor, PDFMonkey and others

Would really love to find a way to do this.

Thanks in advance

For generating PDF invoice I used

PDF make might look complicated (which is true to be honest) but instead you will get a lot more customization options.

For instance:

  • Footer and header options
  • Watermarks
  • Automatic page counter
  • Programmable options (like insert fooder and header only from page 2)
  • pdfmake has a great playground where you can test and visually see all your PDF - http://pdfmake.org/playground.html

Thanks , very happy to try another 3rd party but do you have or could you create a step by step guide on how to do this?

As an example what should the report/template (that you want to convert to a pdf) look like - can you use multiple queries to populate it? should you use the API Form which i tried and it generated probably over a 100 fields but then i got lost !!

What I wrote above is not about converting WEB page to PDF it is generating PDF based on Wappler APIs outputs.
Unfortunately there is no step-by-step instruction which will lead do desired result.

I would suggest you to read how to create custom extension in Wappler - Writing Custom Modules and Formatters (NodeJS)

Your question seems a bit confusing.
you want a server side PDF generator?
You want to include Apex which is a client side technology?

Hi Brian,

Thanks, from your response i can now rule out using server side PDF generation, but the ask is still the same.
I have a number of reports/web pages - see attached screenshot / example of the type of pdf i would like to automatically generate, adding header, footer etc information.

Thanks

I confess, unless there was a huge number to generate i would create the page, flag non printed elements from printing and then simply use the print to PDF feature of OSX/Windows and manually email to clients but I guess you need a fully automated solution

I do really…although the stop gap is to have a help button which informs clients that they can get a pdf of the report by using the browser print capability but this cant be a long term solution.

It could be a long term solution if done correctly. The print button for each browser is in a different location. Therefore it is best to create a print button that emulates the browser’s print button by adding onclick="window.print();return false to the button.

To format a printed page, use the CSS3 Paged Media (@page rule).

The problem is that not all browser have incorporated Paged Media. The uptake is about 70% which is too low for general use.

Along comes Page.js a polyfill for non-complying browsers.

For more,

This is what a dynamically created PDF-preview looks like in Wappler

If you want to try it out, don’t forget to add async when loading the polyfill for a dynamic page.

<script async src="https://unpkg.com/pagedjs/dist/paged.polyfill.js" defer></script>
3 Likes

Excellent many thanks Ben … Will give it a go

hav eyou tried using this - you can pass URL of your page to generate a PDF from it. depending on your page load time, you can add wait time as well within the custom module code.

its not fully no-code solution. but with little effort you can have nice solution. we’ve used this for complex chart based PDF generation with images being fetched from private S3 source - works well.

1 Like

Thanks Nishkarsh, will give it a go

Hello,

If you can install libraries on your server try out wkhtmltopdf.

It is probably the most widely used tool to convert html to pdf. It also has command line interface so you can do shell execute on server.

https://wkhtmltopdf.org/

I have never used it directly but there are a few open source EPRs build in Python that use this extensively.

HTH

I know it has been a while, but I am using this library and am struggling with getting headers and footers generated with a custom module.

Did you manage to get yours generated and would you be willing to share your code?

I am importing mine as a js template and then running the library within wappler.

TIA

wappler code

const fs = require('fs');
const path = require('path');
const PdfPrinter = require('pdfmake');

exports.perdiem = async function (options = {}) {
    const filename = this.parseOptional(options.filename, 'output.pdf');
    const companyName = this.parseRequired(options.companyName, '*', 'The company name is required');
    const companyFiscalID = this.parseRequired(options.companyFiscalID, '*', 'The company fiscal ID is required');
    const staffName = this.parseRequired(options.staffName, '*', 'The staff name is required');
    const staffFiscalID = this.parseRequired(options.staffFiscalID, '*', 'The staff fiscal ID is required');
    const rateNacional = this.parseRequired(options.rateNacional, '*', 'The rate national is required');
    const rateInternacional = this.parseRequired(options.rateInternacional, '*', 'The rate international is required');

    let template;

    try {

        // Import JS template
        //test js file here "http://pdfmake.org/playground.html"

        const templatePath = path.join(process.cwd(), 'public/assets/templates/Perdiem.js');
        template = require(templatePath);

        console.log('Template imported with sucess');
    } catch (error) {
        console.error('Error importing template:', error);
        throw error;
    }

    // Clones original template
    const docDefinition = JSON.parse(JSON.stringify(template));


    // replace template vars
    function replaceVars(obj) {
        if (typeof obj === 'string') {
            return obj
                .replace('{{companyName}}', companyName || '')
                .replace('{{companyFiscalID}}', companyFiscalID || '')
                .replace('{{staffName}}', staffName || '')
                .replace('{{staffFiscalID}}', staffFiscalID || '')
                .replace('{{rateNacional}}', rateNacional || '')
                .replace('{{rateInternacional}}', rateInternacional || '');
        } else if (Array.isArray(obj)) {
            return obj.map(replaceVars);
        } else if (typeof obj === 'object' && obj !== null) {
            const newObj = {};
            for (const k in obj) {
                newObj[k] = replaceVars(obj[k]);
            }
            return newObj;
        }
        return obj;
    }

    const filledDoc = replaceVars(docDefinition);

    // font definition
    const fonts = {
        Roboto: {
            normal: path.join(process.cwd(), 'public/assets/fonts/Roboto-Regular.ttf'),
            bold: path.join(process.cwd(), 'public/assets/fonts/Roboto-Medium.ttf'),
            italics: path.join(process.cwd(), 'public/assets/fonts/Roboto-Italic.ttf'),
            bolditalics: path.join(process.cwd(), 'public/assets/fonts/Roboto-MediumItalic.ttf')
        }
    };

    const printer = new PdfPrinter(fonts);
    const pdfDoc = printer.createPdfKitDocument(filledDoc);

    const chunks = [];

    const buffer = await new Promise((resolve, reject) => {
        pdfDoc.on('data', chunk => chunks.push(chunk));
        pdfDoc.on('end', () => resolve(Buffer.concat(chunks)));
        pdfDoc.on('error', err => {
            console.error('Erro ao gerar PDF:', err);
            reject(err);
        });
        pdfDoc.end();
    });

    // Stores file temporarily, to be used in Wappler “File Upload”
    const uploadDir = path.join(process.cwd(), 'public/uploads/company/perdiem');
    if (!fs.existsSync(uploadDir)) {
        fs.mkdirSync(uploadDir, { recursive: true });
    }

    const { v4: uuidv4 } = require('uuid');
    const fileName = `perdiem-${uuidv4()}.pdf`;
    const tempPath = path.join(uploadDir, fileName);

    fs.writeFileSync(tempPath, buffer);

    return {
        fileToUpload: {
            path: tempPath,
            filename: fileName,
            contentType: 'application/pdf'
        }
    };

};

js code

var dd = {
    defaultStyle: {
    font: 'Roboto'
  },
    header: function(currentPage, pageCount) { 
        {
            return {
            table: {
              widths: ['auto', '*', 'auto'],
              heights: 50,
              body: [
                [
                  {
                    image: companyImage,
                    width: 40,
                    margin: [40, 5, 0, 0],
                    opacity: 1
                  },
                  {
                    text: '{{companyName}}',
                    alignment: 'center',
                    fontSize: 12,
                    bold: false,
                    margin: [-200, 19, 0, 0],
                    opacity: 0.7
                  },
                  {
                    text: 'NIPC {{companyFiscalID}}',
                    alignment: 'right',
                    fontSize: 12,
                    margin: [0, 19, 45, 0],
                    opacity: 0.7
                  }
                ]
              ]
            },
            layout: 'noBorders'
          };
        }
    },
    
    footer: function(currentPage, pageCount) { 
        {
            return {
                table: {
                  widths: ['*', 'auto','*'], // coluna esquerda expansiva, direita justa
                  body: [
                    [
                      {
                        image: DOLogo,
                        width: 30,
                        alignment: 'left',
                        margin: [40, -25, 10, 25],
                        opacity: 1
                      },
                      {
                        text: 'DigitalOffice',
                        alignment: 'center',
                        fontSize: 12,
                        bold: false,
                        margin: [-350, -13, 0, 0],
                        opacity: 0.7
                      },
                      {
                        text: `${currentPage} / ${pageCount}`,
                        alignment: 'right',
                        margin: [10, -12, 50, 5],
                        opacity: 0.7
                      }
                    ]
                  ]
                },
                layout: 'noBorders'
              };
        }
    },

        
    
	content: [
	{ text: '\n\n\n'},
	
	
	{
		style: 'tableExample',
		color: '#444',
		table: {
			widths: [510, '*', 200,'*'],
			headerRows: 1,
			body: [
				[
					{
						text: 'Ajudas de Custo',
						style: 'tableHeader',
						borderColor: ['#ffffff', '#0cccc0', '#ffffff', '#0cccc0'],
						alignment: 'center',
						fontSize: 20, 
						bold: true,
						
					},
				],
				[
					{
					    text:'', 
					    borderColor: ['#ffffff', '#ffffff', '#ffffff', '#ffffff'],
					}
				],
			],
		},
	},
		
		{ 
		    text: '\n<Ano> / <Mês>', 
		    style: 'header',
		    fontSize: 20, 
			bold: true,
			alignment: 'right',
		},
		
	
		
		
		
		{ text: ' ', style: 'header'},
		
		{
			style: 'tableExample',
			color: '#444',
			table: {
				widths: [90, '*', 120],
				headerRows: 1,
				// keepWithHeaderRows: 1,
				body: [
					[
						{
							text: 'Colaborador',
							style: 'tableHeader',
							
							borderColor: ['#ffffff', '#0cccc0', '#ffffff', '#0cccc0'],
							alignment: 'left',
							fontSize: 15, 
							bold: true
						},
						{
							text: '{{staffName}}',
							style: 'tableHeader',
							
							borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
							alignment: 'right',
							fontSize: 13, 
							bold: false
						},
						{
							text: 'NIF {{staffFiscalID}}',
							style: 'tableHeader',
							borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
							alignment: 'right',
							fontSize: 13, 
							bold: false
						},
					],
				],
			},
		},
		
		{ 
	        text: '\n\n\n\n',
	        style: 'header',
	        fontSize: 8, 
	    },
		{
    		style: 'tableExample',
    		color: '#444',
    		table: {
    			widths: [510, '*', 200,'*'],
    			headerRows: 1,
    			body: [
    				[
    					{
                          text: [
                            { text: 'Resumo ', fontSize: 14, bold: true },
                            { text: '(mensal)', fontSize: 10, italics: true }
                          ],
                          style: 'tableHeader',
                          borderColor: ['#ffffff', '#0cccc0', '#ffffff', '#0cccc0'],
                          alignment: 'center'
                        },
    				],
    				[
    					{
    					    text:'', 
    					    borderColor: ['#ffffff', '#ffffff', '#ffffff', '#ffffff'],
    					}
    				],
    			],
    		},
    	},
    	
    	{ 
	        text: '\n\n',
	        style: 'header',
	        fontSize: 11,
	        bold: true
	    },
		{
			style: 'tableExample',
			color: '#444',
			table: {
				widths: [80, 80, 80, 80,80,'*'],
				headerRows: 1,
				// keepWithHeaderRows: 1,
				body: [
					[
						{
							text: 'Nacional',
							style: 'tableHeader',
							borderColor: ['#ffffff', '#ffffff', '#0cccc0', '#0cccc0'],
							alignment: 'left',
							fontSize: 11, 
							bold: true
						},
						{
							text: '100%',
							style: 'tableHeader',
							borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: '75%',
							style: 'tableHeader',
							
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: '50%',
							style: 'tableHeader',
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: '25%',
							style: 'tableHeader',
							
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: 'Total',
							style: 'tableHeader',
							borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						
					],
					
				
					[
						{
						    text: 'Dias',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						}
					],
					[
						{
						    text: 'Reembolso',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						}
					],
					
				],
			},
		},
		
		{ 
	        text: '\n\n',
	        style: 'header',
	        fontSize: 11,
	        bold: true
	    },
		
		{
			style: 'tableExample',
			color: '#444',
			table: {
				widths: [80, 80, 80, 80,80,'*'],
				headerRows: 1,
				// keepWithHeaderRows: 1,
				body: [
					[
						{
							text: 'Internacional',
							style: 'tableHeader',
							borderColor: ['#ffffff', '#ffffff', '#0cccc0', '#0cccc0'],
							alignment: 'left',
							fontSize: 11, 
							bold: true
						},
						{
							text: '100%',
							style: 'tableHeader',
							borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: '75%',
							style: 'tableHeader',
							
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: '50%',
							style: 'tableHeader',
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: '25%',
							style: 'tableHeader',
							
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: 'Total',
							style: 'tableHeader',
							borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						
					],
					
				
					[
						{
						    text: 'Dias',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						}
					],
					[
						{
						    text: 'Reembolso',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						}
					],
					
				],
			},
		},
		
		{ 
	        text: '\n\n',
	        style: 'header',
	        fontSize: 10,
	        bold: true
	    },
		
		{
    		style: 'tableExample',
    		color: '#444',
    		table: {
    			widths: [510, '*', 200,'*'],
    			headerRows: 1,
    			body: [
    				[
    					{
    						text: 'Total',
    						style: 'tableHeader',
    						borderColor: ['#ffffff', '#ffffff', '#ffffff', '#0cccc0'],
    						alignment: 'center',
    						fontSize: 14, 
    						bold: true,
    						
    					},
    				],
    				[
    					{
    					    text:'', 
    					    borderColor: ['#ffffff', '#ffffff', '#ffffff', '#ffffff'],
    					}
    				],
    			],
    		},
    	},
    	
    	{ 
	        text: '\n',
	        style: 'header',
	        fontSize: 10,
	        bold: true
	    },
		
		{
			style: 'tableExample',
			color: '#444',
			table: {
				widths: [80, 80, 80, 80,80,'*'],
				headerRows: 1,
				// keepWithHeaderRows: 1,
				body: [
					[
						{
							text: 'Nac. + Int.',
							style: 'tableHeader',
							borderColor: ['#ffffff', '#ffffff', '#0cccc0', '#0cccc0'],
							alignment: 'left',
							fontSize: 11, 
							bold: true
						},
						{
							text: '100%',
							style: 'tableHeader',
							borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: '75%',
							style: 'tableHeader',
							
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: '50%',
							style: 'tableHeader',
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: '25%',
							style: 'tableHeader',
							
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: 'Total',
							style: 'tableHeader',
							borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						
					],
					
				
					[
						{
						    text: 'Dias',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						}
					],
					[
						{
						    text: 'Reembolso',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						}
					],
					
				],
			},
		},
		
		
		
		{ 
	        text: '\n\n\n\n\n\n\n\n',
	        style: 'header',
	        fontSize: 8, 
	    },
	    
		{ 
	        text: '\n______________________________________________________________________________________________________________________________________________',
	        style: 'header',
	        fontSize: 8, 
	        opacity: 0.5,
	    },
	    {
          text: [
            { text: '\nElaborado nos termos e para os efeitos do ', fontSize: 8, bold: false,opacity: 0.5 },
            { text: 'ART. 2.º N.º 3 Alínea d) do Código do IRS e Ard. 23.º N.º 1 Alínea d) do Código do IRC.\n', fontSize: 8,opacity: 0.5 },
            { text: '\nAbonos respeitantes a deslocações no país e ao estrangeiro, para assistência a clientes, prospecção e realização dos\n', fontSize: 8,opacity: 0.5 },
            { text: 'proveitos e manutenção da fonte produtora.', fontSize: 8,opacity: 0.5 },
            
          ]
        },
	    
	    { 
	        text: '______________________________________________________________________________________________________________________________________________',
	        style: 'header',
	        fontSize: 8, 
	        opacity: 0.5,
	    },
		{ 
	        text: '\nValores para Ajudas de Custo no ano vigente: No país: {{rateNacional} €  |  No estrangeiro: {{rateInternacional}} €',
	        style: 'header',
	        fontSize: 8,
	        opacity: 0.5,
	    },
	    { 
	        text: '______________________________________________________________________________________________________________________________________________',
	        style: 'header',
	        fontSize: 8,
	        opacity: 0.5,
	    },
	   
	    
	    //page break - trip detail follows
		{ text: '', pageBreak: 'after' },
	    
	    { text: '\n\n', style: 'header'},
		
		{
    		style: 'tableExample',
    		color: '#444',
    		table: {
    			widths: [510, '*', 200,'*'],
    			headerRows: 1,
    			body: [
    				[
    					{
                          text: [
                            { text: 'Detalhe ', fontSize: 14, bold: true },
                            { text: '(por viagem)', fontSize: 10, italics: true }
                          ],
                          style: 'tableHeader',
                          borderColor: ['#ffffff', '#0cccc0', '#ffffff', '#0cccc0'],
                          alignment: 'center'
                        },
    				],
    				[
    					{
    					    text:'', 
    					    borderColor: ['#ffffff', '#ffffff', '#ffffff', '#ffffff'],
    					}
    				],
    			],
    		},
    	},
		
		{ text: '\n', style: 'header'},
		
		{
			style: 'tableExample',
			color: '#444',
			table: {
				widths: [61, 102, 102,102,102],
				headerRows: 1,
				// keepWithHeaderRows: 1,
				body: [
					[
						{
							text: '# dias',
							style: 'tableHeader',
							borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'left',
							fontSize: 12, 
							bold: true
						},
						{
							text: 'Data de início',
							style: 'tableHeader',
							
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'left',
							fontSize: 12, 
							bold: true
						},
						{
							text: 'Data de Fim',
							style: 'tableHeader',
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'left',
							fontSize: 12, 
							bold: true
						},
						{
							text: 'Hora de início',
							style: 'tableHeader',
							
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'left',
							fontSize: 12, 
							bold: true
						},
						{
							text: 'Hora de Fim',
							style: 'tableHeader',
							borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
							alignment: 'left',
							fontSize: 12, 
							bold: true
						},
						
					],
					
				
					[
						{
						    text: '#NUMDIAS#',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						}
					],
					
				],
			},
		},
		{ 
	        text: ' ',
	        style: 'header',
	        fontSize: 8, 
	    },
		{
			style: 'tableExample',
			color: '#444',
			table: {
				widths: [61,213, '*'],
				headerRows: 1,
				// keepWithHeaderRows: 1,
				body: [
					[
						
						{
							text: ' ',
							style: 'tableHeader',
							
							borderColor: ['#ffffff', '#ffffff', '#ffffff', '#ffffff'],
							alignment: 'left',
							fontSize: 12, 
							bold: true
						},
						{
							text: 'País',
							style: 'tableHeader',
							
							borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'left',
							fontSize: 12, 
							bold: true
						},
						{
							text: 'Localidade(s)',
							style: 'tableHeader',
							borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
							alignment: 'left',
							fontSize: 12, 
							bold: true
						},
						
					],
					
				
					[
						{
						    text: ' ',
						    borderColor: ['#ffffff', '#ffffff', '#ffffff', '#ffffff'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#ffffff', '#ffffff', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						}
					],
					
				],
			},
		},
		{ 
	        text: ' ',
	        style: 'header',
	        fontSize: 8, 
	    },
		{
			style: 'tableExample',
			color: '#444',
			table: {
				widths: [61,213, '*'],
				headerRows: 1,
				// keepWithHeaderRows: 1,
				body: [
					[
						
						{
							text: ' ',
							style: 'tableHeader',
							
							borderColor: ['#ffffff', '#ffffff', '#ffffff', '#ffffff'],
							alignment: 'left',
							fontSize: 12, 
							bold: true
						},
						{
							text: 'Descrição do serviço\n(direito a ajudas de custo)',
							style: 'tableHeader',
							
							borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'left',
							fontSize: 12, 
							bold: true
						},
						{
							text: 'Notas',
							style: 'tableHeader',
							borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
							alignment: 'left',
							fontSize: 12, 
							bold: true
						},
						
					],
					
				
					[
						{
						    text: ' ',
						    borderColor: ['#ffffff', '#ffffff', '#ffffff', '#ffffff'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#ffffff', '#ffffff', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						}
					],
					
				],
			},
		},
		
		{ 
	        text: ' ',
	        style: 'header',
	        fontSize: 8, 
	    },
		
		{
			style: 'tableExample',
			color: '#444',
			table: {
				widths: [61,65, 65, 65, 65,65,'*'],
				headerRows: 1,
				// keepWithHeaderRows: 1,
				body: [
					[
						{
							text: '',
							style: 'tableHeader',
							borderColor: ['#ffffff', '#ffffff', '#ffffff', '#ffffff'],
							alignment: 'left',
							fontSize: 12, 
							bold: true
						},
						{
							text: '',
							style: 'tableHeader',
							borderColor: ['#ffffff', '#ffffff', '#0cccc0', '#0cccc0'],
							alignment: 'left',
							fontSize: 12, 
							bold: true
						},
						{
							text: '100%',
							style: 'tableHeader',
							borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: '75%',
							style: 'tableHeader',
							
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: '50%',
							style: 'tableHeader',
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: '25%',
							style: 'tableHeader',
							
							borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						{
							text: 'Total',
							style: 'tableHeader',
							borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
							alignment: 'center',
							fontSize: 12, 
							bold: true
						},
						
					],
					
				
					[
						{
						    text: '',
						    borderColor: ['#ffffff', '#ffffff', '#ffffff', '#ffffff'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: 'Dias',
						    borderColor: ['#ffffff', '#ffffff', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#ffffff', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '---',
						    borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						}
					],
					[
						{
						    text: '',
						    borderColor: ['#ffffff', '#ffffff', '#ffffff', '#ffffff'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: 'Reembolso',
						    borderColor: ['#ffffff', '#ffffff', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#ffffff', '#ffffff', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#0cccc0', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						},
						{
						    text: '--- €',
						    borderColor: ['#0cccc0', '#0cccc0', '#ffffff', '#0cccc0'],
						    fontSize: 10, 
							bold: false
						}
					],
					
				],
			},
		},
		{ 
	        text: '\nAprovado digitalmente em <dd/mm/yyyy>, por <NomeAprovador>',
	        style: 'header',
	        alignment: 'right',
	        fontSize: 8,
	        opacity: 0.5,
        },
		 													
	]
	
}
module.exports = dd;
  1. Your so said "Wappler code" and "JS Code" should be combined in 1 file. Maybe you have your own logic/workflow regarding "templates", but I'm not aware of them
  2. I'm doing validation/logic/etc. on a Wappler side aka. data bindings that I've setup in hjson file
  3. Here is my example of footer and header:
header: (currentPage, pageCount) => {
  return [
    {
      margin: [30, 20, 30, 0],
      table: {
        widths: ['50%', '50%'],
        body: [
          [
            {
              image: 'public/assets/images/reports/Placeholder-Logo.png',
              fit: [100, 50],
              alignment: 'left',
              margin: [0, 0, 0, 0]
            },
            {
              stack: [
                {
                  text: '123 Example Street, Sampletown, Metrocity, 12345',
                  style: 'headingText',
                  alignment: 'right'
                },
                {
                  text: 'P.O. BOX 4567, Otherplace, 67890',
                  style: 'headingText',
                  alignment: 'right'
                },
                {
                  text: 'Tel: +1 (555) 123-4567 | Fax: +1 (555) 765-4321',
                  style: 'headingText',
                  alignment: 'right'
                },
                {
                  text: 'info@example.com | www.example-portal.com',
                  style: 'headingText',
                  alignment: 'right'
                }
              ],
              margin: [0, 8, 0, 0]
            }
          ]
        ]
      },
      layout: { defaultBorder: false }
    },
    {
      canvas: [
        {
          type: 'line',
          x1: 0,
          y1: 0,
          x2: 520,
          y2: 0,
          lineWidth: 0.5,
          color: '#d3d3d3'
        }
      ],
      margin: [40, 5, 40, 20]
    }
  ];
},

footer: (currentPage, pageCount) => {
  return {
    margin: [40, 10, 40, 20],
    table: {
      widths: ['50%', '50%'],
      body: [
        [
          {
            text: 'https://www.example-portal.com',
            style: 'footerTextLinkStyle',
            alignment: 'left'
          },
          {
            text: `Page ${currentPage} of ${pageCount}`,
            style: 'footerTextPageCounterStyle',
            alignment: 'right'
          }
        ]
      ]
    },
    layout: { defaultBorder: false }
  };
},

  1. My advice would be first design the PDF in PDFMAKE Sandbox so that each and every change would be visible for you right away, and then transfer it to your JS Code.
1 Like

Thank you.

I basically have created a js file with the file structure which I call a PDF template.
I read that into my custom module, that I use in server actions.

The custom module replaces variables via data bindings I have set up in hjson as you have.

I found out the problem.. I was importing the "pdfmake structure/template" via

const docDefinition = JSON.parse(JSON.stringify(template));

JSON.stringify got rid of the header and footer section.

I have removed it and just kept

const templatePath = path.join(process.cwd(), 'public/assets/templates/Perdiem.js');
template = require(templatePath);

Thank you for the help and sharing your code!

1 Like