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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions