Giter VIP home page Giter VIP logo

express-handlebars's Introduction

Express Handlebars

Join the chat at https://gitter.im/ericf/express-handlebars

A Handlebars view engine for Express which doesn't suck.

npm version dependency status

This package used to be named express3-handlebars. The previous express-handlebars package by @jneen can be found here.

Goals & Design

I created this project out of frustration with the existing Handlebars view engines for Express. As of version 3.x, Express got out of the business of being a generic view engine — this was a great decision — leaving developers to implement the concepts of layouts, partials, and doing file I/O for their template engines of choice.

Goals and Features

After building a half-dozen Express apps, I developed requirements and opinions about what a Handlebars view engine should provide and how it should be implemented. The following is that list:

  • Add back the concept of "layout", which was removed in Express 3.x.

  • Add back the concept of "partials" via Handlebars' partials mechanism.

  • Support a directory of partials; e.g., {{> foo/bar}} which exists on the file system at views/partials/foo/bar.handlebars, by default.

  • Smart file system I/O and template caching. When in development, templates are always loaded from disk. In production, raw files and compiled templates are cached, including partials.

  • All async and non-blocking. File system I/O is slow and servers should not be blocked from handling requests while reading from disk. I/O queuing is used to avoid doing unnecessary work.

  • Ability to easily precompile templates and partials for use on the client, enabling template sharing and reuse.

  • Ability to use a different Handlebars module/implementation other than the Handlebars npm package.

Package Design

This package was designed to work great for both the simple and complex use cases. I intentionally made sure the full implementation is exposed and is easily overridable.

The package exports a function which can be invoked with no arguments or with a config object and it will return a function (closed over sensible defaults) which can be registered with an Express app. It's an engine factory function.

This exported engine factory has two properties which expose the underlying implementation:

  • ExpressHandlebars(): The constructor function which holds the internal implementation on its prototype. This produces instance objects which store their configuration, compiled and precompiled templates, and expose an engine() function which can be registered with an Express app.

  • create(): A convenience factory function for creating ExpressHandlebars instances.

An instance-based approach is used so that multiple ExpressHandlebars instances can be created with their own configuration, templates, partials, and helpers.

Installation

Install using npm:

$ npm install express-handlebars

Usage

This view engine uses sensible defaults that leverage the "Express-way" of structuring an app's views. This makes it trivial to use in basic apps:

Basic Usage

Directory Structure:

.
├── app.js
└── views
    ├── home.handlebars
    └── layouts
        └── main.handlebars

2 directories, 3 files

app.js:

Creates a super simple Express app which shows the basic way to register a Handlebars view engine using this package.

var express = require('express');
var exphbs  = require('express-handlebars');

var app = express();

app.engine('handlebars', exphbs());
app.set('view engine', 'handlebars');

app.get('/', function (req, res) {
    res.render('home');
});

app.listen(3000);

views/layouts/main.handlebars:

The main layout is the HTML page wrapper which can be reused for the different views of the app. {{{body}}} is used as a placeholder for where the main content should be rendered.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Example App</title>
</head>
<body>

    {{{body}}}

</body>
</html>

views/home.handlebars:

The content for the app's home view which will be rendered into the layout's {{{body}}}.

<h1>Example App: Home</h1>

Running the Example

The above example is bundled in this package's examples directory, where it can be run by:

$ cd examples/basic/
$ npm install
$ npm start

Using Instances

Another way to use this view engine is to create an instance(s) of ExpressHandlebars, allowing access to the full API:

var express = require('express');
var exphbs  = require('express-handlebars');

var app = express();
var hbs = exphbs.create({ /* config */ });

// Register `hbs.engine` with the Express app.
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');

// ...still have a reference to `hbs`, on which methods like `loadPartials()`
// can be called.

Note: The Advanced Usage example demonstrates how ExpressHandlebars instances can be leveraged.

Template Caching

This view engine uses a smart template caching strategy. In development, templates will always be loaded from disk, i.e., no caching. In production, raw files and compiled Handlebars templates are aggressively cached.

