├── .gitignore ├── README.md ├── app.js ├── config └── config.js ├── controllers └── controller.js ├── data └── data.js ├── download └── report.pdf ├── index.js ├── package.json ├── routes └── routes.js └── views ├── index.html └── js └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | #modules 2 | /node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Generate PDF through the HTML content. 2 | 3 |

Screenshots

4 | 5 | 6 | 7 | 8 | 9 | * html-pdf 10 | * node-fetch 11 | * requestify 12 | * izitoast 13 | 14 | ## DATA EXAMPLE 15 | 16 | ``` js 17 | [ 18 | { 19 | img: "https://bakedpizza.com.co/media/catalog/product/cache/1/small_image/500x500/9df78eab33525d08d6e5fb8d27136e95/h/a/hamburguesa_doble_carne_mex.png", 20 | name: "Hamburgueza Todo Terreno", 21 | description: "A description which may flow for several lines and give context to the content." 22 | }, 23 | { 24 | img: "https://cdn.discstore.com/media/catalog/product/cache/1/image/1800x/040ec09b1e35df139433887a97daa66f/p/i/pizza-top_2.jpg", 25 | name: "Pizza de Peperonni y Champignones", 26 | description: "A description which may flow for several lines and give context to the content." 27 | }, 28 | { 29 | img: "https://http2.mlstatic.com/excelentes-taquizas-y-comida-mexicana-D_NQ_NP_751821-MLM25734431910_072017-O.jpg", 30 | name: "Arroz de Mariscos", 31 | description: "A description which may flow for several lines and give context to the content." 32 | }, 33 | { 34 | img: "http://www.diabetesforecast.org/2014/09-sep/es/cuban-pollo-asado-Sep14-es.jpg", 35 | name: "Pollo Asado", 36 | description: "A description which may flow for several lines and give context to the content." 37 | }, 38 | { 39 | img: "http://amantesdelacocina.com/cocina/wp-content/uploads/2011/11/arepa2x.jpg", 40 | name: "Arepa Rellena", 41 | description: "A description which may flow for several lines and give context to the content." 42 | }, 43 | { 44 | img: "https://res.cloudinary.com/civico/image/upload/c_fill,f_auto,fl_lossy,h_500,q_auto,w_500/v1404245583/entity/image/file/003/000/53b3164db9dd5d13ca000003.jpg", 45 | name: "Picada", 46 | description: "A description which may flow for several lines and give context to the content." 47 | }, 48 | { 49 | img: "http://www.pidaloya.co/wp-content/uploads/2016/09/pechugapollo.jpg", 50 | name: "Cerdo con papas a la francesa", 51 | description: "A description which may flow for several lines and give context to the content." 52 | }, 53 | { 54 | img: "http://www.pidaloya.co/wp-content/uploads/2016/09/desgra.jpg", 55 | name: "Mazorca desgranada", 56 | description: "A description which may flow for several lines and give context to the content." 57 | } 58 | ] 59 | ``` 60 | 61 | ``` bash 62 | # install dependencies 63 | npm install 64 | ``` 65 | 66 | # Change IP Address 67 | ```js 68 | Path: './config/config.js' 69 | 70 | 'use strict' 71 | 72 | const config = { 73 | ip: '192.168.1.66', 74 | port: process.env.PORT || 3000 75 | } 76 | ``` 77 | 78 | ```js 79 | Path: './views/js/main.js' 80 | 81 | const url = { 82 | ip: '192.168.1.67', 83 | port: 3000 84 | } 85 | ``` 86 | 87 | ``` bash 88 | # start project 89 | npm run dev 90 | ``` 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const express = require('express') 4 | const app = express() 5 | const bodyParser = require('body-parser') 6 | const ejs = require('ejs') 7 | const routes = require('./routes/routes') 8 | 9 | app.use((req, res, next) => { 10 | res.header("Access-Control-Allow-Origin", "*"); 11 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, user, admin_secret"); 12 | next(); 13 | }); 14 | 15 | app.set('views', 'views'); 16 | app.set('view engine', 'html'); 17 | app.engine('html', ejs.renderFile); 18 | 19 | app.use(express.static('download')); 20 | app.use(express.static('views/js')); 21 | 22 | app.use(bodyParser.urlencoded({ extended: false })); 23 | app.use(bodyParser.json()); 24 | app.use('/', routes); 25 | 26 | module.exports = app; 27 | -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const config = { 4 | ip: '192.168.1.67', 5 | port: process.env.PORT || 3000 6 | } 7 | 8 | module.exports = config -------------------------------------------------------------------------------- /controllers/controller.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const config = require('../config/config') 4 | const pdf = require('html-pdf') 5 | const requestify = require('requestify') 6 | const data = require('../data/data').values 7 | 8 | const ConvertBodyToPDF = async (req, res) => { 9 | 10 | let validate = req.body.data 11 | 12 | if (validate) { 13 | 14 | const request = await requestify.get(`http://${config.ip}:${config.port}/report`) 15 | const response = await request 16 | 17 | let head = ` 18 | 19 | busy 20 | 21 | 22 | 23 | 24 | 25 | ` 28 | 29 | let htmlBody = `${head} ${response.body}` 30 | 31 | let htmlOptions = { 32 | format: 'Letter', 33 | border: '1cm' 34 | } 35 | 36 | let downloadPath = `${__dirname.split('controllers')[0]}/download/report.pdf` 37 | 38 | pdf.create(htmlBody, htmlOptions).toFile(downloadPath, (error, success) => { 39 | 40 | if (error) 41 | res.json({ data: error }) 42 | else 43 | res.json({ data: success }) 44 | 45 | }) 46 | 47 | } 48 | 49 | } 50 | 51 | const JSON_Generate = (req, res) => { 52 | return res.status(200).send(data) 53 | } 54 | 55 | module.exports = { 56 | ConvertBodyToPDF, 57 | JSON_Generate 58 | } -------------------------------------------------------------------------------- /data/data.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | exports.values = [ 4 | { 5 | img: 'https://bakedpizza.com.co/media/catalog/product/cache/1/small_image/500x500/9df78eab33525d08d6e5fb8d27136e95/h/a/hamburguesa_doble_carne_mex.png', 6 | name: 'Hamburgueza Todo Terreno', 7 | description: 'A description which may flow for several lines and give context to the content.' 8 | }, 9 | { 10 | img: 'https://cdn.discstore.com/media/catalog/product/cache/1/image/1800x/040ec09b1e35df139433887a97daa66f/p/i/pizza-top_2.jpg', 11 | name: 'Pizza de Peperonni y Champignones', 12 | description: 'A description which may flow for several lines and give context to the content.' 13 | }, 14 | { 15 | img: 'http://rinconcitopaisa2.com/wp-content/uploads/2015/05/arrozcon-mariscos.jpg', 16 | name: 'Arroz de Mariscos', 17 | description: 'A description which may flow for several lines and give context to the content.' 18 | }, 19 | { 20 | img: 'http://www.diabetesforecast.org/2014/09-sep/es/cuban-pollo-asado-Sep14-es.jpg', 21 | name: 'Pollo Asado', 22 | description: 'A description which may flow for several lines and give context to the content.' 23 | }, 24 | { 25 | img: 'https://amantesdelacocina.com/wp-content/uploads/2011/11/arepa2x.jpg', 26 | name: 'Arepa Rellena', 27 | description: 'A description which may flow for several lines and give context to the content.' 28 | }, 29 | { 30 | img: 'https://res.cloudinary.com/civico/image/upload/c_fill,f_auto,fl_lossy,h_500,q_auto,w_500/v1404245583/entity/image/file/003/000/53b3164db9dd5d13ca000003.jpg', 31 | name: 'Picada', 32 | description: 'A description which may flow for several lines and give context to the content.' 33 | }, 34 | { 35 | img: 'https://i.pinimg.com/236x/68/2a/01/682a01bc21cd2e02cb1bc60012e77b58--wordpress-delicious-food.jpg', 36 | name: 'Cerdo con papas a la francesa', 37 | description: 'A description which may flow for several lines and give context to the content.' 38 | }, 39 | { 40 | img: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPsLFq94cxrS5E_FbULjuipe7qWWxMIrSs6eY0IBrJclJpJy-5', 41 | name: 'Mazorca desgranada', 42 | description: 'A description which may flow for several lines and give context to the content.' 43 | } 44 | ] -------------------------------------------------------------------------------- /download/report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmbl1685/html-to-pdf-nodejs/ad1659fdf77c2b71021732269abed284cd2f2635/download/report.pdf -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const config = require('./config/config') 4 | const app = require ('./app') 5 | 6 | app.listen(config.port, () => { 7 | console.log('Server started') 8 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-to-pdf-nodejs-express", 3 | "version": "2.0.0", 4 | "description": "Convert HTML to PDF", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "dev": "nodemon index.js" 9 | }, 10 | "author": "Juan Batty", 11 | "license": "ISC", 12 | "dependencies": { 13 | "body-parser": "^1.17.2", 14 | "ejs": "^2.5.6", 15 | "express": "^4.15.3", 16 | "html-pdf": "^2.1.0", 17 | "node-fetch": "^1.7.1", 18 | "nodemon": "^1.11.0", 19 | "requestify": "^0.2.5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /routes/routes.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const express = require('express') 4 | const routes = express.Router() 5 | const controller = require('../controllers/controller') 6 | const fetch = require('node-fetch') 7 | const config = require('../config/config') 8 | 9 | let array = [] 10 | 11 | const GetData = (async () => { 12 | const request = await fetch(`http://${config.ip}:${config.port}/data`) 13 | const response = await request.json() 14 | array = response 15 | })() 16 | 17 | routes.post('/pdf', controller.ConvertBodyToPDF) 18 | routes.get('/data', controller.JSON_Generate) 19 | 20 | routes.get('/report', (req, res) => { 21 | res.render('index', { title: 'HTML to PDF', data: array }) 22 | }) 23 | 24 | routes.get('/', (req, res) => { 25 | res.redirect('/report') 26 | }) 27 | 28 | module.exports = routes -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%=title%> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 |
20 |

