-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Solution
The typical layout makes use of inline partials as follows:
layout.handlebars:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{> title}}</title>
</head>
<body>
<nav>
{{> nav}}
</nav>
<main>
{{> content}}
</main>
{{> scripts}}
</body>home.handlebars:
{{#> layout}}
{{#*inline "title"}}My title{{/inline}}
{{#*inline "nav"}}
My navigation
{{/inline}}
{{#*inline "content"}}
My content
{{/inline}}
{{#*inline "scripts"}}
<script>...</script>
{{/inline}}
{{/layout}}Templates using the layout must specify all inline templates, or else rendering will throw an exception. To make specifying a certain inline template optional, we can use partial blocks as follows:
</main>
- {{> scripts}}
+ {{#> scripts}}{{/scripts}}
</body>Another option is to use partial parameters, which works best for plain strings:
<meta name="viewport" content="width=device-width, initial-scale=1">
- <title>{{> title}}</title>
+ <title>{{#with title}}{{.}}{{else}}Default title{{/with}}</title>
</head>The home template would then pass this parameter via {{#> layout title="My title"}}. *On a side note, we could create a helper to shorten the with-else syntax to something along the lines of {{coalesce title "Default title"}}.
So basically, we can mimic the following:
abstract class Layout {
public $title= 'Default title';
public abstract function nav();
public abstract function content();
public function scripts() { return ''; }
}
class Home extends Layout {
public $title= 'My title';
public function nav() { return 'My navigation'; }
public function content() { return 'My content'; }
public function scripts() { return '<script>...</script>'; }
}Limitations
However, this does not work for more than one level. For example, we cannot have some common navigation which is inherited for all pages, but then replaced by a specific one inside a page template: The common navigation would always be used due to how inline templates' scoping works.
One solution to this is to call optional templates in the common layer:
{{#*inline "nav"}}
{{#> site-nav}}Default navigation{{/site-nav}}
{{/inline}}This would render the Default navigation if the template does not specify a site-nav inline partial, its contents otherwise.
See also
- https://jfreeman.dev/blog/2012/03/23/template-inheritance-for-handlebars/ - uses custom helpers
- https://github.com/sandcastle/handlebars-extended, which uses
{{!layout ...}}and supports multiple layers of nesting. - https://stackoverflow.com/questions/33043006/is-there-a-handlbars-equivalent-to-jades-extend suggesting
{{#block ...} - https://github.com/shannonmoeller/handlebars-layouts - which uses helpers
{{#extend "layout" foo="bar"}}