Serve both text and image data when rendering a template? - node.js

Trying to send both text and image data as local variables to a server-side rendered page using templates.
I know I have to set the Content-Type as 'image/png'.
How can I set the Content-Type for one variable when all the other variables are text?
Thank you so much!
res.render('profile.html', { locals: {
tasks: tasks,
msgExists: '',
name: req.user.name,
email: req.user.email,
photo: req.user.photo //how to set Content-Type for this variable?
}});

the template is rendered on server side, and then sent to client. this means the content type of the response is "text/html", not "image/png".
your variables do not have "Content-Type", as they are not HTML responses. if you want to embed an image in an html, and you know its raw data, it usually looks like this:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="/>
now if you have the image data (like whatever after base64 part) somehow stored in a JS variable, and want to pass it to your template renderer to embed that image in the resulting html, it would look like this:
// node js code:
imgData = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
res.render('my.template.html', {
imgData,
// whatever else ...
});
<!-- template code: -->
<img src="data:image/png;base64,{{imgData}}"/>
<!-- whatever else -->
of course this can change a bit based on the exact format you are storing the image data.

Related

Base64 encoded image sent using sendgrid not displayed in mails

I am trying to send a logo image in the HTML using sendGrid mailing service. I went through a lot of articles and tried a lot but still could not figure out on how to display inline image using content_id.
I went through following links but still could not make it work:
[Link1][1]: https://sendgrid.com/blog/embedding-images-emails-facts/
[Link2][2]: https://github.com/sendgrid/sendgrid-nodejs/issues/841
Few of the answers suggest to add image as a base64 string in HTML but that does not work with few web mailing clients.
Here is my code:
`
try
{
const msg = {
to: `${customerData.raw[0].invoicemail}`, // List of receivers email address
from : 'xxxx#xxx.com',
subject: `${subject}`, // Subject line
html: emailData, // HTML body content
attachments:
[
{filename:`${filename}`,content:invoice,type:"application/pdf"},
{filename:'logo.jpg',content:logoImageData,content_id:'logo',type:"image/jpg",disposition:"inline"}
],
}
sgMail.send(msg)
//return this.mailerService.sendEmail(msg)
}
catch
{
return false
}`
And here is the HTML block:
<img src = "content_id:logo" alt = "Logo Image" width=140 height=20>
Also, the image is being received as an attachment but is not displayed.
Sorry for the very late reply, I've also had trouble getting my image to display using SendGrid.
#Abhijeet, have you tried replacing src = "content_id:logo" with src="cid:logo"?
For other beginners, like myself, using SendGrid I will include the other steps that #Abhijeet already has done.
Upload and convert your image file to base64 (we'll call this logoImageData).
We want to replace the beginning of the base64 encoded image string using String.prototype.replace().
export const cleanLogoImageData = logoImageData.replace('data:image/jpeg;base64,' , '')
Now, in the attachments for mail data for SendGrid, you need to add these to your object: filename, content, contentType, content_id, and disposition.
const msg = {
attachments: [
{
filename: "logo.jpg",
content: cleanLogoImageData,
contentType: "image/jpeg",
content_id: "logo",
disposition: "inline",
},
],
}
content_id and disposition: "inline" are what got my logo to show.
Finally, in the html file holding your <img />, you must set the src as cid:logo or whatever your content_id is. And moreover, it's recommended to add align="top" and display: block in your img tag for better and consistent cross-platform email display of the image.
<img align="top"
style="display: block"
src="cid:logo"
alt="company logo"
/>

Why is JSON.stringify needed to retain an object's value when importing it from node.js into an EJS template?

Environment: Node.js, Express, EJS
When JSON.stringify() is used to process objects passed from node.js to an EJS template the objects retain their original values. Although it works I find this result unexpected. JSON.stringify turns objects into strings. Why does this appear to work in reverse in this instance?
In the Node.js file:
app.get('/', function(req, res) {
let myArray = [1, 5];
let myObject = {
cats: 2,
dogs: 0
}
res.render('index', { myArray, myObject });
})
EJS:
<script>
let importedArray = <%- JSON.stringify(myArray) %>;
let importedObject = <%- JSON.stringify(myObject) %>;
</script>
Rendered version in browser:
Although I find this result unexpected it works perfectly fine.
<script>
let importedArray = [1,5];
let importedObject = {"cats":2,"dogs":0};
</script>
Rendered after both JSON.stringify() are removed in EJS file:
The values are lost and the browser throws an error. I would have thought the unescaped output tag <%- would be enough but it's not.
<script>
let importedArray = 1,5;
let importedObject = [object Object];
</script>
Because when you're trying to specify the source code for a script that will live inside a <script> tag inside a web page, you need to generate RAW Javascript source code that will make your object in the web page.
So, you need some method of turning your live server-side Javascript object back into Javascript source code that describes the same object. JSON.stringify() is one such way to generate that Javascript source.
If you don't use something like JSON.stringify() and just pass your actual Javascript object, the EJS will see that it's not a string and it will call obj.toString() on it to try to get a string representation of it. Unfortunately, the implemention of .toString() for a Javascript object just generates "[object Object]" which is completely useless in an EJS template. So, you can't do it that way - you have to manually generate the correct Javascript source code string. And, JSON.stringify() is one such way to do that.
because ejs only render string text, and when use toString on a json, it will get '[object Object]' instead of your real content

Convert HTML page to PDF with CSS3 Support

I'm working on a small project whereby I create multiple CVs (or resumes) via an interface I've built in Vue + Laravel, which I can then export to PDF.
I'm having issues though when I export the PDF. Laravel DOMPDF doesn't let me have CSS3 properties inside the PDF, for example flex, or CSS variables. I believe PDFs only support CSS 2.0, but I have seen multiple PDFs being exported that are an exact carbon-copy of the website. For example, resume.io - when you create a CV via their site, they can export it and make it look exactly like the website version.
My question is: does anyone know of a library that I could use that ties into Vue or Laravel that will produce a carbon-copy of the website template into a PDF?
I have tried a few JS libraries that take screenshots of certain elements on the page, then try and fit them together, but it just doesn't work. I basically need a specific element on the page to be selectable and then saved to a PDF. Please see my example below:
As you can see, the white area is the CV preview, so I need that whole section saved to a PDF, minus the right hand side menu and the top-bar. I'm planning on building some really cool templates, but if I can't use modern CSS practices then it's going to be quite hard to make them into a PDF.
At the moment, I've got two views, the CV preview which you can see above, then another view which re-uses partials that are inserted in the PDF template. Obviously though, reusing the partials which have modern CSS applied then makes the PDF break or look broken.
My stack:
Laravel
Vue.js
TailwindCSS
Laravel-DOMPDF
If anyone could advise on the best way to go about this, I'd really appreciate it.
TIA
Since you didn't mentioned the converted page is in Vue or Blade, I'll explain both way.
Here's the Library, which all you need is to design a Blade view, then do something like this
Route::get('/doc', function () {
//
$data = Marketers::all();
// LoadView with $data
$pdf = PDF::loadView('pdf',$data)->setPaper('A4');
// LoadView with Compact
$pdf = PDF::loadView('pdf',compact('data'))->setPaper('A4');
// Then Download it
return $pdf->download('pdf.pdf');
});
Now in Vue you need jsPDF and htmlToCanvas or htmlToIMage
i used HTMLtoImage because i had some character issues for persian language so I'll help you base on HtmlToImage Library.
<template>
// Part you want to Convert to pdf or ...
<div ref="contentz" id="jsPdf" >
// Contents
<div/>
</template>
downloadFull(t) {
let self = this
switch (t) {
case 1:
const doc = new jsPDF("l", "mm", "a4");
htmlToImage.toCanvas(document.getElementById('jsPdf'))
.then(function (canvas) {
var img = canvas.toDataURL("image/jpeg");
var width = doc.internal.pageSize.getWidth();
var height = doc.internal.pageSize.getHeight();
doc.addImage(img, 'JPEG', 0, 0, width, 0);
doc.save('app.pdf');
})
.catch(function (error) {
self.$notifications.failedNotificationOnGetData(self)
});
break;
case 2:
htmlToImage.toJpeg(document.getElementById('jsPdf'), { quality: 1 })
.then(function (dataUrl) {
var link = document.createElement('a');
link.download = 'kalabala.jpeg';
link.href = dataUrl;
link.click();
})
.catch(function (error) {
self.$notifications.failedNotificationOnGetData(self)
});
}
break;
default:
break;
}
}
So here's my function which downloadFull(t) t will be the file type, you might don't need it or you can improve it with simple if/else without switch, first you you will import libraries like :
import jsPDF from 'jspdf';
import htmlToImage from 'html-to-image';
Then set page dimensions for jsPDF, then simply use HtmlToImage to get Canvas then set width, height and image Canvas with variables then simply add image to doc and save it. my functions are same but in first switch case I'll get PDF, in second I'll get JPEG.
If you're trying to do the first way but get download link in Vue page you must do the controller just like i explained at above then in VUE when u do API call you should use BLOB to download the file. Here's the Example :
getPDF(type) {
axios({
url : '/api/api_name/exportPDF',
method: 'POST',
responseType: 'blob'
})
.then(res => {
const url = window.URL.createObjectURL(new Blob([res.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'pdf.pdf');
document.body.appendChild(link);
link.click();
})
},
Good Luck.

Pass variable from ajax to jade/pug

Am obtaining the values from nodejs via ajax/fetch and need to pass the same to Pug.
Any help here would be much appreciated
sample.pug
button#searchVal Search
script(type='text/javascript', src='/lib/onClick.js')
br
br
table#table(div='')
each row in slaJobs
tr
th#cbs-tab-header(div='') !{row.jobname}
th#cbs-tab-header(div='') !{row.job_type}
th#cbs-tab-header(div='') !{row.autosys_instance}
dummy.js
.then(function(data) {
console.log(data.jobsHeader)
console.log(data.slaJobs)
})
Need to set value of data.slaJobs from js to slaJobs of Pug
if you can afford to refresh the pug page for the result:
make a request with ajax to some route in node.js.
in the route handler - where you render the pug view - you can pass arguments to your view template.
so when the pug page will be rendered - It will already contain your data.
if you can't afford to refresh:
1. script tag in your template. handle the response and inject the data to the HTML page like any other AJAX
if you are getting data via ajax you should iterate the payload data in a forEach loop and generate HTML into current document. Actually pug is rendered on the server and then the output as HTML will be given to the user.
You can't show your data in your pug view engine while it is already rendered and shown to the user.
a sample:
fetch('localhost:3000/products')
.then(data => data.json())
.then(data => {
var div= document.getElementById('div');
data.forEach(item=>{
var p = document.createElement('p');
p.textContent = item[0] + ' ' + item[1];
div.appendChild(p);
}
})

Convert HTML to searchable PDF using PhantomJS / Node.js

I am generating PDF's server side with an HTML template that gets completed with data from the client and server. The code below works, but:
1) The PDF file is 5x bigger than when it is 'Saved as PDF' on the client side.
2) The PDF is not searchable.
I am assuming both of these problems stem from PhantomJS generating a raster vs. vector based PDF. What should I do differently (hoping I am just missing an PhantomJS option or two...)??
var phantom = require('phantom');
req.body['invoicenumber'] = 15010001;
phantom.create(function(ph){
ph.createPage(function(page) {
page.set('paperSize', { format: 'Letter',orientation: 'portrait', margin: '1cm' });
page.open("html/template.html", function(status) {
page.evaluate(function(data) {
$(function() { populate(data); });
},function() {
var quotenumber
page.render('quotes/'+req.body['invoicenumber']+'.pdf', function(){
ph.exit();
res.send(req.body['invoicenumber']+'.pdf');
});
},req.body);
});
});
})
MINOR UPDATE: Increasing the margin so the page is not scaled up reduces the file size, but still 2.5x the client side 'Save as PDF'...
In the html template try using any of these header tags (h1,h2...h6) to wrap your content. The content inside these headers tags will be rendered as text in the generated pdf. Hence it should be searchable. This will also reduce good amount of pdf file size. Not sure why div, p, table etc tags are rendered as image in the pdf.

Resources