5

I have simple case, where a templates (text/templates) includes another like this

`index.html`

{{ template "image_row" . }}


`image_row.html`

{{ define "image_row" }}

   To stuff here

{{ end }}

Now I want to reuse the image row template. Let's say I would like to pass a simple number, so that the image_row template builds up rows according to this number

I'd like to have something like that (where 5 is the additional argument)

index.html

{{ template "image_row" . | 5 }}

How could I achieve that in this case?

xhallix
  • 2,919
  • 5
  • 37
  • 55
  • Please, clarify: your question is about `text/template` ? Because `text/tempate` allows nested template definitions. – Alex Yu Apr 15 '17 at 14:58

2 Answers2

7

I'm not sure whether there exists a builtin solution for passing multiple arguments to a template invocation but, in case there isn't one, you could define a function that merges its arguments and returns them as a single slice value, then you can register that function and use it in the template invocation.

Something like:

func args(vs ...interface{}) []interface{} { return vs }
t, err := template.New("t").Funcs(template.FuncMap{"args":args}).Parse...

Then, in your index.html, you would do this:

{{ template "image_row" args . 5 }}

And then inside your image_row template you can access the arguments with the builtin index function like this:

{{ define "image_row" }}

   To stuff here {{index . 0}} {{index . 1}}

{{ end }}

https://play.golang.org/p/gkdtvvJ1bb

mkopriva
  • 35,176
  • 4
  • 57
  • 71
  • Is that somehow possible to use with `ParseFiles`? – xhallix Apr 16 '17 at 11:42
  • `ParseFiles`, `ParseGlob`... doesn't matter how you parse the template as longs as you've got the func registered with the `Funcs` method, the resulting template will have access to that func. – mkopriva Apr 16 '17 at 11:45
  • If you mean the function `template.ParseFiles` as opposed to the method `*Template.ParseFiles` then **no**, because you cannot reference a func that hasn't been yet registered. So you need to first create a `template.Template` with `template.New` then register the functions you want to use, and then call the `ParseFiles` method on that template. – mkopriva Apr 16 '17 at 11:52
  • at first, thanks for your help and sorry for making this more complicated then it should. For my case it does not working I made a non working example here https://play.golang.org/p/lJrfBs--d5 but never the less I think that is the right direction and I will find the solution sooner or later I guess :) – xhallix Apr 16 '17 at 12:00
  • The snippet in the answer is just an approx example. `ParseFiles` returns two values not one, a `*Template` and an `error` value, so instead of `t := ...` do `t, err := ...` or wrap the whole `ParseFiles` call into the `template.Must` func as is in the playground example of my answer. I'll update the snippet to reflect this. – mkopriva Apr 16 '17 at 12:10
  • I got it working, here is also a good example http://goinbigdata.com/example-of-using-templates-in-golang/ Thank you – xhallix Apr 16 '17 at 12:15
  • No problem, I'm glad you got it working. – mkopriva Apr 16 '17 at 12:16
  • Is this thread-safe? – SOFe Jun 24 '18 at 12:56
  • 1
    @SOFe Depends on what you mean exaclty, do you have a specific concern regarding specific value types? If you're passing non-pointer values around then it ought to be safe for concurrent use, if on the other hand you're passing around pointer values then you might run into issues, but Go's `sync` package provides you with enough tools to implement a concurrency-safe solution. – mkopriva Jun 24 '18 at 13:05
4

There is no builtin for this. You can add a function that creates a map and use that in the child template:

func argsfn(kvs ...interface{}) (map[string]interface{}, error) {
  if len(kvs)%2 != 0 {
    return nil, errors.New("args requires even number of arguments.")
  }
  m := make(map[string]interface{})
  for i := 0; i < len(kvs); i += 2 {
    s, ok := kvs[i].(string)
    if !ok {
        return nil, errors.New("even args to args must be strings.")
    }
    m[s] = kvs[i+1]
  }
  return m, nil
}

Add it the function to the template like this:

t := template.Must(template.New("").Funcs(template.FuncMap{"args": argsfn}).Parse(......

Use it like this:

{{template "image_row" args "row" . "a" 5}}{{end}}

{{define "image_row"}}
     {{$.row}} {{$.a}}
{{end}}

Run it in the playground

The advantage of using a map is that the arguments are "named". The advantage of using a slice as described in another answer is that the code is much simpler.

Charlie Tumahai
  • 113,709
  • 12
  • 249
  • 242