Skip to content

Template inheritance #17

@thekid

Description

@thekid

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions