How do you send an object to a helper in Handlebars? - node.js

Learning handlebars and Express I'm trying to learn of a way to send an object without always having to build in the render. For example if I have:
const details = {
version: process.env.npm_package_version,
author: 'foobar'
}
I can send this to my footer.hbs in partials from:
app.get('/', (req, res) => {
res.render('index', {
details
})
})
but looking for a way to send it to a template file and not always in a render I've read in the documentation about block helpers and tried:
// Define paths for Express config
const publicDir = path.join(__dirname, '../public')
const viewsPath = path.join(__dirname, '../templates/views')
const partialsPath = path.join(__dirname, '../templates/partials')
// Setup hbs engine and views location
app.set('view engine', 'hbs')
app.set('views', viewsPath)
hbs.registerPartials(partialsPath)
hbs.registerHelper('appDetails', () => {
const details = {
version: process.env.npm_package_version,
author: 'foobar'
}
return details
})
but in my directory /partials from file footer.hbs I try to use the helper:
<footer>
<p>Created by {{details.author}} | version: {{details.version}}</p>
</footer>
and it doesn't work. I've searched the site and I've read:
How to set a variable for the main handlebars layout without passing it to every route?
nodejs + HBS (handlebars) : passing data to partials
Passing variables through handlebars partial
In my Node and Express app is there a way to send data to the partials file without having to always send it in render?