The easiest way to control template/view caching is through Express' view cache setting:

app.enable('view cache');

Express enables this setting by default when in production mode, i.e.:

process.env.NODE_ENV === "production"

Note: All of the public API methods accept options.cache, which gives control over caching when calling these methods directly.

Layouts

A layout is simply a Handlebars template with a {{{body}}} placeholder. Usually it will be an HTML page wrapper into which views will be rendered.

This view engine adds back the concept of "layout", which was removed in Express 3.x. It can be configured with a path to the layouts directory, by default it's set to relative to express settings.view + layouts/

There are two ways to set a default layout: configuring the view engine's defaultLayout property, or setting Express locals app.locals.layout.

The layout into which a view should be rendered can be overridden per-request by assigning a different value to the layout request local. The following will render the "home" view with no layout:

app.get('/', function (req, res, next) {
    res.render('home', {layout: false});
});

Helpers

Helper functions, or "helpers" are functions that can be registered with Handlebars and can be called within a template. Helpers can be used for transforming output, iterating over data, etc. To keep with the spirit of logic-less templates, helpers are the place where logic should be defined.

Handlebars ships with some built-in helpers, such as: with, if, each, etc. Most application will need to extend this set of helpers to include app-specific logic and transformations. Beyond defining global helpers on Handlebars, this view engine supports ExpressHandlebars instance-level helpers via the helpers configuration property, and render-level helpers via options.helpers when calling the render() and renderView() methods.

The following example shows helpers being specified at each level:

app.js:

Creates a super simple Express app which shows the basic way to register ExpressHandlebars instance-level helpers, and override one at the render-level.

var express = require('express');
var exphbs  = require('express-handlebars');

var app = express();

var hbs = exphbs.create({
    // Specify helpers which are only registered on this instance.
    helpers: {
        foo: function () { return 'FOO!'; },
        bar: function () { return 'BAR!'; }
    }
});

app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');

app.get('/', function (req, res, next) {
    res.render('home', {
        showTitle: true,

        // Override `foo` helper only for this rendering.
        helpers: {
            foo: function () { return 'foo.'; }
        }
    });
});

app.listen(3000);

views/home.handlebars:

