Go template programming (Partials: pipe, use as functions, recursive)

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 (either true or false). This output is then retrieved as a boolean value by piping it to eq "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 with shortcode-templates the parameter-syntax looks more precise with {{< myshortcode param=1 >}}

If I overlooked something and this is possible, please let me know :wink:

13 Likes

wow, very cool. I want to explore this myself. Do you happen to have a site that is working with these techniques?

The only point I’d like to add about “Use partial templates as functions” is that it’s very much white-space sensitive, depending on how you are receiving/comparing the “returned” value. So for this technique you would probably always need to wrap all forms within {{- and -}} (and not have consecutive <!-- .. --> HTML comment lines because white spaces created by those don’t get wiped by {{-/-}}).

I have not used the partial piping technique but I happen to have an example each of the other 2 techniques:

  • Use partial templates as functions: version_str2float.html | Usage examples
  • Recursive partial templates: debugprint.html | Usage examples in the comment header of that file (I got so excited when I discovered recursive use of partials when writing this, that I have the <!-- Recursive call FTW! --> comment after each recursive call :slight_smile:.)
3 Likes

I’ve used debugprint.html myself - very handy. :slight_smile:

I’m using a partial as a function here:

Which calls (not the most elegant) a template which chains several replaceRE calls to strip script tags from content that may be problematic in an Atom or RSS feed:

@solutionroute @kaushalmodi thanks for the examples.

@RickCogley Unfortunately the source is closed source currently. I want to get this open sourced once the basic work is done, but I’ll have to check some legal issues first (regarding images etc.). I’ll update the post once I have this working.

@kaushalmodi is there a list of these handy things (like debugprint) anywhere?

1 Like

This partial has been moved to its own repo.

Very cool examples.

I’d like to show one I created yesterday for a new project. A way to easily use SVG icons (when you have them as separate .svg files) like the ones from Zondicons.com.

<!--
    Easily use SVG icons on your templates with custom CSS classes (there's also a shortcode for markdown files)
    This partial lets you have a folder with lots os SVG icons as separate .svg files without the need to have a sprite file.
    Usage: partial "icons.html" (dict "icon" "download" "class" "w-4 h-4")
-->
<!-- Grab the folder path were the files are located. I'm not using the static folder for this one. -->
{{ $icon_path := printf "%s%s%s" "/themes/hugo-mix-tailwind/src/assets/zondicons/" .icon ".svg" }}
{{ $icon := readFile $icon_path }}
<!-- After reading the file content above, using Regular Expressions I remove the open and close SVG tag so I can pass custom classes to it via a dictionary on the partial call. -->
{{ $icon := replaceRE "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\">" "$1" $icon }}
{{ $icon := replaceRE "</svg>" "$1" $icon }}
<svg class="fill-current {{.class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">{{- $icon | safeHTML }}</svg>

The theme is under construction, but the repo is available here: https://github.com/sjardim/hugo-mix-tailwindcss

1 Like

@qw3ry: might I ask you, why your version {{- range .Site -}} works whereas {{- range .Site.Pages.ByWeight -}} does not? I’m trying to use your great recursion but I need my pages listed by menu weight and I’m baffled why lowercase pages works byt uppercase Pages does not work in this loop.

Sorry, but I have no idea. But the debugging template is very helpful to find what variables are provided. Sometimes they are case sensitive and stuff… If you cannot figure out how to do this you can try to sort the pages manually

OK, thanks. I’ll look at the template. Nice tool, I’m sure.

Thanks for mentioning that gist.

Btw here’s the latest home of that debugprint template:

I’ll update that gist asking people to navigate to there.

The repo is for a Hugo theme component containing just that. So it should be now easy to use that on any site. Also the repo would contain many other changes I made to that partial since I wrote that gist.