Handlebars is a great templating language with partial view support. It’s highly extensible and adjustable with custom helper functions to add individual features if required.
If you’ve ever worked with the Jade templating engine, you probably know and used the block feature which adds a placeholder into your layout which can be replaced with custom content from child views. Handlebars lacks support for this feature, even though it’s a nice and easy way to add stylesheets and scripts within subviews if necessary.
This guide shows you how to add the Jade-like block functionality to handlebars and how to use it within your layouts.
hapi Series Overview
- What You’ll Build
- Prepare Your Project: Stack & Structure
- Environment Variables and Storing Secrets
- Set Up MongoDB and Connect With Mongoose
- Sending Emails in Node.js
- Load the User’s Profile Picture From Gravatar Using Virtuals in Mongoose
- Implement a User Profile Editing Screen
- Generate a Username in Mongoose Middleware
- Displaying Seasons and Episodes for TV Shows with Mongoose Relationship Population
- Implementing Pagination for Movies
- Implement a Watchlist
- Create a Full Text Search with MongoDB
- Create a REST API with JSON Endpoints
- Update Mongoose Models for JSON Responses
- API Pagination for TV Shows
- Customize API Endpoints with Query Parameters
- Always Throw and Handle API Validation Errors
- Advanced API Validation With Custom Errors
- Create an API Documentation with Swagger
- Customize Your Swagger API Documentation URL
- Describe Endpoint Details in Your Swagger API Documentation
- 10 Tips on API Testing With Postman
- JWT Authentication in Swagger API Documentation
- API Versioning with Request Headers
- API Login With Username and Password to Generate a JWT
- JWT Authentication and Private API Endpoints
- Refresh Tokens With JWT Authentication
- Create a JWT Utility
- JWT Refresh Token for Multiple Devices
- Check Refresh Token in Authentication Strategy
- Rate Limit Your Refresh Token API Endpoint
- How to Revoke a JWT
- Invalidate JWTs With Blacklists
- JWT Logout (Part 1/2)
- JWT “Immediate” Logout (Part 2/2)
- A Better Place to Invalidate Tokens
- How to Switch the JWT Signing Algorithm
- Roll Your Own Refresh Token Authentication Scheme
- JWT Claims 101
- Use JWT With Asymmetric Signatures (RS256 & Co.)
- Encrypt the JWT Payload (The Simple Way)
- Increase JWT Security Beyond the Signature
- Unsigned JSON Web Tokens (Unsecured JWS)
- JWK and JWKS Overview
- Provide a JWKS API Endpoint
- Create a JWK from a Shared Secret
- JWT Verification via JWKS API Endpoint
- What is JOSE in JWT
- Encrypt a JWT (the JWE Way)
- Authenticate Encrypted JWTs (JWE)
- Encrypted and Signed JWT (Nested JWT)
- Bringing Back JWT Decoding and Authentication
- Bringing Back JWT Claims in the JWT Payload
- Basic Authentication With Username and Password
- Authentication and Remember Me Using Cookies
- How to Set a Default Authentication Strategy
- Define Multiple Authentication Strategies for a Route
- Restrict User Access With Scopes
- Show „Insufficient Scope“ View for Routes With Restricted Access
- Access Restriction With Dynamic and Advanced Scopes
- hapi - How to Fix „unknown authentication strategy“
- Authenticate with GitHub And Remember the Login
- Authenticate with GitLab And Remember the User
- How to Combine Bell With Another Authentication Strategy
- Custom OAuth Bell Strategy to Connect With any Server
- Redirect to Previous Page After Login
- How to Implement a Complete Sign Up Flow With Email and Password
- How to Implement a Complete Login Flow
- Implement a Password-Reset Flow
- Views in hapi 9 (and above)
- How to Render and Reply Views
- How to Reply and Render Pug Views (Using Pug 2.0)
- How to Create a Dynamic Handlebars Layout Template
- Create and Use Handlebars Partial Views
- Create and Use Custom Handlebars Helpers
- Specify a Different Handlebars Layout for a Specific View
- How to Create Jade-Like Layout Blocks in Handlebars
- Use Vue.js Mustache Tags in Handlebars Templates
- How to Use Multiple Handlebars Layouts
- How to Access and Handle Request Payload
- Access Request Headers
- How to Manage Cookies and HTTP States Across Requests
- Detect and Get the Client IP Address
- How to Upload Files
- Quick Access to Logged In User in Route Handlers
- How to Fix “handler method did not return a value, a promise, or throw an error”
- How to Fix “X must return an error, a takeover response, or a continue signal”
- Query Parameter Validation With Joi
- Path Parameter Validation With Joi
- Request Payload Validation With Joi
- Validate Query and Path Parameters, Payload and Headers All at Once on Your Routes
- Validate Request Headers With Joi
- Reply Custom View for Failed Validations
- Handle Failed Validations and Show Errors Details at Inputs
- How to Fix AssertionError, Cannot validate HEAD or GET requests
What’s the Problem?
When using templates within handlebars, you can’t define custom content placeholders which can be replaced with content defined within subviews. This functionality makes sense if you want to add individual stylesheets or scripts from within child views. Specifically, we only need the script to load Disqus comments within the post detail view. We don’t need the script to be loaded on every page and only want it to be added to the rendered HTML when necessary (when reading a specific post).
Solution
To be honest, we were looking for a solution for quite some time. Some days ago, when we finally understood the actual problem, we indeed found a solution. We wanted to implement and started a last research if there is something out there to be reused. And finally, we found the functionality already created by Roman Shtylman within his repository: handlebars-extend-block.
What this small script of 22 lines does: it adds two helper functions to handlebars which extend the template language by two functions block
and extend
. The block
function is an array which can be extended by the extend
function.
Hapi View Configuration
The hapi framework isn’t required to add the block functionality to handlebars. We use hapi as our framework of choice and that’s why we use the following code snippet to illustrate the addition of the handlebars-extend-block
plugin to the default handlebars package.
const Hapi = require('hapi')
const Handlebars = require('handlebars');
const ExtendedHbs = require('handlebars-extend-block');
// Create hapi server instance
const server = new Hapi.Server({
host: 'localhost',
port: 3000
})
async function liftOff() {
await server.register({
plugin: require('vision') // add template rendering support in hapi
})
server.views({
engines: {
html: ExtendedHbs(Handlebars)
},
path: 'views',
layoutPath: 'views/layout',
layout: 'default',
partialsPath: 'views/partials'
})
}
liftOff()
As you can see in the snippet above, we pass the handlebars
object into the extend()
function. The extend()
function returns the decorated handlebars object which can be passed into hapi as the view rendering language.
Layout Templates
Let’s look at some precise template layouts and illustrate the new functionality with the help of an example. We’re using a default layout with two defined blocks: css
and js
. The {{{content}}}
placeholder is used within the default layout to put the rendered HTML at its place.
The following snippet shows the default layout:
Default Layout
<!DOCTYPE html>
<html>
<head>
{{! Document Settings }}
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Extend Handlebars Layouts</title>
<link rel="stylesheet" href="/assets/css/screen.css" />
{{{block "css"}}}
</head>
<body>
{{! Everything else gets inserted here }}
{{{content}}}
<script src="/assets/js/jquery.min.js"></script>
{{{block "js"}}}
</body>
</html>
The css
and js
blocks will be automatically replaced with the defined content from within subviews. Handlebars will render the block contents if you specified one.
Partial Layout
The partial layout below extends the css
as well as the js
block. We’re adding our custom CSS stylesheet and an additionally javascript file. You already guessed it, this partial view is just used for illustration purposes …
{{!< layout/default}}
{{! The tag above means - insert everything within this file into the {{{content}}} of the default.html template }}
{{#extend "css"}}
<link rel="stylesheet" href="/assets/css/my-additional.css" />
{{/extend}}
<h1>This is the title</h1>
<p>Hello from partial view</p>
{{#extend "js"}}
<script src="/assets/js/my-personal-javascript.js"></script>
{{/extend}}
Result
The rendered HTML will look like this. The defined blocks within the default layout will be replaced with the content from child views. If you don’t extend the blocks within the child views, handlebars will omit the specific block and leave it empty.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Extend Handlebars Layouts</title>
<link rel="stylesheet" href="/assets/css/screen.css" />
<link rel="stylesheet" href="/assets/css/my-additional.css" />
</head>
<body>
<h1>This is the title</h1>
<p>Hello from partial view</p>
<script src="/assets/js/jquery.min.js"></script>
<script src="/assets/js/my-personal-javascript.js"></script>
</body>
</html>
Conclusion
As you saw within the examples above, the block helpers to manipulate the default layout from within subviews benefits when used correctly. You should add stylesheets and javascript files which is required on every page within the default layout. Extending specific views with additional functionality like comments is a great use case to use layout blocks and add required scripts or styling.
We hope this helps and if you run into issues when applying this guide to your project, please don’t hesitate to tweet us @futurestud_io or leave a comment.
Additional Resources
- NPM package adding
#block
and#extend
helper to handlebars: handlebars-extend-block