I want to send an email using Nodemailer and Handlebars but I can't use a dynamic template.
Here is my setup. template is the template name and it looks like sign-up-mail-template Now when I send this email I will receive Error: ENOENT: no such file or directory, open '/home/ubuntu/backend/src/templates/sign-up-email-template.handlebars'
If I change the defaultLayout to sign-up-email-template.hbs will send the email without any problem but even if I request to send a different email template let's say password-reset-template which will be called in mailOptions it will send the defaultLayout.
Can please anyone explain to me what am I doing wrong here?
Also if const template = "password-reset-template.hbs" It will receive Error: ENOENT: no such file or directory, open '/home/ubuntu/backend/src/templates/password-reset-template.hbs.handlebars'
const transporter = nodemailer.createTransport(
smtpTransport({
host: constants.EMAIL_HOST,
port: constants.SMTP_PORT,
secure: false,
requireTLS: true,
auth: {
user: constants.EMAIL_USER,
pass: constants.EMAIL_PASSWORD
}
})
);
const handlebarsOptions = {
viewEngine: {
extName: ".hbs",
partialsDir: "./src/templates/",
layoutsDir: "./src/templates/",
defaultLayout: template
},
viewPath: path.resolve("./src/templates/"),
extName: ".html"
};
transporter.use("compile", hbs(handlebarsOptions));
const mailOptions = {
to: email,
from: '"Welcome" no-reply#mail.com',
template,
subject,
context: {
code: promoCode
}
};
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
throw new Error(error.message);
}
console.log("Message sent: %s", info.message);
});
You need to create a main.handlebars file like bellow.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
{{{body}}}
</body>
</html>
{{body}} is where you sign-up-email-template.handlebars will be placed.
Hope help you!
You can try this out, and remember to make your template folder correctly. Default Layout remains empty so you can set your template inside mail options.
const handlebarOptions = {
viewEngine: {
extName: '.hbs',
partialsDir: 'views',//your path, views is a folder inside the source folder
layoutsDir: 'views',
defaultLayout: ''//set this one empty and provide your template below,
},
viewPath: 'views',
extName: '.hbs',
};
mailTransport.use('compile', hbs(handlebarOptions));
const mailOptions = {
from: '"Spammy Corp." <ad.timely#gmail.com>',
to: 'nipunkavishka#gmail.com',
template: 'index',//this is the template of your hbs file
};
Make sure to use
extName to extname in viewEngine object for partials
var options = {
viewEngine: {
extname: '.hbs',
layoutsDir: 'views/layouts/',
defaultLayout : 'template.hbs',
partialsDir : 'views/partials/'
},
viewPath: 'views/emails/',
extName: '.hbs'
};
Directory
=> views/layouts/template.hbs
=> views/emails/forgot-password.hbs
=> views/partials/header.hbs
/footer.hbs
In template.hbs
<html>
<body>
<div class="container">
{{>header}}
{{{body}}}
{{>footer}}
</div>
</body>
</html>
Related
I'm trying to generate multi-page invoices using puppeteer and handlebars and can't figure out how to make the payment information always stick to the bottom of the last page.
Let's say I have some template HTML like this:
<!DOCTYPE html>
<html lang="en">
<body>
{{#each items}}
<div style="page-break-after: always">{{this.val}}</div>
{{/each}}
<div>This should be at the bottom of the previous page.</div>
</body>
</html>
...and a function to generate a PDF like this:
async function generateInvoice() {
// Generating HTML
const data = { items: [{ val: 1 }, { val: 2 }, { val: 3 }] };
const html = Handlebars.compile((await fs.readFile("./template.html")).toString());
const compiledHTML = encodeURIComponent(html(data));
// Setting up browser
const browser = await Puppeteer.launch({
args: ["--no-sandbox", "--disable-setuid-sandbox"],
headless: true,
});
const page = await browser.newPage();
await page.goto(`data:text/html;charset=UTF-8,${compiledHTML}`, {
waitUntil: "networkidle0",
});
// Writing PDf to out.pdf
await page.pdf({
path: "out.pdf",
format: "A4",
});
await browser.close();
}
How could I modify that code to reflect what I want to do?
I've toyed around with the page-break attributes and absolute positioning but couldn't get anything to work.
I'm trying to get my express to serve files from my public folder but I can't seem to get anything to work here. I have tried so many variations in my file paths yet I can't seem to get pug to cooperate with node. I can see the html at the very least from index but neither the css nor the music files want to load. And yes I have looked at many questions similar to mine. I still can't determine why it's not working. please help.
I get these errors in dev tools
Refused to apply style from 'http://localhost:3000/public/music.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
GET http://localhost:3000/public/Music/Pink_Floyd/WishYouWereHere/ShineOnYouCrazyDiamond(PartsI-V).mp3 404 (Not Found)
my file structure is like this:
js
-server.js
public
-Music
--artist
---album
----musicfiles.mp3
-music.css
views
-index.pug
My node.js code:
var jsmediatags = require('jsmediatags');
var express= require('express');
var app=express();
var pug=require('pug');
const path=require('path')
const fs=require('fs');
const { error } = require('console');
var tagsArray=Array();
const port=3000;
const host='localhost';
app.use('/public', express.static(path.join(__dirname, 'public')))
app.set('views','./views');
app.set('view engine', 'pug');
const server =app.listen(port, host, ()=>{
console.log("server started at "+host+" port:"+port);
});
process.on('SIGTERM', () => {
server.close(() => {
console.log('Process terminated')
})
})
jsmediatags.read("./public/Music/Pink_Floyd/WishYouWereHere/ShineOnYouCrazyDiamond(PartsI-V).mp3", {
onSuccess: function(tag) {
var tags=tag.tags;
tagsArray=[tags.artist,tags.track,tags.album,tags.title,tags.picture];
var artist=tags.artist;
var album=tags.album;
var title=tags.title;
var track=tags.track;
var base64Url=Buffer.from(tags.picture.data).toString("base64");
// var base64Url=Buffer.from(base64String).toString("base64");
var artInfo="data:"+tags.picture.format+";base64,"+base64Url;
console.log(`data:${tags.picture.format};base64,`);
app.get('/', (req, res)=>{
res.render("index", {
artist:tags.artist,
album: tags.album,
title: tags.title,
track: tags.track,
art: artInfo
});
console.log('done');
});
},
onError: function(error) {
console.log(':(', error.type, error.info);
}
});
My pug code:
html(lang="en")
head
meta(charset='UTF-8')
meta(http-equiv='X-UA-Compatible' content='IE=edge')
meta(name='viewport' content='width=device-width, initial-scale=1.0')
title Music App
link(rel='stylesheet' type='text/css' href='/public/music.css')
body
img#albumC(src=art alt='album art')
.playbox
.play
.reflect
.playRef
.pausebox
.pause
.PA.center
.PB.center
.reflect
.pause
.PAref.center
.PBref.center
.stopbox
.stop
.reflect
.stopRef
h2 Artist: #{artist}
h2 Album: #{album}
h2 Song: #{title}
h2 Track: #{track}
audio(controls src="/public/Music/Pink_Floyd/WishYouWereHere/ShineOnYouCrazyDiamond(PartsI-V).mp3")
From your directory sketch, it looks to me like your public directory is a sibling (not a sub-directory) to the js directory where server.js is. So, instead of this:
app.use('/public', express.static(path.join(__dirname, 'public')))
you would to go up a level first like this:
app.use('/public', express.static(path.join(__dirname, '../public')))
To go up a directory and then down to the public directory.
That would then match up with this tag you have:
link(rel='stylesheet' type='text/css' href='/public/music.css')
Hi I'm new to web development and I'm trying to play around with Cytoscape.js. I decided to use Node.js, Express, and Express-handlebars to run my webpage. I wanted to use Cytoscape.js to display a graph however I'm not sure how to use handlebars to get a reference to the container to initialize the cytoscape object.
Here's my main.handlebars file:
<!doctype html>
<html>
<head>
<title>Hello Cytoscape</title>
</head>
<body>
{{{body}}}
</body>
</html>
Here's my home.handlebars file:
<div id="cy"></div>
Here's my .js file:
/**
*
*/
'use strict';
var express = require('express');
var app = express();
var cytoscape = require('cytoscape');
//layout defaults to main, located in the views layout folder
var handlebars = require('express-handlebars').create({defaultLayout:'main'});
app.use(express.static('public'));
//sets the template engine to use for the express object
//a template engine will implement the view part of the app
app.engine('handlebars', handlebars.engine);
app.set('view engine', 'handlebars');
//initialize the cytoscape object
var cy = cytoscape({
//<----not sure how to do this----->
container: document.getElementById('cy'), // container to render in
elements: [ // list of graph elements to start with
{ // node a
data: { id: 'a' }
},
{ // node b
data: { id: 'b' }
},
{ // edge ab
data: { id: 'ab', source: 'a', target: 'b' }
}
],
style: [ // the stylesheet for the graph
{
selector: 'node',
style: {
'background-color': '#666',
'label': 'data(id)'
}
},
{
selector: 'edge',
style: {
'width': 3,
'line-color': '#ccc',
'target-arrow-color': '#ccc',
'target-arrow-shape': 'triangle'
}
}
],
layout: {
name: 'grid',
rows: 1
},
// initial viewport state:
zoom: 1,
pan: { x: 0, y: 0 },
// interaction options:
minZoom: 1e-50,
maxZoom: 1e50,
zoomingEnabled: true,
userZoomingEnabled: true,
panningEnabled: true,
userPanningEnabled: true,
boxSelectionEnabled: false,
selectionType: 'single',
touchTapThreshold: 8,
desktopTapThreshold: 4,
autolock: false,
autoungrabify: false,
autounselectify: false,
// rendering options:
headless: false,
styleEnabled: true,
hideEdgesOnViewport: false,
hideLabelsOnViewport: false,
textureOnViewport: false,
motionBlur: false,
motionBlurOpacity: 0.2,
wheelSensitivity: 1,
pixelRatio: 'auto'
});
app.get('/', function (req, res) {
var context = {};
res.render('home', context);
});
//listener all for unrecognized urls
//return 404 not found response
app.use(function(req,res){
res.status(404);
res.render('404');
});
//listener for errors generate on server
//return 500 response
app.use(function(err, req, res, next){
console.error(err.stack);
res.type('plain/text');
res.status(500);
res.render('500');
});
if (module === require.main) {
// [START server]
// Start the server
var server = app.listen(process.env.PORT || 8080, function () {
var port = server.address().port;
console.log('App listening on port %s', port);
});
// [END server]
}
module.exports = app;
So I guess my question is how do I get a reference to the div container id="cy" to initialize my Cytoscape.js graph using express-handlebars, thanks in advance for any help.
If you run Cytoscape on the serverside -- as you're doing in your example -- then it's not running and not shown on the clientside.
Unless you're doing serverside-only graph analysis, you should be using Cytoscape on the clientside. Your page (main.handlebars) is the driver of everything clientside, so put your Cytoscape code there. Or in the page, reference your Cytoscape code with a <script> tag.
I want to create a PDF from a template in Jade using PhantomJS, I can create the PDF document, but the CSS is not applying, I create two routes the first one render the template to the web browser and the second one generate de PDF with the exact same jade template, in the browser all is right, but the PDF don't apply CSS.
My app.js is:
var express = require('express'),
routes = require('./routes'),
http = require('http'),
path = require('path'),
bodyParser = require('body-parser');
var app = express();
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(express.static(path.join(__dirname, 'public')));
app.post('/api/cotizacion/generar', routes.cotizacion.generar);
app.get('/api/cotizacion/generar', routes.cotizacion.testGenerar);
app.all('*', function(req, res) {
res.send(404);
});
http.createServer(app).listen(app.get('port'), function(){
console.info('Express server listening on port ' + app.get('port'));
});
The controller file is cotizacion.js:
var phantom = require('phantom');
exports.generar = function(req, res, next){
if(!req.body.cotizacion){
return next(new Error('No se enviaron datos de cotización'));
}
var cotizacion = req.body.cotizacion;
req.app.render('test', {cotizacion: cotizacion}, function(err, html){
generarPDF(cotizacion, html, function(){
res.send("1");
});
});
};
exports.testGenerar = function(req, res, next){
res.render('test', {});
};
generarPDF = function(cotizacion, html, callback){
phantom.create(function(ph){
ph.createPage(function(page){
page.set('paperSize', { format: 'Letter' });
page.set('content', html);
page.render(__dirname + '/test.pdf', function(err){
ph.exit();
callback();
});
});
});
}
And finally my test.jade is:
doctype html
html
head
title Cotización PDF
link(rel='stylesheet', href='/css/pdf.css')
body
h1 Test
The following works, but is a little annoying
doctype html
html
head
title Cotización PDF
| <style type="text/css">
| body{ color: green; }
| </style>
body
h1 Test
The project structure is:
/
app.js
public/
css/
pdf.css
routes/
index.js
cotizador.js
views/
test.jade
Ok here is our solution for similar problem. Below JADE template, notice hrefs with a reference to JSON object properties:
doctype html
html(lang="en")
head
link(rel='stylesheet', type="text/css", media="all", href=report.statics.bootstrap)
link(rel='stylesheet', type="text/css", media="all", href=report.statics.custom)
body
Here is a code snippet, used for providing to the template CSS paths:
var createStatics = function (req) {
var hostName = url.format({protocol: req.protocol,
host: req.get('host')
});
return {
bootstrap: hostName + '/assets/reports/bootstrap-report.css',
custom: hostName + '/assets/reports/custom.css',
}
}
Next here is a JADE template compilation:
var fn = jade.compile(template);
Transportation object, used for passing required by JADE template utility objects:
var report = {
statics: statics,
title: 'Simple report title'
};
Rendering HTML report and retrieved as a stream...
var output = fn({
report: report
});
This came out recently: https://meteorhacks.com/server-side-rendering.html but there doesn't seem to be a full fledged example of how to use this with iron-router.
If I had a template like:
/private/post_page.html
{{title}}
{{#markdown}} {{body}} {{/markdown}}
How would I populate it with a single records attributes from a request for a specific ID ?
E.g page requested was localhost:3000/p/:idofposthere how to populate it with data and render it in iron-router for that route / server side?
Actually a bit easier than you think (I just followed Arunoda's SSR example and converted it to iron-router for you):
if(Meteor.isServer) {
Template.posts.getPosts = function(category) {
return Posts.find({category: category}, {limit: 20});
}
}
Router.map(function() {
this.route('home');
this.route('view_post', {
path: 'post/:id',
where:"server",
action : function() {
var html = SSR.render('posts', {category: 'meteor'})
var response = this.response;
response.writeHead(200, {'Content-Type':'text/html'});
response.end(html);
}
});
});
It only gets tricky if you share the same route for client and server side for example, if you want it to render client-side based on user agent.
Source: We use this strategy on our own apps.
UPDATE
While the above code is simply what the question is asking for, we can get this working to follow Google's Ajax spec by checking the ?_escaped_fragment_= query string before we reach the client..
Basically, what we mostly don't know about Iron-Router is that if you have identical routes declared for server and client, the server-side route is dispatched first and then the client-side.
Here's the main javascript (with annotations):
ssr_test.js
Router.configure({
layout: 'default'
});
Posts = new Mongo.Collection('posts');
// Just a test helper to verify if we area actually rendering from client or server.
UI.registerHelper('is_server', function(){
return Meteor.isServer ? 'from server' : 'from client';
});
myRouter = null;
if(Meteor.isServer) {
// watch out for common robot user-agent headers.. you can add more here.
// taken from MDG's spiderable package.
var userAgentRegExps = [
/^facebookexternalhit/i,
/^linkedinbot/i,
/^twitterbot/i
];
// Wire up the data context manually since we can't use data option
// in server side routes while overriding the default behaviour..
// not this way, at least (with SSR).
// use {{#with getPost}} to
Template.view_post_server.helpers({
'getPost' : function(id) {
return Posts.findOne({_id : id});
}
});
Router.map(function() {
this.route('view_post', {
path: 'post/:id', // post/:id i.e. post/123
where: 'server', // this route runs on the server
action : function() {
var request = this.request;
// Also taken from MDG's spiderable package.
if (/\?.*_escaped_fragment_=/.test(request.url) ||
_.any(userAgentRegExps, function (re) {
return re.test(request.headers['user-agent']); })) {
// The meat of the SSR rendering. We render a special template
var html = SSR.render('view_post_server', {id : this.params.id});
var response = this.response;
response.writeHead(200, {'Content-Type':'text/html'});
response.end(html);
} else {
this.next(); // proceed to the client if we don't need to use SSR.
}
}
});
});
}
if(Meteor.isClient) {
Router.map(function() {
this.route('home');
this.route('view_post', { // same route as the server-side version
path: 'post/:id', // and same request path to match
where: 'client', // but just run the following action on client
action : function() {
this.render('view_post'); // yup, just render the client-side only
}
});
});
}
ssr_test.html
<head>
<title>ssr_test</title>
<meta name="fragment" content="!">
</head>
<body></body>
<template name="default">
{{> yield}}
</template>
<template name="home">
</template>
<template name="view_post">
hello post {{is_server}}
</template>
<template name="view_post_server">
hello post server {{is_server}}
</template>
THE RESULT:
I uploaded the app at http://ssr_test.meteor.com/ to see it in action, But it seems to crash when using SSR. Sorry about that. Works fine if you just paste the above on Meteorpad though!
Screens:
Here's the Github Repo instead:
https://github.com/electricjesus/ssr_test
Clone and run!
SSR is lacking real life examples, but here is how I got it working.
if (Meteor.isServer)
{
Router.map(function() {
this.route('postserver', {
where: 'server',
path: '/p/:_id',
action: function() {
// compile
SSR.compileTemplate('postTemplate', Assets.getText('post_page.html'));
// query
var post = Posts.findOne(this.params._id);
// render
var html = SSR.render('postTemplate', {title: post.title, body: post.body});
// response
this.response.writeHead(200, {'Content-Type': 'text/html'});
this.response.write(html);
this.response.end();
}
});
});
}
Assets are documented here: http://docs.meteor.com/#assets.