The app's home view which uses helper functions to help render the contents.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Example App - Home</title>
</head>
<body>

    <!-- Uses built-in `if` helper. -->
  {{#if showTitle}}
    <h1>Home</h1>
  {{/if}}

    <!-- Calls `foo` helper, overridden at render-level. -->
    <p>{{foo}}</p>

    <!-- Calls `bar` helper, defined at instance-level. -->
    <p>{{bar}}</p>

</body>
</html>

More on Helpers

Refer to the Handlebars website for more information on defining helpers:

Metadata

Handlebars has a data channel feature that propagates data through all scopes, including helpers and partials. Values in the data channel can be accessed via the {{@variable}} syntax. Express Handlebars provides metadata about a template it renders on a {{@exphbs}} object allowing access to things like the view name passed to res.render() via {{@exphbs.view}}.

The following is the list of metadata that's accessible on the {{@exphbs}} data object:

  • cache: Boolean whether or not the template is cached.
  • view: String name of the view passed to res.render().
  • layout: String name of the layout view.
  • data: Original data object passed when rendering the template.
  • helpers: Collection of helpers used when rendering the template.
  • partials: Collection of partials used when rendering the template.

API

Configuration and Defaults

There are two main ways to use this package: via its engine factory function, or creating ExpressHandlebars instances; both use the same configuration properties and defaults.

var exphbs = require('express-handlebars');

// Using the engine factory:
exphbs({ /* config */ });

// Create an instance:
exphbs.create({ /* config */ });

The following is the list of configuration properties and their default values (if any):

handlebars=require('handlebars')

The Handlebars module/implementation. This allows for the ExpressHandlebars instance to use a different Handlebars module/implementation than that provided by the Handlebars npm package.

extname=".handlebars"

The string name of the file extension used by the templates. This value should correspond with the extname under which this view engine is registered with Express when calling app.engine().

The following example sets up an Express app to use .hbs as the file extension for views:

var express = require('express');
var exphbs  = require('express-handlebars');

var app = express();

app.engine('.hbs', exphbs({extname: '.hbs'}));
app.set('view engine', '.hbs');

Note: Setting the app's "view engine" setting will make that value the default file extension used for looking up views.

layoutsDir

Default layouts directory is relative to express settings.view + layouts/ The string path to the directory where the layout templates reside.

Note: If you configure Express to look for views in a custom location (e.g., app.set('views', 'some/path/')), and if your partialsDir is not relative to express settings.view + layouts/, you will need to reflect that by passing an updated path as the layoutsDir property in your configuration.

partialsDir

Default partials directory is relative to express settings.view + partials/ The string path to the directory where the partials templates reside or object with the following properties:

  • dir: The string path to the directory where the partials templates reside.
  • namespace: Optional string namespace to prefix the partial names.
  • templates: Optional collection (or promise of a collection) of templates in the form: {filename: template}.

Note: If you configure Express to look for views in a custom location (e.g., app.set('views', 'some/path/')), and if your partialsDir is not relative to express settings.view + partials/, you will need to reflect that by passing an updated path as the partialsDir property in your configuration.

Note: Multiple partials dirs can be used by making partialsDir an array of strings, and/or config objects as described above. The namespacing feature is useful if multiple partials dirs are used and their file paths might clash.

defaultLayout

The string name or path of a template in the layoutsDir to use as the default layout. main is used as the default. This is overridden by a layout specified in the app or response locals. Note: A falsy value will render without a layout; e.g., res.render('home', {layout: false});.

helpers

An object which holds the helper functions used when rendering templates with this ExpressHandlebars instance. When rendering a template, a collection of helpers will be generated by merging: handlebars.helpers (global), helpers (instance), and options.helpers (render-level). This allows Handlebars' registerHelper() function to operate as expected, will providing two extra levels over helper overrides.

compilerOptions

An object which holds options that will be passed along to the Handlebars compiler functions: Handlebars.compile() and Handlebars.precompile().

Properties

The public API properties are provided via ExpressHandlebars instances. In additional to the properties listed in the Configuration and Defaults section, the following are additional public properties:

engine

A function reference to the renderView() method which is bound to this ExpressHandlebars instance. This bound function should be used when registering this view engine with an Express app.

extname

The normalized extname which will always start with . and defaults to .handlebars.

compiled

An object cache which holds compiled Handlebars template functions in the format: {"path/to/template": [Function]}.

precompiled

An object cache which holds precompiled Handlebars template strings in the format: {"path/to/template": [String]}.

Methods

The following is the list of public API methods provided via ExpressHandlebars instances:

Note: All of the public methods return a Promise (with the exception of renderView() which is the interface with Express.)

getPartials([options])

Retrieves the partials in the partialsDir and returns a Promise for an object mapping the partials in the form {name: partial}.

By default each partial will be a compiled Handlebars template function. Use options.precompiled to receive the partials as precompiled templates — this is useful for sharing templates with client code.

Parameters:

  • [options]: Optional object containing any of the following properties:

    • [cache]: Whether cached templates can be used if they have already been requested. This is recommended for production to avoid unnecessary file I/O.

    • [precompiled=false]: Whether precompiled templates should be provided, instead of compiled Handlebars template functions.

The name of each partial corresponds to its location in partialsDir. For example, consider the following directory structure:

views
└── partials
    ├── foo
    │   └── bar.handlebars
    └── title.handlebars

2 directories, 2 files

getPartials() would produce the following result:

var hbs = require('express-handlebars').create();

hbs.getPartials().then(function (partials) {
    console.log(partials);
    // => { 'foo/bar': [Function],
    // =>    title: [Function] }
});

getTemplate(filePath, [options])

Retrieves the template at the specified filePath and returns a Promise for the compiled Handlebars template function.

Use options.precompiled to receive a precompiled Handlebars template.

Parameters:

  • filePath: String path to the Handlebars template file.

  • [options]: Optional object containing any of the following properties:

    • [cache]: Whether a cached template can be used if it have already been requested. This is recommended for production to avoid necessary file I/O.

    • [precompiled=false]: Whether a precompiled template should be provided, instead of a compiled Handlebars template function.

getTemplates(dirPath, [options])

Retrieves all the templates in the specified dirPath and returns a Promise for an object mapping the compiled templates in the form {filename: template}.

Use options.precompiled to receive precompiled Handlebars templates — this is useful for sharing templates with client code.

Parameters:

  • dirPath: String path to the directory containing Handlebars template files.

  • [options]: Optional object containing any of the following properties:

    • [cache]: Whether cached templates can be used if it have already been requested. This is recommended for production to avoid necessary file I/O.

    • [precompiled=false]: Whether precompiled templates should be provided, instead of a compiled Handlebars template function.

render(filePath, context, [options])

Renders the template at the specified filePath with the context, using this instance's helpers and partials by default, and returns a Promise for the resulting string.

Parameters:

  • filePath: String path to the Handlebars template file.

  • context: Object in which the template will be executed. This contains all of the values to fill into the template.

  • [options]: Optional object which can contain any of the following properties which affect this view engine's behavior:

    • [cache]: Whether a cached template can be used if it have already been requested. This is recommended for production to avoid unnecessary file I/O.

    • [data]: Optional object which can contain any data that Handlebars will pipe through the template, all helpers, and all partials. This is a side data channel.

    • [helpers]: Render-level helpers that will be used instead of any instance-level helpers; these will be merged with (and will override) any global Handlebars helper functions.

    • [partials]: Render-level partials that will be used instead of any instance-level partials. This is used internally as an optimization to avoid re-loading all the partials.

renderView(viewPath, options|callback, [callback])

Renders the template at the specified viewPath as the {{{body}}} within the layout specified by the defaultLayout or options.layout. Rendering will use this instance's helpers and partials, and passes the resulting string to the callback.

This method is called by Express and is the main entry point into this Express view engine implementation. It adds the concept of a "layout" and delegates rendering to the render() method.

The options will be used both as the context in which the Handlebars templates are rendered, and to signal this view engine on how it should behave, e.g., options.cache=false will load always load the templates from disk.

Parameters:

  • viewPath: String path to the Handlebars template file which should serve as the {{{body}}} when using a layout.

  • [options]: Optional object which will serve as the context in which the Handlebars templates are rendered. It may also contain any of the following properties which affect this view engine's behavior:

    • [cache]: Whether cached templates can be used if they have already been requested. This is recommended for production to avoid unnecessary file I/O.

    • [data]: Optional object which can contain any data that Handlebars will pipe through the template, all helpers, and all partials. This is a side data channel.

    • [helpers]: Render-level helpers that will be merged with (and will override) instance and global helper functions.

    • [partials]: Render-level partials will be merged with (and will override) instance and global partials. This should be a {partialName: fn} hash or a Promise of an object with this shape.

    • [layout]: Optional string path to the Handlebars template file to be used as the "layout". This overrides any defaultLayout value. Passing a falsy value will render with no layout (even if a defaultLayout is defined).

  • callback: Function to call once the template is retrieved.

Hooks

The following is the list of protected methods that are called internally and serve as hooks to override functionality of ExpressHandlebars instances. A value or a promise can be returned from these methods which allows them to perform async operations.

_compileTemplate(template, options)

This hook will be called when a Handlebars template needs to be compiled. This function needs to return a compiled Handlebars template function, or a promise for one.

By default this hook calls Handlebars.compile(), but it can be overridden to preform operations before and/or after Handlebars compiles the template. This is useful if you wanted to first process Markdown within a Handlebars template.

Parameters:

  • template: String Handlebars template that needs to be compiled.

  • options: Object compilerOptions that were specified when the ExpressHandlebars instance as created. This object should be passed along to the Handlebars.compile() function.

_precompileTemplate(template, options)

This hook will be called when a Handlebars template needs to be precompiled. This function needs to return a serialized Handlebars template spec. string, or a promise for one.

By default this hook calls Handlebars.precompile(), but it can be overridden to preform operations before and/or after Handlebars precompiles the template. This is useful if you wanted to first process Markdown within a Handlebars template.

Parameters:

  • template: String Handlebars template that needs to be precompiled.

  • options: Object compilerOptions that were specified when the ExpressHandlebars instance as created. This object should be passed along to the Handlebars.compile() function.

_renderTemplate(template, context, options)

This hook will be called when a compiled Handlebars template needs to be rendered. This function needs to returned the rendered output string, or a promise for one.

By default this hook simply calls the passed-in template with the context and options arguments, but it can be overridden to perform operations before and/or after rendering the template.

Parameters:

  • template: Compiled Handlebars template function to call.

  • context: The context object in which to render the template.

  • options: Object that contains options and metadata for rendering the template:

    • data: Object to define custom @variable private variables.

    • helpers: Object to provide custom helpers in addition to the globally defined helpers.

    • partials: Object to provide custom partials in addition to the globally defined partials.

Examples

This example shows the most basic way to use this view engine.

This example is more comprehensive and shows how to use many of the features of this view engine, including helpers, partials, multiple layouts, etc.

As noted in the Package Design section, this view engine's implementation is instance-based, and more advanced usages can take advantage of this. The Advanced Usage example demonstrates how to use an ExpressHandlebars instance to share templates with the client, among other features.

License

This software is free to use under the Yahoo! Inc. BSD license. See the LICENSE file for license text and copyright information.

express-handlebars's People

Contributors

asos-albinotonnina avatar calvinmetcalf avatar crashthatch avatar ericf avatar erikeckhardt avatar feygon avatar geekg1rl avatar gitter-badger avatar inerte avatar jacob-faber avatar jconniff avatar jfbrennan avatar joanniclaborde avatar josephuz avatar kamui545 avatar kevmo avatar knoxcard avatar paulbgd avatar sahat avatar squaretone avatar tineler avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

express-handlebars's Issues

Compress html

I don't know if express3-handlebars could compress the rendered html just as the jade does.

Production ready?

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

Add support for multiple partials directories

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.

Resolve paths to avoid duplicates in caches

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.

Intercepting Template Before Compile

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....

Extend for multilingual layouts and templates

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.

exposing a nested object to a layout

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?

Express 4.0?

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?

Location script is executed seems to matter

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 cding 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?

Passing other variables to layout than body?

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?

Add usage docs on Handlebars helpers

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.

Partials with integers as name cause parsing error

Steps to reproduce:

  1. create partial in views/partials/errors/500.js
  2. use partial in a view, e.g. {{> 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.

Express locals as options in public API

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.

Add unit tests

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.

Add htmlmin option

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.

Support render-level partials and helpers

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?

Template literals

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

Cannot loop Arrays coming froma Helper method

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?

extname configuration property doesn't work

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.

Shared layout

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>

Indent option. Mmmmh, indent option!

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

Support `Handlebars.registerHelper()` during `render()`?

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.

ember.js usage

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.

SafeString

How can I use new Handlebars.SafeString() ?

Limit options passed from render methods to helper methods

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.

THX

Hi i have no issue i only wanna say thx for your work ....

Add examples

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.

Add docs about `extname`

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.

Use with SWAG

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.

How using it in a phonegap app?

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?

extname getting ignored for templates but not layouts

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.

Feature: contentFor block helper

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?

Uncaught TypeError: Object function (Handlebars ... } has no method 'call'

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?

Templates break when empty

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)

Difficulty using nested partials

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}}

best practice to share handlebars helpers to the client

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.

Directory of partials

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?

Path of dirPath cannot be resolved

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

SafeString not usable

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.

Adding helper to check object property value

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?

Add loadTemplates() API

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.