There are two ways a Handlebars Helper can add data to the rendering context of a template:
1) By directly mutating the template's context or 2) By using private variables
Example: https://codepen.io/weft_digital/pen/JjPZwvQ
The following helper updates or adds the data points name and newData to the template's global context, and it also passes a private variable, using the data option.
Handlebars.registerHelper('datainjection', function(context, options) {
this.name = "Bob";
this.newData = "Updated"
return context.fn(this, { data: {private:"pirate"} });
});
Before you call the {{#datainjection}}{{/datainjection}} block in your template, {{name}} will be set to whatever value you pass to the render function, but every occurrence of {{name}} within or after the {{#datainjection}}{{/datainjection}} block will use the updated value, which is "Bob" in this case.
The private variable we are passing can only be accessed within the {{#datainjection}}{{/datainjection}} block using the "#" decorator.
For example:
{{#datainjection}}{{#private}}{{/datainjection}}
will render the string "pirate"

Related

template layout for PugJs

I used NodeJs with Handlebars and thought about switching to PugJs because some functionality is native - using Handlebars requires some helper functions / partials.
In Handlebars I have to define a layout and pass in the template. In PugJs I created two example routes
First route file:
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.render('index', {
title: 'Home',
template: 'main'
});
});
module.exports = router;
Second route file:
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.render('index', {
title: 'Page2',
template: 'pageTwo'
});
});
module.exports = router;
As you can see I always have to render my index file and pass in the desired pug file as a template variable.
index.pug
doctype html
html
include ./header.pug
body
include ./templates/#{template}.pug
header.pug
head
title #{title} template!
main.pug
script(src='./client/main.js')
h1 main content here
pageTwo.pug
p pageTwo
When rendering the pug files I get this error
ENOENT: no such file or directory, open
'...\views\templates\#{template}.pug'
How can I replace #{template} with the correct template variable?
Dynamic template selection isn't a feature of pug, I believe it has something to do with how pug pre-compiles everything into a JavaScript function that stays in-memory inside node.js when it runs. The benefit of that is super-fast page rendering, so we're happy to keep using pug and just work around this.
Speaking of which, you can accomplish what you want to do using conditional logic:
doctype html
html
include ./header.pug
body
if template === 'pageOne'
include ./templates/pageOne.pug
else if template === 'pageTwo'
include ./templates/pageTwo.pug
else
include ./templates/home.pug
You should also look into the extends feature to simplify adding the header into your templates and mixins to resuse code between templates. You might find that these features could provide a better solution to your requirements with some redesign.

Load templates from existing file

I'm using Handlebars in my NodeJS application as my templating engine.
I've put all my templates in a views folder like so :
-
- /controllers
- /views
- index.html
- server.js
Here's my code to render the template when the user access a given URL (using express for routing) :
app.get("/", function(req, res){
var template = handlebars.compile("views/index.html");
var data = {"name": "Charles"};
var result = template(data);
res.send(result);
});
I'm trying to render a template from a file, but it's not working. This is what the browser outputs directly when I'm accessing the / URL :
views/index.html
That makes sense, since it's interpreting the given param as a string directly and not as a path to an external template.
How can I load my template file (in this case the one in views/index.html to a variable, so that I can then render the template?
The only examples I found were storing all the templates in a file and loading them via AJAX, but all these examples were from "front-end" handlebars and not when using it with Node.
Is it possible to achieve what I want? I looked at the documentation but it's hard to find good infos for handlebars with NodeJS.
From your description, it sounds like you want handlebars as view engine, with dynamic views. You don't need to do this manually, here is an example (using express-handlebars):
var handlebars = require('express-handlebars');
app.engine('.html', handlebars({layout: false, extname: '.html'}));
app.set('view engine', '.html');
app.get("/:view", function(req, res){
var view = req.params.view;
res.render(view, { "name": "Charles" }); // Whatever data you want
});
With handlebars you have to load the file yourself or you can precompile the files (using grunt/gulp maybe) I feel way more confortable with swig ( http://paularmstrong.github.io/swig/ )
It is very simple to use. And it has also integration with express if you want.
var swig = require('swig');
swig.renderFile('/path/to/template.html', {
pagename: 'awesome people',
authors: ['Paul', 'Jim', 'Jane']
});
In your case
app.get("/", function(req, res){
res.send(swig.renderFile('views/index.html', {"name": "Charles"}));
});

How to serve rendered Jade pages as if they were static HTML pages in Node Express?

Usually you render a Jade page in a route like this:
app.get('/page', function(req, res, next){
res.render('page.jade');
});
But I want to serve all Jade pages (automatically rendered), just like how one would serve static HTML
app.use(express.static('public'))
Is there a way to do something similar for Jade?
"static" means sending existing files unchanged directly from disk to the browser. Jade can be served this way but that is pretty unusual. Usually you want to render jade to HTML on the server which by definition is not "static", it's dynamic. You do it like this:
app.get('/home', function (req, res) {
res.render('home'); // will render home.jade and send the HTML
});
If you want to serve the jade itself for rendering in the browser, just reference it directly in the url when loading it into the browser like:
$.get('/index.jade', function (jade) {
//...
});
https://github.com/runk/connect-jade-static
Usage
Assume the following structure of your project:
/views
/partials
/file.jade
Let's make jade files from /views/partials web accessable:
var jadeStatic = require('connect-jade-static');
app = express();
app.configure(function() {
app.use(jadeStatic({
baseDir: path.join(__dirname, '/views/partials'),
baseUrl: '/partials',
jade: { pretty: true }
}));
});
Now, if you start your web server and request /views/partials/file.html in browser you
should be able see the compiled jade template.
Connect-jade-static is good, but not the perfect solution for me.
To begin with, here are the reasons why I needed jade:
My app is a single page app, there are no HTMLs generated from templates at runtime. Yet, I am using jade to generate HTML files because:
Mixins: lots of repeated / similar code in my HTML is shortened by the use of mixins
Dropdowns: I know, lots of people use ng-repeat to fill the options in a select box. This is a waste of CPU when the list is static, e.g., list of countries. The right thing to do is have the select options filled in within the HTML or partial. But then, a long list of options makes the HTML / jade hard to read. Also, very likely, the list of countries is already available elsewhere, and it doesn’t make sense to duplicate this list.
So, I decided to generate most of my HTML partials using jade at build time. But, this became a pain during development, because of the need to re-build HTMLs when the jade file changes. Yes, I could have used connect-jade-static, but I really don’t want to generate the HTMLs at run time — they are indeed static files.
So, this is what I did:
Added a 'use' before the usual use of express.static
Within this, I check for the timestamps of jade and the corresponding html file
If the jade file is newer, regenerate the html file
Call next() after the regeneration, or immediately, if regeneration is not required.
next() will fall-through to express.static, where the generated HTML will be served
Wrap the ‘use’ around a “if !production” condition, and in the build scripts, generate all the HTML files required.
This way, I can also use all the goodies express.static (like custom headers) provides and still use jade to generate these.
Some code snippets:
var express = require('express');
var fs = require('fs')
var jade = require('jade');
var urlutil = require('url');
var pathutil = require('path');
var countries = require('./countries.js');
var staticDir = 'static'; // really static files like .css and .js
var staticGenDir = 'static.gen'; // generated static files, like .html
var staticSrcDir = 'static.src'; // source for generated static files, .jade
if (process.argv[2] != 'prod') {
app.use(‘/static', function(req, res, next) {
var u = urlutil.parse(req.url);
if (pathutil.extname(u.pathname) == '.html') {
var basename = u.pathname.split('.')[0];
var htmlFile = staticGenDir + basename + '.html';
var jadeFile = staticSrcDir + basename + '.jade';
var hstat = fs.existsSync(htmlFile) ? fs.statSync(htmlFile) : null;
var jstat = fs.existsSync(jadeFile) ? fs.statSync(jadeFile) : null;
if ( jstat && (!hstat || (jstat.mtime.getTime() > hstat.mtime.getTime())) ) {
var out = jade.renderFile(jadeFile, {pretty: true, countries: countries});
fs.writeFile(htmlFile, out, function() {
next();
});
} else {
next();
}
} else {
next();
}
});
}
app.use('/static', express.static(staticDir)); // serve files from really static if exists
app.use('/static', express.static(staticGenDir)); // if not, look in generated static dir
In reality, I have a js file containing not just countries, but various other lists shared between node, javascript and jade.
Hope this helps someone looking for an alternative.

How to create global variables accessible in all views using Express / Node.JS?

Ok, so I have built a blog using Jekyll and you can define variables in a file _config.yml which are accessible in all of the templates/layouts. I am currently using Node.JS / Express with EJS templates and ejs-locals (for partials/layouts. I am looking to do something similar to the global variables like site.title that are found in _config.yml if anyone is familiar with Jekyll. I have variables like the site's title, (rather than page title), author/company name, which stay the same on all of my pages.
Here is an example of what I am currently doing.:
exports.index = function(req, res){
res.render('index', {
siteTitle: 'My Website Title',
pageTitle: 'The Root Splash Page',
author: 'Cory Gross',
description: 'My app description',
indexSpecificData: someData
});
};
exports.home = function (req, res) {
res.render('home', {
siteTitle: 'My Website Title',
pageTitle: 'The Home Page',
author: 'Cory Gross',
description: 'My app description',
homeSpecificData: someOtherData
});
};
I would like to be able to define variables like my site's title, description, author, etc in one place and have them accessible in my layouts/templates through EJS without having to pass them as options to each call to res.render. Is there a way to do this and still allow me to pass in other variables specific to each page?
After having a chance to study the Express 3 API Reference a bit more I discovered what I was looking for. Specifically the entries for app.locals and then a bit farther down res.locals held the answers I needed.
I discovered for myself that the function app.locals takes an object and stores all of its properties as global variables scoped to the application. These globals are passed as local variables to each view. The function res.locals, however, is scoped to the request and thus, response local variables are accessible only to the view(s) rendered during that particular request/response.
So for my case in my app.js what I did was add:
app.locals({
site: {
title: 'ExpressBootstrapEJS',
description: 'A boilerplate for a simple web application with a Node.JS and Express backend, with an EJS template with using Twitter Bootstrap.'
},
author: {
name: 'Cory Gross',
contact: 'CoryG89#gmail.com'
}
});
Then all of these variables are accessible in my views as site.title, site.description, author.name, author.contact.
I could also define local variables for each response to a request with res.locals, or simply pass variables like the page's title in as the optionsparameter in the render call.
EDIT: This method will not allow you to use these locals in your middleware. I actually did run into this as Pickels suggests in the comment below. In this case you will need to create a middleware function as such in his alternative (and appreciated) answer. Your middleware function will need to add them to res.locals for each response and then call next. This middleware function will need to be placed above any other middleware which needs to use these locals.
EDIT: Another difference between declaring locals via app.locals and res.locals is that with app.locals the variables are set a single time and persist throughout the life of the application. When you set locals with res.locals in your middleware, these are set everytime you get a request. You should basically prefer setting globals via app.locals unless the value depends on the request req variable passed into the middleware. If the value doesn't change then it will be more efficient for it to be set just once in app.locals.
You can do this by adding them to the locals object in a general middleware.
app.use(function (req, res, next) {
res.locals = {
siteTitle: "My Website's Title",
pageTitle: "The Home Page",
author: "Cory Gross",
description: "My app's description",
};
next();
});
Locals is also a function which will extend the locals object rather than overwriting it. So the following works as well
res.locals({
siteTitle: "My Website's Title",
pageTitle: "The Home Page",
author: "Cory Gross",
description: "My app's description",
});
Full example
var app = express();
var middleware = {
render: function (view) {
return function (req, res, next) {
res.render(view);
}
},
globalLocals: function (req, res, next) {
res.locals({
siteTitle: "My Website's Title",
pageTitle: "The Root Splash Page",
author: "Cory Gross",
description: "My app's description",
});
next();
},
index: function (req, res, next) {
res.locals({
indexSpecificData: someData
});
next();
}
};
app.use(middleware.globalLocals);
app.get('/', middleware.index, middleware.render('home'));
app.get('/products', middleware.products, middleware.render('products'));
I also added a generic render middleware. This way you don't have to add res.render to each route which means you have better code reuse. Once you go down the reusable middleware route you'll notice you will have lots of building blocks which will speed up development tremendously.
For Express 4.0 I found that using application level variables works a little differently & Cory's answer did not work for me.
From the docs: http://expressjs.com/en/api.html#app.locals
I found that you could declare a global variable for the app in
app.locals
e.g
app.locals.baseUrl = "http://www.google.com"
And then in your application you can access these variables & in your express middleware you can access them in the req object as
req.app.locals.baseUrl
e.g.
console.log(req.app.locals.baseUrl)
//prints out http://www.google.com
In your app.js you need add something like this
global.myvar = 100;
Now, in all your files you want use this variable, you can just access it as myvar
One way to do this by updating the app.locals variable for that app in app.js
Set via following
var app = express();
app.locals.appName = "DRC on FHIR";
Get / Access
app.listen(3000, function () {
console.log('[' + app.locals.appName + '] => app listening on port 3001!');
});
Elaborating with a screenshot from #RamRovi example with slight enhancement.
you can also use "global"
Example:
declare like this :
app.use(function(req,res,next){
global.site_url = req.headers.host; // hostname = 'localhost:8080'
next();
});
Use like this:
in any views or ejs file
<%
console.log(site_url);
%>
in js files
console.log(site_url);
With the differents answers, I implemented this code to use an external file JSON loaded in "app.locals"
Parameters
{
"web": {
"title" : "Le titre de ma Page",
"cssFile" : "20200608_1018.css"
}
}
Application
var express = require('express');
var appli = express();
var serveur = require('http').Server(appli);
var myParams = require('./include/my_params.json');
var myFonctions = require('./include/my_fonctions.js');
appli.locals = myParams;
EJS Page
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title><%= web.title %></title>
<link rel="stylesheet" type="text/css" href="/css/<%= web.cssFile %>">
</head>
</body>
</html>
Hoping it will help
What I do in order to avoid having a polluted global scope is to create a script that I can include anywhere.
// my-script.js
const ActionsOverTime = require('#bigteam/node-aot').ActionsOverTime;
const config = require('../../config/config').actionsOverTime;
let aotInstance;
(function () {
if (!aotInstance) {
console.log('Create new aot instance');
aotInstance = ActionsOverTime.createActionOverTimeEmitter(config);
}
})();
exports = aotInstance;
Doing this will only create a new instance once and share that everywhere where the file is included. I am not sure if it is because the variable is cached or of it because of an internal reference mechanism for the application (that might include caching). Any comments on how node resolves this would be great.
Maybe also read this to get the gist on how require works:
http://fredkschott.com/post/2014/06/require-and-the-module-system/

Get compiled jade template inside view?

I have a "partial" template that I want to use both client-side and server-side.
Is there some method or filter or something that's very similar to include except that instead of executing the template immediately, it returns a client-compiled function which I could then assign to a JS variable and use throughout my script?
At present, I'm doing this:
exports.list = function(req, res){
res.render('file/list', {
...
fileItemTemplate: jade.compile(fs.readFileSync(path.join(req.app.get('views'),'file','file-item.jade')), {client: true})
});
};
And then in my template I have:
ul#folder-list.thumbnails
each file in files
include file-item
...
script(type='text/javascript')
var fileItemTemplate = !{fileItemTemplate};
And in this way I can render some items to HTML on page-load, and then add some more in later by rendering the partial as data comes in.
This works, but it doesn't feel very DRY because I have to read in the file, and deal with filepaths and stuff in the route, and then essentially redeclare the exact same variable client-side.
Is there a nice way of doing this?
Something like this would be ideal:
script(type='text/javascript')
var fileItemTemplate = !{compile file-item};
A possible solution could be JadeAsset. See also the discussion here.
You can hook assets into Express:
assets.on('complete', function() {
var app = express.createServer();
app.configure(function() {
app.use(assets); // that's all you need to do
});
app.listen(8000);
});
To create your Jade assets:
var assets = new AssetRack([
new rack.JadeAsset({
url: '/templates.js',
dirname: __dirname + '/templates'
})
]);

Resources