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
Solution
The typical layout makes use of inline partials as follows:
layout.handlebars:
home.handlebars:
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:
Another option is to use partial parameters, which works best for plain strings:
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:
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:
This would render the Default navigation if the template does not specify a site-nav inline partial, its contents otherwise.
See also
{{!layout ...}}and supports multiple layers of nesting.{{#block ...}{{#extend "layout" foo="bar"}}