I worked a lot with go templates and would like to share some nice/useful concepts I discovered.
Pipe partial templates to other partial templates:
You can pass the html created by a template to another template.
If you have a wrapper structure that is often used, but with different content, you can put everything in templates and pipe the output of the content-template to the wrapper-template.
In my case that was a clock icon (by fontawesome) and either a single time or a timespan. The result looks like this:
<!-- icon.html -->
<div class="d-flex flex-nowrap">
<div><i class="far fa-clock"></i></div>
<div>{{ . }}</div>
</div>
<!-- content1.html -->
<p>some content here</p>
<!-- content2.html -->
<p> some other content</p>
<!-- main.html -->
{{ if $something }}
{{ partial "content1.html" . | partial "icon.html" }}
{{ else }}
{{ partial "content2.html" . | partial "icon.html" }}
{{ end }}
Use partial templates as functions:
A template is a kind of function with exactly 1 parameter and 1 return value. We can build on this:
- Using a map/dictionary as parameter we can have an arbitrary number of parameters
- By parsing the returned string we can process the return value further
This way we can easily put complex decision logic into a partial template. I did this when building my site menu because .IsMenuCurrent
did not behave the way I needed it to.
<!-- isCurrent.txt -->
{{- $sameWithSubnodes := relURL (trim .current.URL "/") | eq (relURL (trim .page.URL "/")) -}}
{{- $sameWithoutSubnodes := .current.IsMenuCurrent .menu .page -}}
{{- cond .page.HasChildren $sameWithSubnodes $sameWithoutSubnodes -}}
<!-- menu.html -->
{{ $currentPage := . }}
{{ range .Site.Menus.main }}
{{ $isCurrent := partial "isCurrent.txt" (dict "page" . "current" $currentPage "menu" "main") | eq "true" }}
<li class="{{ if $isCurrent }} current {{ end }}"></li>
{{ end }}
- In
isCurrent.txt
, only the last line creates an output (eithertrue
orfalse
). This output is then retrieved as a boolean value by piping it toeq "true"
. - For the arguments, a
dict
is used. See the docs for futher information.
Recursive partial templates:
I even went one step further and build a recursive template.
I have done this for my menu template - I have a menu with nested lists; at first I copied the template for the top level and pasted it in the lower levels, but using recursion the template is less cluttered:
<!-- menu-rec.html -->
{{ $menuName := .menu }}
{{ $currentPage := .current }}
{{ $currentLevel := .level }}
<ul class="level-{{ $currentLevel }}">
{{ range .pages }}
<li class="level-{{ $currentLevel }}">
{{ if .HasChildren }}
{{ .Pre }}
<div class="d-flex justify-content-between">
<a href="{{ .URL }}">{{ .Name }}</a>
</div>
{{ partial "menu-rec.html" (dict "menu" $menuName "current" $currentPage "level" (add $currentLevel 1) "pages" .Children) }}
{{else}}
<a href="{{.URL}}">{{ .Pre }}{{ .Name }}</a>
{{end}}
</li>
{{end}}
</ul>
The usage gets easier: I can use the same template for the main menu and the footer menu:
<!-- main menu -->
{{ partial "menu-rec.html" (dict "menu" "main" "current" . "level" 1 "pages" .Site.Menus.main) }}
<!-- footer menu -->
{{ partial "menu/menu-rec.html" (dict "menu" "footer" "current" . "level" 1 "pages" .Site.Menus.footer) }}
Conclusion
For me, these techniques reduce the template size quite a bit. I hope this helps someone struggeling with the same things. If you have suggestions to improve the example code, please let me know.
I’d very much like to have an easier and more powerful way to write these templates. Things I still struggle with:
- These
{{
and}}
everywhere. Some of my templates consist solely of lines starting with{{
and ending with}}
. It would be great if there where a possibility to group multiple statements into a single{{
-}}
-block - Return types. I’d love if I could declare a go return type like boolean and just use the partial like any other function.
- Multiple parameters. The
dict
-Syntax kind of works, but withshortcode
-templates the parameter-syntax looks more precise with{{< myshortcode param=1 >}}
If I overlooked something and this is possible, please let me know