ericf / express-handlebars Goto Github PK
View Code? Open in Web Editor NEWA Handlebars view engine for Express which doesn't suck.
License: BSD 3-Clause "New" or "Revised" License
A Handlebars view engine for Express which doesn't suck.
License: BSD 3-Clause "New" or "Revised" License
How can I use new Handlebars.SafeString()
?
All the the configuration properties could be expressed as application settings via Express. Would it be a good idea to switch these to app settings?
The docs are light on the main thing a layout needs, a {{{body}}}
placeholder.
Hi,
First of all, thank you all for this great project. I love handlebars and I'm starting with NodeJS so this is great.
I have one question though, is it possible to to output {{{ }}} blocks inside a template which aren't handled by the template engine? I want to combine node JS templating with my existing handlebars (front-end) templates.
For instance, I would like to render this in my .hbs file:
<script id="product-detail-row" type="text/x-handlebars-template"> {{{productDetailLabel}}} </script>Which will be handled by my client-side script. However, since this code is in a .hbs file which is rendered by express3-handlebars, this would need to be skipped by a sort of literal.
Is this possible?
Thanks
The public API methods which are prefixed with "get" do not have useful return values. This is a bad signal to the caller. Instead, prefix the methods with "load".
This change should be in a minor version bump.
Steps to reproduce:
views/partials/errors/500.js
{{> errors/500}}
The result is:
[Error: Parse error on line 8:
...er> {{> errors/500}} {{> error
---------------------^
Expecting 'ID', got 'INTEGER']
On a side note, there is no documentation regarding the use of partials.
When registering helpers in create(), you can't use handlebars' SafeString method, even if you separately require the handlebars package.
var expressHandlebars = require('express3-handlebars'),
Handlebars = require('handlebars');
var hbs = expressHandlebars.create({
defaultLayout: 'main',
helpers: {
info: function(game) {
return new expressHandlebars.SafeString('<a href="about:blank"');
},
info2: function(game) {
return new Handlebars.SafeString('<a href="about:blank"');
}
}
});
Neither works.
When my node script is executed from a different directory to that which it lives, this library can't locate the templates. I'm still new to nodejs, so I don't know if this is just a given that you'll always start the node process from the directory it lives?
This can be reproduced with this repo itself. If rather than navigating to examples/basic
and running the example from there, you instead fire if off from somewhere else, you get the error Error: Failed to lookup view "home"
.
The reason I run into this is because I have a script that starts the process without cd
ing first. Obviously I can just tweak that, but I was using a different template engine before that had no problems - so it led to a fair bit of head scratching when making the change, so I wonder if it's a bug?
Love the project. By far the easiest to get going so far for this node.js/express newbie.
The express-hbs project has a really neat feature I'd love to see added. I'm not well enough versed yet to create this and file a pull request. The contentFor helper is basically equiv to content placeholders in asp.net and very useful. Basically allows to adding content to a parent template/layout. Here's the usage from express-hbs
layout.handlebars
{{{block "pageScripts"}}}
index.handlebars
{{#contentFor "pageScripts"}}
CONTENT HERE
{{/contentFor}}
Is this something we can get added, or would it be possible to get a helper function we can add as middleware?
I have an array that looks like this:
var menu =
[
{
type: "category",
labels: [
{lang: "en", text: "appetizer"},
{lang: "nl", text: "voorgerecht"}
],
descriptions: [
{lang: "en", text: "great snacks before the main dish"},
{lang: "nl", text: "lekkere snacks voor het hoofdgerecht"}
],
visible: true,
items: [
{
type: "item",
labels: [
{lang: "en", text: "French bread"},
{lang: "nl", text: "stokbrood"}
],
descriptions: [
{lang: "en", text: "french bread with garlic butter"},
{lang: "nl", text: "stokbrood bereid met knoflook"}
],
visible: true,
price: {
currency: "EUR",
value: 1.5
}
},
{
type: "category",
labels: [
{lang: "en", text: "antipasta"},
{lang: "nl", text: "antipasta"}
],
descriptions: [
{lang: "en", text: "apptizer but only pasta"},
{lang: "nl", text: "pasta als voorgerecht"}
],
visible: true,
items: [
{
type: "item",
labels: [
{lang: "en", text: "paesana alla gorgonzola"},
{lang: "nl", text: "paesana met gorgonzola kaas"}
],
descriptions: [
{lang: "en", text: "great pizza with lots of cheese"},
{lang: "nl", text: "lekkere pizza met veel kaas"}
],
visible: true,
price: {
currency: "EUR",
value: 1.5
}
}
]
}
]
},
{
type: "item",
labels: [
{lang: "en", text: "paesana alla gorgonzola"},
{lang: "nl", text: "paesana met gorgonzola kaas"}
],
descriptions: [
{lang: "en", text: "great pizza with lots of cheese"},
{lang: "nl", text: "lekkere pizza met veel kaas"}
],
visible: true,
price: {
currency: "EUR",
value: 1.5
}
}
];
I pass this menu array to the view like so:
res.render("manage/menu", {
menu: menu
});
Now I would like to render this menu on the server side in my views using handlebars. I need to differentiate between each array element: it can be either a category (element.type === "category"
) or a normal item (element.type === "item"
). Something like this:
{{#each menu}}
{{#if elementIsCategory this}}
category
{{else}}
item
{{/if}}
{{/each}}
And this helper:
res.render("manage/menu", {
menu: menu,
helpers: {
elementIsCategory: function(element) {
console.log(this);
console.log(this.get("type"));
console.log(this.get("resto"));
return element.type === "category";
}
}
});
I don't even see the console.log lines. How should this be done instead?
Is it possible to use SWAG https://github.com/elving/swag with exphbs? And could you recommend a good implementation of it / example?
This is how I have started it, but I'm sure I'm missing something and would this conflict (using different modules)?
var exphbs = require('express3-handlebars');
var hbs = require('express3-handlebars').create();
//swag for handlebars
var Handlebars = require('handlebars');
var Swag = require('swag');
Swag.registerHelpers(Handlebars);
Sorry for the noob question and hope ok to ask here.
Add documentation to the README about setting the file extname
to use in all the various places, include in Express: app.engine()
, and "view engine" setting.
This is merely a question.
A while ago, I saw someone write in a tutorial a snippet of code that uses a similar handlebars library - in this tutorial he set Express' app.engine as "hbr" and somehow this made him able to separate ember.js' Handlebars templates into their own files without using require.js or even wrapping them in script tags - would this be possible in yours?
I do see the advanced example in the Readme where you pass an exposeTemplates
object to the root index and I'm hoping it's for a case like this - I just want to clear my confusion.
There currently is not much information about how to use and register Handlebars helper functions with this module. Since it doesn't unique by segmenting helpers between ExpressHandlebars
instances, there needs to be better documentation.
Would it be possible to add an htmlmin flag to minify the generated HTML before it gets sent to the client? I'm not having any success with html-minifier because it crashes on Handlebars {{bind-attr}}
syntax.
The README does not state if this project is production ready. Is this the case?
PS I really appreciate this project! With Node we got javascript on the server. But we also got fragmentation in view engines and module management among other things. This a great step in the right direction! DS
I have move the middleware as separate file.
I thought it is the problem
bu have cloned your repository and i ran the advanced example, i have tried my changes direktly , everything seem too work
then i have move the advance example as separate folder/project installed npm stuff.
tried to run - error
path.js:116
throw new TypeError('Arguments to path.resolve must be strings');
^
TypeError: Arguments to path.resolve must be strings
at Object.exports.resolve (path.js:116:15)
at ExpressHandlebars.extend._loadDir (C:\server\www\testproject-handlebars\node_modules\express3-handlebar
s\lib\express-handlebars.js:234:24)
at fn (C:\server\www\testproject-handlebars\node_modules\async\lib\async.js:579:34)
at Object._onImmediate (C:\server\www\testproject-handlebars\node_modules\async\lib\async.js:495:34)
at processImmediate [as _immediateCallback] (timers.js:330:15)
inspected the code of the module
on line 233
dirPath = path.resolve(dirPath);
if i console log here
console.log(dirPath);
dirPath is an array
[ 'shared/templates/', 'views/partials/' ]
please help
Hi All, This may seem like a stupid question but I couldn't find any documentation to state otherwise but does this NPM package support express 4.0?
As far as I am aware only helpers can expose a variable to a layout (and therefore every request).
I want to expose a config object such that I can access its nested properties in layout.handlebars. I did this:
server.engine('handlebars', exphbs({
defaultLayout: 'main',
layoutsDir: config.http.web.layoutsDir,
helpers: {
config: function() { return config; }
}}));
And layout.handlebars
:
<footer>
<p>MyApp version {{{config.version}}}.</p>
</footer>
Unfortunately this doesn't work. Why not? Is there any way to expose a nested object to every layout/view such that I can access its properties everywhere?
Helpers and partials can only be specified at the instance level. It would be useful to have the ability to specify helpers and/or partials at the render/request level. For example:
app.get('/', function (req, res, next) {
function formatName(name) {
return name.first + ' ' + name.last;
}
res.render('home', {
name: {
first: 'Eric',
last : 'Ferraiuolo'
},
helpers: {
formatName: formatName
}
});
});
The question is whether these helpers
are merged with the instance's, or if they override them completely?
Hi, I've got this configuration
app.configure(function () {
app.use(express.static(__dirname + '/public'));
app.disable('x-powered-by');
app.use(express.bodyParser());
app.engine('handlebars', exphbs({
defaultLayout: 'layout',
layoutsDir: 'views/',
extname: '.hbs'
}));
app.set('view engine', 'handlebars');
});
app.configure('production', function () {
app.enable('trust proxy');
app.use(express.compress());
});
app.get('/', function (req, res) {
res.render('home');
});
And then my views directory looks like this:
views/
- layout.hbs
- home.hbs
And with that setup, I get this error when visiting /
:
Error: Failed to lookup view "home"
at Function.app.render (/Users/nathan/pagerank.nfriedly.com/node_modules/express/lib/application.js:493:17)
at ServerResponse.res.render (/Users/nathan/pagerank.nfriedly.com/node_modules/express/lib/response.js:753:7)
at /Users/nathan/pagerank.nfriedly.com/pr-app.js:31:9
at callbacks (/Users/nathan/pagerank.nfriedly.com/node_modules/express/lib/router/index.js:161:37)
at param (/Users/nathan/pagerank.nfriedly.com/node_modules/express/lib/router/index.js:135:11)
at pass (/Users/nathan/pagerank.nfriedly.com/node_modules/express/lib/router/index.js:142:5)
at Router._dispatch (/Users/nathan/pagerank.nfriedly.com/node_modules/express/lib/router/index.js:170:5)
at Object.router (/Users/nathan/pagerank.nfriedly.com/node_modules/express/lib/router/index.js:33:10)
at next (/Users/nathan/pagerank.nfriedly.com/node_modules/express/node_modules/connect/lib/proto.js:199:15)
at multipart (/Users/nathan/pagerank.nfriedly.com/node_modules/express/node_modules/connect/lib/middleware/multipart.js:64:37)
BUT, if I rename home.hbs
to home.handlebars
so that my views directory looks like this:
views/
- layout.hbs
- home.handlebars
Then everything works.
I had some Handlebar templates using .hbs
so I used the extname
config property to adjust the default, but nothing was rendering.. so I renamed my templates to .handlebars
and rendering works now.
I've my hbs files with partial imports like {{> partials/partial}} but the only way I found to make this work is using absolute path: C:\projects\myproject\view\fo\desktop\templates
Tried under linux as well without success.
I'm missing something?
Looking for best practice / suggestions to expose handlebars helpers to the client.
crazy idea:
I'm thinking of having grunt write a js-file to public/js
containing all needed helpers client-side. This based on introspecting all handlebars-templates in the shared
folder (as per the advanced example) and looking for used helpers.
Add a section to the usage docs on how partials work.
The README contains examples of how to use this package, but it doesn't include any runnable code examples. Add these examples would be very useful to people who are just starting out with this module, and provide some integration tests.
Hello,
Is it possible to minify output files with express3-handlebars ? Or is it planned for a next update ?
Regards
J.Durand
I'm trying to share a template on the client side but am running into an exception thrown on the client side.
In my Express route I do the following to share the template:
var
srcd = process.cwd() + "/src",
exphbs = require("express3-handlebars"),
config = require(srcd + "/config"),
hbs = exphbs.create({partialsDir: config.http.web.partialsDir});
module.exports = function handleManageMenu(req, res) {
//expose template partials
hbs.loadTemplates(config.http.web.partialsDir, {
cache: (config.environment === "production") ? true : false,
precompiled: true
}, function(err, templates) {
if (err) return console.error(err.stack);
//expose templates
res.locals.exposedData.templates = templates;
//render
res.render("manage/menu");
});
};
This works just fine. The templates
are exposed to the client using JSON.
I then revive
the templates on the client side as done in your advanced example:
var Handlebars = require("../../vendor/handlebars"),
exposedData = require("../../exposedData"),
revive = Handlebars.template,
templates = exposedData.get("templates"),
menu = exposedData.get("menu");
exports.run = function() {
Handlebars.templates = Handlebars.templates || {};
for (var key in templates) {
if (templates.hasOwnProperty(key)) {
Handlebars.templates[key] = Handlebars.template(templates[key]);
}
}
console.log(Handlebars.templates["menu/menu.handlebars"]);
};
This seems fine, the console.log returns a function in the form function (context, options) { ...}
.
However.. when I then call this function with the menu
as the context (menu is an array), things go horribly wrong:
console.log(Handlebars.templates["menu/menu.handlebars"](menu));
results in:
Uncaught TypeError: Object function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); partials = this.merge(partials, Handlebars.partials); data = data || {};
var buffer = "", stack1, self=this;
function program1(depth0,data) {
var buffer = "", stack1;
buffer += "\n\n ";
stack1 = helpers['if'].call(depth0, depth0.items, {hash:{},inverse:self.program(4, program4, data),fn:self.program(2, program2, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n\n";
return buffer;
}
function program2(depth0,data) {
var buffer = "", stack1;
buffer += "\n\n ";
stack1 = self.invokePartial(partials['menu/category'], 'menu/category', depth0, helpers, partials, data);
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n\n ";
return buffer;
}
function program4(depth0,data) {
var buffer = "", stack1;
buffer += "\n\n ";
stack1 = self.invokePartial(partials['menu/item'], 'menu/item', depth0, helpers, partials, data);
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n\n ";
return buffer;
}
buffer += "<div class=\"menu\">\n";
stack1 = helpers.each.call(depth0, depth0, {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n</div>";
return buffer;
} has no method 'call'
Note that the exact same template works fine on the server side, with the exact same menu object (array).
The menu/menu.handlebars template is defined as:
<div class="menu">
{{#each this}}
{{#if this.items}}
{{> menu/category this}}
{{else}}
{{> menu/item this}}
{{/if}}
{{/each}}
</div>
Any idea what's going wrong?
Since Handlebars doesn't offer this functionality it would be truly awesome if you could add the functionality to exp3hbs.
A function implementing this could be as simple as:
/**
* @param {string} sInput
* @param {number} levels – How many levels of indentation to apply
* @param {string} [char] – Character to use for indentation, defaults to tab char.
* @returns {string}
*/
function indent(sInput, levels, char) {
var indentedLineBreak;
char = char || '\t';
indentedLineBreak = '\n'+ ( new Array( levels + 1 ).join( char ) );
return indentedLineBreak + sInput.split('\n').join(indentedLineBreak);
}
res.render('foo', {indent: 2})
for example would render the foo
view with two levels of indentation. Indentation would be completely optional of course, as a performance hit must be expected obviously.
Let me know if you're interested in a pull request. :)
There was a pull requet at Handlebars for this: handlebars-lang/handlebars.js#237
Currently, there is no way to load a collection of templates from a directory outside of of the configured partialsDir
. Having an API to loadTemplates()
from any directory would be very useful, and the loadPartials()
method could call it.
This feature is especially helpful for people who want to share more than just their partialsDir
with the client side of their app.
Hi i have no issue i only wanna say thx for your work ....
This package has 0 unit tests :(
Adding tests is very important now that so many people and apps rely on this module. The current thinking is to use Mocha for unit tests.
I have a site that has fb's og-tags in head-section which should be able to be modified according to content that comes out from mongoose model and also urls containing information of current page, like searched category or tag for example.
In general I mean all kinds of dynamic data that varies with users search terms, simplest example would be title-tag changing according to search query.
Hope you got the point!
How should I approach this problem using express3-handlebars?
I would like the ability to intercept the rendered result before it is sent as req.body to do html minification.
Adding support for multiple partials directories will enable people to have their partials split across various directories. One common use-case is shared vs. server-only partials, where the shared partials are also used on the client, e.g., an app could have "shared/templates"
and "views/partials"
directories which contain templates that can be used as partials.
When naming the partials for use in other templates, each partials dir would be considered a root, therefore "views/partials/header.handlebars"
would be available via {{> header}}
. This creates a situation for naming collisions, which can be managed in a last-one-wins fashion.
Currently an ExpressHandlebars
instance only accesses the Handlebars.helpers
during instance creation. This means that any calls to Handlebars.registerHelper()
that occur after the instance is created will not be used.
Support for using all helpers registered the Handlebars
can be implemented by always merging: global, instance, and render level helpers before each call to render.
This seems like reasonable behavior and worth the performance tradeoff (merging should be fast), to gain the expected behavior that Handlebars.registerHelper()
should work anytime it's called.
Eric, big fan of this module. Powerful stuff.
This is a question, not an issue:
I would like to intercept my templates and partials before they get compiled and cached, and process them with another module.
For example, just say I wanted to use juice to inline all my css, is there a mechanism for applying such transformations? If it were in the module it would probably happen somewhere in express-handlebars.js line 132.
What would you recommend, given the underlying structure, if there was a way to preload the files, transform them. Is it possible would you say, without extending the module itself?
Our other option is to just juice the templates in a grunt script, but it would be good to have this in memory since we don't have a lot of templates....
The render()
and renderView()
methods should not simply pass the options
they receive from the caller along to the helper methods they call.
The following doesn't make sense and will break:
app.get('/', function (req, res, next) {
res.render('home', {
precompiled: true
});
});
This will end up calling getPartials()
and getTemplate()
with options.precompiled = true
, and throw an error.
I'm having some trouble using partials nested in a folder; ex: {{> path/to/my/partial}}
Here's how I have things set up:
/server.js
/views/index.hbs
/views/cats/index.hbs
/views/cats/listing.hbs
Express configuration is like so...
var express = require('express');
var express_handlebars = require('express3-handlebars');
var router = express();
router.engine( 'hbs', express_handlebars({
extname: '.hbs',
partialsDir: __dirname +'/views'
}) );
router.set( 'view engine', 'hbs' );
router.set( 'views', __dirname +'/views' );
router.get( '/', function( req, res ){
res.render('index');
});
Finally, imagine index.hbs
is like so:
<h1>Index</h1>
{{> cats/index}}
File and dir paths should always be resolved before the pathnames are used as keys in the caches. Doing this will avoid caching duplicates, but mean paths must be resolved before looking for them in the cache.
Hello,
Is it possible to minify output files with express3-handlebars ? Or is it planned for a next update ?
Regards
J.Durand
I'm using your tool in a node app.
But now I need a phonegap app that needs to talk to my node app and needs to replicate the page "panel" with more local .js.
I know it sounds stupid but I'm confuese: is it possible to use express3-handlebars in phonegap?
If I run an empty template through express3-handlebars, I get this response from every template:
Error: You must pass a string or Handlebars AST to Handlebars.compile. You passed
at new Error (unknown source)
at Error.Handlebars.Exception (/Users/Tim/Work/repos/solidus/node_modules/express3-handlebars/node_modules/handlebars/lib/handlebars/utils.js:8:41)
at exports.attach.Handlebars.compile (/Users/Tim/Work/repos/solidus/node_modules/express3-handlebars/node_modules/handlebars/lib/handlebars/compiler/compiler.js:1254:11)
at extend.loadTemplate (/Users/Tim/Work/repos/solidus/node_modules/express3-handlebars/lib/express-handlebars.js:137:46)
at extend._loadFile (/Users/Tim/Work/repos/solidus/node_modules/express3-handlebars/lib/express-handlebars.js:296:35)
at fs.readFile (fs.js:176:14)
at fs.close (/Users/Tim/Work/repos/solidus/node_modules/grunt/node_modules/rimraf/node_modules/graceful-fs/graceful-fs.js:90:5)
at Object.oncomplete (fs.js:297:15)
Hello,
do you please like to explain a bit what is going on here and why:
For example what does revive() do ?
Thank You very Much !
<script>
(function () {
var revive = Handlebars.template,
templates = Handlebars.templates = Handlebars.templates || {};
{{#templates}}
templates['{{{name}}}'] = revive({{{template}}});
{{/templates}}
}());
</script>
It's not possible to pass app.locals
or res.locals
as template context/rendering options into the public API methods. The methods that accept options perform a check if the argument is a function, and if it is it's treated as a callback. app.locals
and res.locals
are function too however. Unfortunately Express doesn't offer a way of getting all the locals' properties at once.
In order to use Express locals the exphbs API one is forced to copy the properties over to a fresh object.
Hi Eric,
First off, Great job on the module. I really enjoy it, but i have a problem that i think might be a bug :-)
I have a template where i loop over some categories like this:
{{#Categories}}
<div class="checkbox">
<label>
<input type="checkbox" name="category" value="{{key}}">{{name}}
</label>
</div>
{{/Categories}}
This works great if i render my view like this:
app.get('/', function (req, res) {
res.render('Home', { Categories: GlobalSettings.Categories });
});
But if i define a helper method that return the same array of Categories it doesn't work is i expect:
var hbs = exphbs.create({
defaultLayout: 'main',
helpers: {
Categories: function () { return GlobalSettings.Categories }
}
});
app.get('/', function (req, res) {
res.render('Home');
});
When i add the array as a helper method my output is rendered like this:
[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]
Can i only return flat types from a helper method?
I don't know if express3-handlebars could compress the rendered html just as the jade does.
My view/layout structure is:
views
|- de
|- layouts
|- main_layout.html
|- main_layout.txt
|- user
|- register.html
|- register.txt
|- en
|- layouts
|- main_layout.html
|- main_layout.txt
|- user
|- register.html
|- register.txt
Depending on the users language different layouts and templates will be rendered.
At the moment i extend / overwrite your function
ExpressHandlebars.prototype._resolveLayoutPathOld = express3Handlebars.ExpressHandlebars.prototype._resolveLayoutPath;
ExpressHandlebars.prototype._resolveLayoutPath = function(layoutPath) {
layoutPath = this._resolveLayoutPathOld(layoutPath);
return layoutPath.replace(/%language%/, this.language);
};
var templateHtml = express3Handlebars.create({
helpers: templateHelpers,
defaultLayout: "main_mail.html",
layoutsDir: "views/%language%/layouts/"
});
var templateText = express3Handlebars.create({
helpers: templateHelpers,
defaultLayout: "main_mail.txt",
layoutsDir: "views/%language%/layouts/"
});
Before render i set de ExpressHandlebars.language variable.
Call render with language Param for the template
app.render(language + template + ".html", fields, ....
Nice will be a param in the fields-param, when the template got rendered and this param will be uses for layouts too.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.