Productos más vendidos 21 |
Mes de Julio 2017

22 |
23 |
24 |
25 |
26 | <% for (var i=0 ; i 27 |
28 |
29 | 30 |
31 |
32 | 33 | <%=data[i].name%> 34 | 35 |
36 | Date 37 | Category 38 |
39 |
40 | <%=data[i].description%> 41 |
42 |
43 |
44 | <% } %> 45 |
46 |
47 |
48 |
49 | 51 |
52 |
53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /views/js/main.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const btn_pdf = document.getElementById('btn_pdf') 4 | const container_main = document.getElementById('container_main') 5 | const loader = document.getElementById('loader') 6 | 7 | const url = { 8 | ip: '192.168.1.67', 9 | port: 3000 10 | } 11 | 12 | const config = { 13 | method: 'POST', 14 | headers: { 15 | 'Content-Type': 'application/json' 16 | }, 17 | body: JSON.stringify({ data: true }) 18 | } 19 | 20 | btn_pdf.addEventListener('click', () => { 21 | 22 | container_main.style.display = 'none' 23 | loader.style.display = 'block' 24 | 25 | const _url = `http://${url.ip}:${url.port}/pdf` 26 | 27 | if (document.getElementById("icon_pdf") !== null) { 28 | Reset() 29 | return 30 | } 31 | 32 | fetch(_url, config) 33 | .then(res => res.json()) 34 | .then(res => { 35 | 36 | const img = document.createElement('img') 37 | img.src = 'https://image.flaticon.com/icons/svg/337/337946.svg' 38 | img.id = 'icon_pdf' 39 | document.getElementById('div_pdf').appendChild(img) 40 | document.getElementById('icon_pdf').setAttribute('onclick', "window.open('/report.pdf', '_blank')") 41 | Reset() 42 | 43 | }).catch(err => { 44 | Reset() 45 | alert('Error:' + err) 46 | }) 47 | 48 | }) 49 | 50 | function Reset() { 51 | loader.style.display = 'none' 52 | container_main.style.display = 'block' 53 | iziToast.show({ 54 | id: 'haduken', 55 | theme: 'dark', 56 | icon: 'icon-contacts', 57 | title: 'PDF generado correctamente.', 58 | message: 'Revise al final de la página.', 59 | position: 'topCenter', 60 | transitionIn: 'flipInX', 61 | transitionOut: 'flipOutX', 62 | progressBarColor: 'rgb(0, 255, 184)', 63 | image: 'https://image.flaticon.com/icons/svg/337/337946.svg', 64 | imageWidth: 50, 65 | layout: 2, 66 | backgroundColor: '#D9534F', 67 | iconColor: 'rgb(0, 255, 184)' 68 | }); 69 | } --------------------------------------------------------------------------------