GithubHelp home page GithubHelp logo

shannonmoeller / handlebars-layouts Goto Github PK

View Code? Open in Web Editor NEW
359.0 9.0 33.0 419 KB

Handlebars helpers which implement layout blocks similar to Jinja, Nunjucks (Swig), Pug (Jade), and Twig.

Home Page: http://npm.im/handlebars-layouts

License: MIT License

JavaScript 68.64% HTML 31.36%
handlebars layouts templating partials nodejs layout embed blocks

handlebars-layouts's Introduction

handlebars-layouts

NPM version Downloads Build Status Coverage Status Tip

Handlebars helpers which implement layout blocks similar to Jade, Jinja, Nunjucks, Swig, and Twig.

Install

With Node.js:

$ npm install handlebars-layouts

With Bower:

$ bower install shannonmoeller/handlebars-layouts

API

Helpers are generated by passing in your instance of Handlebars. This allows you to selectively register the helpers on various instances of Handlebars.

layouts(handlebars) : Object

  • handlebars Handlebars - An instance of Handlebars.

Generates an object containing the layout helpers suitible for passing into registerHelper.

var handlebars = require('handlebars'),
    layouts = require('handlebars-layouts');

handlebars.registerHelper(layouts(handlebars));

layouts.register(handlebars) : Object

  • handlebars Handlebars - An instance of Handlebars.

Both generates an object containing the layout helpers and registers them with Handlebars automatically.

var handlebars = require('handlebars'),
    layouts = require('handlebars-layouts');

layouts.register(handlebars);

Helpers

