Handlebars.js

Handlebars.js is a templating engine like jinja2, but entirely in JavaScript.

Personally I’ve only found pug templates very nice to work with as I love the minimal syntax.

What makes handlebars cool is it’s ability to render templates both server and client side, if you’re running node.js on the server.

Running it in the frontend is super easy, check this example from the handlebars docs;

1
2
3
4
5
6
7
8
<!-- Include Handlebars from a CDN -->
<script src='https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js'></script>
<script>
  // compile the template
  var template = Handlebars.compile("Handlebars <b>{{doesWhat}}</b>");
  // execute the compiled template and print the output to the console
  console.log(template({ doesWhat: "rocks!" }));
</script>

Basics

The arguments of this template function can be accessed within curly braces and supports full js objects that may be accessed with dot notation just like you’d expect.

The “evaluation context” may be switched using “{{#with something}}{{/with}}” or “{{#each something}}{{/each}}” for looping over some list.

There is also template comments using the syntax “{{! }}” or “{{!– –}}” to allow “mustaches like }}”. Handlebars comments will be stripped when rendering, but html comments passes through.

Helpers and Partials

Handlebars helpers enable you to register javascript functions that may run inside templates. To register a helper run;

Handlebars.registerHelper('name', function (string) {return 'SomeString'})
This function may use the evaluation context as “this” within the function.

Or to make it a block helper, take two arguments;

Handlebars.registerHelper('name', function (items, options) {return 'SomeString'})

The normal helpers are called by “{{helpername [args…]}}”. Block helpers switch the evaluation context. The helper function for these always take options and usually context as arguments.

You can also register partials like helpers with;

Handlebars.registerPartial('name', 'template string with {{blocks}}')

It should be noted that handlebars by default html escapes all results from these blocks/helpers. If you want to avoid this then “triple mustaches” is what you’re looking for. Calling any helper function like “{{{helpername}}}” will bypass the html escaping.

Examples

This script is running on this page;

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103

// This does mostly the same as that example from the official docs
let simple = () => {
  let template = Handlebars.compile("Handlebars <b>{{doesWhat}}</b>")
  let out = template({ doesWhat: "rocks!" })
  console.log(out)
  return out
}

// This is how handlebars comments work
let comments = Handlebars.compile(`
{{! This comment will not show up in the output}}
<!-- This comment will show up as HTML-comment -->
{{!-- This comment may contain mustaches like }} --}}
inspect element here!`)()

// This uses each for context switching and uses a nested links parameter
let nested = Handlebars.compile(`
<ul>
  {{#each links}}
  <li><a href='{{href}}' target='_blank'>{{text}}</a></li>
  {{/each}}
</ul>`)({
  links: [{
    'href': 'https://handlebarsjs.com/guide',
    'text': 'Handlebars official guide'
  }, {
    'href': 'https://io.sivert.pw',
    'text': 'Awesome blog!'
  }]
})

// Simple helper
Handlebars.registerHelper('censor', (str) => {
  return str.replaceAll('fuck', 'f***').replaceAll('hell', 'h***')
})

// Block helper
Handlebars.registerHelper('noop', (options) => {
  return options.fn(this)
})

// Handlebars registered partial
Handlebars.registerPartial('people', `
<ul>
  {{#each members}}
  <li>{{name}} age {{age}} from {{home}}</li>
  {{/each}}
</ul>`)

// Calling non-existent partials with block syntax uses the block content as fallback
let advanced = Handlebars.compile(`
{{censor profanity}}

{{#> people members}}
  <b>Some thing went wrong, man!</b>
{{/people}}

<ul>
  {{#each members}}
  <li>{{name}} age {{age}} from {{home}}</li>
  {{/each}}
</ul>`)({
  'profanity': 'fuckin hell man!',
  'members': [{
    'home': 'Never Never Land',
    'name': 'Peter',
    'age': 120
  }, {
    'home': 'Trollhättan',
    'name': 'Clark',
    'age': 75
  }]
})

// Run "main" after all is loaded
document.addEventListener("DOMContentLoaded", function () {
  // Add a output element to the end of the Table of Contents
  let output = document.createElement('div')
  output.id = 'output'
  document.getElementById('meta').append(output)

  // When creating a partials you may use {{> @partial-block }} to capture the block content
  Handlebars.registerPartial('layout', '<br /><div>{{> content }}</div>')

  // Here we use those "triple mustaches"!
  let template = Handlebars.compile(`
{{#> layout}}
  {{#*inline "content"}}
    <h4>Examples output:</h4>
    <h5>Functions:</h5>
    <p>Simple: {{{simple}}}</p>
    <p>Nested: {{{nested}}}</p>
    <p>Comments: {{{comments}}}</p>
    <p>Advanced: {{{advanced}}}</p>
  {{/inline}}
{{/layout}}`)

  // Render and insert the template above
  output.innerHTML = template({
    simple: simple(), nested: nested, comments: comments, advanced: advanced
  })
})