Skip to content

Instantly share code, notes, and snippets.

@Wilfred
Forked from mmcgahan/gist:9fa045d98c7c122f1c0b
Last active October 23, 2023 16:37
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Wilfred/715ae4e22642cfff1dbd to your computer and use it in GitHub Desktop.
Save Wilfred/715ae4e22642cfff1dbd to your computer and use it in GitHub Desktop.

Template composition with inclusion

Every template language I have seen provides some mechanism for one template to include another, thus supporting the reuse of repeated elements like headers and footers. The included templates are called partials in Mustache parlance:

<!-- home.hbs -->
<html>
<body>
  {{> header}}
  <p> HOME </p>
  {{> footer}}
</body>
</html>
<!-- about.hbs -->
<html>
<body>
  {{> header}}
  <p> ABOUT </p>
  {{> footer}}
</body>
</html>
<!-- header.hbs -->
<p> HEADER </p>
<!-- footer.hbs -->
<p> FOOTER </p>
This is not the DRYest implementation, however. The <html> and <body> tags are copied on every page. Adding scripts and stylesheets will only aggravate the situation. Larger sections can be abstracted:
<!-- home.hbs -->
{{> top}}
<p> HOME </p>
{{> bottom}}
<!-- about.hbs -->
{{> top}}
<p> ABOUT </p>
{{> bottom}}
<!-- top.hbs -->
<html>
<body>
  {{> header}}
<!-- bottom.hbs -->
  {{> footer}}
</body>
</html>

This pattern is recommended when template inheritance is unavailable. It is fragile and rigid, though. Some tags, like and here, are split among the included templates - terrible for maintenance and readability. To customize a portion of an included template for each page, like the <title>, the templates must be divided even further:

<!-- home.hbs -->
{{> top-before-title}}
Home
{{> top-after-title}}
<p> HOME </p>
{{> bottom}}
<!-- about.hbs -->
{{> top-before-title}}
About
{{> top-after-title}}
<p> ABOUT </p>
{{> bottom}}
<!-- top-before-title.hbs -->
<html>
<head>
  <title>
<!-- top-after-title.hbs -->
  </title>
</head>
<body>
  {{> header}}

This can quickly grow into a mess (and already has by some standards).

Template composition with inheritance (and inclusion)

Template inheritance comes, I believe, from Django. It nicely addresses the above issues with template composition by essentially providing a mechanism for implementing the Dependency Inversion Principle.

With template inheritance, a base template has specially annotated sections of content that can be overwritten by deriving templates. Deriving templates then declare their base template and replacement content:

<!-- base.hbs -->
<html>
<head>
  <title>{{#title}} Default Title {{/title}}</title>
</head>
<body>
  {{> header}}
  {{#content}}
    This will be default content that appears in a
    deriving template if it does not declare a
    replacement for the "content" section.
  {{/content}}
  {{> footer}}
</body>
</html>
<!-- home.hbs -->
{{derives base}}
{{#title}} Home {{/title}}
{{#content}} HOME {{/content}}
<!-- about.hbs -->
{{derives base}}
{{#title}} About {{/title}}
{{#content}} ABOUT {{/content}}

Much better. Fewer templates are needed, and no context needs to be split.

Template inheritance in Handlebars

Unfortunately, the Mustache specification does not prescribe support for template inheritance. Handlebars, which offers a number of improvements over vanilla Mustache, does not include it either. Dust does, but I prefer the syntax and helper interface of Handlebars. The good news is that Handlebars (perhaps unintentionally) exposes its registry of partials which can be used along with a couple of simple block helpers to implement template inheritance.

Three constructs are needed:

  1. For base templates: A block of default content
  2. For deriving templates: A block of replacement content
  3. A declaration of the base template

The block block helper will replace its section with the partial of the same name if it exists:

handlebars.loadPartial = function (name) {
  var partial = handlebars.partials[name];
  if (typeof partial === "string") {
    partial = handlebars.compile(partial);
    handlebars.partials[name] = partial;
  }
  return partial;
};

handlebars.registerHelper("block",
  function (name, options) {
    /* Look for partial by name. */
    var partial
      = handlebars.loadPartial(name) || options.fn;
    return partial(this, { data : options.hash });
  });

It will be used to specify default content in base templates:

{{#block "name"}}
  Default content
{{/block}}

Do not confuse the name block of this block helper with the general concept of block helpers. The name is simply an (admittedly potentially confusing) coincidence that was chosen to be consistent with existing systems supporting template inheritance, like Django.

The partial block helper generates no output and instead registers a section of content as a named partial in the Handlebars runtime:

handlebars.registerHelper("partial",
  function (name, options) {
    handlebars.registerPartial(name, options.fn);
  });

It can be used to declare any inline partial, but in the context of template inheritance, it annotates replacement content in deriving templates:

{{#partial "name"}}
  Replacement content
{{/partial}}

For the final piece, declaring a base template, we will resort to normal template inclusion (partials). In contrast to existing template inheritance convention, this declaration will occur at the end of a deriving template rather than the beginning. The reason why becomes apparent when we consider the dataflow:

  1. The partials for the base and deriving templates are registered.
  2. The user requests a rendering of the deriving template.
  3. Handlebars instantiates the partial for the deriving template:
    • At the beginning of the deriving template, a number of partial blocks will register partials for sections of replacement content.
    • At the end of the deriving template, the partial for the base template will be included.
  4. Handlebars instantiates the partial for the base template:
  5. Each #block block (forgive me) will be replaced by the partial of the given name if one was registered in the deriving template. Otherwise, its given content will be used as the default.

#Conclusion

The running example can be rewritten using these helpers:

<!-- base.hbs -->
<html>
<head>
  <title>
    {{#block "title"}} Default Title {{/block}}
  </title>
</head>
<body>
  {{> header}}
  {{#block "content"}}
    This will be default content that appears in a
    deriving template if it does not declare a
    replacement for the "content" section.
  {{/block}}
  {{> footer}}
</body>
</html>
<!-- home.hbs -->
{{#partial "title"}} Home {{/partial}}
{{#partial "content"}} HOME {{/partial}}
{{> base}}
<!-- about.hbs -->
{{#partial "title"}} About {{/partial}}
{{#partial "content"}} ABOUT {{/partial}}

{{> base}}
@jacamera
Copy link

I realize this is an old gist but it's highly ranked when searching for how to do templating using Handlebars. It's a great writeup but Handlebars now supports this functionality natively thanks to Partial Blocks and Inline Partials.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment