summaryrefslogtreecommitdiffstatshomepage
path: root/www/content/essays/template-fragments.md
blob: 2bfb096910d4f5ba14745e7a85dce0a57dcc8d43 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
+++
title = "Template Fragments"
description = """\
  In this essay, Carson Gross explores the concept of template fragments, a feature in server-side rendering (SSR) \
  that allows partial rendering of content within templates. He highlights the benefits of using template fragments in \
  hypermedia-driven applications, providing a cleaner and more maintainable approach compared to traditional template \
  decomposition. Carson showcases the use of template fragments in the Chill templating language and discusses how \
  this feature improves the developer experience when working with htmx and other hypermedia-oriented libraries. He \
  also includes examples and known implementations of template fragments in various programming languages."""
date = 2022-08-03
updated = 2023-03-18
authors = ["Carson Gross"]
[taxonomies]
tag = ["posts"]
+++

Template fragments are a relatively rare Server Side Rendering (SSR) template library feature that allow you to render a
_fragment_ or partial bit of the content within a template, rather than the entire template.  This feature is very handy in 
[Hypermedia Driven Applications](@/essays/hypermedia-driven-applications.md) because it allows you to decompose a particular
view for partial updates _internally_ without pulling fragments of the template out to separate files for rendering,
creating a large number of individual template files.  

By keeping all the HTML in a single file, it is also easier to reason about how a feature works.  This follows the
[Locality of Behavior](@/essays/locality-of-behaviour.md) design principle.

## Motivation

Let's look at how template fragments, in an obscure templating language for java called
[chill templates](https://github.com/bigskysoftware/chill/tree/master/chill-script), can help us build an HDA.

Here is a simple chill template, `/contacts/detail.html` that displays a contact:

##### /contacts/detail.html
```html
<html>
    <body>
        <div hx-target="this">
          #if contact.archived
          <button hx-patch="/contacts/${contact.id}/unarchive">Unarchive</button>
          #else
          <button hx-delete="/contacts/${contact.id}">Archive</button>
          #end
        </div>
        <h3>Contact</h3>
        <p>${contact.email}</p>
    </body>
</html>
```

In the template we have an archiving feature where, depending on the archive state of the contact, we either display an "Archive"
or an "Unarchive" button, both powered by htmx and issuing HTTP requests to different end points.

When we click whichever of the two buttons is being shown, we want to replace the content in the `div` that surrounds 
the button with an updated button.  (Note the `hx-target="this"` on the div, so we are targeting that div's innerHTML for
replacement.)  This will effectively flip the back and forth between "Archive" and "Unarchive".  

Now, unfortunately, if we wanted to render only the buttons and not the rest of this template, this would typically involve
splitting the buttons out to their own template file and including it in this template, like so:

##### /contacts/detail.html
```html
<html>
    <body>
        <div hx-target="this">
          #include archive-ui.html
        </div>
        <h3>Contact</h3>
        <p>${contact.email}</p>
    </body>
</html>
```

##### /contacts/archive-ui.html
```html
#if contact.archived
<button hx-patch="/contacts/${contact.id}/unarchive">Unarchive</button>
#else
<button hx-delete="/contacts/${contact.id}">Archive</button>
#end
```

Now we have two templates.  We can now render the `archive-ui.html` template separately, but this split reduces the 
visibility of the archiving feature: it is less obvious what is going on when you are looking just at the `detail.html` 
template.  

When pushed to extremes, decomposing templates like this can lead to quite a few small template fragments which, in
total, become difficult to manage and to reason about.

### Template Fragments To The Rescue

To address this issue, chill templates has a `#fragment` directive.  This directive allows you to specify a block of 
content within a template and render _just that bit of content_:

##### /contacts/detail.html Using a Fragment
```html
<html>
    <body>
        <div hx-target="this">
          #fragment archive-ui
            #if contact.archived
            <button hx-patch="/contacts/${contact.id}/unarchive">Unarchive</button>
            #else
            <button hx-delete="/contacts/${contact.id}">Archive</button>
            #end
          #end
        </div>
        <h3>Contact</h3>
        <p>${contact.email}</p>
    </body>
</html>
```

With this fragment defined in our template, we can now render either the entire template:

```java
  Contact c = getContact();
  ChillTemplates.render("/contacts/detail.html", "contact", c);
```

Or we can render only the `archive-ui` _fragment_ of the template

```java
  Contact c = getContact();
  ChillTemplates.render("/contacts/detail.html#archive-ui", "contact", c);
```

We would use the first option when we want to render the entire detail page for the contact.

We would use the second option when we handled the archive/unarchive actions and wished only to rerender the buttons.

Note that, with fragments, we are able to keep our UI together in a single file and see exactly what is going on with 
the feature, without bouncing around between different template files.  This provides a cleaner and more obvious
implementation of the feature.

## Known Template Fragment Implementations

Fragments (and the ability to render them directly in controllers) appear to be a relatively rare feature in templating
libraries and provide an excellent opportunity for improving the developer experience when working with htmx and other
hypermedia-oriented libraries.

Here are some known implementations of the fragment concept:

* Go
  * [Standard Library (use block actions)](https://pkg.go.dev/text/template) [[demo]](https://gist.github.com/benpate/f92b77ea9b3a8503541eb4b9eb515d8a)
* Java
  * [Thymeleaf](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#fragment-specification-syntax)
  * [Chill Templates (currently in early alpha)](https://github.com/bigskysoftware/chill/tree/master/chill-script)
  * [Quarkus Qute](https://quarkus.io/guides/qute-reference#fragments)
  * [JStachio (mustache)](https://jstach.io/doc/jstachio/current/apidocs/#mustache_fragments)
* JavaScript
  * [Jeasx](https://www.jeasx.dev) - see [example for htmx](https://expo.jeasx.dev/fragments)
* PHP
  * [Latte](https://latte.nette.org/en/template-inheritance#toc-blocks) - Use the 3rd parameter to only render 1 block from the template -  `$Latte_Engine->render('path/to/template.latte', [ 'foo' => 'bar' ], 'content');`
  * [Laravel Blade](https://laravel.com/docs/10.x/blade#rendering-blade-fragments) - includes built-in support for template fragments as of v9.x
  * [Twig](https://twig.symfony.com/doc/3.x/api.html#rendering-templates) - `$template->renderBlock('block_name', ['the' => 'variables', 'go' => 'here']);`
* Python
  * [Django Render Block Extension](https://pypi.org/project/django-render-block/) - see [example code for htmx](https://github.com/spookylukey/django-htmx-patterns/blob/master/inline_partials.rst)
  * [jinja2-fragments package](https://github.com/sponsfreixes/jinja2-fragments)
  * [jinja_partials package](https://github.com/mikeckennedy/jinja_partials) ([discussion](https://github.com/mikeckennedy/jinja_partials/issues/1) on motivation)
  * [chameleon_partials package](https://github.com/mikeckennedy/chameleon_partials)
  * [htmlgenerator](https://github.com/basxsoftwareassociation/htmlgenerator)
  * [django-template-partials](https://pypi.org/project/django-template-partials/) ([repository](https://github.com/carltongibson/django-template-partials))
* .NET
  * [Giraffe.ViewEngine.Htmx](https://github.com/bit-badger/Giraffe.Htmx/tree/main/src/ViewEngine.Htmx)
* Rust
  * [MiniJinja](https://docs.rs/minijinja/latest/minijinja/struct.State.html#method.render_block)

Please [let me know](/discord) if you know of others, so I can add them to this list.