DEV Community

Cover image for Passing multiple arguments to golang sub-templates
MoniqueLive
MoniqueLive

Posted on

Passing multiple arguments to golang sub-templates

Have you ever wanted to pass multiple arguments to a go sub-template? If you google it you'll be convinced it's not possible. But bear with me.

In go templates you can pass a single "argument" (pipeline in go parlance) to a "sub-template" defined block. But by creating a simple helper function you can pass as many arguments as you want. Simply add this function to your FuncMap:

func(els ...any) []any {
    return els
}
Enter fullscreen mode Exit fullscreen mode

And you'll be able to create constructs such as:

{{ template "MyTemplate" (arr "first" 123 .Some.Value) }}
{{ template "MyTemplate" (arr "second" 456 .Other.Value) }}

{{ define "MyTeplate" }}
  {{ $strArg := index . 0 }}
  {{ $intArg := index . 1 }}
  {{ $valArg := index . 2 }}

  This is my str {{ $strArg }} parameter.
  ...
{{ end }}
Enter fullscreen mode Exit fullscreen mode

I named arr my helper func, but you can call it whatever you want.

Enjoy!

Top comments (8)

Collapse
 
andrewpillar profile image
Andrew Pillar

With Go templates it's much more preferable to define a struct containing the data you wish to expose to the template, for example,

data := TemplateData{
    Post: Post{Title: "Example post", Author: "Andrew"},
    User: User{Username: "andrew"},
}


tmpl.Execute(w, data)
Enter fullscreen mode Exit fullscreen mode

and in your template you would have something like,

{{define "body"}}
    <h1>{{.Post.Title}}</h1>
    by {{.Post.Author}}
{{end}}
Enter fullscreen mode Exit fullscreen mode

this provides a more stricter mechanism by which to control what data is in a template, and more clarity too since you would be referring to the data via the field names. Furthermore, it provides some type checking beforehand via the struct's fields.

Collapse
 
moniquelive profile image
MoniqueLive

Agreed. But eventually in the rare case when you need a more dynamic approach now you know how.

Collapse
 
andrewpillar profile image
Andrew Pillar

In the case of having a more dynamic approach I would then prefer map[string]any.

Thread Thread
 
moniquelive profile image
MoniqueLive

Actually we're talking about different things.
The example you showed is related to data sent from Go to the template.
My post is about a template calling a sub-template.
Imagine an html form builder, where each field is constructed by calling a sub template:

<form...>
  {{ template "field" (arr "title" .MP3.Title) }}
  {{ template "field" (arr "artist" .MP3.Artist) }}
...
</form>

{{ define "field" }}
  {{ $fieldName := index . 0 }}
  {{ $fieldValue := index . 1 }}
  <label for="{{ $fieldName }}" ... >
  </label>
  <input id="{{ $fieldName }}" name="{{ $fieldName }}" type="text" value="{{ $fieldValue }}"/>
{{ end }}
Enter fullscreen mode Exit fullscreen mode

The data sent from Go to the template is a struct (MP3 info), field names have nothing to do with the MP3 data, they're a frontend issue.

Thread Thread
 
andrewpillar profile image
Andrew Pillar • Edited

In that case I would simply change your implementation to not use an arr function for grabbing arbitrary values and still use a map. See the playground link for an example as to what I mean: go.dev/play/p/5Hiajt4H2Z5

A map function is defined which takes the pairs of values and puts them into a map[string]any, these can then be accessed from the sub-template. This provides more clarity of the data being accessed since the values can be referred to by the index name.

Thread Thread
 
simonstorlschulke profile image
Simon Storl-Schulke

This is very helpful thanks for that!

Collapse
 
klausbreyer profile image
Klaus Breyer

Nice! This was really helping me. However, it needs to be:

{{ template "MyTemplate" (els "first" 123 .Some.Value) }}
{{ template "MyTemplate" (els "second" 456 .Other.Value) }}

Collapse
 
moniquelive profile image
MoniqueLive • Edited

In the end I wrote: “I named arr my helper func, but you can call it whatever you want.”, els is the name of the arguments. The name of the function you define on your FuncMap:

template.New("hello.gohtml").Funcs(template.FuncMap{
    "arr": func(els any) []any { return els }
})
Enter fullscreen mode Exit fullscreen mode