Squiggly

Blocks and partials

define / block / partial — composing templates from pieces.

Two ways to share template content across multiple files, with quite different shapes.

Partials

A partial is a template loaded by name from a TemplateLoader. The renderer looks up the name through the loader’s String => Option[TemplateAST] callback, then renders the result with whatever data context you pass in.

{{ partial 'topbar' . }}
{{ partial 'page-toc' .page }}
{{ partial 'footer' (with title='Hello') }}

The first argument is a string literal — the name. The second is the data context to render with. Use . to pass the current context unchanged.

Partials are dynamic — looked up at render time, useful when the partial isn’t known until you’ve parsed the templates.

Wire up partials by passing a loader to the renderer:

val partials: TemplateLoader = name => myPartialMap.get(name)
val renderer = new TemplateRenderer(partials = partials)

Define / block

define and block work together to support a “fill-in-the-blanks” layout pattern. A parent template declares slots with {{ block name . }} … {{ end }} (with optional default content). A child template populates those slots with {{ define name }} … {{ end }}.

The renderer’s blocks field is a mutable.HashMap[String, TemplateAST] carrying the bindings; render the child first to populate it, then render the parent.

A two-pass example

baseof.html (the parent layout):

<!DOCTYPE html>
<html>
<head><title>{{ .page.title }}</title></head>
<body>
  <main>
    {{ block content . }}
      <p>Default content</p>
    {{ end }}
  </main>
</body>
</html>

page.html (the child):

{{ define content }}
  <article>
    <h1>{{ .page.title }}</h1>
    {{ .body }}
  </article>
{{ end }}

To render the page:

val parent = parser.parse(baseof)
val child  = parser.parse(page)

val renderer = new TemplateRenderer()

renderer.render(data, child)   // first pass: populates `define` blocks
renderer.render(data, parent)  // second pass: `block content` finds the
                               // populated definition and substitutes

The first render writes nothing visible — its purpose is the define side effect.

This is the pattern juicer uses for its _default/baseof.html + _default/file.html / _default/folder.html layout combinations.

Default content

{{ block name . }} … {{ end }} renders its inner content if no define populated the block.

{{ block sidebar . }}
  <p>No sidebar configured.</p>
{{ end }}

If sidebar was never defined, the default is what gets rendered.

When to pick which

SituationUse
Reusable fragment with its own data context (header, card, badge)Partial
“Outer shell” + variable inner content per page kinddefine / block
Many similar pages with different bodiesdefine / block
Optional decorations chosen at render timePartial

Both compose well — your baseof block can {{ partial 'topbar' . }} inside it.