Skip to content

Template inheritance #17

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

Activity

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

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