{{#extend [partial] [context] [key=value ...]}}

  • partial String - Name of partial to render.
  • context Object (Optional) - A custom context for the partial.
  • attributes Object (Optional) - Arbitrary values that will be added to the partial data context.

Loads a layout partial of a given name and defines block content.

{{#extend "layout" foo="bar"}}
    {{#content "title" mode="prepend"}}Example - {{/content}}
{{/extend}}

The {{#extend}} helper allows you to reason about your layouts as you would class extension where the above is equivalent to the following psuedo code:

class Page extends Layout {
    constructor() {
        this.foo = 'bar';
    }

    title() {
        return 'Example - ' + super();
    }
}

{{#embed [partial] [context] [key=value ...]}}

  • partial String - Name of partial to render.
  • context Object (Optional) - A custom context for the partial.
  • attributes Object (Optional) - Arbitrary values that will be added to the partial data context.

Allows you to load a partial which itself extends from a layout. Blocks defined in embedded partials will not conflict with those in the primary layout.

{{#extend "layout"}}

    {{#content "body"}}
        {{#embed "gallery"}}
            {{#content "body"}}
                <img src="1.png" alt="" />
                <img src="2.png" alt="" />
            {{/content}}
        {{/embed}}

        {{#embed "modal" foo="bar" name=user.fullName}}
            {{#content "title" mode="prepend"}}Image 1 - {{/content}}
            {{#content "body"}}<img src="1.png" alt="" />{{/content}}
        {{/embed}}
    {{/content}}

{{/extend}}

The {{#embed}} helper allows you to reason about your partials as you would class instantiation where the above is equivalent to the following psuedo code:

class Page extends Layout {
    body() {
        var gallery = new Gallery();

        gallery.replaceBody('<img src="1.png" alt="" />\n<img src="2.png" alt="" />');

        var modal = new Modal({
            foo: 'bar',
            name: this.user.fullName
        });

        modal.prependTitle('Image 1 - ');
        modal.replaceBody('<img src="1.png" alt="" />');

        return gallery.toString() + modal.toString();
    }
}

{{#block [name]}}

  • name String - Block identifier.

Defines a named block, with optional default content. Blocks may have content appended, prepended, or replaced entirely when extending or embedding. You may append and prepend to the same block multiple times.

{{#block "header"}}
    <h1>Hello World</h1>
{{/block}}

{{#block "main"}}
    <p>Lorem ipsum...</p>
{{/block}}

{{#block "footer"}}
    <p>&copy; 1970</p>
{{/block}}

{{#content [name] mode="(append|prepend|replace)"}}

  • name String - Identifier of the block to modify.
  • mode String (Optional) - Means of providing block content. Default: replace.

Sets block content, optionally appending or prepending using the mode attribute.

Layout:

<html>
    ...
    <body>
        {{#block "header"}}
            <h1>Hello World</h1>
        {{/block}}

        {{#block "main"}}
            <p>Lorem ipsum.</p>
        {{/block}}

        {{#block "footer"}}
            <p>&copy; 1999</p>
        {{/block}}
    </body>
</html>

Page:

{{#extend "layout"}}

    {{#content "header"}}
        <h1>Goodnight Moon</h1>
    {{/content}}

    {{#content "main" mode="append"}}
        <p>Dolor sit amet.</p>
    {{/content}}

    {{#content "footer" mode="prepend"}}
        <p>MIT License</p>
    {{/content}}

{{/extend}}

Output:

<html>
    ...
    <body>
        <h1>Goodnight Moon</h1>

        <p>Lorem ipsum.</p>
        <p>Dolor sit amet.</p>

        <p>MIT License</p>
        <p>&copy; 1999</p>
    </body>
</html>

Conditional Blocks

There are times where you need to wrap a block with an element or use a different class depending on whether content has been provided for a block. For this purpose, the content helper may be called as a subexpression to check whether content has been provided for a block.

For example, you may wish to have an optional column in a grid layout:

{{!-- layout.hbs --}}
<div class="grid">
    <div class="grid-col {{#if (content "right")}}grid-col_2of3{{else}}grid-col_full{{/if}}">
        {{{block "left"}}}
    </div>
    {{#if (content "right")}}
        <div class="grid-col grid-col_1of3">
            {{{block "right"}}}
        </div>
    {{/if}}
</div>

For a page that only needs a left column, you may omit defining content for the right block:

{{!-- page.html --}}
{{#extend "layout"}}

    {{#content "left"}}
        <p>Left</p>
    {{/content}}

{{/extend}}

Resulting in:

<div class="grid">
    <div class="grid-col grid-col_full">
        <p>Left</p>
    </div>
</div>

For a page with two columns, simply define content for both blocks:

{{!-- page.html --}}
{{#extend "layout"}}

    {{#content "left"}}
        <p>Left</p>
    {{/content}}

    {{#content "right"}}
        <p>Right</p>
    {{/content}}

{{/extend}}

Resulting in:

<div class="grid">
    <div class="grid-col grid-col_2of3">
        <p>Left</p>
    </div>
    <div class="grid-col grid-col_1of3">
        <p>Right</p>
    </div>
</div>

Example

layout.hbs

<!doctype html>
<html lang="en-us">
<head>
    {{#block "head"}}
        <title>{{title}}</title>

        <link rel="stylesheet" href="assets/css/screen.css" />
    {{/block}}
</head>
<body>
    <div class="site">
        <div class="site-hd" role="banner">
            {{#block "header"}}
                <h1>{{title}}</h1>
            {{/block}}
        </div>

        <div class="site-bd" role="main">
            {{#block "body"}}
                <h2>Hello World</h2>
            {{/block}}
        </div>

        <div class="site-ft" role="contentinfo">
            {{#block "footer"}}
                <small>&copy; 2013</small>
            {{/block}}
        </div>
    </div>

    {{#block "foot"}}
        <script src="assets/js/controllers/home.js"></script>
    {{/block}}
</body>
</html>

page.html

{{#extend "layout"}}
    {{#content "head" mode="append"}}
        <link rel="stylesheet" href="assets/css/home.css" />
    {{/content}}

    {{#content "body"}}
        <h2>Welcome Home</h2>

        <ul>
            {{#items}}
                <li>{{.}}</li>
            {{/items}}
        </ul>
    {{/content}}

    {{#content "foot" mode="prepend"}}
        <script src="assets/js/analytics.js"></script>
    {{/content}}
{{/extend}}

Putting Them Together

var handlebars = require('handlebars');
var layouts = require('handlebars-layouts');

// Register helpers
handlebars.registerHelper(layouts(handlebars));

// Register partials
handlebars.registerPartial('layout', fs.readFileSync('layout.hbs', 'utf8'));

// Compile template
var template = handlebars.compile(fs.readFileSync('page.html', 'utf8'));

// Render template
var output = template({
    title: 'Layout Test',
    items: [
        'apple',
        'orange',
        'banana'
    ]
});

console.log(output);

Output (prettified for readability)

<!doctype html>
<html lang="en-us">
<head>
    <title>Layout Test</title>

    <link rel="stylesheet" href="assets/css/screen.css" />
    <link rel="stylesheet" href="assets/css/home.css" />
</head>
<body>
    <div class="site">
        <div class="site-hd" role="banner">
            <h1>Layout Test</h1>
        </div>

        <div class="site-bd" role="main">
            <h2>Welcome Home</h2>
            <ul>
                <li>apple</li>
                <li>orange</li>
                <li>banana</li>
            </ul>
        </div>

        <div class="site-ft" role="contentinfo">
            <small>&copy; 2013</small>
        </div>
    </div>

    <script src="assets/js/analytics.js"></script>
    <script src="assets/js/controllers/home.js"></script>
</body>
</html>

Contribute

Standards for this project, including tests, code coverage, and semantics are enforced with a build tool. Pull requests must include passing tests with 100% code coverage and no linting errors.

Test

$ npm test

MIT ยฉ Shannon Moeller

handlebars-layouts's People

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

handlebars-layouts's Issues

Expose 'helpers' publicly

It would be nice of layouts.helpers was exposed for public consumption so it can be registered how and when the user sees fit.

Error working with Html-webpack-plugin

I am using handlebars-layouts with webpack and I use Html-webpack-plugin to generate html file. But if i use extends, webpack throws below error:

ERROR in Template execution failed: Error: Missing helper: "extend"

I am using handlebars-loader with webpack and below is my webpack config.

module: {
       rules: [{
           test: /\.tsx?$/,
           use: 'ts-loader',
           exclude: /node_modules/
       },
       {
           test: /\.hbs$/,
           use: [{
               loader:'handlebars-loader',
               options:{
                   partialDirs: path.resolve(__dirname, "./app/partials"),
                   helperDirs: path.resolve(__dirname, "./node_modules/handlebars-layouts"),
               }
           }]
       }]
   },
.
.
.
.

 new HtmlWebpackPlugin({ 
           filename: 'add.form.html', // name of the output file
           chunks: ['add'], // name of the entry point defined above
           template: './app/add/add.form.hbs' // path to the html for the webpart
       }),

Change of behaviour of for inheritance with 2.0

Since the new release, templates inheriting template inheriting a top level template doesn't seem to be aware of the top level blocks. (sorry, i can't figure a way to articulate this cleany).

To better illustrate this, see this bin for v1.1.0 and this one with current master

A quick glance at the history let me think this change was introduced by 04ade65.
I'm not sure this behavior is intended or not. Let me know, and i'll try to dig further in the code.

Deep "inheritance"

Need to add support for this:

<!-- layout -->
<!doctype html>
<html>
    <head>
        <title>{{block "title"}}</title>
    </head>
    <body>
        {{block "body"}}
    </body>
</html>
<!-- one-column -->
{{#extend "layout"}}
    {{#append "body"}}
        <article>
            <header>
                {{block "header"}}
            </header>
            {{#block "content"}}
                <p>Nothing to see here.</p>
            {{/block}}
        </article>
    {{/append}}
{{/extend}}
<!-- blog-post -->
{{#extend "one-column"}}
    {{#append "title"}}Hello World{{/append}}

    {{#append "header"}}
        <h1>Hello World</h1>
    {{/append}}

    {{#replace "content"}}
        <p>Lorem ipsum...</p>
    {{/replace}}
{{/extend}}

Conditional layout

I'm trying to achieve conditional layout, is it possible ATM?

{{#if isLightLayout}}
    {{#extend "lightLayout"}}
{{else}}
    {{#extend "darkLayout"}}
{{/if}}

block is unable to render when being wrapped by an each loop

Context+What I checked

  • The blocks are wraped on a layout and extended with context, therefore:
  • The blocks compile correctly when outside the each loop
  • Rendering blocks wrapped on an each loop always result in a failed output, with or without errors depending on how the block name is specified

Env

  • handlebars: ^4.7.7,
  • handlebars-layouts: ^3.1.4,
  • node: v18.6.0

Compile with hard errors

In both of the following cases, the error is the same:

TypeError: Cannot create property '$$layoutStack' on string 'a'
    at getStack (redacted\node_modules\handlebars-layouts\index.js:11:25)
    at applyStack (redacted\node_modules\handlebars-layouts\index.js:16:14)
    at String.block (redacted\node_modules\handlebars-layouts\index.js:169:4)
    at String.wrapper (redacted\node_modules\handlebars\lib\handlebars\internal\wrapHelper.js:10:19)
    at eval (eval at createFunctionContext (redacted\node_modules\handlebars\lib\handlebars\compiler\javascript-compiler.js:265:23), <anonymous>:11:127)
    at prog (redacted\node_modules\handlebars\lib\handlebars\runtime.js:333:12)
    at execIteration (redacted\node_modules\handlebars\lib\handlebars\helpers\each.js:50:9)
    at Object.<anonymous> (redacted\node_modules\handlebars\lib\handlebars\helpers\each.js:63:13)
    at Object.wrapper (redacted\node_modules\handlebars\lib\handlebars\internal\wrapHelper.js:10:19)
    at Object.eval (eval at createFunctionContext (redacted\node_modules\handlebars\lib\handlebars\compiler\javascript-compiler.js:265:23), <anonymous>:19:49)

Variable block name

Trying to render a block with a variable name

{{#each modules as |module|}}
<div class="card px-4 py-4 mb-3">
    {{#block module}}{{/block}}
</div>
{{/each}}

with the data

{
  "modules":["a","b","c"]
}

Fixed block name

Trying to render a block with a constant name

{{#each modules as |module|}}
<div class="card px-4 py-4 mb-3">
    {{#block "a"}}{{/block}}
</div>
{{/each}}

Successful compile, no errors, but invalid/unwanted output

Nested variable block name error

In this case there are no errors being thrown, but the {{#block ...}} results in a blankspace in every iteration.

{
  "modules":[
    {
      "type":"a"
    } 
  ]
}

with the tamplate

{{#each modules as |module|}}
<div class="card px-4 py-4 mb-3">
    {{#block (lookup . 'type')}}{{/block}}
</div>
{{/each}}

Unexpected whitespace from `{{#content}}` block

Not sure if this is a bug. This is the first time I'm trying this for logical structure and layout of handlebar templates. Here is what I have so far:

layout.hbs:

<!DOCTYPE html>
<html lang="en">
  <head>
    {{> document-head }}
  </head>

  <body>
    {{> header }}
    {{#block "main"}}{{/block}}
    {{> footer }}
  </body>
</html>

document-head.hbs (partial):

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

header.hbs (partial):

<header>
  <h1>Sample page</h1>
</header>

footer.hbs (partial):

<footer>
  <p>Sample footer</p>
</footer>

index.hbs:

{{#extend "layout"}}

  {{#content "main"}}
    <div class="container">
      <div id="menu"></div>
      <div id="content">
        <p>Hello world!!</p>
      </div>
    </div>
  {{/content}}

{{/extend}}

Here is the output of index.html generated from index.hbs. Notice the extra whitespace in front of <div class="container">.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  </head>

  <body>
    <header>
      <h1>Sample page</h1>
    </header>
        <div class="container">
      <div id="menu"></div>
      <div id="content">
        <p>Hello world!!</p>
      </div>
    </div>

    <footer>
      <p>Sample footer</p>
    </footer>
  </body>
</html>

is there a way to use inline partials?

handlebarsjs has an inline partials feature now. It would be nice to be able to do {{#embed 'my_inline_partial'}}{{/embed}}. Is there a different way to use them I'm missing?

Inline partials also do not appear to work from within a child context of a block or content or embed. Where the inline partial was defined in a parent context.

Dynamically load partials, aka `res.render`

When booting up the server, the partial templates are baked into the process and do not change when the layout hbs files are updated. This is unlike res.render which appears to use the latest file contents for each render. Is there a way to achieve this same functionality for partial templates? thanks!

customizing syntax

Hi,
how easy it is to customize syntax?

i want to use it for laravel so it would be better, if handlebar shares same syntax as laravel

ex: displaying a variable {{$var}} instead of {{var}}

do we need to do that at root level in handlebars or can be done in your layouts plugin?

Possible 'ifBlock' helper

Basically, I'd like to include extra HTML in my layout conditionally, based on whether a particular {{#content}} has been included on a page.

I wrote a little helper that extends your helpers:

// Include Assemble & Helpers
var assemble = require('assemble');
var handlebars = require('handlebars');
var layouts = require('handlebars-layouts')(handlebars);

handlebars.registerHelper('ifBlock', function (names, options) {
  actions = this._layoutActions || {};
  names = names.split('||');
  for (var i=0; i < names.length; i++) {
    var name = names[i].trim();
    var action = actions[name];
    if (action) {
      return options.fn(this);
    }
  }
});

a use example would be:

layout--a.hbs


---
title: layout--a

---
{{#extend "master"}}
  {{#content "master__head" mode="append"}}
    {{#block "head"}}{{/block}}
  {{/content}}
  {{#content "master__body"}}
    <main role="main" class="layout layout--a">
      <div class="row">
        {{#ifBlock "region--a"}}
        <div class="region region--a">
        {{#block "region--a"}}{{/block}}
        </div>
        {{/ifBlock}}
        {{#ifBlock "region--b"}}
        <div class="region region--b">
        {{#block "region--b"}}{{/block}}
        </div>
        {{/ifBlock}}
      </div>
    </main>
  {{/content}}
  {{#content "master__foot" mode="append"}}
    {{#block "foot"}}{{/block}}
  {{/content}}
{{/extend}}

page.hbs


---
title: Page

---
{{#extend "layout--a"}}
  {{#content "region--a"}}
  <p>content</p>
  {{/content}}
{{/extend}}

...The output of page.hbs would not include the <div class="region region--b"> markup as I did not use {{#content "region--b"}}

I wondered if this might be worth including as part of your helpers?

Error: missing partial: 'html/template'

I have been struggling quite a bit with generating a static site using gulp, handlebars, and handlebars layouts. I suspect I am doing something wrong but I cannot for the life of me figure out what it is. I am not sure if I am applying/ identifying a layout file correctly. Should it be as a partial?

I am using the following:

  • gulp 4.0.2
  • gulp hb 8.0.0
    -handlebars-layouts 3.1.4

This is in my gulp file:

const { src, dest } = require('gulp');
const GulpHb = require('gulp-hb');

const {handlebars: config } = require('../config');

function getHbStream(config) {
  return GulpHb({ debug: true })
    .helpers(require('handlebars-layouts'))
    .partials(config.partials)
    .partials(config.layouts)
    .helpers({...config.helpers})
    .data(config.data);
}

function buildHandlebars() {
  const hbStream = getHbStream(config);

  return src(config.src)
    .pipe(hbStream)
    .pipe(dest(config.dest));
}

module.exports = buildHandlebars;

the configuration information looks like this:

  handlebars: {
    watch: path.join(sourceDir, sourceDirs.templates, '**/*.hbs'),
    src: path.join(sourceDir,sourceDirs.templates,'*.hbs' ),
    layouts: path.join(sourceDir,sourceDirs.templates,'layouts/*.hbs' ),
    dest: buildDir,
    data: path.join(sourceDir, 'data', '*.json'),
    partials: path.join(sourceDir, sourceDirs.templates, 'partials', '**/*.hbs'),
    helpers: {
      'equal': function handlebarsEqualHelper(name, value, options) {
        return this.context[name] === value ? options.fn(this) : options.inverse(this);
      },
      'set': function _handlebarsVariablesHelper(name, options) {
        const content = options.fn(this);
        this.context[name] = content;
      },
    },
  },

What is the correct value for a layout partial?
In none of my handlebars files do I have an 'html/template' partial. I'm assuming this is an internal of handlebars-layouts.

Stack error:

Error: Missing partial: 'html/template'
    at Object.extend (\node_modules\handlebars-layouts\index.js:120:11)
    at Object.eval [as main] (eval at createFunctionContext (\node_modules\handlebars\dist\cjs\handlebars\compiler\javascript-compiler.js:266:23), <anonymous>:5:96)
    at main (node_modules\handlebars\dist\cjs\handlebars\runtime.js:176:32)

Need help using this with webpack

Hello,
I am trying to use theese layouts with webpack, and have webpack build a hbs file to an html file, but experiencing many issues. I am quite new with thiw and with javascript in general, so I might be missing something very stupid.

Here is my webpack config:

// webpack.common.js
const path = require('path');

module.exports = {
  entry: {
    app: './src/js/app.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    clean: true,
    filename: './js/app.js',
  },
  module: {
    rules: [
      {
        test: /\.css$/,               // Targets all .css files
        include: path.resolve(__dirname, 'src/css'), // Adjust if your CSS files are located elsewhere
        use: [
          'style-loader', // Adds CSS to the DOM by injecting a <style> tag
          'css-loader',   // Interprets `@import` and `url()` like `import/require()` and resolves them
          'postcss-loader' // Processes CSS with PostCSS
        ]
      },
      {
        test: /\.hbs$/, // Handle .hbs files
        loader: 'handlebars-loader',
        options: {
          precompileOptions: {
            knownHelpersOnly: false
          },
          partialDirs: [
            './src/partials',
          ],
          inlineRequires: '/img/',
          runtime: path.resolve(__dirname, 'src/js/handlebars-runtime'),
        },
      }
    ]
  },
};
// webpack.config.prod.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');

module.exports = merge(common, {
  mode: 'production',
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: './src/de.hbs',
    }),
    new CopyPlugin({
      patterns: [
        { from: './src/img', to: 'img' },
        { from: './src/css', to: 'css' },
        { from: './src/js/vendor', to: 'js/vendor' },
        { from: './src/icon.svg', to: 'icon.svg' },
        { from: './src/favicon.ico', to: 'favicon.ico' },
        { from: './src/robots.txt', to: 'robots.txt' },
        { from: './src/icon.png', to: 'icon.png' },
        { from: './src/404.html', to: '404.html' },
        { from: './src/site.webmanifest', to: 'site.webmanifest' },
      ],
    }),
  ],
});
// src/js/handlebars-runtime.js
const fs = require('fs');
const Handlebars = require('handlebars/runtime');
const layouts = require('handlebars-layouts');


Handlebars.registerHelper(layouts(Handlebars));
// partial languages is in src/js/partials/languages.hbs (../partials/languages.hbs)
const languages = fs.readFileSync('./src/partials/languages.hbs', 'utf8');
const base = fs.readFileSync('./src/partials/base.hbs', 'utf8');
Handlebars.registerPartial('languages', languages);
Handlebars.registerPartial('base', base);

module.exports = Handlebars;

This is based on the html boilerplate webstorm gave me.

And here is the error I am getting:

ERROR in Template execution failed: TypeError: handlebars.compile is not a function

ERROR in   TypeError: handlebars.compile is not a function

I would really appreciate any help on this matter

If content blocks no longer works

{{#if @content.right }}

The above block always evaluates to false since the content helpers are being evaluated after the partial.

Changing the line

getStack(context).push(fn);

in the extend helper to

fn(context);

fixes the issue, but this is most likely not the correct solution.

Multiple updates to a block.

Currently only one change to a block will ever be applied, meaning you can't prepend and append to a block at the same time. Later directives win. Changes should be queued and applied in order.

assemble / template

Hey @shannonmoeller, we love this lib you've created, thank you!

I'm going to be releasing a new version of handlebars-helpers in the next couple of days - and assemble v0.6.0 will be directly after that, and I just wanted to make sure it's okay with you if I direct our users to your lib for layout helpers going forward. From our standpoint it just makes more sense, especially with the features you introduced in v1.0 - no need for us to continue reinventing the wheel with this.

Also, we still get a lot of requests on assemble and related projects for "more advanced" layout helpers, I can try to link a couple here, but I wanted to make sure this was okay with you first, and if so, get your thoughts on how we might collaborate to implement some of the things that we'd like to have - also keeping in mind that it needs to make sense for what you'd like to achieve with this project as well.

if you'd rather us just keep things separate, I totally won't be offended. Either way I appreciate what you've created here!

(btw, we won't be using the janky register stuff anymore, I appreciate you showing some love for our users by supporting that!)

cc/ @doowb

#extend vs #embed

Originally from @kflorence in #11:


Yeah, just thinking out loud here. Not suggesting that is the appropriate syntax by any means, just that there seems to be some shortcuts for certain use cases.

I think I'm still a little confused between {{#extend}} and {{#embed}} -- for example, in the following case which one should I be using?

layouts/default.hbs

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>{{title}}</title>
  </head>
  <body>
    <main class="main" role="main">
      {{{block "main"}}}
    </main>
  </body>
</html>

partials/component.hbs

<div class="component">
  {{{block "partials/component/content"}}}
</div>

views/component.hbs

{{#extend "layouts/default"}}
  {{#content "layouts/default/main"}}
    {{!-- embed or extend? --}}
    {{#embed "partials/component"}}
      {{#content "partials/component/content"}}
        foo bar
      {{/content}}
    {{/embed}}
  {{/content}}
{{/extend}}

I've named the content blocks kind of strangely because they seemed to be causing trouble if they shared the same names, not sure if that was my error or not.


It seems like only {{#embed}} works in that context, but it doesn't really match with how I interpret the docs (since "partials/component" does not extend any layout).

Also, there seems to be some weird stuff going on with the contexts. Assuming there is a top level "file" property in data:

{{#extend "layouts/component"}}
  {{#content "layouts/component/content"}}
    {{debug "one" ../file}}
    {{#embed "partials/component"}}
      {{#content "partials/component/content"}}
        {{debug "two" ../../../file}}
      {{/content}}
    {{/embed}}
  {{/content}}
{{/extend}}

Logs:

----------------------------------------
one
<File "components/buttons/index.html" <Buffer 7b 7b 23 65 78 74 65 6e 64 20 22 6c 61 79 6f 75 74 73 2f 63 6f 6d 70 6f 6e 65 6e 74 22 7d 7d 0a 20 20 7b 7b 23 63 6f 6e 74 65 6e 74 20 22 6c 61 79 6f 75 ...>>
----------------------------------------
----------------------------------------
two
<File "components/buttons/index.html" <Buffer 7b 7b 23 65 78 74 65 6e 64 20 22 6c 61 79 6f 75 74 73 2f 63 6f 6d 70 6f 6e 65 6e 74 22 7d 7d 0a 20 20 7b 7b 23 63 6f 6e 74 65 6e 74 20 22 6c 61 79 6f 75 ...>>
----------------------------------------

Where I would expect "two" to reference the file by ../../file instead of ../../../file

Apologies for slightly hijacking this thread :)

TypeError: arg.hasOwnProperty is not a function

When used together with Express.js and when passing res.locals as the template context which uses handlebars-layouts, the following error happens:

TypeError: arg.hasOwnProperty is not a function
    at mixin ([snip]/node_modules/handlebars-layouts/index.js:72:12)

As can be seen from the code, hasOwnProperty is expected to be a function in the objects:

if (arg.hasOwnProperty(key)) {
    target[key] = arg[key];
}

But this is not the case with Express.js:

// express/lib/middleware/init.js
res.locals = res.locals || Object.create(null);

Where the prototype of the res.locals is not Object.

It only shows the path to the partial

The Template
`

Toggle navigation VKS
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
  <ul class="nav navbar-nav">
    <li class="active"><a href="/">Home <span class="sr-only">(current)</span></a></li>
    <li><a href="arcade">Arcade</a></li>
  </ul>
</div><!-- /.navbar-collapse -->
Arcade
`

The View Being Rendered
{{#extend "frontend"}} {{/extend}}

Global template options not exposed to layout options.

Not working:

<nav class="app-nav {{#block 'navClassName' mode='append'}}{{/block}}">
  <ul>
    {{#each links}}
      <li>{{intlGet labelPath}}</li>
    {{/each}}
  </ul>
</nav>
{{#extend "nav"}}
  {{#content "navClassName"}}app-nav-horizontal{{/content}}
{{/extend}}

Workaround:

<nav class="app-nav {{#block 'navClassName' mode='append'}}{{/block}}">
  <ul>
    {{#block 'links' mode='append'}}{{/block}}
  </ul>
</nav>
{{#extend "nav"}}
  {{#content "navClassName"}}app-nav-horizontal{{/content}}
  {{#content "links"}}
    {{#each links}}
      <li>{{intlGet labelPath}}</li>
    {{/each}}
  {{/content}}
{{/extend}}

See intlGet helper description

Shorthand syntax for simple use cases

It would be nice if there were defaults for simple use cases, for example when a layout has only one block.

Take the following example:

Layout:

<div class="component">
  {{#block "content"}}
    component content goes here
  {{/block}}
</div>

Page:

{{#layout "component"}}
  {{#content "content"}}
    component content goes here
  {{/content}}
{{/layout}}

I could instead write something shorter, like:

{{#layout "component" content="component"}}
  component content goes here
{{/layout}}

Or:

{{#layout "component"}}
  {{content}}
    component content goes here
  {{content}}
{{/layout}}

Where {{content}} without a name defaults to some value, like "content". This same syntax should also be available for embeds.

weird issue with blocks

super simple scenario, works without express (by doing a manual compile and template()) but when using res.render i get the following error :

TypeError: /media/sf_work/testing/partial/App/views/partials/dashboard.hbs: Cannot read property 'body' of undefined
   at getBlocks (/media/sf_work/testing/partial/node_modules/handlebars-layouts/index.js:6:18)
   at Object.handlebars.registerHelper.block (/media/sf_work/testing/partial/node_modules/handlebars-layouts/index.js:59:26)
   at Object.eval (eval at <anonymous> (/media/sf_work/testing/partial/node_modules/hbs/node_modules/handlebars/dist/cjs/handlebars/compiler/javascript-compiler.js:189:23), <anonymous>:13:161)
   at /media/sf_work/testing/partial/node_modules/hbs/node_modules/handlebars/dist/cjs/handlebars/runtime.js:86:31
   at /media/sf_work/testing/partial/node_modules/hbs/node_modules/handlebars/dist/cjs/handlebars/compiler/compiler.js:465:21
   at /media/sf_work/testing/partial/node_modules/hbs/lib/hbs.js:80:17
   at /media/sf_work/testing/partial/node_modules/hbs/lib/hbs.js:61:11
   at Function.Waiter.done (/media/sf_work/testing/partial/node_modules/hbs/lib/async.js:60:16)
   at /media/sf_work/testing/partial/node_modules/hbs/lib/hbs.js:56:15
   at fs.js:266:14

Layout :

<div id="wrapper">
    {{#block "body"}}

    {{/block}}
</div>

partial :

{{#extend "layout"}}
    {{#append "body"}}
asdasdasd
    {{/append}}
{{/extend}}

Like i mentioned earlier, when doing

app.get('/' ,function (req, res) {
  res.render('partials/dashboard',{
    include_file : 'index',
    location : 'home'
  });
});

i get the above error. If i do a

      hbs.registerPartial('layout', fs.readFileSync(__dirname + '/views/layout.hbs', 'utf8'))
        var template = hbs.compile(hbs,handlebars.partials['partials/dashboard']);


app.get('/' ,function (req, res) {
  res.send(template({
            title: 'Layout Test',
            items: [
                'apple',
                'orange',
                'banana'
            ]
        }));
});

works like a charm, i get the correct output. One weird thing i noticed is that on the Express scenario, doing a console.log in the getBlocks function of the var blocks i get the following output

{}
{ body: [ { should: 'append', fn: [Object] } ] }
undefined

catching the undefined solved the error but the partial is not added to the layout.
I am using the 0.3.3 version and 4.8.8 of express

Any help would be most welcomed at this point.

ReferenceError: Handlebars is not defined

This is not partucular a problem of your npm package. I run into this regularely :(

I import handlebars with the standard require code for npm:

var handlebars = require('handlebars'), layouts = require('handlebars-layouts');
handlebars.registerHelper(layouts(handlebars));

This throws the error:

ReferenceError: Handlebars is not defined

I can only include it with this:

import handlebars from 'handlebars';

other npm packages work with with the require command just fine

E.g. this works var c3 = require("c3");

But why?

[object Object] when using {{#content}}

Hi,
I have the following structure:

// layouts/default.html

<!doctype html>
<html lang="en">
  <head>
    {{> head}}
    {{#block "header_misc"}}{{/block}}
  </head>
  <body>
    <h1>Default Layout</h1>

    {{{contents}}}

    {{> footer}}
  </body>
</html>

// partials/head.html

<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{title}}</title>
<script src="//cdn.polyfill.io/v2/polyfill.min.js"></script>


// partials/footer.html

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-beta1/jquery.min.js"></script>

// src/pages/index.md

---
title: Homepage

---

{{#extend "default"}}
  {{content "header_misc"}}
    <h1>Hello there</h1>
  {{/content}}
{{/extend}}
<ul>
  {{#if collections.articles}}
    {{#each collections.articles}}
      <li>
        <h3>{{this.title}}</h3>
        <article>{{this.contents}}</article>
      </li>
    {{/each}}
  {{else}}
    nope
  {{/if}}

  `test`
</ul>

// Built file
    <!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Homepage</title>
    <script src="//cdn.polyfill.io/v2/polyfill.min.js"></script>

  </head>
  <body>
    <h1>Default Layout</h1>

    <p>[object Object]<ul>
      <li>
        <h3>First Post</h3>
        <article>
This is a blog post here
</article>
      </li></p>
<p>  <code>test</code>
</ul></p>


    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-beta1/jquery.min.js"></script>
  </body>
</html>

No matter what though, the built file only outputs [object Object] on the page. I'm assuming I'm missing something fairly obvious, so any help would be great!

Add params to #embed

Hi, is there a way to add params or data to the embeds, just like this???

Component

<div>
  <h2>I'm visible</h2>
  {{#if hideTitle}} <h2>Hide me!!! if you can...</h2> {{/if}}
  {{#block "content"}}{{/block}}
</div>

Implementation

{{#embed "component" hideTitle=true}}
  {{#content "content"}}<h4>This is the new way</h4>{{/content}}
{{/embed}}

Result

<div>
  <h2>I'm visible</h2>
  <h4>This is the new way</h4>
</div>

Or maybe you can pass the context??? I don't know much about Handlebars so, if this functionality already exist, can you give a example??? Thanks a lot.

{{#embed}} causes Assemble 0.6.x to fail build

All has been working great with handlebars-layouts and Assemble 0.6.x until I tried to use {{#embed}}

Gulpfile:

// Config
var config = {
  src: 'src/',
  dist: 'dist/',
  scripts: 'scripts/',
  styles: 'styles/',
  images: 'images/'
}

// Include Gulp
var gulp = require('gulp');

// Include general plugins
var plugins = require('gulp-load-plugins')({
  pattern: [
    'gulp-*',
    'del',
    'browser-sync'
  ]
});

var reload = plugins.browserSync.reload;

// Include Assemble & Helpers
var assemble = require('assemble');
var handlebars = require('handlebars');
var helpers = {
  layouts: require('handlebars-layouts'),
  repeat: require('handlebars-helper-repeat')
};
// Register Helpers
handlebars.registerHelper(helpers.layouts(handlebars));
handlebars.registerHelper('repeat', helpers.repeat);
handlebars.registerHelper('either', function () {
  var options = arguments[arguments.length - 1];
  for (var i = 0; i < arguments.length - 1; i++) {
    if (arguments[i]) {
      return options.fn(this);
    }
  }
  return options.inverse(this);
});

// Build Markup
gulp.task('markup-compile', function () {
  assemble.layouts(config.src + 'templates/**/*.hbs');
  assemble.partials(config.src + 'templates/components/**/*.hbs');
  assemble.pages(config.src + 'pages/*.hbs');
  assemble.data([config.src + 'data/**/*.{json,yml}']);
  gulp.src(config.src + 'pages/*.hbs')
    .pipe(plugins.plumber())
    .pipe(plugins.assemble(assemble, {
      scripts: config.scripts,
      styles: config.styles,
      images: config.images
    }))
    .pipe(plugins.extname())
    .pipe(gulp.dest(config.dist));
});

Layout:

---
layout: layout--blank

---
{{#block "layout--blank__body"}}{{/block}}

Partial:

---
component: panel

---
{{#extend "layout--blank"}}
  {{#content "layout--blank__body"}}
    <section class="panel {{modifier}} {{class}}" id="{{id}}">
      <div class="panel__inner">
        {{#if title}}
        <header class="panel__header">
          <h3 class="panel__title">{{title}}</h3>
        </header>
        {{/if}}
        {{#if @content.body}}
        <div class="panel__body">
          {{#block "body"}}{{/block}}
        </div>
        {{/if}}
      </div>
    </section>
  {{/content}}
{{/extend}}

Page:

---
page-title: Builder

---
{{#extend "layout--a"}}
  {{#content "region--a"}}
    {{partial "page-header" builder}}
    {{partial "page-controls"}}
  {{/content}}
  {{#content "region--b"}}
    Sidebar
  {{/content}}
  {{#content "region--c"}}
    {{#each builder.panels}}
      {{#embed "panel" this}}
        {{#content "body"}}Body Text{{/content}}
      {{/embed}}
    {{/each}}
  {{/content}}
{{/extend}}

Causes this error:

screen shot 2015-04-24 at 13 27 31

Using handlebars-helpers with metalsmith (and consolidate.js)

I'm using metalsmith and metalsmith-templates to build a static site generator. Metalsmith-templates is based on tj holowaychuck's consolidate.js and I've configured it to use handlebars.

Now I'd really like to use handlebars-layouts as well. I've been trying to incorporate handlebars-layouts in the way suggested in your readme, by registering the helpers like so:

// excerpt from https://github.com/superwolff/cape/blob/handlebars-layouts/gulp/util/metalsmith.js#L24
var Handlebars = require('handlebars');
var layouts = require('handlebars-layouts');

layouts(Handlebars);

and am testing it by extending a simple (registered) partial:

{{! excerpt from https://github.com/superwolff/cape/blob/handlebars-layouts/src/index.html#L10 }}
{{#extend "footer"}}{{/extend}}

However this does not seem to work. All other bits of handlebars functionality seem to be working (partials, custom helpers, etc.), but the {{#extend}} block helper isn't processed at all. It turns up untouched in the output:

<!-- example excerpt from dist/index.html -->
{{#extend "footer"}}{{/extend}}

It looks to me like the handlebars-layouts block helpers aren't being registered. Which is weird because I am registering other helpers successfully (here for example). Could this be solved by using the Handlebars.registerHelper method perhaps? Or is handlebars-layouts incompatible with what I'm trying to do?


full code here: https://github.com/superwolff/cape/tree/handlebars-layouts

Passing params to partials

Hello, I'm having an issue trying to pass params to partial views, it seems to throw an error when I try the following for example:

{{> myPartial somevar='123' }}

Is this something that's not supported?

Thanks!

Extending partial causes error (handlebars.compile is not a function)

Trying to implement handlebars-layouts, I get error when extending a partial:
ERROR in Template execution failed: TypeError: handlebars.compile is not a function

  1. I registered the helpers and partials (handlebars.js):
const Handlebars = require('handlebars/runtime')
const layouts = require('handlebars-layouts')
const fs = require('fs')

Handlebars.registerHelper(layouts(Handlebars))

const partial = fs.readFileSync('./src/components/partial.hbs', 'utf-8')
Handlebars.registerPartial('partial', partial)

module.exports = Handlebars

  1. Added the handlebars runtime to handlebars-loader options in webpack config (webpack.config.js):
/* ... */
test: /\.hbs$/,
loader: 'handlebars-loader',
options: {
    runtime: SRC_PATH + '/helpers/handlebars',
    helperDirs: [SRC_PATH + '/helpers']
}
/* ... */

  1. Partial component partial.hbs
  2. Extending partial webpack-layouts.hbs


What am I missing?
When I register the partial as follows, no error is appearing, but the partial is not extended (handlebars.js)

Handlebars.registerPartial('partial', () => {
  return partial
})


Repro repository:
https://github.com/martyan/start-coding/tree/handlebars-layouts

Commit with handlebars-layouts implementation:
martyan/start-coding@ad75830

Trying to get this to work with Express

I saw there was another closed issue about this but it didn't really help me. I have a frontend framework that's currently using this plugin with gulp to compile my templates into HTML but now I'm looking to move to an express server and for the server to serve the templates as responses instead.

Here's a simplified version of what I'm working with:

Directories:

- app.js
- build
- src
    - layouts
        - main.hbs
        - generic.hbs
    - partials
        - header.hbs
    - pages
        - index.hbs
    - styles
    - scripts
    - images

app.js

let express = require("express");
let exphbs = require("express-handlebars");
let layouts = require("handlebars-layouts");

let app = express();

let hbs = exphbs.create({
	extname: ".hbs",
	layoutsDir: "src/layouts/",
	defaultLayout: "main",
	partialsDir: ["src/partials/"]
});

var handlebars = hbs.handlebars;

handlebars.registerHelper(layouts(handlebars));

app.engine(".hbs", hbs.engine);
app.set("view engine", ".hbs");
app.set("views", "src/");

app.get("/", function (req, res) {
	res.render("pages/index");
});

app.listen(3000, function(){
	console.log("Now listening on port 3000");
});

index.hbs

{{#extend "generic"}}
	{{#content "content"}}
		Hello world
	{{/content}}
{{/extend}}

generic.hbs

{{#extend "main"}}
	{{#content "body"}}
		<div class="padded-h-xl padded-v-md">
			{{#block "content"}}{{/block}}
		</div>
	{{/content}}
{{/extend}}

And the error I get:

Error: Missing partial: 'generic'

No idea if I'm going about this the right way or not. I tried searching for "express-handlebars" and "handlebars-layouts" to try and find some code examples of them both being used together but couldn't find anything useful.

Any ideas?

Default "mode"

Is there a way to change the default of mode?
Actually is set on replace but I want to change it.

Extend partials by path.

Hi, sometime register partials could be annoying, I find it is not hard to implement path support. Would you like a pull request for this feature?

Use with ember

Do you have any tips? because when im using Helpers.register(Ember.Handlebars); and/or Ember.Handlebars.registerHelper(Helpers(Ember.Handlebars)); it doesn't register!

I can see that the variable helpers is an empty object

Append

I have a layout like this:

Hello From layout.hbs
{{#block "styles"}}{{/block}}
{{#block "from-page"}}{{/block}}

and a page like this:

{{#extend "layout"}}
  {{#content 'styles' mode='append'}}
    <link href="/css/foo.css" rel="stylesheet">
  {{/content}}
  {{#content "from-page"}}
    Hello from page.hbs
    {{#embed "component"}}{{/embed}}
  {{/content}}
{{/extend}}

and a component like this:

Hello from component.hbs
{{#content 'styles' mode='append'}}
  <link href="/css/bar.css" rel="stylesheet">
{{/content}}

I'd like the component styles to append to the layout section, bubbling up through the page.

Is there a way to do this?

Extending block pollute each others output

Sorry for the weird title, but this is a tricky one and couldn't really find a good name for this issue.

See this requirebin for more context.

I'm not sure I'm not doing something totally wrong here, so let me know if I miss something and I'll try to fill a PR if it's totally valid scenario.

Example of using it with express-handlebars

Hi, is this module still up to date?

Can you show the example of how to use it with express-handlebars, if they are compatible.
PS. I use express-handlebars this way:

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

But having the issue of adding handlebars-layouts to it.

The following doesn't work, and gives me error - Missing helper "extend", Missing helper "block"

    layouts.register(handlebars);
// or
    handlebars.registerHelper(layouts(handlebars));

Thank you.

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.